cc-sessions
針對從「管家」(或任何能存取同一 Emacs 伺服器的工作階段)管理 claude-code-ide.el 工作階段的具體 emacsclient --eval 配方。原理詳見 majordom.md;此處僅收錄現成可用的呼叫。
關鍵:唯一安全的啟動指令是 claude-code-ide-start
claude-code-ide-continue、claude-code-ide-resume 以及舊的 claude-code-ide(均不帶參數)— 切勿在 headless 環境中直接呼叫。當目錄已有啟用中工作階段時,它們都會觸發阻塞的 (read-string "Instance name (empty for auto): "),在沒有使用者於鍵盤前的情況下會永久凍結(已在三者上經實證確認)。唯一的例外是使用者直接在互動式 M-x 中執行。
claude-code-ide-start(簽章為 (&optional instance-name continue resume))能以單一呼叫涵蓋所有三種情況,且永遠不會互動式詢問名稱:
;; 正常啟動新對話
(let ((default-directory "/home/eugine/job/<project>/")
(claude-code-ide-cli-extra-flags "--remote-control <project>"))
(claude-code-ide-start "<name-or-nil>"))
;; 替代 claude-code-ide-continue
(claude-code-ide-start "<name-or-nil>" t nil)
;; 替代 claude-code-ide-resume(在 claude 終端機內部開啟互動式選取器,
;; 而非透過 Emacs 迷你快取 — 呼叫本身不會阻塞 emacsclient)
(claude-code-ide-start "<name-or-nil>" nil t)
instance-name(第一個參數)若為 nil ⇒ 自動編號(vedmid-2、vedmid-3...)。僅在使用者已為新副本指定名稱/目標時才明確設定 — 若不確定,請詢問使用者,不要自行編造。
啟動前:檢查現有狀態
(mapcar (lambda (b) (list (buffer-name b) (and (get-buffer-process b) (process-status (get-buffer-process b)))))
(seq-filter (lambda (b) (string-match-p "claude-code\[<project>" (buffer-name b))) (buffer-list)))
- 無啟用中工作階段 →
(claude-code-ide-start nil t nil)(將承接專案的最後一段對話,如同continue)。 - 有啟用中工作階段 → 切勿為了「重新啟動」而將其停止。使用相同的
claude-code-ide-start啟動額外的副本(若目標是選取特定舊對話則使用resume=t,否則建立並行新對話)。
啟動後:透過在緩衝區中實際搜尋來確認遙控模式
沒有凍結並不證明 --remote-control 已生效(曾有案例:忘記了旗標 — 工作階段乾淨地啟動了,但未處於遙控模式)。
(with-current-buffer "*claude-code[<project>]*"
(save-excursion (goto-char (point-min))
(if (search-forward "remote-control" nil t) "FOUND" "NOT FOUND")))
若為 NOT FOUND — 不一定是錯誤:檢查緩衝區的完整內容(buffer-substring-no-properties (point-min) (point-max)),該行可能出現在已渲染範圍之外,或者(對於大型工作階段)在 eat 的可見滾動區之外 — 終端機本身僅保留近期歷史,而非整個對話。
停止工作階段:先停用 query-on-exit,再停止
claude-code-ide-stop 本身是安全的,且不取決於工作階段是否具有 remote-control。但正常的 Emacs 提示 Buffer "..." has a running process; kill it? 會非同步地彈出(在 process-sentinel 中,已在 claude-code-ide-stop 返回之後)— 因此圍繞呼叫本身的 let 包裝無濟於事。請預先以單獨呼叫停用進程上的標記:
(let ((proc (get-buffer-process "*claude-code[<project>]*")))
(when proc (set-process-query-on-exit-flag proc nil)))
(with-current-buffer "*claude-code[<project>]*" (claude-code-ide-stop))
若目錄有多個副本(vedmid、vedmid:2、vedmid:masazh...)— 在該緩衝區的背景下使用 with-current-buffer(而非透過 default-directory)呼叫 claude-code-ide-stop:確認訊息將指出已停止的是哪一個("Stopping Claude Code vedmid:masazh...")。
若提示仍舊逃脫並掛起(ps aux | grep emacsclient 顯示卡住的進程)— 切勿自行嘗試解除封鎖(無重複的 --eval、無訊號送至 emacs)— 請要求使用者在即時 Emacs 中回應。風險的完整背景請參閱 emacs.md。
在互動式選取器中導航(恢復清單、權限提示)
工作階段清單(resume)和權限確認選單是在終端機內部渲染的(非透過 Emacs 迷你快取)— 它們透過原始的 process-send-string 以標準 ANSI 序列控制,而非透過 emacsclient --eval 的 Elisp 呼叫:
(process-send-string (get-buffer-process "*claude-code[<project>]*") "\e[B") ; 向下箭頭
(process-send-string (get-buffer-process "*claude-code[<project>]*") "\e[A") ; 向上箭頭
(process-send-string (get-buffer-process "*claude-code[<project>]*") "\e[6~") ; Page Down
(process-send-string (get-buffer-process "*claude-code[<project>]*") "\r") ; Enter(確認)
每次發送按鍵後,先重新讀取緩衝區內容(buffer-substring-no-properties)以查看新的游標位置(❯)和標題 (N of M),然後再繼續移動或確認 — 不要未經檢查就盲目移動數步。直接選擇確認選單項目的數字鍵(如 "3")未經實證為可靠 — 使用箭頭導航 + Enter 更穩妥。
切換 permission-mode(Shift+Tab)— 與 auto-accept 不同
終端機狀態列(⏸ manual mode on、⏵⏵ accept edits on、⏸ plan mode on、⏵⏵ auto mode on)完全由 claude 二進位檔本身控制 — Emacs 對此一無所知。循環(Shift+Tab,四個狀態):manual → accept edits → plan → auto → manual。
(process-send-string (get-buffer-process "*claude-code[<project>]*") "\e[Z")
切換偶爾會觸發中繼的工具確認提示(Do you want to proceed?)— 若目標是返回 auto 模式,請透過箭頭導航(非數字)選擇 "Yes, and switch to auto mode" 選項,然後按 Enter。
claude-code-ide-toggle-auto-accept — 獨立的 Emacs 函數,非相同的狀態
不要與上述的 permission-mode 循環混淆。此函數僅切換工作階段結構上的 Elisp 標記,且僅影響 MCP 工具 openDiff(在儲存檔案前提出 ediff 審視建議):ON → 不顯示 ediff,檔案立即儲存(FILE_SAVED);OFF → 正常的 ediff。終端機狀態列不會因此呼叫而改變。
(with-current-buffer "*claude-code[<project>]*" (claude-code-ide-toggle-auto-accept))
向使用者顯示緩衝區內容,「如同他們正在觀看」
(with-current-buffer "*claude-code[<project>]*" (buffer-substring-no-properties (point-min) (point-max)))
對於大型工作階段,請進行截斷((max (point-min) (- (point-max) N)) (point-max))— 完整內容可能達數 MB,儘管 eat 的可見滾動區始終有限(實際渲染的部分不會無限增長)。