会话 switch/fork/import
AgentSessionRuntime 是 AgentSession 的外层包装器,负责「会话替换」。一个运行中的进程里,AgentSession 实例可能被换掉多次:/new 开新会话、/resume 切到旧会话、/fork 从某条消息分叉、/import 导入外部 jsonl。每次替换都要拆掉旧 session、创建新服务、重新绑扩展、恢复事件订阅。这个文件就是这套替换流程的容器。
职责
- 持有当前 session:
_session和_services字段存当前活的实例,通过session/services/cwdgetter 暴露。见packages/coding-agent/src/core/agent-session-runtime.ts:67-97。 - new / switch / fork / import:四个替换方法,先发 before 事件(扩展可 cancel),再
teardownCurrent,再apply新 runtime。见packages/coding-agent/src/core/agent-session-runtime.ts:175-198、packages/coding-agent/src/core/agent-session-runtime.ts:200-232、packages/coding-agent/src/core/agent-session-runtime.ts:234-320、packages/coding-agent/src/core/agent-session-runtime.ts:329-364。 - 事件钩子:
session_before_switch/session_before_fork/session_shutdown三类扩展事件,扩展可 cancel 替换。见packages/coding-agent/src/core/agent-session-runtime.ts:115-147。 - rebind 回调:host(InteractiveMode 或 rpc-mode)通过
setRebindSession注册一个在 session 替换后调用的回调,用来重新绑扩展 UI、重订阅事件。见packages/coding-agent/src/core/agent-session-runtime.ts:99-113、packages/coding-agent/src/core/agent-session-runtime.ts:166-173。 - 工厂复用:
createRuntime闭包在createAgentSessionRuntime时传入,之后每次替换都复用同一个工厂,保证 cwd/agentDir/扩展路径一致。见packages/coding-agent/src/core/agent-session-runtime.ts:382-400。
设计动机
为什么不直接 this.session = new AgentSession(...)?因为替换涉及三件事:旧 session 要发 shutdown 事件让扩展清理资源;新 session 要从同一路径加载服务(settings、auth、resource loader);UI 层要重新订阅事件、重绑扩展命令上下文。这三件事的顺序很关键——扩展先收到 shutdown 再 dispose,否则扩展持有的引用会变成野指针。teardownCurrent → apply → finishSessionReplacement 的三段式是这个顺序的硬保证。
createRuntime 工厂复用也值得说:每次替换都重新解析一遍 cwd、agentDir、扩展路径太重,而且参数会漂移。闭包捕获 CLI 一次解析出的配置,后续替换都用同一份,保证多次 /resume 后扩展路径不会忽然失效。
关键文件
packages/coding-agent/src/core/agent-session-runtime.ts:67-97—class AgentSessionRuntime字段与 getter。packages/coding-agent/src/core/agent-session-runtime.ts:149-164—teardownCurrent和apply,替换的核心两步。packages/coding-agent/src/core/agent-session-runtime.ts:166-173—finishSessionReplacement,触发 host 的 rebind 回调。packages/coding-agent/src/core/agent-session-runtime.ts:175-198—switchSession,resume 已有 jsonl。packages/coding-agent/src/core/agent-session-runtime.ts:200-232—newSession,支持parentSession形成分叉树。packages/coding-agent/src/core/agent-session-runtime.ts:234-320—fork,positionbefore/at两种语义。packages/coding-agent/src/core/agent-session-runtime.ts:329-364—importFromJsonl,复制外部 jsonl 进 sessionDir 再 switch。packages/coding-agent/src/core/agent-session-runtime.ts:382-400—createAgentSessionRuntime,初始 runtime 工厂入口。
替换三段式:
// packages/coding-agent/src/core/agent-session-runtime.ts:149-164
private async teardownCurrent(reason: SessionShutdownEvent["reason"], targetSessionFile?: string): Promise<void> {
await emitSessionShutdownEvent(this.session.extensionRunner, {
type: "session_shutdown",
reason,
targetSessionFile,
});
this.beforeSessionInvalidate?.();
this.session.dispose();
}
private apply(result: CreateAgentSessionRuntimeResult): void {
this._session = result.session;
this._services = result.services;
this._diagnostics = result.diagnostics;
this._modelFallbackMessage = result.modelFallbackMessage;
}fork 在 position: "at" 时直接拿选中条目作分叉点,before 时拿父条目,并提取用户消息文本回填到编辑器:
// packages/coding-agent/src/core/agent-session-runtime.ts:251-258
if (position === "at") {
targetLeafId = selectedEntry.id;
} else {
if (selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
throw new Error("Invalid entry ID for forking");
}
targetLeafId = selectedEntry.parentId;
selectedText = extractUserMessageText(selectedEntry.message.content);
}数据流
session 替换的统一流程:
边界与失败
- fork 选中非用户消息:
position: "before"时只能从用户消息分叉,否则抛Invalid entry ID for forking,见packages/coding-agent/src/core/agent-session-runtime.ts:254-256。 - fork 失败回滚:
sourceManager.createBranchedSession返回 null 时抛Failed to create forked session,但此时旧 session 已 dispose,调用方需要处理这种「中间态」错误,见packages/coding-agent/src/core/agent-session-runtime.ts:286-288。 - import 文件不存在:
existsSync检查后抛SessionImportFileNotFoundError,不静默失败,见packages/coding-agent/src/core/agent-session-runtime.ts:330-333。 - import cwd 缺失:
assertSessionCwdExists校验导入 jsonl 的 cwd 是否还能访问,interactive 模式 prompt 让用户重选,见packages/coding-agent/src/core/agent-session-runtime.ts:351-352。 - dispose 顺序:
quitreason 同样走teardownCurrent流程,保证扩展在进程退出时收到 shutdown 事件。
小结
AgentSessionRuntime 把会话替换抽象成统一三段式:teardown → apply → rebind。四种入口(new/switch/fork/import)共用同一套 teardown 和工厂闭包,扩展通过 before 事件可 cancel。装配工厂内部细节看 createAgentSession 装配,被替换的 session 本身看 AgentSession 编排层。