會話 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 編排層。