@x-otto/cli 0.0.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +120 -0
  2. package/bin/otto.js +61 -0
  3. package/dist/auth-command-CXAB3_hs.js +11 -0
  4. package/dist/auth-command-CXAB3_hs.js.map +1 -0
  5. package/dist/build-id-BX4QHzM0.js +3 -0
  6. package/dist/build-id-BX4QHzM0.js.map +1 -0
  7. package/dist/bundled-extensions/plugin-otto-wire-protocols/otto-plugin.json +8 -0
  8. package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin-dist/meta.json +1 -0
  9. package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin-dist/plugin.cjs +60023 -0
  10. package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin.ts +64 -0
  11. package/dist/bundled-extensions/plugin-otto-wire-protocols/src/anthropic-messages.ts +916 -0
  12. package/dist/bundled-extensions/plugin-otto-wire-protocols/src/anthropic-usage.ts +81 -0
  13. package/dist/bundled-extensions/plugin-otto-wire-protocols/src/openai-completions.ts +158 -0
  14. package/dist/bundled-extensions/plugin-otto-wire-protocols/src/openai-responses.ts +575 -0
  15. package/dist/bundled-extensions/plugin-skill-inductor/otto-plugin.json +8 -0
  16. package/dist/bundled-extensions/plugin-skill-inductor/plugin-dist/meta.json +1 -0
  17. package/dist/bundled-extensions/plugin-skill-inductor/plugin-dist/plugin.cjs +52781 -0
  18. package/dist/bundled-extensions/plugin-skill-inductor/plugin.ts +77 -0
  19. package/dist/bundled-extensions/plugin-skill-inductor/src/candidate-detector.ts +210 -0
  20. package/dist/bundled-extensions/plugin-skill-inductor/src/candidate-digest.ts +146 -0
  21. package/dist/bundled-extensions/plugin-skill-inductor/src/candidate-store.ts +87 -0
  22. package/dist/bundled-extensions/plugin-skill-inductor/src/episode-store.ts +122 -0
  23. package/dist/bundled-extensions/plugin-skill-inductor/src/episode.ts +125 -0
  24. package/dist/bundled-extensions/plugin-skill-inductor/src/extraction-prompt.ts +97 -0
  25. package/dist/bundled-extensions/plugin-skill-inductor/src/inductor-service.ts +183 -0
  26. package/dist/bundled-extensions/plugin-skill-inductor/src/similarity.ts +58 -0
  27. package/dist/command-dispatcher-BgStVcCF.js +13 -0
  28. package/dist/command-dispatcher-BgStVcCF.js.map +1 -0
  29. package/dist/config-migration-DatflIxs.js +21 -0
  30. package/dist/config-migration-DatflIxs.js.map +1 -0
  31. package/dist/daemon-COESl-zU.js +21 -0
  32. package/dist/daemon-COESl-zU.js.map +1 -0
  33. package/dist/daemon-base-YOWgYYN7.js +3 -0
  34. package/dist/daemon-base-YOWgYYN7.js.map +1 -0
  35. package/dist/debug-CiosFwme.js +34 -0
  36. package/dist/debug-CiosFwme.js.map +1 -0
  37. package/dist/discover-with-bundled-CznLjkkw.js +3 -0
  38. package/dist/discover-with-bundled-CznLjkkw.js.map +1 -0
  39. package/dist/doctor-C84-hhEr.js +30 -0
  40. package/dist/doctor-C84-hhEr.js.map +1 -0
  41. package/dist/doctor-attach-_zeIJewx.js +42 -0
  42. package/dist/doctor-attach-_zeIJewx.js.map +1 -0
  43. package/dist/doctor-cdp-K_mIFRfA.js +5 -0
  44. package/dist/doctor-cdp-K_mIFRfA.js.map +1 -0
  45. package/dist/doctor-heap-Rr2nNvcw.js +11 -0
  46. package/dist/doctor-heap-Rr2nNvcw.js.map +1 -0
  47. package/dist/doctor-ps-OL3FGQYu.js +8 -0
  48. package/dist/doctor-ps-OL3FGQYu.js.map +1 -0
  49. package/dist/extension-Bhzoel6s.js +169 -0
  50. package/dist/extension-Bhzoel6s.js.map +1 -0
  51. package/dist/extension-plugin-CnF6O_zK.js +7 -0
  52. package/dist/extension-plugin-CnF6O_zK.js.map +1 -0
  53. package/dist/extension-plugin-DWcakwRx.js +2 -0
  54. package/dist/extension-v-liRnUX.js +2 -0
  55. package/dist/feedback-BES_6ebi.js +21 -0
  56. package/dist/feedback-BES_6ebi.js.map +1 -0
  57. package/dist/feedback-BRbLUn9u.js +2 -0
  58. package/dist/feishu-setup-D6vnuNLw.js +6 -0
  59. package/dist/feishu-setup-D6vnuNLw.js.map +1 -0
  60. package/dist/index.d.ts +6 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +20 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/install-DlVJRHRT.js +11 -0
  65. package/dist/install-DlVJRHRT.js.map +1 -0
  66. package/dist/install-WCcQ9k9M.js +2 -0
  67. package/dist/mcp-DbTYT7hB.js +12 -0
  68. package/dist/mcp-DbTYT7hB.js.map +1 -0
  69. package/dist/migrate-boDXhEyV.js +14 -0
  70. package/dist/migrate-boDXhEyV.js.map +1 -0
  71. package/dist/model-auth-C27iG6Jq.js +3 -0
  72. package/dist/model-auth-C27iG6Jq.js.map +1 -0
  73. package/dist/model-auth-C4bsWvvn.js +2 -0
  74. package/dist/observe-CVg-MRM2.js +32 -0
  75. package/dist/observe-CVg-MRM2.js.map +1 -0
  76. package/dist/parse-cli-tFDOeVU1.js +3 -0
  77. package/dist/parse-cli-tFDOeVU1.js.map +1 -0
  78. package/dist/persistenced-CCwHu1Ww.js +36 -0
  79. package/dist/persistenced-CCwHu1Ww.js.map +1 -0
  80. package/dist/proxy-BvdBsB1O.js +2 -0
  81. package/dist/proxy-evg7ZvoD.js +3 -0
  82. package/dist/proxy-evg7ZvoD.js.map +1 -0
  83. package/dist/ps-DKrluEuy.js +9 -0
  84. package/dist/ps-DKrluEuy.js.map +1 -0
  85. package/dist/registry-B3nfN62q.js +2 -0
  86. package/dist/registry-D0NONjLv.js +6 -0
  87. package/dist/registry-D0NONjLv.js.map +1 -0
  88. package/dist/remote-persistence-server-B_hONoVR.js +35 -0
  89. package/dist/remote-persistence-server-B_hONoVR.js.map +1 -0
  90. package/dist/run-attach-Ci5jYyCa.js +3 -0
  91. package/dist/run-attach-Ci5jYyCa.js.map +1 -0
  92. package/dist/serve-pVR6UIT9.js +21 -0
  93. package/dist/serve-pVR6UIT9.js.map +1 -0
  94. package/dist/service-client-BLNeugM9.js +3 -0
  95. package/dist/service-client-BLNeugM9.js.map +1 -0
  96. package/dist/time-travel-ZozuPPI-.js +21 -0
  97. package/dist/time-travel-ZozuPPI-.js.map +1 -0
  98. package/dist/version-C0qM-qJM.js +3 -0
  99. package/dist/version-C0qM-qJM.js.map +1 -0
  100. package/package.json +61 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-dispatcher-BgStVcCF.js","names":["detectLanguage","tuiDetect","handleClear","formatBytes","handleList","handleRemove","handleList","handleClear"],"sources":["../src/modes/run-modes.ts","../src/modes/interactive-mode.ts","../src/i18n/index.ts","../src/hot-upgrade.ts","../src/token-estimation.ts","../src/context-budget.ts","../src/commands/interactive-utils.ts","../src/view-model-adapter.ts","../src/edited-file-content.ts","../src/commands/draft-entries.ts","../src/commands/interactive-resume.ts","../src/commands/background-session-store.ts","../src/commands/handlers/session.ts","../src/commands/handlers/attach.ts","../src/commands/handlers/fork.ts","../src/commands/handlers/inspect-context.ts","../src/commands/handlers/usage.ts","../src/commands/shape-note.ts","../src/commands/handlers/memory-compact.ts","../src/commands/handlers/workspace.ts","../src/commands/handlers/upgrade-check.ts","../src/commands/handlers/about.ts","../src/commands/handlers/feedback.ts","../src/commands/provider-auth-options.ts","../src/commands/ftue/ftue-snapshot.ts","../src/commands/ftue/ftue-settings.ts","../src/commands/ftue/ftue-controller.ts","../src/commands/ftue/provider-ranking.ts","../src/commands/ftue/model-ranking.ts","../src/commands/setup-wizard.ts","../src/commands/handlers/setup.ts","../src/commands/model-effort-record.ts","../src/commands/handlers/misc.ts","../src/commands/handlers/schedule.ts","../src/commands/handlers/draft.ts","../src/commands/handlers/queue.ts","../src/commands/handlers/pause-continue.ts","../src/commands/handlers/keybindings.ts","../src/commands/interactive-input-setup.ts","../src/commands/handlers/shortcuts.ts","../src/commands/handlers/open-file.ts","../src/command-dispatcher.ts"],"sourcesContent":["import { getLastAssistantText } from '@x-otto/coding'\n\nimport { MaxToolTurnsError, PromptBudgetExceededError } from '@x-otto/agent'\nimport type { AgentSession } from '@x-otto/runtime'\nimport type { RenderOptions } from '@x-otto/tui'\n\n/**\n * CLI 边界的结构化回合终止原因(对齐业界参考实现的 TurnStatus/TurnAbortReason、error_max_turns\n * 的最小集)。供交互层据此用**独立于流式文本去重**的通道渲染终止通知,而非塞进 assistant 文本流。\n */\nexport type TurnEndReason = 'completed' | 'max-tool-turns' | 'budget-exceeded' | 'aborted' | 'error'\n\nfunction buildMaxTurnsNotice(error: MaxToolTurnsError): string {\n const ceiling =\n error.extensions > 0\n ? `约 ${error.effectiveMaxTurns} 次(含 ${error.extensions} 次自动扩展,每次 ${error.maxTurns})`\n : `${error.maxTurns} 次`\n // RFC-104 M129 审核遗留③:配置 token 预算时窗口已 ×4(error.maxTurns 是有效值非原始配置),\n // 提示用户勿以为 --max-tool-turns 失效。\n return (\n `⚠ 已达工具回合上限(${ceiling}),提前停止——任务可能未完成。` +\n `\\n 重试可加 --max-tool-turns <n>(每轮预算,实际上限 ≈ n×${1 + error.extensions};如 --max-tool-turns ${error.maxTurns * 2})。` +\n `\\n 注:配置了 --prompt-token-budget 时轮次窗口自动 ×4(此处报的是放大后的有效值)。`\n )\n}\n\nfunction describeMaxTurns(session: AgentSession, error: MaxToolTurnsError): string {\n const produced = getLastAssistantText(session.session.messages()) || error.partialText\n const notice = buildMaxTurnsNotice(error)\n return produced ? `${produced}\\n${notice}` : notice\n}\n\n/** RFC-104 D3:token/时间预算超额通知(R7 诚实报数——维度 + used/budget 真实值)。 */\nfunction buildPromptBudgetNotice(error: PromptBudgetExceededError): string {\n const detail =\n error.dimension === 'tokens'\n ? `输出 token 预算 ${error.used}/${error.budget}`\n : `时间预算 ${Math.round(error.used / 1000)}s/${Math.round(error.budget / 1000)}s`\n return (\n `⚠ 已达本回合${error.dimension === 'tokens' ? '输出 token' : '墙钟时间'}预算(${detail}),提前停止——任务可能未完成。` +\n `\\n 重试可加 ${error.dimension === 'tokens' ? '--prompt-token-budget <n>' : '--prompt-time-budget <分钟>'} 提升预算。`\n )\n}\n\nfunction describePromptBudget(session: AgentSession, error: PromptBudgetExceededError): string {\n const produced = getLastAssistantText(session.session.messages()) || error.partialText\n const notice = buildPromptBudgetNotice(error)\n return produced ? `${produced}\\n${notice}` : notice\n}\n\nexport interface JsonModeResult {\n sessionId: string\n status: string\n messageCount: number\n response: string\n}\n\nexport type InteractiveResult =\n | { type: 'response'; text: string; endReason?: TurnEndReason; notice?: string }\n | { type: 'system'; text: string }\n | { type: 'exit'; restart?: boolean }\n | { type: 'noop' }\n | { type: 'help' }\n\nexport interface PrintModeOptions {\n writer?: (text: string) => void\n tui?: boolean\n tuiOptions?: RenderOptions\n /**\n * 回合非正常终止(如撞工具预算)时回调,携带结构化原因 + 通知文本。\n * 提供此回调即表示调用方会**自行结构化渲染通知**——此时 runPrintMode 的 string 返回**不再**附带\n * 通知(只回 produced),避免交互层既走回调又把通知塞进 result.text 造成双渲;未提供则维持旧契约。\n */\n onTurnEnd?: (reason: TurnEndReason, notice: string) => void\n}\n\nexport async function runPrintMode(\n session: AgentSession,\n prompt: string,\n opts: PrintModeOptions | ((text: string) => void) = {},\n): Promise<string> {\n const normalized: PrintModeOptions = typeof opts === 'function' ? { writer: opts } : opts\n const { writer, tui = false, tuiOptions, onTurnEnd } = normalized\n const defaultWriter = (text: string) => process.stdout.write(`${text}\\n`)\n const write = writer ?? defaultWriter\n const startTime = Date.now()\n\n try {\n await session.prompt(prompt)\n } catch (error) {\n // RFC-104 评审 A2:MaxToolTurnsError 与 PromptBudgetExceededError 对称 catch——\n // 缺一侧则新错误裸抛 CLI 顶层,重演 RFC-008 §1 裸栈崩溃。\n if (error instanceof MaxToolTurnsError || error instanceof PromptBudgetExceededError) {\n const isBudget = error instanceof PromptBudgetExceededError\n const notice = isBudget ? buildPromptBudgetNotice(error) : buildMaxTurnsNotice(error)\n onTurnEnd?.(isBudget ? 'budget-exceeded' : 'max-tool-turns', notice)\n const response = onTurnEnd\n ? getLastAssistantText(session.session.messages()) || error.partialText\n : isBudget\n ? describePromptBudget(session, error)\n : describeMaxTurns(session, error)\n if (tui) {\n const { printMessages } = await import('@x-otto/tui')\n const msgs = session.session.messages()\n printMessages(msgs, { ...tuiOptions, startTime, contextWindow: 0 })\n write(response)\n } else {\n write(response)\n }\n return response\n }\n throw error\n }\n\n if (onTurnEnd && session.budgetSteeredThisPrompt) {\n onTurnEnd(\n 'max-tool-turns',\n '⚠ 已达工具回合上限——已自动收尾本回合(任务可能未全部完成,可继续指示我,或加 --max-tool-turns 提升预算)。',\n )\n }\n\n if (tui) {\n const { printMessages } = await import('@x-otto/tui')\n const msgs = session.session.messages()\n printMessages(msgs, {\n ...tuiOptions,\n startTime,\n contextWindow: session.model.contextWindow ?? 0,\n })\n return getLastAssistantText(msgs)\n }\n\n const response = getLastAssistantText(session.session.messages())\n write(response)\n return response\n}\n\nexport async function runJsonMode(session: AgentSession, prompt: string): Promise<JsonModeResult> {\n try {\n await session.prompt(prompt)\n } catch (error) {\n if (error instanceof MaxToolTurnsError) {\n return {\n sessionId: session.id,\n status: 'max_turns',\n messageCount: session.session.messages().length,\n response: getLastAssistantText(session.session.messages()) || error.partialText,\n }\n }\n if (error instanceof PromptBudgetExceededError) {\n return {\n sessionId: session.id,\n status: 'budget_exceeded',\n messageCount: session.session.messages().length,\n response: getLastAssistantText(session.session.messages()) || error.partialText,\n }\n }\n throw error\n }\n return {\n sessionId: session.id,\n status: session.status,\n messageCount: session.session.messages().length,\n response: getLastAssistantText(session.session.messages()),\n }\n}\n","import { execSync } from 'node:child_process'\n\nimport { createBuiltinCommands } from '@x-otto/tui/compat'\nimport { checkShellCommand } from '@x-otto/shared'\n\nimport type { AgentMessage } from '@x-otto/agent'\nimport type { AgentSession } from '@x-otto/runtime'\nimport type { CommandResult } from '@x-otto/tui'\n\nimport { runPrintMode, type InteractiveResult, type TurnEndReason } from './run-modes'\n\n/**\n * interactive-mode.ts — 有状态交互式 REPL 控制器(`otto -i` 无 TUI 桥接场景,及单测直调)。\n *\n * 与 `run-modes.ts` 的 `runPrintMode`/`runJsonMode`(无状态、单轮、调用即用即弃)生命周期\n * 完全不同——`InteractiveMode` 在构造函数里初始化命令注册表,贯穿整个交互会话持续存活,\n * 处理 `!`shell / `@`file / `/`command / 纯文本四种输入分流。原与 run-modes.ts 同文件,\n * 终局架构 review 后拆分为独立文件(纯移动,零行为变化)。\n */\nexport class InteractiveMode {\n private commandRegistry: import('@x-otto/tui').CommandRegistry\n private modelList: Array<{ id: string; name: string; provider: string }> = []\n private currentModelId: string | undefined\n private onModelChange: ((modelId: string) => Promise<string>) | undefined\n\n constructor(\n private readonly session: AgentSession,\n opts: {\n models?: Array<{ id: string; name: string; provider: string }>\n currentModel?: string\n onModelChange?: (modelId: string) => Promise<string>\n } = {},\n ) {\n this.modelList = opts.models ?? []\n this.currentModelId = opts.currentModel\n this.onModelChange = opts.onModelChange\n this.commandRegistry = createBuiltinCommands()\n }\n\n getRegistry(): import('@x-otto/tui').CommandRegistry {\n return this.commandRegistry\n }\n\n abort(): void {\n this.session.abort()\n }\n\n async handleInput(input: string): Promise<InteractiveResult> {\n if (this.session.status === 'archived') {\n return {\n type: 'system',\n text: '此会话已归档(archived),无法发送新消息。使用 /resume 切换到其他会话。',\n }\n }\n\n const text = input.trim()\n if (text.length === 0) {\n return { type: 'noop' }\n }\n\n // RFC-189 D3/R3:本分支仅纯 print 模式(`otto -p \"!ls\"` 一次性脚本化调用)可达——TUI\n // 交互路径已在 `interactive.ts` 的 mode 分发点(`mode === 'bash' || mode === 'bash_async'`)\n // short-circuit 到异步 `runAsyncBash`,不会落到此处。print 模式无并发 Ink 渲染层需要\n // 保持响应,同步执行在此场景下没有用户可感知的界面冻结问题,故保留 execSync(R3 明确\n // 例外,非本 RFC 治理范围)。\n if (text.startsWith('!')) {\n const cmd = text.slice(1).trim()\n if (!cmd) return { type: 'system', text: 'Usage: !<shell command>' }\n\n // 拒绝危险命令,引导用户走 bash tool\n const check = checkShellCommand(cmd)\n if (!check.safe) {\n return {\n type: 'system',\n text: `Refusing to execute dangerous command (${check.reason}): ${cmd}\\nUse the bash tool via the agent for privileged operations.`,\n }\n }\n\n let output: string\n let isError = false\n try {\n output = execSync(cmd, {\n encoding: 'utf-8',\n timeout: 30_000,\n cwd: this.session.workspaceDir,\n maxBuffer: 1024 * 1024,\n })\n } catch (e) {\n const err = e as { stderr?: string; stdout?: string; message?: string }\n output = err.stderr || err.stdout || err.message || String(e)\n isError = true\n }\n output = output.trimEnd()\n\n this.session.session.append({\n role: 'system',\n subtype: 'local_command',\n level: isError ? 'error' : 'info',\n content: output || '(no output)',\n timestamp: Date.now(),\n } as AgentMessage)\n\n return { type: 'response', text: output || '(no output)' }\n }\n\n if (text.startsWith('@')) {\n const filePath = text.slice(1).trim()\n if (!filePath) return { type: 'system', text: 'Usage: @<file-path>' }\n const response = await runPrintMode(this.session, `Read and analyze the file: ${filePath}`, {\n writer: () => {},\n })\n return { type: 'response', text: response }\n }\n\n if (!text.startsWith('/')) {\n let endReason: TurnEndReason | undefined\n let notice: string | undefined\n const response = await runPrintMode(this.session, text, {\n writer: () => {},\n onTurnEnd: (reason, msg) => {\n endReason = reason\n notice = msg\n },\n })\n return { type: 'response', text: response, endReason, notice }\n }\n\n const [rawCommand, ...rest] = text.slice(1).split(/\\s+/)\n if (!rawCommand) {\n const { showCommandPalette } = await import('@x-otto/tui')\n const result = await showCommandPalette(\n this.commandRegistry.list(),\n process.stdin,\n process.stdout,\n )\n if (!result.command) return { type: 'noop' }\n const cmd = this.commandRegistry.get(result.command)\n if (!cmd) return { type: 'noop' }\n const execResult = await cmd.execute(result.args, {\n signal: new AbortController().signal,\n width: process.stdout.columns ?? 80,\n models: this.modelList,\n currentModel: this.currentModelId,\n onModelChange: this.onModelChange,\n })\n return this._handleCommandResult(execResult)\n }\n\n const args = rest.join(' ')\n const cmd = this.commandRegistry.find(rawCommand!)\n\n if (!cmd) {\n return { type: 'system', text: `Unknown command: /${rawCommand}. Try /help.` }\n }\n\n const result = await cmd.execute(args, {\n signal: new AbortController().signal,\n width: process.stdout.columns ?? 80,\n models: this.modelList,\n currentModel: this.currentModelId,\n onModelChange: this.onModelChange,\n })\n\n return this._handleCommandResult(result)\n }\n\n private _handleCommandResult(result: CommandResult): InteractiveResult {\n // 文本类命令(模型列表/插件命令等)直接透传为系统消息。\n if (typeof result === 'string') {\n return { type: 'system', text: result }\n }\n\n // 控制类命令:判别联合按 kind 分发(取代原字符串哨兵协议)。\n switch (result.kind) {\n case 'exit':\n return { type: 'exit', restart: result.restart }\n case 'help':\n return { type: 'help' }\n case 'clear':\n // 与 commands/handlers/session.ts handleClear 的引擎侧效果对齐(该 handler 优先拦截,\n // 本分支只在绕过 command-dispatcher 直调 InteractiveMode.handleInput 时才会触发,\n // 例如无 TUI 桥接的轻量嵌入场景)。此前只调 clearContext,todo/editedFiles/subagents\n // 面板残留——同一会话对象两条 /clear 路径行为不一致。TUI 专属效果(toast/budget 面板/\n // scrollback 清空)不适用于本类(无 TuiBridge),故不在此处复刻。\n this.session.clearTurnPanels()\n void this.session.clearContext()\n process.stdout.write('\\x1B[2J\\x1B[H')\n return { type: 'noop' }\n case 'abort':\n this.session.abort()\n return { type: 'system', text: 'Aborted current run.' }\n case 'status':\n return {\n type: 'system',\n text: `Session: ${this.session.id} | Status: ${this.session.status}`,\n }\n case 'copy':\n return { type: 'system', text: 'Copy not yet available in CLI mode.' }\n case 'model': {\n if (this.onModelChange) {\n this.onModelChange(result.modelId)\n .then((msg) => {\n process.stdout.write(msg + '\\n')\n })\n .catch((err: unknown) => {\n process.stdout.write(\n `Model switch failed: ${err instanceof Error ? err.message : String(err)}\\n`,\n )\n })\n return { type: 'system', text: `Switching to ${result.modelId}...` }\n }\n return { type: 'system', text: `Model switching not available.` }\n }\n }\n }\n}\n","/**\n * Cli i18n facade(RFC-264:复用 tui 的 react-i18next 实例,不再维护独立 STR 副本)。\n *\n * cli 文案 100% 被 tui i18next 资源覆盖,消费点统一用 `i18next.t`(from '@x-otto/tui')。\n * 本 facade 只保留 detectLanguage + setLanguage(委托 tui initI18n)供 cli 启动/切换调用。\n */\nimport { detectLanguage as tuiDetect, initI18n } from '@x-otto/tui'\n\nimport type { Lang, LangMode } from '@x-otto/tui'\n\nexport type { Lang, LangMode }\n\n/** 自动检测语言(委托 tui)。 */\nexport function detectLanguage(): Lang {\n return tuiDetect()\n}\n\n/** 设置语言:委托 tui i18next(initI18n 幂等 + changeLanguage)。返回最终 locale。 */\nexport function setLanguage(mode: LangMode = 'auto'): Lang {\n const resolved: Lang = mode === 'auto' ? tuiDetect() : mode\n initI18n(resolved)\n return resolved\n}\n","/**\n * hot-upgrade.ts — otto 热升级:npm registry 版本检查 + 自替换 + re-spawn.\n *\n * 启动时异步查远端最新版本;若更高则推送通知 + 记录到 config。\n * /upgrade 命令会下载最新版、原地替换、然后 re-spawn 进程(不需要手动 Ctrl+C)。\n */\nimport { spawn } from 'node:child_process'\n\n/** npm registry 远端版本查询结果。 */\nexport interface VersionInfo {\n /** 远端最新版本号(如 \"0.2.0\")。 */\n latest: string\n /** 当前运行版本。 */\n current: string\n /** 是否比当前更新。 */\n newer: boolean\n}\n\n/** 远端包名。走 @x-otto/cli 而非裸 otto(与发布策略对齐)。 */\nconst PACKAGE_NAME = '@x-otto/cli'\nconst REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`\nconst CHECK_INTERVAL_MS = 6 * 3600 * 1000\n\n/**\n * 从 npm registry 获取最新版本号。\n * 返回格式:`{ version, current, newer }`。\n */\nexport async function fetchLatestVersion(currentVersion: string): Promise<VersionInfo | null> {\n try {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), 5000)\n const res = await fetch(REGISTRY_URL, { signal: controller.signal })\n clearTimeout(timer)\n if (!res.ok) return null\n const data = (await res.json()) as { version?: string }\n const latest = data?.version ?? ''\n if (!latest) return null\n const newer = compareVersions(latest, currentVersion) > 0\n return { latest, current: currentVersion, newer }\n } catch {\n return null\n }\n}\n\n/** semver 简易比较(不依赖 semver 库:解析 major.minor.patch)。 */\nfunction compareVersions(a: string, b: string): number {\n const pa = parseSemver(a)\n const pb = parseSemver(b)\n if (!pa || !pb) return 0\n for (let i = 0; i < 3; i++) {\n if (pa[i]! !== pb[i]!) return pa[i]! - pb[i]!\n }\n return 0\n}\n\nfunction parseSemver(v: string): [number, number, number] | null {\n const m = /(\\d+)\\.(\\d+)\\.(\\d+)/.exec(v)\n if (!m) return null\n return [Number(m[1]), Number(m[2]), Number(m[3])]\n}\n\n/**\n * 执行 npm install -g @x-otto/cli@latest。\n * 返回 true 表示安装成功。\n */\nexport async function upgradeInstall(): Promise<{ ok: boolean; error?: string }> {\n return new Promise((resolve) => {\n try {\n const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'\n const child = spawn(npm, ['install', '-g', `${PACKAGE_NAME}@latest`], {\n stdio: 'inherit',\n shell: true,\n })\n child.on('error', (err) => {\n resolve({ ok: false, error: err.message })\n })\n child.on('exit', (code) => {\n if (code !== 0) {\n resolve({ ok: false, error: `npm install exited with code ${code}` })\n return\n }\n resolve({ ok: true })\n })\n } catch (err) {\n resolve({ ok: false, error: (err as Error).message })\n }\n })\n}\n\n// 唯一 restart spawn 策略是 cli.ts 的陪跑 spawn(非 detached、非 unref,父进程陪跑至子退出);\n// 热升级经 forceUpgrade → upgradeInstall() + restartRequested → cli.ts 同一陪跑路径,不再单独 re-spawn。\n\n/** 是否应检查更新(距上次检查 < CHECK_INTERVAL_MS 则跳过)。 */\nexport function shouldCheckUpdate(lastCheck: number | undefined): boolean {\n if (!lastCheck) return true\n return Date.now() - lastCheck > CHECK_INTERVAL_MS\n}\n\n/** Promise 化的版本检查(完整链路:fetch → compare → persist)。抽离便于集成测试。 */\nexport async function checkForUpdate(opts: {\n currentVersion: string\n lastCheck: number | undefined\n autoCheck: boolean\n persistLastCheck: () => Promise<unknown>\n}): Promise<{ latest: string; current: string; newer: boolean; forced: boolean } | null> {\n const mock = process.env['OTTO_UPGRADE_MOCK'] as string | undefined\n if (mock) {\n const forced = mock === 'force'\n return {\n latest: forced ? '2.0.0' : '0.2.0',\n current: '0.0.1',\n newer: true,\n forced,\n }\n }\n\n if (!opts.autoCheck || !shouldCheckUpdate(opts.lastCheck)) return null\n return fetchLatestVersion(opts.currentVersion)\n .then(async (info) => {\n if (info) {\n void opts.persistLastCheck().catch(() => {})\n if (info.newer) return { ...info, forced: false }\n }\n return null\n })\n .catch(() => null)\n}\n","/**\n * system prompt / tools token 估算(纯函数,基于字符数与 claude tokenizer 对齐)。\n */\nimport { estimateTokensHeuristic } from '@x-otto/memory'\n\n/** 轻量 token 估算(字符分类启发式,单源见 @x-otto/memory estimateTokensHeuristic)。 */\nexport const estimateTokens = estimateTokensHeuristic\n\n/** 工具 schema token 缓存:工具集(按 name 指纹)不变则复用估算值。 */\nlet _cachedToolTokens: number | undefined\nlet _cachedToolFingerprint: string | undefined\n\nexport function getToolTokens(\n tools: ReadonlyArray<{ name: string; description?: unknown; parameters?: unknown }>,\n): number {\n const fingerprint = tools\n .map((t) => t.name)\n .sort()\n .join(',')\n if (_cachedToolTokens !== undefined && _cachedToolFingerprint === fingerprint) {\n return _cachedToolTokens\n }\n const desc = (d: unknown): string =>\n typeof d === 'function' ? '[fn]' : typeof d === 'string' ? d : ''\n const schema = JSON.stringify(\n tools.map((t) => ({\n name: t.name,\n description: desc(t.description),\n parameters: t.parameters,\n })),\n )\n _cachedToolTokens = estimateTokens(schema)\n _cachedToolFingerprint = fingerprint\n return _cachedToolTokens\n}\n\n/** system prompt token 缓存:同一字符串引用/等值文本复用估算(backlog T7——\n * recomputeBudget 被每个 text_delta 驱动,30k+ 字符的 system prompt 每次全量\n * 字符分类扫描是流式期热点;prompt 仅在回合边界/注入变更时变化)。 */\nlet _cachedSystemPromptTokens: number | undefined\nlet _cachedSystemPrompt: string | undefined\n\nexport function getSystemPromptTokens(systemPrompt: string): number {\n if (_cachedSystemPromptTokens !== undefined && _cachedSystemPrompt === systemPrompt) {\n return _cachedSystemPromptTokens\n }\n _cachedSystemPromptTokens = estimateTokens(systemPrompt)\n _cachedSystemPrompt = systemPrompt\n return _cachedSystemPromptTokens\n}\n\n/** 仅测试:重置 tool token 缓存。 */\nexport function _resetToolTokenCache(): void {\n _cachedToolTokens = undefined\n _cachedToolFingerprint = undefined\n _cachedSystemPromptTokens = undefined\n _cachedSystemPrompt = undefined\n}\n","/**\n * 上下文三段预算计算(overhead/messages/free)+ 纯文本渲染。\n * compact/prune 触发由 @x-otto/memory 独立决策;pruneLine/compactLine 仅作进度条标记。\n */\n\nimport {\n resolveContextSize,\n DEFAULT_PRUNE_CONFIG,\n DEFAULT_COMPACTION_CONFIG,\n} from '@x-otto/memory'\n\nimport type { BudgetView } from '@x-otto/tui'\nimport { getSystemPromptTokens, getToolTokens } from './token-estimation'\n\nexport interface ContextBudgetInput {\n /** system prompt 文本(含所有注入)。 */\n systemPrompt: string\n /** 工具定义列表。 */\n tools: ReadonlyArray<{ name: string; description?: unknown; parameters?: unknown }>\n /** 最后一轮 input_tokens(API usage,非 cache 部分)。 */\n lastIn: number\n /** 最后一轮 output_tokens。 */\n lastOut: number\n /** 最后一轮 cache_read_input_tokens。 */\n lastCacheRead: number\n /** 最后一轮 cache_creation_input_tokens。 */\n lastCacheWrite: number\n /** 上下文窗口大小(模型上限)。 */\n contextWindow: number\n /** 模型展示名(可选)。 */\n modelName?: string\n /** 累计 input tokens(整场对话)。 */\n totIn?: number\n /** 累计 output tokens(整场对话)。 */\n totOut?: number\n /** 累计成本(美元,可选)。 */\n cost?: number\n /** 当前会话消息条数(用于 messages 段显示 token(条数))。 */\n messageCount?: number\n /**\n * 尚未被 provider usage 结算的本地实时估算 token(当前 prompt + 已产出 tool result 入模量\n * + 流式输出)。仅在 live 刷新时传递——回合边界计算不传(此时 usage 已完整、走权威口径)。\n */\n streamingOutputTokens?: number\n}\n\nexport type ContextBudget = BudgetView\n\n/**\n * 根据 Claude API usage 数据 + 本地估算,计算三段上下文预算。\n *\n * overheadTokens = estimateTokens(systemPrompt) + getToolTokens(tools) ← 估算\n * totalUsed = cacheRead + cacheWrite + input + output ← API 精确\n * messageTokens = max(0, totalUsed − overheadTokens) ← 历史占用(cache 下含历史)\n * free = contextWindow − overhead − messages ← 三段恒闭合于 window\n */\nexport function computeContextBudget(input: ContextBudgetInput): ContextBudget {\n const {\n systemPrompt,\n tools,\n lastIn,\n lastOut,\n lastCacheRead,\n lastCacheWrite,\n contextWindow,\n modelName = '',\n totIn = lastIn,\n totOut = lastOut,\n cost,\n messageCount = 0,\n streamingOutputTokens = 0,\n } = input\n\n const overheadTokens = getSystemPromptTokens(systemPrompt) + getToolTokens(tools)\n const cacheTotal = lastCacheRead + lastCacheWrite\n const totalUsed = cacheTotal + lastIn + lastOut + streamingOutputTokens\n const messageTokens = Math.max(0, totalUsed - overheadTokens)\n const free = Math.max(0, contextWindow - overheadTokens - messageTokens)\n\n // 经 @x-otto/memory 触发口径单一真源派生(compact 已改绝对 headroom),\n // 避免显示线与引擎实际触发漂移(固定 85% 在大/小窗都骗人)。\n const pruneLine = resolveContextSize(DEFAULT_PRUNE_CONFIG.trigger, contextWindow)\n const compactLine = resolveContextSize(DEFAULT_COMPACTION_CONFIG.trigger, contextWindow)\n const contextPercent = contextWindow > 0 ? (totalUsed / contextWindow) * 100 : 0\n\n return {\n modelName,\n contextWindow,\n contextPercent,\n overheadTokens,\n messageTokens,\n messageCount,\n totalUsed,\n free,\n pruneLine,\n compactLine,\n lastInput: lastIn,\n lastOutput: lastOut,\n totalInput: totIn,\n totalOutput: totOut,\n cost,\n // live = 本次读数掺入了未结算的本地估算(见 streamingOutputTokens 注释)。\n live: streamingOutputTokens > 0,\n }\n}\n\nconst BAR_COLORS: Record<string, string> = {\n overhead: '\\x1b[38;5;214m',\n messages: '\\x1b[38;5;48m',\n free: '\\x1b[38;5;243m',\n}\nconst RESET = '\\x1b[0m'\n\nfunction kfmt(n: number): string {\n if (n < 1000) return String(n)\n if (n < 1_000_000) return `${(n / 1000).toFixed(n < 10_000 ? 1 : 0)}k`\n return `${(n / 1_000_000).toFixed(1)}m`\n}\n\nfunction c(s: string, color: string): string {\n return `${color}${s}${RESET}`\n}\n\nexport function renderContextBarText(b: ContextBudget): string[] {\n const barW = 30\n const pct = Math.max(0, Math.min(100, Math.round(b.contextPercent)))\n\n const kinds: Array<{ kind: string; tokens: number; label: string }> = [\n { kind: 'overhead', tokens: b.overheadTokens, label: 'overhead' },\n { kind: 'messages', tokens: b.messageTokens, label: 'messages' },\n { kind: 'free', tokens: b.free, label: 'free' },\n ]\n const active = kinds.filter((k) => k.tokens > 0)\n const totalTokens = active.reduce((s, k) => s + k.tokens, 0)\n\n const segments: Array<{ kind: string; width: number; tokens: number; label: string }> = []\n let allocated = 0\n for (let i = 0; i < active.length; i++) {\n const k = active[i]!\n const isLast = i === active.length - 1\n const w = isLast ? barW - allocated : Math.max(1, Math.round((k.tokens / totalTokens) * barW))\n segments.push({ kind: k.kind, width: w, tokens: k.tokens, label: k.label })\n allocated += w\n }\n\n const usedLabel = pct >= 90 ? '⚠ ' : pct >= 80 ? '◆ ' : ''\n\n const lines: string[] = []\n\n if (b.modelName) {\n lines.push(`${b.modelName} · ${kfmt(b.contextWindow)} tokens`)\n } else {\n lines.push(`${kfmt(b.contextWindow)} tokens`)\n }\n lines.push('')\n\n const barStr = segments.map(seg => c('█'.repeat(seg.width), BAR_COLORS[seg.kind] ?? RESET)).join('')\n lines.push(`${usedLabel}${String(pct).padStart(3)}% ${barStr} ${kfmt(b.totalUsed)}/${kfmt(b.contextWindow)}`)\n lines.push('')\n\n // 百分比从实际触发线动态派生(随窗口变:200K≈80% / 1M≈96%),不钉死 85%。\n const prunePct = b.contextWindow > 0 ? Math.round((b.pruneLine / b.contextWindow) * 100) : 0\n const compactPct = b.contextWindow > 0 ? Math.round((b.compactLine / b.contextWindow) * 100) : 0\n if (pct > 55) {\n lines.push(`▼ prune (${prunePct}%) ${kfmt(b.pruneLine)}`)\n }\n if (pct > 75) {\n lines.push(`▼ compact (${compactPct}%) ${kfmt(b.compactLine)}`)\n }\n lines.push('')\n\n for (const seg of active) {\n const block = c('█', BAR_COLORS[seg.kind] ?? RESET)\n const count = seg.kind === 'messages' && b.messageCount > 0 ? `(${b.messageCount})` : ''\n lines.push(` ${block} ${seg.label.padEnd(10)} ${kfmt(seg.tokens)}${count}`)\n }\n lines.push('')\n\n lines.push(`last ${kfmt(b.lastInput)} in · ${kfmt(b.lastOutput)} out · cost ${b.cost !== undefined ? '$' + b.cost.toFixed(4) : 'N/A'}`)\n lines.push(`total ${kfmt(b.totalInput)} in · ${kfmt(b.totalOutput)} out`)\n\n return lines\n}\n","/**\n * interactive-utils.ts — 交互式 TUI 的小工具函数/常量。\n *\n * 从 interactive.ts 拆分出来以控制文件行数。零行为变更。\n */\nimport { discoverFileCommands } from '@x-otto/coding'\nimport { isAuthError, classifyAuthErrorType } from '@x-otto/ai'\n\nimport type { App, NotificationKind } from '@x-otto/coding'\nimport type { PermissionMode } from '@x-otto/setting'\n\n/**\n * 统一的 provider 展示名兜底表(大小写一致)。\n *\n * 回归修复(真实用户反馈 \"/login 需要根据插件来显示,不是固定的\"):`anthropic`/\n * `deepseek`/`openai-codex`/`github-copilot` 此前硬编码在这里——插件声明式 provider\n * (如 `plugin-anthropic:anthropic`)若不在这张表里就退化成裸 id 展示,且每新增一个插件\n * provider 都要手工改宿主代码,违反 RFC-105\"宿主对具体插件零认知\"原则。现在这些名字改为\n * 从插件 manifest 的 `providerEntrySchema.name` 字段动态推导(见\n * `packages/coding/src/plugin/provider-loader.ts` 装载时回填 `ProviderDescriptor.name`,\n * `model-auth.ts` 的 `displayName()` 优先读取),本表已缩减为只保留没有任何 descriptor/OAuth\n * 注册路径可达的历史遗留兜底项——`openai`(标准 OpenAI API-key provider,当前未被任何插件/\n * 内置注册声明,只有用户手动设置对应凭据时才会出现在 `/auth` 列表里)。\n * 查找优先级见 `model-auth.ts displayName()`:oauth.get(p)?.name → getProviderDescriptor(p)?.name\n * → 本表 → 裸 id。\n */\nexport const PROVIDER_DISPLAY_NAMES: Record<string, string> = {\n openai: 'OpenAI',\n}\n\n/**\n * 权限 mode 循环顺序(**UI 策略**,RFC-077 ARCH-077-12 从 App 下沉到宿主呈现层):\n * Confirm → Auto → Autopilot(bypass) → …;专家档(strict/readonly)不在循环内——从它们切回 confirm。\n * App 只保留 setPermissionMode 持久化机制,循环次序由宿主决定。\n */\nexport function nextPermissionMode(cur: PermissionMode): PermissionMode {\n const cycle: PermissionMode[] = ['confirm', 'auto', 'bypass']\n const idx = cycle.indexOf(cur)\n return cycle[(idx + 1) % cycle.length] ?? 'confirm'\n}\n\n/**\n * 通知 kind → 视觉 level 映射(**UI 策略**,RFC-077 ARCH-077-12 从 App 下沉到宿主呈现层):\n * error→error;需批准/输入→warning;其余(turn_complete 等)→info。App 透传原始 kind,呈现严重度由宿主决定。\n */\nexport function notificationLevel(kind: NotificationKind): 'info' | 'warning' | 'error' {\n return kind === 'error'\n ? 'error'\n : kind === 'approval_required' || kind === 'input_required'\n ? 'warning'\n : 'info'\n}\n\n/** submit catch 场景分流结果(RFC-147 M3 D-C2 场景矩阵,供 classifySubmitError 产出)。 */\nexport interface SubmitErrorClassification {\n /** 认证失效(结构化 PROVIDER_AUTH_MISSING/REFRESH_FAILED 或文本兜底 401/unauthorized/api key)。 */\n authError: boolean\n /** authError 子类:token 刷新失败(vs 本地无凭据/服务端 401)——影响提示文案选择。 */\n isRefreshFailed: boolean\n /** 模型不受当前账号支持。 */\n unsupported: boolean\n /** 瞬时限流文案命中(自动退避已耗尽后仍 429,非硬配额 rejected——那条走 rateLimitInfo 事件路径)。 */\n rateLimit: boolean\n /**\n * RFC-153 D6:错误来源插件 id(透传自 `error.pluginSource`,`CodedError`/`ToolResult` 上\n * 由插件适配层打的标记)。仅服务\"标记来源、供 UI 展示\",不参与本函数任何分类判定逻辑\n * (规则 8:不改变现有重试/降级决策)。\n */\n pluginSource: string | undefined\n}\n\n/**\n * submit catch 错误场景分类(RFC-147 M3 D-C2 场景矩阵,从 interactive.ts 抽出的纯函数——\n * 原逻辑内联在 runInteractiveCommand 巨型闭包中不可单测,见 error-retry-classification.test.ts)。\n *\n * 优先信任结构化 `error.code`(PROVIDER_AUTH_MISSING/PROVIDER_AUTH_REFRESH_FAILED,RFC-137);\n * 文本正则兜底覆盖底层 SDK 未走 ProviderError 包装、直接抛裸 Error 的边界情况。\n * 硬配额耗尽(rate.limit.exhausted 事件/rateLimitInfo)不在本函数范围——那条走独立的事件驱动\n * 路径(同步 emit 早于本函数消费的 catch block),本函数只覆盖文本正则可判定的瞬时/无事件场景。\n *\n * 终局 review 统一(2026-07-21):结构化 code 判定改为委托引擎侧 `isAuthError`/\n * `classifyAuthErrorType`(`@x-otto/provider`,经 `@x-otto/ai` re-export)而非本函数独立\n * `=== 'PROVIDER_AUTH_MISSING'` 字面量比较——此前 CLI 与引擎(`work-loop-stream-error.ts`\n * 的 `stream.auth-failed` 分类)各自维护一份相同的错误码判断,未来引擎新增分类时 CLI\n * 侧不会自动跟进,是隐性漂移点。errCode 在调用方(`handleSubmitCatchError`)已被摊平为\n * 字符串(只读取 `err.code` 单层,不像引擎函数那样走 3 层 cause 链),故构造最小合成对象\n * `{ code: errCode }` 喂给引擎函数——两者只读 `.code`/`.cause`/`.status`/`.statusCode` 字段,\n * 合成对象足以复用同一套判定,不需要改变本函数对外签名(避免连锁改动调用点与既有测试)。\n */\nexport function classifySubmitError(\n msg: string,\n errCode: string | undefined,\n pluginSource?: string,\n): SubmitErrorClassification {\n const syntheticError = errCode !== undefined ? { code: errCode } : undefined\n const structuredAuthError = syntheticError !== undefined && isAuthError(syntheticError)\n const isRefreshFailed =\n structuredAuthError && classifyAuthErrorType(syntheticError!) === 'refresh-failed'\n const authError =\n structuredAuthError ||\n /\\b401\\b|authentication|unauthorized|api[\\s_-]?key|invalid.*key/i.test(msg)\n const unsupported = /model_not_supported|not\\s+supported|invalid.*model|model.*invalid/i.test(msg)\n const rateLimit = /\\b429\\b|rate.limit|rate_limit|exceed.*rate/i.test(msg)\n return { authError, isRefreshFailed, unsupported, rateLimit, pluginSource }\n}\n\nexport function formatEffortLabel(effort: string | undefined): string {\n if (!effort) return ''\n // RFC-235:xhigh → 'XHigh'(驼峰保留 H 大写);其余(low/medium/high/max)首字母大写。\n if (effort.toLowerCase() === 'xhigh') return 'XHigh'\n return effort.charAt(0).toUpperCase() + effort.slice(1)\n}\n\n/**\n * Switch — 布尔开关态的统一文案格式化:「符号 + 文字」(如 `◉ 开启` / `○ 关闭`)。\n *\n * 命令→SelectDialog 通道的开关项走字符串 `trailingLabel`(非 JSX),无法塞入 JSX 组件,\n * 故 Switch 的载体是本纯函数——把各面板重复的 `on ? ◉ 开 : ○ 关` 三元收成单一可复用点。\n * 符号先行做非辨色状态锚点、文字辅助可读性,与 InspectorPane/SchedulePane 全仓开关渲染\n * 惯例一致(FIGURES.toggleOn/Off 前缀)。\n *\n * @param on 开关是否开启。\n * @param glyphs 符号对(FIGURES.toggleOn / FIGURES.toggleOff)。\n * @param labels 当前 locale 的开/关文案(STR.toggleOn / STR.toggleOff)。\n */\nexport function formatSwitch(\n on: boolean,\n glyphs: { on: string; off: string },\n labels: { on: string; off: string },\n): string {\n return on ? `${glyphs.on} ${labels.on}` : `${glyphs.off} ${labels.off}`\n}\n\n/**\n * 模型显示名格式化:\n * `id·Effort (128K)` / `id·Effort (1.2M)`;无 id → `(no model)`;registry 查不到窗口 → 裸 id。\n */\nexport function formatDisplayModel(\n app: { modelRegistry: { find: (id: string) => { contextWindow?: number } | undefined } },\n id: string | undefined,\n effort?: string,\n): string {\n if (!id) return '(no model)'\n const effortLabel = formatEffortLabel(effort)\n const name = effortLabel ? `${id}·${effortLabel}` : id\n const cw = app.modelRegistry.find(id)?.contextWindow\n if (!cw) return name\n const win = cw >= 1_000_000 ? `${(cw / 1_000_000).toFixed(1)}M` : `${Math.round(cw / 1000)}K`\n return `${name} (${win})`\n}\n\n/**\n * 发现文件式 slash 命令(.claude/commands + .otto/commands,信任门已在发现层把关)+\n * definePlugin computed 命令(M082-04-04)的注册(RFC-199 D3 自 interactive.ts 提取,\n * 行为零变化)。内置同名不覆盖(built-in wins);computed 在后,不夺内置/文件名。\n * 实际展开+注入见 interactive.ts onSubmit 回退分支。\n */\nexport function registerDiscoveredCommands(\n reg: {\n has: (name: string) => boolean\n register: (cmd: {\n name: string\n description: string\n aliases: undefined\n type: 'action'\n source: 'builtin'\n execute: () => string\n }) => void\n },\n app: Pick<App, 'workspaceDir' | 'activePlugins' | 'computedPluginCommands'>,\n): Array<{ name: string; description?: string; body: string }> {\n const fileCommands = [\n ...discoverFileCommands(app.workspaceDir, app.activePlugins()),\n ...app.computedPluginCommands(),\n ]\n for (const fc of fileCommands) {\n if (!reg.has(fc.name)) {\n reg.register({\n name: fc.name,\n description: fc.description ?? 'Project command (.claude/.otto)',\n aliases: undefined,\n type: 'action',\n source: 'builtin',\n execute: () => '',\n })\n }\n }\n return fileCommands\n}\n","/**\n * TUI ChatMessage view model 适配器:AgentMessage → ConversationMessage(纯函数单向映射)。\n */\n\nimport type {\n TextContent as ProtoTextContent,\n ThinkingContent as ProtoThinkingContent,\n ToolCall as ProtoToolCall,\n A2uiContent as ProtoA2uiContent,\n ContentPart,\n Message,\n} from '@x-otto/ai'\n\nimport type {\n ChatMessage,\n ToolCall as TuiToolCall,\n TextContent as TuiTextContent,\n ThinkingContent as TuiThinkingContent,\n A2uiContent as TuiA2uiContent,\n MessageContent,\n} from '@x-otto/tui'\n\n// ── 原子转换 ──────────────────────────────────────────────\n\n/** protocol ToolCall → tui ToolCall */\nexport function mapToolCall(tc: ProtoToolCall): TuiToolCall {\n return {\n id: tc.id,\n type: 'function',\n function: {\n name: tc.name,\n arguments: JSON.stringify(tc.arguments),\n },\n }\n}\n\n/** protocol ThinkingContent → tui ThinkingContent */\nexport function mapThinking(tc: ProtoThinkingContent): TuiThinkingContent {\n return { type: 'thinking', thinking: tc.text }\n}\n\n/** protocol TextContent → tui TextContent(签名/附加字段忽略) */\nexport function mapText(tc: ProtoTextContent): TuiTextContent {\n return { type: 'text', text: tc.text }\n}\n\n/** protocol A2uiContent → tui A2uiContent(字段结构一致,原样透传)。 */\nexport function mapA2ui(tc: ProtoA2uiContent): TuiA2uiContent {\n return { type: 'a2ui', version: tc.version, components: tc.components }\n}\n\n// ── 组合转换 ──────────────────────────────────────────────\n\n/** assistant content 块 → (渲染内容, 工具调用) 二元拆分 */\nexport function splitAssistantContent(\n blocks: ReadonlyArray<ProtoTextContent | ProtoThinkingContent | ProtoToolCall | ProtoA2uiContent>,\n): { content: MessageContent[]; toolCalls: TuiToolCall[] } {\n const content: MessageContent[] = []\n const toolCalls: TuiToolCall[] = []\n for (const b of blocks) {\n switch (b.type) {\n case 'text':\n if (b.text.trim()) content.push(mapText(b))\n break\n case 'thinking':\n if (b.text.trim()) content.push(mapThinking(b))\n break\n case 'tool_call':\n toolCalls.push(mapToolCall(b))\n break\n case 'a2ui':\n content.push(mapA2ui(b))\n break\n }\n }\n return { content, toolCalls }\n}\n\n/** 取单条消息的文本(user/tool_result 用)。图片跳过。 */\nexport function joinMessageText(\n content: string | ReadonlyArray<ContentPart | ProtoTextContent>,\n): string {\n if (typeof content === 'string') return content.trim()\n return content\n .filter((c): c is ProtoTextContent => c.type === 'text')\n .map((c) => c.text)\n .join('')\n .trim()\n}\n\n/** 工具调用参数的首个有意义键作预览(对齐 cli 实时桥:command/path/file_path/pattern/query)。 */\nexport function argPreview(args: Record<string, unknown>): string | undefined {\n if (Object.keys(args).length === 0) return undefined\n const pick =\n args['command'] ?? args['path'] ?? args['file_path'] ?? args['pattern'] ?? args['query']\n if (typeof pick === 'string') return pick\n return JSON.stringify(args)\n}\n\n// ── 整批转换 ──────────────────────────────────────────────\n\n/**\n * RFC-300 M3b:跨页配对 seed——补页逐页转换时注入跨页累积的 tool_call 信息,\n * 使\"result 在较新页、call 在更老页\"的跨页工具对也能正确配对渲染。\n */\nexport interface ChatMessageConversionSeed {\n /** 跨页已见 tool_call(id → block)——供本页 tool_result 配 name/arguments。 */\n toolCalls?: ReadonlyMap<string, ProtoToolCall>\n /** 跨页已见 tool_result 的 toolCallId——本页对应 tool_call 不再渲染 pending(其 result 已渲染)。 */\n resolvedIds?: ReadonlySet<string>\n}\n\n/**\n * AgentMessage[](session.messages() 产出)→ TUI ChatMessage[]。\n *\n * 与 cli 实时事件桥(reduceToTui)产出同款 ChatMessage 形状:\n * - user → UserMessage(取文本,过滤 internal)\n * - assistant → AssistantMessage(文本/思考内容 + 无 tool_result 配对的 tool_call 补成 ToolMessage)\n * - tool_result → ToolMessage(配对上一条 tool_call 的 name/arguments)\n *\n * seed(可选):跨页配对上下文(M3b)——先注入 seed 再扫本页,使跨页 tool 对\n * 正确配对;不传时行为与既往完全一致(向后兼容)。\n */\nexport function agentMessagesToChatMessages(\n messages: ReadonlyArray<unknown>,\n seed?: ChatMessageConversionSeed,\n): ChatMessage[] {\n const toolCalls = new Map<string, ProtoToolCall>()\n const resolvedIds = new Set<string>()\n\n if (seed?.toolCalls) {\n for (const [id, block] of seed.toolCalls) toolCalls.set(id, block)\n }\n if (seed?.resolvedIds) {\n for (const id of seed.resolvedIds) resolvedIds.add(id)\n }\n\n for (const raw of messages) {\n const m = raw as Message\n if (m?.role === 'assistant' && Array.isArray(m.content)) {\n for (const block of m.content) {\n if (block.type === 'tool_call') toolCalls.set(block.id, block)\n }\n } else if (m?.role === 'tool_result') {\n resolvedIds.add(m.toolCallId)\n }\n }\n\n const out: ChatMessage[] = []\n for (const raw of messages) {\n const m = raw as Message\n if (!m || typeof m.role !== 'string') continue\n if ((m as { internal?: boolean }).internal) continue\n\n if (m.role === 'user') {\n const text = joinMessageText(m.content)\n if (text) out.push({ role: 'user', content: text })\n continue\n }\n\n if (m.role === 'assistant') {\n const { content } = splitAssistantContent(\n (m.content as ReadonlyArray<ProtoTextContent | ProtoThinkingContent | ProtoToolCall | ProtoA2uiContent>) ?? [],\n )\n if (content.length > 0) out.push({ role: 'assistant', content })\n for (const block of (m.content as ReadonlyArray<ProtoTextContent | ProtoThinkingContent | ProtoToolCall | ProtoA2uiContent>) ?? []) {\n if (block.type === 'tool_call' && !resolvedIds.has(block.id)) {\n out.push({\n role: 'tool',\n name: block.name,\n args: argPreview(block.arguments),\n argsObj: block.arguments,\n result: undefined,\n isError: false,\n })\n }\n }\n continue\n }\n\n if (m.role === 'tool_result') {\n const call = toolCalls.get(m.toolCallId)\n const argsObj = call?.arguments ?? {}\n out.push({\n role: 'tool',\n name: m.toolName || call?.name || 'tool',\n args: argPreview(argsObj),\n argsObj,\n result: joinMessageText(m.content),\n isError: m.isError,\n })\n continue\n }\n\n // RFC-144 M2:lifecycleAsync 工具完成通知——单行标记(对齐 CompactBoundaryMessage/\n // PruneBoundaryMessage 既有渲染模式),区别于 ToolMessage(工具启动时的 placeholder\n // tool_result 已走上面的 tool_result 分支渲染,本分支只渲染\"完成后\"的旁路通知)。\n if (m.role === 'system_notification') {\n const notif = m as { content: string; isError?: boolean }\n out.push({\n role: 'tool_completion',\n content: notif.content,\n isError: notif.isError ?? false,\n })\n continue\n }\n\n // 终局(2026-07-31):model/effort 切换的持久化记录(L1 system entries,见\n // model-effort-record.ts)——resume 时转回与实时路径同款的单行标记 ChatMessage,\n // 使切换在跨会话转录里同样可见。content 存的是已格式化 label。\n // 其余 system 子类型(turn_duration/memory_saved/compact_boundary 等)维持跳过。\n // (role 收窄到 never:协议 Message 类型只含四角色,system 是宿主扩展,先 string 化。)\n if ((m as { role?: string }).role === 'system') {\n const sys = m as { subtype?: string; content?: unknown }\n const label = typeof sys.content === 'string' ? sys.content : ''\n if (sys.subtype === 'model_switch') {\n out.push({ role: 'model_switch', label })\n continue\n }\n if (sys.subtype === 'effort_switch') {\n out.push({ role: 'effort_switch', label })\n continue\n }\n // 流程暂停三态持久化(interactive-events.ts appendPauseL1)——resume 后转录\n // 保留「暂停过/取消过/恢复过」标记(渲染层 message-lines 已有对应类型)。\n if (sys.subtype === 'run_paused' || sys.subtype === 'run_resumed' || sys.subtype === 'run_pause_cancelled') {\n out.push({ role: sys.subtype, label } as ChatMessage)\n continue\n }\n continue\n }\n }\n return out\n}\n","import { readFileSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { dirname } from 'node:path'\nimport { spawnSync, execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\n\nimport { getToolCallsByAssistantMessage, type AssistantMessage } from '@x-otto/ai'\n\nimport type { AgentMessage } from '@x-otto/agent'\n\nconst execFileAsync = promisify(execFile)\n\n/**\n * 从会话转录中提取编辑文件的原内容(oldContent/newContent)。\n * resume 时在 session metadata 中只存了 stat(addedLines/removedLines),\n * 不存内容;但转录中的 tool_use 消息包含完整 arguments,可据此还原 diff 视图。\n */\nexport function extractEditedFileContentFromTranscript(\n messages: readonly AgentMessage[],\n toolCallId: string,\n operation: 'edit' | 'write' | 'delete',\n): { oldContent?: string; newContent?: string } {\n for (const msg of messages) {\n if (msg.role !== 'assistant') continue\n const tcs = getToolCallsByAssistantMessage(msg as unknown as AssistantMessage)\n for (const tc of tcs) {\n if (tc.id !== toolCallId) continue\n const args = tc.arguments\n if (operation === 'edit') {\n return {\n oldContent: typeof args.oldContent === 'string' ? args.oldContent : undefined,\n newContent: typeof args.newContent === 'string' ? args.newContent : undefined,\n }\n }\n if (operation === 'write') {\n return {\n newContent: typeof args.content === 'string' ? args.content : undefined,\n }\n }\n }\n }\n return {}\n}\n\n/**\n * M80(RFC-054):editedFiles 内容恢复的 git 兜底——当 transcript 反解析为空(消息已被\n * pruneOldMessages 裁掉)时,从工作区现算 old/new 内容:old = `git show HEAD:<repo-relative>`、\n * new = 当前磁盘内容。语义为 net-vs-HEAD(与 per-edit 略异,但仅在 transcript 缺失时启用,\n * 否则面板展开为空 diff——见 [[deepseek-orphan-toolresult-400-rootcause]] 同源)。只读 git,5s 超时。\n */\nexport function gitFallbackEditedFileContent(\n absPath: string,\n operation: 'edit' | 'write' | 'delete',\n): { oldContent?: string; newContent?: string } {\n const dir = dirname(absPath)\n const git = (args: readonly string[]): string | null => {\n const r = spawnSync('git', [...args], { cwd: dir, timeout: 5000, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 })\n return r.status === 0 ? r.stdout : null\n }\n let newContent: string | undefined\n if (operation !== 'delete') {\n try { newContent = readFileSync(absPath, 'utf-8') } catch { /* 文件已不存在 */ }\n }\n let oldContent: string | undefined\n const rel = git(['ls-files', '--full-name', '--', absPath])\n if (rel && rel.trim()) {\n const shown = git(['show', `HEAD:${rel.trim()}`])\n if (shown !== null) oldContent = shown // null=HEAD 无此文件(新增)→ 留 undefined(全增)\n }\n return { oldContent, newContent }\n}\n\nconst GIT_MAX_BUFFER_BYTES = 10 * 1024 * 1024\nconst GIT_TIMEOUT_MS = 5000\n\n/**\n * RFC-217 D2:`gitFallbackEditedFileContent` 的异步版本,供 `EditedFileFullPane`「整个\n * 文件 diff」模式(用户运行期主动按键触发)使用——TUI 单线程渲染循环不可被同步子进程\n * 调用阻塞(对齐 RFC-089 响应性治理),故不能复用上面的 `spawnSync` 版本。\n *\n * 与同步版逐字对应的语义:old = `git show HEAD:<repo-relative>`、new = 当前磁盘内容,\n * net-vs-HEAD(自上次提交以来的累积净变更,与 per-edit 语义不同,见 RFC §0.3)。\n *\n * 与同步版的实现差异:\n * - `execFile`(promisify)+ `await`,不阻塞事件循环;\n * - 超时用 `AbortSignal.timeout(GIT_TIMEOUT_MS)` 而非 `timeout` 选项——Node 16.14+\n * 原生支持,子进程收到 abort 会被 kill(不仅是 Promise reject),无需手动管理定时器;\n * - `maxBuffer` 显式设为 `GIT_MAX_BUFFER_BYTES`(对齐同步版)——`execFile` 默认\n * `maxBuffer` 仅 1MB,大文件 `git show` 命中默认值会抛\n * `stdout maxBuffer length exceeded`。\n *\n * 为何不改造同步版为共用实现:同步版唯一消费方是 `interactive-resume.ts`(`--continue`\n * 启动路径,TUI 渲染循环尚未启动,阻塞可接受),强行 async 化会让 `async` 传染整个\n * resume 流程,超出必要范围。两个函数并存是有意设计,服务两个不同约束的调用点。\n */\nexport async function gitFallbackEditedFileContentAsync(\n absPath: string,\n operation: 'edit' | 'write' | 'delete',\n): Promise<{ oldContent?: string; newContent?: string; error?: string }> {\n const dir = dirname(absPath)\n // RFC-217 对抗评审必须修复项1:`git` 命令本身执行失败(非 git 仓库/git 不可用/超时)\n // 与\"命令成功但结果为空\"(如 `ls-files` 命中未追踪文件)是两种不同语义,调用方须能\n // 区分——前者是 R5「不可用」,后者是合法的\"新文件\"。用 `{ok, stdout}` 而非裸\n // `string | null` 承载这个区分(`stdout===null` 已不足以表达)。\n const git = async (args: readonly string[]): Promise<{ ok: boolean; stdout: string | null }> => {\n try {\n const { stdout } = await execFileAsync('git', [...args], {\n cwd: dir,\n encoding: 'utf-8',\n maxBuffer: GIT_MAX_BUFFER_BYTES,\n signal: AbortSignal.timeout(GIT_TIMEOUT_MS),\n })\n return { ok: true, stdout }\n } catch {\n return { ok: false, stdout: null } // 非 0 退出码 / 超时 abort / 非 git 仓库 / git 不可用\n }\n }\n\n let newContent: string | undefined\n if (operation !== 'delete') {\n try {\n newContent = await readFile(absPath, 'utf-8')\n } catch {\n /* 文件已不存在 */\n }\n }\n\n let oldContent: string | undefined\n const rel = await git(['ls-files', '--full-name', '--', absPath])\n if (!rel.ok) {\n // `git ls-files` 本身失败(非 git 仓库、git 未安装、超时等)——edit 操作下这意味着\n // \"无法确认旧版本\",不能静默展示为\"全增\"(那会让用户误以为整个文件都是新写的)。\n // write 操作本身允许\"没有旧版本\"(新建文件),故只对 edit 操作报错。\n if (operation === 'edit') {\n return { error: 'not a git repository (or git unavailable)', newContent }\n }\n return { oldContent, newContent }\n }\n if (rel.stdout && rel.stdout.trim()) {\n const shown = await git(['show', `HEAD:${rel.stdout.trim()}`])\n if (shown.ok && shown.stdout !== null) oldContent = shown.stdout\n // shown.ok===false 或 stdout===null:HEAD 无此文件(合法的\"新文件\")→ 留 undefined。\n }\n return { oldContent, newContent }\n}\n","/**\n * draft-entries.ts — 草稿条目的纯逻辑单一真源(命名生成/排序),供 interactive.ts(Ctrl+S/O 内联\n * 回调)与 handlers/draft.ts(/draft 命令)共用,避免两处独立实现漂移。\n *\n * 草稿本体随会话快照持久化(@x-otto/session PersistedSessionConfig.drafts),本模块不做 IO,\n * 只提供\"给定当前列表 → 生成新条目/排序视图\"的纯函数。\n */\nexport interface DraftItem {\n id: string\n name: string\n text: string\n savedAt: number\n}\n\n/** 草稿列表上限(对齐迁移前 DraftStore 上限)。 */\nexport const MAX_DRAFT_ENTRIES = 50\n\n/** 按 savedAt 降序(最新在前)排序视图——不修改入参。 */\nexport function sortDrafts(entries: readonly DraftItem[]): DraftItem[] {\n return [...entries].sort((a, b) => b.savedAt - a.savedAt)\n}\n\n/**\n * 自动生成不重复的草稿名称。name 显式给定且非空白时原样返回(trim 后);\n * 否则在 existing 中查找不冲突的 `draft-{序号}`(序号从 existing.length+1 起,\n * 冲突则递增——防止删除条目后序号回退撞上仍存在的旧名字)。\n */\nexport function resolveDraftName(name: string | undefined, existing: readonly DraftItem[]): string {\n if (name != null && name.trim().length > 0) return name.trim()\n const existingNames = new Set(existing.map((e) => e.name))\n let seq = existing.length + 1\n let candidate = `draft-${seq}`\n while (existingNames.has(candidate)) {\n seq++\n candidate = `draft-${seq}`\n }\n return candidate\n}\n\n/** 构造一条新草稿条目(id/savedAt 现取),不修改入参、不做上限裁剪(调用方用 capDrafts)。 */\nexport function makeDraftItem(name: string | undefined, text: string, existing: readonly DraftItem[]): DraftItem {\n const savedAt = Date.now()\n return {\n id: `${savedAt.toString(36)}-${Math.random().toString(36).slice(2, 6)}`,\n name: resolveDraftName(name, existing),\n text,\n savedAt,\n }\n}\n\n/** 超出上限时裁掉最旧的(保留末尾 MAX_DRAFT_ENTRIES 条,入参已按新增顺序排列时等价于\"丢最旧\")。 */\nexport function capDrafts(entries: readonly DraftItem[]): DraftItem[] {\n return entries.length <= MAX_DRAFT_ENTRIES ? [...entries] : entries.slice(-MAX_DRAFT_ENTRIES)\n}\n","/**\n * interactive-resume.ts — --continue 恢复会话时的转录回放 + 回合面板状态恢复。\n *\n * restoreResumedSession 批量回放\n * 可见消息(单次写出避免逐条抖动)+ fail-soft 恢复 tasks/turnBoundaries/editedFiles/subagents\n * 四块持久化面板态(任一损坏的持久化态不得 brick 掉 resume)。\n */\nimport type { AgentMessage } from '@x-otto/agent'\nimport { i18next } from '@x-otto/tui'\nimport { agentMessagesToChatMessages } from '../view-model-adapter'\nimport { deriveTurnBoundaries, insertTurnSummaries } from '../session-contract/reduce'\nimport { extractEditedFileContentFromTranscript, gitFallbackEditedFileContent } from '../edited-file-content'\nimport { sortDrafts } from './draft-entries'\n\n/** TUI 实例类型(startTui 返回值)。 */\ntype TuiInstance = Awaited<ReturnType<typeof import('@x-otto/tui').startTui>>\n\n/**\n * `agentMessagesToChatMessages(visibleMessages())` + 插回持久化回合摘要行(RFC-108 D3\n * 扩展字段 turnSummaries,见 TurnSummaryEntry 头注释)——resume 场景的统一转换入口。\n *\n * **调用顺序约束(关键)**:必须在 `tui.messages.pushMessages()` 之前调用,且 `turnBoundaries`\n * 必须从**本函数返回的增补后数组**派生(而非原始 agentMessagesToChatMessages 结果)——\n * 实时产出路径(`reduceToTui` 的 `prompt.end` 分支)里,摘要行本身会 `pushMessage` 进\n * TUI 消息流并让 `state.messageIndex++`,下一回合 `addTurnBoundary` 记录的位置天然包含\n * 了上一回合摘要行的占位。resume 时若不把摘要行插回数组就直接派生 turnBoundaries,\n * 会产生\"实时 vs resume 后 turnBoundaries 语义不一致\"(TaskDetailPane 的 per-turn 消息\n * 切片会因此错位)——必须先插入、再派生,复现与实时路径等价的索引语义。\n */\nexport function buildResumeChatMessages(\n session: import('@x-otto/coding').AgentSession,\n): import('@x-otto/tui').ChatMessage[] {\n const base = agentMessagesToChatMessages(session.session.visibleMessages())\n const turnSummaries = session.session.getTurnSummaries()\n if (!turnSummaries || turnSummaries.length === 0) return base\n const turnCount = session.session.getTurnCount() ?? base.filter(m => m.role === 'user').length\n return insertTurnSummaries(base, turnSummaries, turnCount)\n}\n\nexport interface RestoreResumedSessionDeps {\n /** resume 提示文案(含 ↻ 前缀,渲染时剥离)。 */\n resumedNote: string\n /** 会话是否真被找到——决定 toast 标题;由调用方直接传,避免按文案 locale 反推。 */\n isFound: boolean\n session: import('@x-otto/coding').AgentSession\n projState: ReturnType<typeof import('@x-otto/session-contract').createProjectorState>\n tui: TuiInstance\n}\n\nexport function restoreResumedSession(deps: RestoreResumedSessionDeps): void {\n const { resumedNote, isFound, session, projState, tui } = deps\n\n // 批量回放:单次写出整段历史,避免逐条 inkWrite 的 N 次 live 区抖动(resume 大会话)。\n // 同一份转换结果(已插回摘要行)供 turnBoundaries 派生复用(见下方),避免二次转换。\n const resumeChatMsgs = buildResumeChatMessages(session)\n tui.messages.pushMessages(resumeChatMsgs)\n tui.info.showToast(isFound ? i18next.t('cmd.titleSessionResumed') : i18next.t('cmd.titleSession'), resumedNote.replace(/^↻ /, ''))\n\n // RFC-253 M-T5:--continue 恢复到已有历史会话后,把路由从 Home('/home')切到对话('/')。\n // 现在 initialPath 已在 startTui 侧按 resume 感知直接 seed 为 '/'(消除首帧闪 Home),本\n // 调用是幂等兜底(setTopView 内 navigate('/',{reset}) 在已 '/' 时 no-op)——保留以覆盖\n // initialPath 未能命中的边角(如 resume 探测与 seed 判据口径差异)。仅在真找到会话且有历史\n // 消息时切换——会话未找到(isFound=false)时保持 Home,让用户从首屏开始新对话。\n if (isFound && resumeChatMsgs.length > 0) {\n tui.info.setTopView({ session: session.id })\n }\n\n restoreSessionPanels({ session, projState, tui, chatMsgs: resumeChatMsgs })\n}\n\nexport interface RestoreSessionPanelsDeps {\n session: import('@x-otto/coding').AgentSession\n projState: ReturnType<typeof import('@x-otto/session-contract').createProjectorState>\n tui: TuiInstance\n /**\n * 已转换的 ChatMessage(--continue 复用回放结果,避免二次转换);缺省则内部调用\n * `buildResumeChatMessages` 重算(含插回摘要行,与 /resume 切会话路径的\n * `handlers/session.ts` 保持相同处理)。\n */\n chatMsgs?: import('@x-otto/tui').ChatMessage[]\n}\n\n/**\n * 恢复会话五块持久化面板态(tasks/turnBoundaries/editedFiles/subagents/turnSummaries)——\n * 不回放转录、不 toast。供 --continue(restoreResumedSession 内)与 /resume 切会话\n * (restoreSwitchedSession)共用。fail-soft:任一损坏的持久化态不得 brick 掉恢复。\n */\nexport function restoreSessionPanels(deps: RestoreSessionPanelsDeps): void {\n const { session, projState, tui } = deps\n const resumeChatMsgs = deps.chatMsgs ?? buildResumeChatMessages(session)\n\n try {\n // 恢复持久化的任务面板状态\n // 回填 turn(绝对回合号)——TaskDetailPane 据此 + 派生 turnBoundaries 过滤消息。\n //\n // 全终态过滤(与 interactive.ts 提交前清理规则对称):若上次会话退出时清空动作\n // 未能三层齐清(历史遗留数据,或修复前的旧会话快照),落盘的 todoList 可能已是\n // 全部 done/skipped/failed 的\"过期已完成清单\"——resume 时不应把它当作待办复活到\n // 面板(真实用户反馈:resume 后看到已完成的旧任务清单)。同时顺手把 L1 自愈为空,\n // 不留着让下一次同样的判断再跑一遍。\n const savedTasks = session.session.getTodoList()\n const isAllTerminal = (t: { status?: string }) =>\n t.status === 'done' || t.status === 'skipped' || t.status === 'failed'\n if (savedTasks && savedTasks.length > 0 && !savedTasks.every(isAllTerminal)) {\n projState.todoList = savedTasks.map(t => ({\n id: t.id,\n title: t.title,\n status: t.status as 'pending' | 'in_progress' | 'done' | 'skipped' | 'failed',\n turn: t.turn,\n }))\n tui.stores.tasks.setTasks(savedTasks.map(t => ({\n id: t.id ?? '',\n title: t.title ?? '',\n status: (t.status as 'pending' | 'in_progress' | 'done' | 'skipped' | 'failed') || 'pending',\n turn: t.turn,\n })), undefined)\n } else if (savedTasks && savedTasks.length > 0) {\n // 全终态的陈旧数据——自愈清空 L1,避免下次 resume 重复判断同一份垃圾数据。\n session.session.setTodoList([])\n // RFC-295 M2 D5:面板不复活过期已完成清单的行为不变,但用户此刻\"看不到任务列表\"\n // 与「因崩溃丢失」现象完全一致、无法区分(RFC-295 原因2)。补一次非侵入 toast\n // 明确告知\"是完成、不是丢失\"——不改变面板展示逻辑,只补一条可分辨依据。\n tui.info.showToast(i18next.t('cmd.titleSession'), i18next.t('cmd.resumeTodosAllDone'))\n }\n\n // 从已回放的 ChatMessage 流派生 turnBoundaries(末尾对齐 turnCount),\n // 并续接 currentTurn——使 resume 后 TaskDetailPane 的 per-turn 切片正确。pushMessages 已在上方\n // 写出 visibleMessages 的转换结果;此处对同一转换结果派生(含 thinking/tool、无 TUI-only summary)。\n {\n const savedTurnCount = session.session.getTurnCount()\n const userTurns = resumeChatMsgs.filter(m => m.role === 'user').length\n const total = savedTurnCount ?? userTurns\n if (total > 0) {\n tui.stores.tasks.setTurnBoundaries(deriveTurnBoundaries(resumeChatMsgs, total))\n projState.currentTurn = total // 新回合从此续增(prompt.start 自 total+1 起)\n }\n }\n\n // 恢复持久化的编辑文件面板状态(stat 行;diff 内容\n // transcript 反解析优先、转录已裁时 git 现算兜底——见 gitFallbackEditedFileContent)。\n const savedEditedFiles = session.session.getEditedFiles()\n if (savedEditedFiles && savedEditedFiles.length > 0) {\n const msgs = session.session.messages() as readonly AgentMessage[]\n tui.stores.tasks.setEditedFiles(savedEditedFiles.map(f => {\n const op = f.operation as 'edit' | 'write' | 'delete'\n let content = f.toolCallId\n ? extractEditedFileContentFromTranscript(msgs, f.toolCallId, op)\n : {}\n // 转录被裁→反解析为空→git 现算兜底(消除「pruned 后展开空 diff」静默失败)。\n if (content.oldContent === undefined && content.newContent === undefined) {\n content = gitFallbackEditedFileContent(f.path, op)\n }\n return {\n path: f.path,\n operation: op,\n addedLines: f.addedLines,\n removedLines: f.removedLines,\n timestamp: f.timestamp,\n turnId: f.turnId,\n toolCallId: f.toolCallId,\n oldContent: content.oldContent,\n newContent: content.newContent,\n }\n }))\n }\n\n // 恢复持久化的子代理面板状态(stat-only;id+name+status)。\n const savedSubagents = session.session.getSubagents()\n if (savedSubagents && savedSubagents.length > 0) {\n for (const s of savedSubagents) {\n tui.plugins.addSubagent({ id: s.id, name: s.name, status: s.status, startedAt: s.startedAt })\n }\n }\n\n // 恢复持久化的草稿列表(随会话快照走本地/远端存储,见 @x-otto/session\n // PersistedSessionConfig.drafts)。tui.setDrafts 是全量替换(非追加)——无论目标会话是否\n // 有草稿都调用,天然覆盖上一会话残留(换会话后草稿面板正确切换为新会话自己的草稿,\n // 空列表时清空 DraftPane,不误留旧会话的草稿)。\n tui.stores.tasks.setDrafts(sortDrafts(session.session.getDrafts() ?? []))\n } catch (e) {\n console.error(`\\x1b[90m\\x1b[2m[resume] panel state restore skipped (non-fatal): ${e instanceof Error ? e.message : String(e)}\\x1b[0m`)\n }\n}\n","/**\n * background-session-store.ts — RFC-234 M2:后台会话集合(per-session 事件缓冲 + 状态摘要)\n *\n * 每个后台会话持有一份 BackgroundSessionState:\n * - 转录投影(当前完整消息列表的 ChatMessage[] 投影,turn 边界重建)\n * - in-flight streaming 文本(stream.event 逐条累积,turn 边界清空)\n * - 状态摘要(busy/idle/needs-input)\n * - pendingGate(HITL gate 请求,M4 填充)\n *\n * 生命周期:\n * - busy:瞬态 bg 订阅者活跃,实时更新 streaming + 状态\n * - idle:回合完成,订阅者已自退订,快照冻结\n * - removed:kill 或 attach 回前台时从 Map 移除\n *\n * 对齐 web-ui ConversationShard per-session 分片模型(conversation-store.ts:31)。\n * 对齐规则4:后台 streaming 只更新此 Map,不触发前台渲染。\n */\nimport type { ChatMessage } from '@x-otto/tui'\nimport { agentMessagesToChatMessages } from '../view-model-adapter'\n\nimport type { AgentSessionEvent } from '@x-otto/interchange'\n\n// ── 类型 ──\n\nexport type BackgroundStatus = 'busy' | 'idle' | 'needs-input'\n\nexport interface BackgroundSessionState {\n id: string\n transcript: ChatMessage[]\n streaming: string\n status: BackgroundStatus\n lastActivityAt: number\n pendingGate?: {\n type: string\n id: string\n data: Record<string, unknown>\n }\n}\n\n// ── 模块级 Map(前台 attach 的会话不在此处) ──\n\nconst store = new Map<string, BackgroundSessionState>()\n\nexport function getBackgroundSession(id: string): BackgroundSessionState | undefined {\n return store.get(id)\n}\n\nexport function removeBackgroundSession(id: string): void {\n store.delete(id)\n // 清理残留的去抖定时器(否则 kill/驱逐后定时器仍持有 stale state 引用)\n const timer = debounceTimers.get(id)\n if (timer) { clearTimeout(timer); debounceTimers.delete(id) }\n}\n\nexport function listBackgroundSessions(): BackgroundSessionState[] {\n return [...store.values()]\n}\n\n// ── 状态构造 ──\n\n/**\n * 从前台会话创建初始快照(detach 时调用)。\n * 若会话 busy,后续由瞬态 bg 订阅者实时更新;若 idle,快照即终态。\n */\nexport function createBackgroundState(\n id: string,\n messages: ReadonlyArray<unknown>,\n busy: boolean,\n): BackgroundSessionState {\n const state: BackgroundSessionState = {\n id,\n transcript: agentMessagesToChatMessages(messages),\n streaming: '',\n status: busy ? 'busy' : 'idle',\n lastActivityAt: Date.now(),\n }\n store.set(id, state)\n return state\n}\n\n/**\n * 更新后台会话的消息投影(turn 边界调用,如 prompt.end)。\n * 从 AgentSession 的当前消息列表重新投影。\n */\nexport function updateBackgroundTranscript(\n id: string,\n messages: ReadonlyArray<unknown>,\n): void {\n const state = store.get(id)\n if (!state) return\n state.transcript = agentMessagesToChatMessages(messages)\n state.lastActivityAt = Date.now()\n}\n\n// ── 状态变更通知(去抖,M2c) ──\n\n/** 外部注册的状态变更监听器(一个后台会话状态翻转时通知,不含流式 streaming 事件)。 */\ntype StatusListener = (state: BackgroundSessionState) => void\nconst statusListeners = new Set<StatusListener>()\n\n/** 注册状态变更监听器,返回退订句柄。 */\nexport function onBackgroundStatusChange(listener: StatusListener): () => void {\n statusListeners.add(listener)\n return () => { statusListeners.delete(listener) }\n}\n\n/** per-session 去抖定时器——同会话连续状态翻转只最后一次通知。 */\nconst debounceTimers = new Map<string, ReturnType<typeof setTimeout>>()\nconst DEBOUNCE_MS = 300\n\nfunction emitStatusDebounced(state: BackgroundSessionState): void {\n const existing = debounceTimers.get(state.id)\n if (existing) clearTimeout(existing)\n debounceTimers.set(\n state.id,\n setTimeout(() => {\n debounceTimers.delete(state.id)\n for (const l of statusListeners) l(state)\n }, DEBOUNCE_MS),\n )\n}\n\n// ── 瞬态后台订阅者工厂(M1→M2 升级) ──\n\n/**\n * 创建瞬态后台事件处理闭包(detach busy 会话时,替代 M1 的 no-op handler)。\n *\n * 返回 [handler, getState]——handler 给 subscribe(),getState 供外部读取当前快照。\n * handler 在 prompt.end/error 时自退订(transient),状态转为 idle 后冻结。\n * 状态翻转时经去抖通知外部监听器(规则4:不触发前台渲染,只通知状态徽标变化)。\n */\nexport function createBackgroundSessionHandler(\n state: BackgroundSessionState,\n getMessages: () => ReadonlyArray<unknown>,\n): [(event: AgentSessionEvent) => void, () => BackgroundSessionState] {\n let streaming = ''\n\n const setStatus = (next: BackgroundStatus): void => {\n if (state.status === next) return\n state.status = next\n emitStatusDebounced(state)\n }\n\n const handler = (event: AgentSessionEvent): void => {\n if (event.type === 'stream.event' && event.event?.type === 'text_delta') {\n streaming += event.event.delta\n state.streaming = streaming\n state.lastActivityAt = Date.now()\n return\n }\n if (event.type === 'prompt.end') {\n state.streaming = ''\n streaming = ''\n setStatus('idle')\n updateBackgroundTranscript(state.id, getMessages())\n return\n }\n if (event.type === 'error') {\n state.streaming = ''\n streaming = ''\n setStatus('idle')\n state.lastActivityAt = Date.now()\n return\n }\n if (event.type === 'approval.required' || event.type === 'ask.user.required') {\n state.status = 'needs-input'\n state.pendingGate = {\n type: event.type,\n id: String((event as Record<string, unknown>).id ?? ''),\n data: (event as Record<string, unknown>).data as Record<string, unknown> ?? {},\n }\n state.lastActivityAt = Date.now()\n emitStatusDebounced(state)\n return\n }\n }\n\n return [handler, () => state]\n}\n","/**\n * /resume /new /compact /archive 等会话管理命令组。\n */\nimport { i18next } from '@x-otto/tui'\nimport { stat, unlink } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nimport { InteractiveMode } from '../../modes/interactive-mode'\nimport type { AgentSessionInfo, SessionArchiveExport } from '@x-otto/coding'\nimport type { CliContext } from '../../command-dispatcher'\nimport { computeContextBudget } from '../../context-budget'\nimport { formatDisplayModel } from '../interactive-utils'\nimport { buildResumeChatMessages } from '../interactive-resume'\nimport { createBackgroundState, createBackgroundSessionHandler, getBackgroundSession, removeBackgroundSession, listBackgroundSessions } from '../background-session-store'\n\n/**\n * /clear — Reset conversation context (RFC-029).\n * Clears todo list, engine context (L2 eviction + boundary marker),\n * TUI display buffer, and shows a toast.\n * Full transcript preserved for time-travel; session id unchanged.\n */\nexport async function handleClear(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n // RFC-194 D4:busy 时引擎侧 clearContext 会先 abort 再 settle(守卫在 AgentSession,\n // service 路由同享)——这里仅补用户感知:提示\"正在中止\"避免误以为卡死。\n if (ctx.state.session.isBusy) {\n ctx.tui.info.showToast('/clear', 'Aborting current task before clearing…')\n }\n ctx.state.todoList = []\n ctx.callbacks.resetCurrentTurn()\n // RFC-156:/clear 清空对话上下文——占用归零,提醒区间计数显式重置(不依赖回落 fallback\n // 隐式救回,见 RFC-156 §9 边界风险)。\n ctx.callbacks.onContextCompacted()\n ctx.state.session.clearTurnPanels()\n await ctx.state.session.clearContext()\n ctx.tui.lifecycle.clearSession()\n\n const model = ctx.state.session.model\n // clearSession() 把 taskStore.modelName 重置为启动值——需立即刷回当前会话真实模型,\n // 否则用 /model 切过模型后 /clear 页脚会反向回退(session.model 仍正确但显示错误)。\n if (model) {\n ctx.tui.stores.tasks.setModelName(formatDisplayModel(ctx.app, model.id, ctx.state.session.thinkingLevel))\n }\n const contextWindow: number = model?.contextWindow ?? 200_000\n const budget = computeContextBudget({\n systemPrompt: ctx.state.session.systemPrompt,\n tools: ctx.state.session.tools,\n lastIn: 0,\n lastOut: 0,\n lastCacheRead: 0,\n lastCacheWrite: 0,\n contextWindow,\n modelName: model?.name ?? '',\n totIn: 0,\n totOut: 0,\n })\n ctx.tui.stores.budget.setBudget(budget)\n ctx.tui.stores.context.setBudget(budget)\n\n ctx.tui.info.showToast(i18next.t('cmd.titleCleared'), i18next.t('cmd.clearedDone'))\n}\n\n/**\n * /rename <text> — 设置自定义会话标题(RFC-078 M135-03,custom 层)。\n * 写入 titleSource='custom',永不被 ai/derived 覆盖(setTitle 等级守卫);\n * 同步更新终端标题。空参数显示用法。\n */\nexport async function handleRename(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const title = args.join(' ').replace(/\\s+/g, ' ').trim()\n if (!title) {\n ctx.tui.info.showToast('/rename', 'Usage: /rename <new title>')\n return\n }\n ctx.state.session.renameSession(title)\n ctx.tui.lifecycle.setTerminalTitle(title)\n ctx.tui.stores.tasks.setSessionTitle(title)\n ctx.tui.info.showToast('✎', title)\n}\n\n/**\n * /resume [<session-id>] [--workspace <workspace-id>] — List and switch sessions.\n * --workspace <id>: filter to sessions from a specific workspace\n * No arg: interactive picker of all sessions (optionally filtered)\n * Arg: exact match → prefix match → substring match\n */\nexport async function handleResume(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n let arg: string | undefined\n let workspaceFilter: string | undefined\n let archivedOnly = false\n let includeHidden = false\n\n // parse --workspace <id> / --archived(RFC-154 D3:查看已归档会话的分离视图)/ --all(显示默认隐藏的探针会话)\n for (let i = 0; i < args.length; i++) {\n if (args[i] === '--workspace' && i + 1 < args.length) {\n workspaceFilter = args[++i]\n } else if (args[i] === '--archived') {\n archivedOnly = true\n } else if (args[i] === '--all') {\n includeHidden = true\n } else if (!args[i]!.startsWith('--')) {\n arg = args[i]\n }\n }\n\n const filter = workspaceFilter ? { workspaceId: workspaceFilter } : undefined\n // listAllSessions(非 listSessions):并入磁盘上尚未加载进内存的历史会话——否则本目录的\n // 旧会话哪怕真实存在于 sessions.db,只要没被 restoreAll 触碰过就永远不出现在 /resume 列表里\n // (见 App.listAllSessions 文档)。switchToSession 下方已有 restore(id) 兜底,能真正切过去。\n const allSessionsUnfiltered = await ctx.app.listAllSessions(filter)\n // RFC-154 D3:/resume 默认隐藏已归档会话(此前恒展示,只加后缀标记,批量归档后列表\n // 条数不减、体验无感);--archived 切到\"只看已归档\"分离视图。按 id 精确匹配(arg 场景)\n // 时不过滤——用户显式给了 id/prefix,视为已知道自己要找哪个会话,不应被隐藏拦住。\n const archivedCount = allSessionsUnfiltered.filter((s) => s.status === 'archived').length\n const isProbeLikeSession = (s: AgentSessionInfo): boolean => {\n const title = (s.title ?? '').trim().toLowerCase()\n return title === 'reply with: ok' ||\n title === 'reply with only your model name/identifier, nothing else.' ||\n title.startsWith('you are a trajectory analyst for an ai coding assistant.')\n }\n const hiddenProbeCount = allSessionsUnfiltered.filter((s) =>\n s.status !== 'archived' && isProbeLikeSession(s)\n ).length\n // 真空会话(从未 prompt 过)过滤:优先信任 entryCount(listColdSessionsOverride 本地\n // 冷会话路径的真实条目数,见 SqliteSessionRepository.listSessions),entryCount 未提供\n // 时回退 messageCount(内存态 listSessions / 远端 listAllPersistedSessions 路径,两者\n // 的 messageCount 均为真实值,不是本地冷会话路径那种恒 0 占位)。按 id 精确匹配(arg\n // 场景)时不过滤——用户已知道自己要找哪个会话(同已归档会话的既有豁免逻辑)。\n const isNonEmpty = (s: AgentSessionInfo): boolean =>\n (s.entryCount ?? s.messageCount) > 0\n const allSessions = arg\n ? allSessionsUnfiltered\n : allSessionsUnfiltered\n .filter((s) => (archivedOnly ? s.status === 'archived' : s.status !== 'archived'))\n .filter((s) => includeHidden || archivedOnly || !isProbeLikeSession(s))\n .filter(isNonEmpty)\n\n const toast = (ctx: CliContext, title: string, content: string) =>\n ctx.tui.info.showToast(title, content)\n\n const switchToSession = async (id: string): Promise<void> => {\n if (id === ctx.state.session.id) {\n toast(ctx, i18next.t('cmd.titleResume'), i18next.t('cmd.resumeAlreadyOn'))\n return\n }\n const restored =\n ctx.app.getSession(id) ?? (await ctx.app.sessionManager.restore(id))\n if (!restored) {\n toast(ctx, i18next.t('cmd.titleResume'), i18next.t('cmd.resumeSessionNotFound', { idShort: id.slice(0, 8) }))\n return\n }\n // RFC-234 M1 detach:切走不 abort,后台会话继续跑。\n // M2:busy 会话挂瞬态 bg 订阅者(事件→BackgroundSessionState),\n // prompt.end/error 自退订;idle 会话只冻结快照,不挂订阅者。\n const oldSession = ctx.state.session as unknown as { id: string; session: { messages(): ReadonlyArray<unknown> } }\n if (ctx.state.session.isBusy) {\n const state = createBackgroundState(\n oldSession.id, oldSession.session.messages(), true,\n )\n const [handler] = createBackgroundSessionHandler(\n state, () => oldSession.session.messages(),\n )\n const unsub = ctx.state.session.subscribe((event) => {\n handler(event)\n if (event.type === 'prompt.end' || event.type === 'error') {\n unsub()\n }\n })\n } else {\n createBackgroundState(oldSession.id, oldSession.session.messages(), false)\n }\n // RFC-338 D7:粘贴缓存随会话切换重绑——先把**旧**会话的状态落盘,再 hydrate\n // 新会话的。必须在 ctx.state.session 改写之前取旧 id(之后就拿不到了)。\n // await 而非 void:hydrate 要在用户能对新会话打字之前完成,否则历史 recall\n // 出的 chip 会短暂显示为失效。fail-soft 已在 binder 内部兜住。\n await ctx.callbacks.pasteBinder?.switchTo(oldSession.id, restored.id)\n\n ctx.callbacks.sessionUnsub?.()\n ctx.state.session = restored\n ctx.state.interactive = new InteractiveMode(restored, {\n models: ctx.state.models,\n currentModel: restored.model?.id,\n onModelChange: ctx.callbacks.applyModel,\n })\n ctx.state.reg = ctx.state.interactive.getRegistry()\n ctx.callbacks.registerAllTuiCommands()\n ctx.state.slashCommands = ctx.callbacks.refreshSlashCommands()\n ctx.tui.plugins.setCommands(ctx.state.slashCommands)\n ctx.callbacks.attachSessionHandler()\n const newModel = restored.model?.id\n if (newModel) {\n ctx.state.currentModelId = newModel\n ctx.state.modelReady = true\n ctx.tui.stores.tasks.setModelName(formatDisplayModel(ctx.app, newModel, ctx.state.session.thinkingLevel))\n } else {\n ctx.state.currentModelId = undefined\n ctx.state.modelReady = false\n ctx.tui.stores.tasks.setModelName('(no model)')\n }\n ctx.tui.lifecycle.clearSession()\n // RFC-234 M3 attach:目标会话在后台 Map 里 → 从缓冲恢复转录(含 in-flight streaming\n // 部分),不走 buildResumeChatMessages(那是从持久化重建,丢 in-flight)。\n // 不在后台 Map 里(冷加载)→ 走现有持久化路径。\n const bgState = getBackgroundSession(restored.id)\n if (bgState) {\n // 从后台缓冲恢复:转录含 in-flight 中间态\n ctx.tui.messages.pushMessages(bgState.transcript)\n // 前台接管:若 busy 仍需订阅(接续 streaming),但不再经 bg handler——前台自己渲染\n removeBackgroundSession(restored.id)\n } else {\n // 冷加载路径,走持久化重建\n // NOTE: bg 缓冲路径 (bgState.transcript) 不插 turnSummaries 摘要行,\n // data 是 agentMessagesToChatMessages(rawMessages) 的原始结果,\n // 与 buildResumeChatMessages(含 turnSummaries) 的转录内容一致 (空摘要时基于同源数据).\n // 对 resume 后的整个历史流而言, turnSummaries 是顶层面板支撑线, 不会在单个回合内错位.\n const switchedChatMsgs = buildResumeChatMessages(restored)\n ctx.tui.messages.pushMessages(switchedChatMsgs)\n }\n // B6:重置投影/归约状态 + 恢复目标会话面板态(否则上一会话 todoList/currentTurn/turnBoundaries 残留)。\n ctx.callbacks.restoreSwitchedSession()\n // RFC-231 M-C-view:切到会话后单向下推顶层视图(viewStore 是 cli session 上下文的派生镜像)。\n ctx.tui.info.setTopView({ session: restored.id })\n toast(\n ctx,\n i18next.t('cmd.titleResume'),\n i18next.t('cmd.resumeSwitched', { idShort: id.slice(0, 8), count: restored.session.messages().length }),\n )\n }\n\n if (arg) {\n const exact = allSessions.find((s: AgentSessionInfo) => s.id === arg)\n if (exact) {\n await switchToSession(exact.id)\n return\n }\n const prefix = allSessions.find((s: AgentSessionInfo) => s.id.startsWith(arg))\n if (prefix) {\n await switchToSession(prefix.id)\n return\n }\n const contains = allSessions.find((s: AgentSessionInfo) => s.id.includes(arg))\n if (contains) {\n await switchToSession(contains.id)\n return\n }\n toast(ctx, i18next.t('cmd.titleResume'), i18next.t('cmd.sessionNoMatch', { arg: arg }))\n return\n }\n\n if (allSessions.length === 0) {\n toast(ctx, i18next.t('cmd.titleResume'), i18next.t('cmd.resumeNoSaved'))\n return\n }\n const currentId = ctx.state.session.id\n\n // RFC-158 D4:置顶会话按 pinGroup 分桶(默认组→具名组字典序,大小写不敏感)排到列表最前,\n // 非置顶会话统一落入\"最近\"桶;每个桶内部保留 listAllSessions 已给出的 updatedAt 降序。\n // 零置顶时完全退化为原始平铺顺序(不产生任何分组语义),不影响 mostRecent/subtitle 的\n // \"最近活跃\"语义(仍读原始 allSessions[0])。\n const hasAnyPinned = allSessions.some((s) => s.pinned)\n function pinGroupSortKey(s: AgentSessionInfo): string {\n if (!s.pinned) return '\\uFFFF' // 非置顶桶排最后(Unicode 上界,字典序恒大于任何具名组)\n const g = s.pinGroup?.trim()\n return g ? `\\u0001${g.toLowerCase()}` : '\\u0000' // 默认组(\\u0000)先于具名组(\\u0001前缀)\n }\n const sortedSessions = [...allSessions].sort((a, b) => {\n const keyCmp = pinGroupSortKey(a).localeCompare(pinGroupSortKey(b))\n if (keyCmp !== 0) return keyCmp\n // 同桶内部:按 updatedAt 降序(最近活跃排最上面)。\n const aTime = a.updatedAt?.getTime() ?? a.createdAt.getTime()\n const bTime = b.updatedAt?.getTime() ?? b.createdAt.getTime()\n return bTime - aTime\n })\n /** 该会话在分组视图下归属的 Divider 标题;零置顶或非置顶会话不产生标签(见 D4)。 */\n function pinDividerGroup(s: AgentSessionInfo): string | undefined {\n if (!hasAnyPinned) return undefined\n if (s.pinned) return s.pinGroup?.trim() || i18next.t('cmd.resumeDefaultPinGroup')\n return i18next.t('cmd.resumeRecentGroup')\n }\n\n function ago(ts: Date | undefined): string {\n if (!ts) return ''\n const s = Math.max(0, Math.floor((Date.now() - ts.getTime()) / 1000))\n if (s < 10) return i18next.t('cmd.agoJustNow')\n if (s < 60) return i18next.t('cmd.agoSeconds', { n: s })\n if (s < 3600) return i18next.t('cmd.agoMinutes', { n: Math.floor(s / 60) })\n if (s < 86400) return i18next.t('cmd.agoHours', { n: Math.floor(s / 3600) })\n return i18next.t('cmd.agoDays', { n: Math.floor(s / 86400) })\n }\n\n const mostRecent = allSessions[0]?.updatedAt\n ? ago(allSessions[0].updatedAt)\n : '—'\n const totalMsgs = allSessions.reduce((sum: number, s: AgentSessionInfo) => sum + (s.messageCount ?? 0), 0)\n const filterLabel = workspaceFilter ? ` [workspace: ${workspaceFilter}]` : ''\n // RFC-154 D3:非 --archived 视图下,若有已归档会话被隐藏,subtitle 追加提示(不静默——\n // 用户应知道列表不是全部会话,且知道怎么看到被隐藏的部分)。\n const hiddenHints = [\n !archivedOnly && archivedCount > 0 ? i18next.t('cmd.resumeArchivedHiddenHint', { count: archivedCount }) : '',\n !archivedOnly && !includeHidden && hiddenProbeCount > 0 ? i18next.t('cmd.resumeProbeHiddenHint', { count: hiddenProbeCount }) : '',\n ].filter(Boolean)\n const hiddenHint = hiddenHints.length > 0 ? ` ${hiddenHints.join(' ')}` : ''\n\n // RFC-234 M5b:后台会话状态查找——/resume 面板直接展示,不再需要独立 /bg 命令。\n const bgMap = new Map(listBackgroundSessions().map((b) => [b.id, b]))\n let runningCount = 0\n let needsInputCount = 0\n const bgStatusDetail = (s: AgentSessionInfo): string => {\n if (s.id === currentId) return ''\n const bg = bgMap.get(s.id)\n if (!bg) {\n // Pool 中非前台会话(sub-agent 等),查 isBusy\n const sess = ctx.app.getSession(s.id)\n if (sess?.isBusy) { runningCount++; return `● ${i18next.t('cmd.resumeStatusBusy')} · ` }\n return ''\n }\n if (bg.status === 'busy') { runningCount++; return `● ${i18next.t('cmd.resumeStatusBusy')} · ` }\n if (bg.status === 'needs-input') { needsInputCount++; return `● ${i18next.t('cmd.resumeStatusNeedsInput')} · ` }\n return `· ${i18next.t('cmd.resumeStatusIdle')} · `\n }\n\n const statusHint = [\n runningCount > 0 ? `${runningCount} ${i18next.t('cmd.resumeStatusBusy')}` : '',\n needsInputCount > 0 ? `${needsInputCount} ${i18next.t('cmd.resumeStatusNeedsInput')}` : '',\n ].filter(Boolean).join(', ')\n const statusHeader = statusHint ? ` · ${statusHint}` : ''\n const subtitle = i18next.t('cmd.resumeSubtitle', { sessions: allSessions.length, msgs: totalMsgs, recent: mostRecent }) + statusHeader + hiddenHint\n const selectTitle = archivedOnly ? i18next.t('cmd.resumeArchivedOnlyTitle', { n: allSessions.length }) : i18next.t('cmd.resumeSelectTitle', { n: allSessions.length })\n\n ctx.tui.modal.openModal({\n kind: 'select',\n title: selectTitle + filterLabel,\n subtitle,\n searchable: true,\n maxVisible: 13,\n options: sortedSessions.map((s: AgentSessionInfo) => {\n const prefix = s.id === currentId ? '●' : ' '\n const pinnedPrefix = s.pinned ? i18next.t('cmd.resumePinnedPrefix') : ''\n const baseTitle = s.title || s.id.slice(0, 8)\n const archivedSuffix = s.status === 'archived' ? i18next.t('cmd.resumeArchivedSuffix') : ''\n // RFC-159 D3:写租约只读探测(/resume 列表提前告知\"切进去会是只读\",避免用户切换\n // 后才在 toast/转录里事后发现改动不会保存——见 session-manager.ts writeLocked 字段)。\n const readOnlySuffix = s.writeLocked ? i18next.t('cmd.resumeReadOnlySuffix') : ''\n const title = `${pinnedPrefix}${baseTitle}${archivedSuffix}`\n const time = ago(s.updatedAt)\n const statusPrefix = bgStatusDetail(s)\n const metaParts = [\n statusPrefix.trim(),\n time,\n i18next.t('welcome.msgCountLabel', { count: s.messageCount ?? 0 }),\n readOnlySuffix.trim(),\n ].filter(Boolean)\n return {\n label: `${prefix} ${title}`,\n trailingLabel: metaParts.join(' · '),\n value: s.id,\n // RFC-125 review 修复:label 含当前项标记前缀,模糊搜索改评分纯标题,\n // 避免装饰字符干扰短 query 匹配。\n searchText: title,\n // RFC-158 D4:分组 Divider 标题(SelectDialog 复用现成 group 渲染管线)。\n group: pinDividerGroup(s),\n }\n }),\n detailPlacement: 'inline',\n onSelect: (id: string) => {\n void switchToSession(id).catch((e: unknown) =>\n toast(ctx, i18next.t('cmd.titleResume'), i18next.t('cmd.resumeFailed', { msg: e instanceof Error ? e.message : String(e) })),\n )\n },\n onCancel: () => {},\n })\n}\n\n/**\n * /reload — Hot-reload capabilities (MCP / providers / declarative agents) without restart.\n */\nexport async function handleReload(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n void ctx.app.reloadCapabilities().then(\n (r) => {\n // RFC-131 T8:统一入口同步全部 TUI 侧贡献(状态栏/渲染器/面板),reload 后需主动\n // 同步(面板另按需现算,无需此步;见 RFC-105 D5/M105-4、D6/M105-5a)。\n ctx.tui.plugins.setPluginRegistrations(ctx.app.getPluginRegistrations())\n ctx.tui.info.showToast(\n i18next.t('cmd.titleReload'),\n i18next.t('cmd.reloadDone', { mcpTools: r.mcpTools, models: r.models, agents: r.agents, teams: r.teams }),\n )\n },\n (e: unknown) =>\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/reload',\n status: 'error',\n summary: i18next.t('cmd.reloadFailed', { msg: e instanceof Error ? e.message : String(e) }),\n }),\n )\n}\n\n/**\n * /diagnose — Surface improvement proposals from recurring hard failures (L1: suggest-only).\n */\nexport async function handleDiagnose(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n const proposals = ctx.app.diagnoser.propose()\n if (proposals.length === 0) {\n ctx.tui.info.showToast(i18next.t('cmd.titleDiagnose'), i18next.t('cmd.diagnoseNone'))\n return\n }\n const lines = proposals.map(\n (p, i: number) =>\n i18next.t('cmd.diagnoseLine', { n: i + 1, signalKind: p.evidence.signalKind, count: p.evidence.count, directive: p.directive }),\n )\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/diagnose',\n status: 'warn',\n summary: i18next.t('cmd.diagnoseSummary', { count: proposals.length }),\n detail: lines.join('\\n'),\n })\n}\n\n/**\n * /archive [<session-id>] — 归档/取消归档会话的 toggle(archived/非-archived 两态互斥,\n * 无需分别记两个命令名)。归档 = 禁止后续输入,resume 列表标注\"归档\"。\n * 不带参数:切换当前会话(未归档→归档,已归档→取消归档)。\n * 带参数:按 exact → prefix → substring 匹配已加载会话后切换。\n */\nexport async function handleArchive(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const arg = args[0]\n\n if (arg) {\n const allSessions = ctx.app.listSessions()\n const target = allSessions.find((s) => s.id === arg) ??\n allSessions.find((s) => s.id.startsWith(arg)) ??\n allSessions.find((s) => s.id.includes(arg))\n\n if (!target) {\n ctx.tui.info.showToast(i18next.t('cmd.titleArchive'), i18next.t('cmd.sessionNoMatch', { arg: arg }))\n return\n }\n\n if (target.status === 'archived') {\n const targetSession = ctx.app.getSession(target.id)\n if (!targetSession) {\n ctx.tui.info.showToast(i18next.t('cmd.titleUnarchive'), i18next.t('cmd.unarchiveNotLoaded', { idShort: target.id.slice(0, 8) }))\n return\n }\n targetSession.unarchive()\n ctx.tui.info.showToast(\n i18next.t('cmd.titleUnarchive'),\n i18next.t('cmd.unarchiveDone', { idShort: target.id.slice(0, 8), title: target.title ?? '' }),\n )\n return\n }\n\n const ok = await ctx.app.archiveSession(target.id)\n if (!ok) {\n ctx.tui.info.showToast(i18next.t('cmd.titleArchive'), i18next.t('cmd.archiveCannot', { idShort: target.id.slice(0, 8) }))\n return\n }\n ctx.tui.info.showToast(\n i18next.t('cmd.titleArchive'),\n i18next.t('cmd.archiveDone', { idShort: target.id.slice(0, 8), title: target.title ?? '' }),\n )\n return\n }\n\n const sess = ctx.state.session\n if (sess.status === 'archived') {\n sess.unarchive()\n ctx.tui.info.showToast(\n i18next.t('cmd.titleUnarchive'),\n i18next.t('cmd.unarchiveDone', { idShort: sess.id.slice(0, 8), title: sess.title ?? '' }),\n )\n return\n }\n sess.archive()\n ctx.tui.info.showToast(\n i18next.t('cmd.titleArchive'),\n i18next.t('cmd.archiveDone', { idShort: sess.id.slice(0, 8), title: sess.title ?? '' }),\n )\n}\n\n/**\n * /pin [<session-id>...] [--group <name>] — 置顶/取消置顶会话(/resume 列表排序权重与\n * 分组展示,与归档状态正交、不影响是否可输入)。RFC-158 D3:\n * 不带任何参数:切换当前会话置顶态(toggle,向后兼容现状)。\n * 单个 session id(含 --group 单目标):完整 toggle/移组语义——已置顶同组→取消置顶,\n * 已置顶不同组→移组,未置顶→置顶(exact → prefix → substring 匹配)。\n * 2+ 个 session id(批量):强制置顶到指定分组(或默认组),不做逐项 toggle\n * (\"部分已置顶部分未置顶\"统一置顶,语义等价于\"确保这些都置顶\")。\n * --group <name> 省略 id 时作用于当前会话。\n * 批量场景里某 id 未找到/未加载 → 不中断整体,最后一次 toast 汇总成功数与失败 id。\n */\nexport async function handlePin(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n\n // ── 解析 --group <name>,剩余 token 全部当 session id ──\n let group: string | undefined\n const ids: string[] = []\n for (let i = 0; i < args.length; i++) {\n if (args[i] === '--group') {\n const value = args[i + 1]\n if (value === undefined || value.startsWith('--')) {\n ctx.tui.info.showToast(i18next.t('cmd.titlePin'), i18next.t('cmd.pinGroupMissingValue'))\n return\n }\n group = value\n i++\n } else {\n ids.push(args[i]!)\n }\n }\n\n // 无 id 参数 + 无 --group → 退回当前会话 toggle(向后兼容现状)。\n if (ids.length === 0 && group === undefined) {\n const sess = ctx.state.session\n if (sess.pinned) {\n sess.unpin()\n ctx.tui.info.showToast(i18next.t('cmd.titleUnpin'), i18next.t('cmd.unpinDone', { idShort: sess.id.slice(0, 8), title: sess.title ?? '' }))\n } else {\n sess.pin()\n ctx.tui.info.showToast(i18next.t('cmd.titlePin'), i18next.t('cmd.pinDone', { idShort: sess.id.slice(0, 8), title: sess.title ?? '' }))\n }\n refreshPinCommandPanel(ctx)\n return\n }\n\n // --group 但未带 id → 作用于当前会话(单目标,保留完整 toggle/移组语义)。\n if (ids.length === 0) {\n pinOneSession(ctx.state.session.id, group, ctx)\n refreshPinCommandPanel(ctx)\n return\n }\n\n // 单个 id(无批量):保留现状 toggle 语义(已置顶→取消,未置顶→置顶)。\n if (ids.length === 1) {\n pinOneSession(ids[0]!, group, ctx)\n refreshPinCommandPanel(ctx)\n return\n }\n\n // 批量(2+ id):强制置顶,不做逐项 toggle(RFC-158 §4「批量'部分已置顶部分未置顶'统一\n // 置顶」——语义等价于\"确保这些都置顶\",已置顶的会话若传入不同组名仍会移组,但绝不取消置顶)。\n const allSessions = ctx.app.listSessions()\n let pinnedCount = 0\n const notFound: string[] = []\n for (const id of ids) {\n const target = allSessions.find((s) => s.id === id) ??\n allSessions.find((s) => s.id.startsWith(id)) ??\n allSessions.find((s) => s.id.includes(id))\n if (!target) {\n notFound.push(id.slice(0, 8))\n continue\n }\n const targetSession = ctx.app.getSession(target.id)\n if (!targetSession) {\n notFound.push(target.id.slice(0, 8))\n continue\n }\n targetSession.pin(group)\n pinnedCount++\n }\n ctx.tui.info.showToast(\n i18next.t('cmd.titlePin'),\n i18next.t('cmd.pinBatchSummary', {\n seg: notFound.length > 0\n ? `${pinnedCount} pinned (${notFound.length} not found: ${notFound.join(', ')})`\n : `${pinnedCount} pinned`,\n }),\n )\n refreshPinCommandPanel(ctx)\n}\n\n/**\n * `/pin` 执行后主动刷新指令面板(RFC-158 D5):/pin 的展示名(/pin ⇄ /unpin)随当前会话\n * `pinned` 状态动态变化,不刷新的话用户执行完 `/pin` 立即再敲 `/` 仍会看到旧展示名。\n * 对齐 `switchToSession` 已有的 refreshSlashCommands + setCommands 组合调用模式。\n */\nfunction refreshPinCommandPanel(ctx: CliContext): void {\n const updated = ctx.callbacks.refreshSlashCommands()\n ctx.tui.plugins.setCommands(updated)\n}\n\n/** 单会话 --group 场景(无批量 id):exact→prefix→substring 匹配已加载会话,走 toggle/移组语义。 */\nfunction pinOneSession(idOrCurrent: string, group: string | undefined, ctx: CliContext): void {\n // 当前会话直达(覆盖\"无 id、只带 --group\"场景,此时 ctx.state.session 未必已出现在\n // ctx.app.listSessions() 的返回集里,如刚创建尚未持久化的会话)。\n if (idOrCurrent === ctx.state.session.id) {\n applyPinWithToast(ctx.state.session, group, ctx)\n return\n }\n const allSessions = ctx.app.listSessions()\n const target = allSessions.find((s) => s.id === idOrCurrent) ??\n allSessions.find((s) => s.id.startsWith(idOrCurrent)) ??\n allSessions.find((s) => s.id.includes(idOrCurrent))\n if (!target) {\n ctx.tui.info.showToast(i18next.t('cmd.titlePin'), i18next.t('cmd.sessionNoMatch', { arg: idOrCurrent }))\n return\n }\n const targetSession = ctx.app.getSession(target.id)\n if (!targetSession) {\n ctx.tui.info.showToast(i18next.t('cmd.titlePin'), i18next.t('cmd.pinNotLoaded', { idShort: target.id.slice(0, 8) }))\n return\n }\n applyPinWithToast(targetSession, group, ctx)\n}\n\n/** 单会话路径的置顶动作 + toast(区分置顶/取消置顶/移组三种反馈文案)。 */\nfunction applyPinWithToast(\n session: { id: string; title?: string; pinned: boolean; pinGroup?: string; pin: (g?: string) => void; unpin: () => void },\n group: string | undefined,\n ctx: CliContext,\n): void {\n const idShort = session.id.slice(0, 8)\n const title = session.title ?? ''\n\n if (session.pinned) {\n const sameGroup = group === undefined ||\n (session.pinGroup ?? '').toLowerCase() === group.toLowerCase()\n if (sameGroup) {\n session.unpin()\n ctx.tui.info.showToast(i18next.t('cmd.titleUnpin'), i18next.t('cmd.unpinDone', { idShort: idShort, title: title }))\n return\n }\n session.pin(group)\n ctx.tui.info.showToast(i18next.t('cmd.titlePin'), i18next.t('cmd.pinGroupDone', { idShort: idShort, title: title, group: group! }))\n return\n }\n\n session.pin(group)\n if (group !== undefined) {\n ctx.tui.info.showToast(i18next.t('cmd.titlePin'), i18next.t('cmd.pinGroupDone', { idShort: idShort, title: title, group: group }))\n } else {\n ctx.tui.info.showToast(i18next.t('cmd.titlePin'), i18next.t('cmd.pinDone', { idShort: idShort, title: title }))\n }\n}\n\n/**\n * /session unlock <session-id> [--force] — 写租约诊断/强制解锁(RFC-171 D4)。\n *\n * 无 --force:只读诊断——显示该会话锁的持有者信息(pid/hostname/存活性/mtime 距今),\n * 不做任何修改。锁不存在时提示\"无需解锁\"。\n * 带 --force:探测到持有者仍存活时,要求二次确认(TUI confirm 弹窗,明确警示数据竞态\n * 风险);确认死亡时直接执行,不阻塞。mtime 距今 < 5s 时额外提示\"可能是正常释放过渡态\",\n * 引导用户先重试而非直接强制(独立评审\"用户误判\"风险的应对,见 RFC-171 §6)。\n *\n * 只操作锁文件本身(`sessionManager.forceReleaseSessionWriteLease`),不触碰会话数据——\n * 解锁后下一次 save 由正常 `acquire()` 接管,不引入新的写路径(RFC-171 §3 D4)。\n */\nexport async function handleSessionUnlock(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const sub = args[0]\n\n if (sub === 'fork') {\n await handleSessionFork(args.slice(1), ctx)\n return\n }\n\n if (sub === 'kill') {\n await handleSessionKill(args.slice(1), ctx)\n return\n }\n\n if (sub !== 'unlock') {\n ctx.tui.info.showToast(i18next.t('cmd.titleSessionCmd'), 'Usage: /session fork | /session unlock <id> [--force] | /session kill <id>')\n return\n }\n\n const rest = args.slice(1)\n const force = rest.includes('--force')\n const sessionId = rest.find((a) => a !== '--force')\n\n if (!sessionId) {\n ctx.tui.info.showToast(i18next.t('cmd.titleSessionUnlock'), i18next.t('cmd.unlockUsage'))\n return\n }\n\n const info = ctx.app.sessionManager.inspectSessionWriteLease(sessionId)\n const idShort = sessionId.slice(0, 8)\n\n if (!info.locked) {\n ctx.tui.info.showToast(i18next.t('cmd.titleSessionUnlock'), i18next.t('cmd.unlockNotLocked', { idShort: idShort }))\n return\n }\n\n const aliveLabel =\n info.alive === true ? i18next.t('cmd.unlockAliveConfirmed')\n : info.alive === false ? i18next.t('cmd.unlockDeadConfirmed')\n : i18next.t('cmd.unlockUnknownAlive')\n const mtimeAgoSec = Math.round((info.mtimeAgoMs ?? 0) / 1000)\n\n if (!force) {\n // 只读诊断分支:展示持有者信息,不修改任何状态。\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/session unlock',\n summary: i18next.t('cmd.unlockInspect', { idShort: idShort, pid: info.pid, hostname: info.hostname, aliveLabel: aliveLabel, mtimeAgoSec: mtimeAgoSec }),\n })\n return\n }\n\n // --force 分支:探测到存活 → 二次确认闸门;探测到死亡/未知 → 直接执行。\n if (info.alive === true) {\n // 心跳停摆但 pid 存活(RFC-171 D2 的 hung 进程死锁场景)——额外提示区别于\"正常活跃写者\"。\n if (info.mtimeAgoMs !== undefined && info.mtimeAgoMs > 60_000) {\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/session unlock --force',\n status: 'warn',\n summary: i18next.t('cmd.unlockHungWarning', { idShort: idShort }),\n })\n }\n ctx.tui.modal.openModal({\n kind: 'confirm',\n title: i18next.t('cmd.unlockConfirmTitle'),\n message: i18next.t('cmd.unlockConfirmMessage', { idShort: idShort, pid: info.pid, hostname: info.hostname }),\n confirmLabel: i18next.t('cmd.unlockConfirmLabel'),\n cancelLabel: i18next.t('cmd.unlockCancelLabel'),\n dangerous: true,\n onConfirm: () => {\n ctx.app.sessionManager.forceReleaseSessionWriteLease(sessionId)\n ctx.tui.info.showToast(i18next.t('cmd.titleSessionUnlock'), i18next.t('cmd.unlockForcedDone', { idShort: idShort }))\n },\n onCancel: () => {\n ctx.tui.info.showToast(i18next.t('cmd.titleSessionUnlock'), i18next.t('cmd.unlockCancelled'))\n },\n })\n return\n }\n\n // 死亡或无法判断(跨 hostname/旧格式):直接执行,但对\"刚释放\"给出提示(用户误判风险缓解)。\n // 独立评审 7.1 修正:判定用原始毫秒值而非取整后的秒数——Math.round(4500ms)=5s,\n // 用 `mtimeAgoSec < 5` 会让 4.5s-4.999s 区间漏判为\"非刚释放\",产生 ±0.5s 误差窗口。\n if ((info.mtimeAgoMs ?? 0) < 5_000) {\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/session unlock --force',\n status: 'warn',\n summary: i18next.t('cmd.unlockRecentReleaseHint', { idShort: idShort }),\n })\n }\n ctx.app.sessionManager.forceReleaseSessionWriteLease(sessionId)\n ctx.tui.info.showToast(i18next.t('cmd.titleSessionUnlock'), i18next.t('cmd.unlockForcedDone', { idShort: idShort }))\n}\n\n/**\n * /session fork — Fork current session into a new branch.\n *\n * Creates a full snapshot copy of the current session's conversation history,\n * then switches to the newly forked session. Enables branching conversations\n * without losing the original context.\n */\nasync function handleSessionFork(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n const sourceId = ctx.state.session.id\n\n const forked = await ctx.app.sessionManager.forkSession(sourceId)\n if (!forked) {\n ctx.tui.info.showToast(i18next.t('cmd.titleSessionFork'), i18next.t('cmd.forkFailed'))\n return\n }\n\n // RFC-234 M1 detach:fork 不 abort 源会话。同 switchToSession 的 M2 后台会话集合模式。\n const oldSession = ctx.state.session as unknown as { id: string; session: { messages(): ReadonlyArray<unknown> } }\n if (ctx.state.session.isBusy) {\n const state = createBackgroundState(oldSession.id, oldSession.session.messages(), true)\n const [handler] = createBackgroundSessionHandler(state, () => oldSession.session.messages())\n const unsub = ctx.state.session.subscribe((event) => {\n handler(event)\n if (event.type === 'prompt.end' || event.type === 'error') {\n unsub()\n }\n })\n } else {\n createBackgroundState(oldSession.id, oldSession.session.messages(), false)\n }\n ctx.callbacks.sessionUnsub?.()\n ctx.state.session = forked\n ctx.state.interactive = new InteractiveMode(forked, {\n models: ctx.state.models,\n currentModel: forked.model?.id,\n onModelChange: ctx.callbacks.applyModel,\n })\n ctx.state.reg = ctx.state.interactive.getRegistry()\n ctx.callbacks.registerAllTuiCommands()\n ctx.state.slashCommands = ctx.callbacks.refreshSlashCommands()\n ctx.tui.plugins.setCommands(ctx.state.slashCommands)\n ctx.callbacks.attachSessionHandler()\n const newModel = forked.model?.id\n if (newModel) {\n ctx.state.currentModelId = newModel\n ctx.state.modelReady = true\n ctx.tui.stores.tasks.setModelName(formatDisplayModel(ctx.app, newModel, ctx.state.session.thinkingLevel))\n } else {\n ctx.state.currentModelId = undefined\n ctx.state.modelReady = false\n ctx.tui.stores.tasks.setModelName('(no model)')\n }\n ctx.tui.lifecycle.clearSession()\n const switchedChatMsgs = buildResumeChatMessages(forked)\n ctx.tui.messages.pushMessages(switchedChatMsgs)\n ctx.callbacks.restoreSwitchedSession()\n ctx.tui.info.setTopView({ session: forked.id })\n ctx.tui.info.showToast(\n i18next.t('cmd.titleSessionFork'),\n i18next.t('cmd.forkSuccess', { idShort: forked.id.slice(0, 8) }),\n )\n}\n\n/**\n * RFC-105 D6 F5/M105-5c:a2ui 交互回流命令。\n * /a2ui-action <componentId> <action> [payload-json]\n * 触发会话事件的 a2ui.action 类型 + 将动作注入为 user-message 起新回合。\n */\nexport async function handleA2uiAction(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const componentId = args[0]\n const action = args[1]\n if (!componentId || !action) {\n ctx.tui.info.showToast('a2ui', 'Usage: /a2ui-action <componentId> <action> [payload-json]')\n return\n }\n let payload: Record<string, unknown> | undefined\n if (args[2]) {\n try {\n payload = JSON.parse(args.slice(2).join(' ')) as Record<string, unknown>\n } catch {\n ctx.tui.info.showToast('a2ui', 'Invalid JSON payload')\n return\n }\n }\n\n const sess = ctx.state.session\n // RFC-105 D6 F5:actionPayload 注入为 user-message,引擎自然起新回合回流结果\n ;(sess as unknown as { session: { emit(e: Record<string, unknown>): void; prompt(t: string): Promise<unknown> } }).session.emit({\n type: 'a2ui.action',\n sessionId: sess.id,\n componentId,\n action,\n payload,\n })\n\n // 注入为一条 user-message 触发新回合(agent 会将此作为新一轮 prompt 起流)\n const promptText = `[a2ui action] component=\"${componentId}\" action=\"${action}\"${payload ? ` payload=${JSON.stringify(payload)}` : ''}`\n ;(sess as unknown as { session: { prompt(text: string): Promise<unknown> } }).session.prompt(promptText)\n .catch((e: unknown) => {\n ctx.tui.info.showToast('a2ui', `Action failed: ${e instanceof Error ? e.message : String(e)}`)\n })\n}\n\n/**\n * /trace-gc — trace TTL 生命周期治理(RFC-172 M3/D3)。\n *\n * 扫描所有会话中 `last_active_at` 早于 N 天前、且非 pinned/archived 的会话,\n * 删除其 trace 诊断文件(不动 session_entries/sessions 表,不影响会话可用性)。\n * 默认 dry-run(只列出候选,不删除),需显式 --apply 才真删。\n *\n * 语义说明:TTL 基于会话最后一次持久化活动时间(agent 引擎产生新内容的时间),\n * 可能与你最后一次查看/交互该会话的时间不同。对长时间后台自主运行的会话,\n * TTL 的意义是“距离最后一次引擎活动”——而非“距离你最后一次打开它”。\n *\n * 用法:/trace-gc [--ttl-days N] [--apply]\n * --ttl-days N TTL 天数(默认 90)\n * --apply 真正删除 trace 文件(不带则 dry-run)\n */\nexport async function handleTraceGc(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n // ── 解析参数 ──\n let ttlDays = 90\n const ttlIndex = args.indexOf('--ttl-days')\n if (ttlIndex >= 0 && ttlIndex + 1 < args.length) {\n const parsed = parseInt(args[ttlIndex + 1]!, 10)\n if (!isNaN(parsed) && parsed > 0) ttlDays = parsed\n }\n // 兼容 --ttl-days=N 写法\n const ttlEq = args.find((a) => a.startsWith('--ttl-days='))\n if (ttlEq) {\n const parsed = parseInt(ttlEq.split('=')[1]!, 10)\n if (!isNaN(parsed) && parsed > 0) ttlDays = parsed\n }\n const apply = args.includes('--apply')\n\n const sessionStorageDir = ctx.app.sessionStorageDir\n if (!sessionStorageDir) {\n ctx.tui.info.showToast('trace-gc', '当前后端不支持 trace 文件管理(非本地磁盘存储)')\n return\n }\n\n // ── 查询与过滤 ──\n const allSessions = await ctx.app.listAllSessions()\n const cutoffMs = Date.now() - ttlDays * 24 * 60 * 60 * 1000\n\n const candidates = allSessions.filter((s) => {\n const lastActive = s.updatedAt ? s.updatedAt.getTime() : 0\n if (lastActive >= cutoffMs) return false\n if (s.status === 'archived') return false\n if (s.pinned) return false\n return true\n })\n\n // ── 查找对应的 trace 文件 ──\n const traceDir = join(sessionStorageDir, 'trace')\n interface CandidateFile {\n session: AgentSessionInfo\n path: string\n size: number\n }\n const files: CandidateFile[] = []\n for (const s of candidates) {\n const tracePath = join(traceDir, `${encodeURIComponent(s.id)}.jsonl`)\n try {\n const sInfo = await stat(tracePath)\n files.push({ session: s, path: tracePath, size: sInfo.size })\n } catch {\n // trace 文件不存在(新会话无 trace、已手动删除等)——跳过\n }\n }\n\n // ── 输出 ──\n if (!apply) {\n // Dry-run\n if (files.length === 0) {\n ctx.tui.info.showToast(\n 'trace-gc',\n `TTL ${ttlDays} 天:没有符合条件的 trace 文件`,\n )\n return\n }\n\n const totalSize = files.reduce((sum, f) => sum + f.size, 0)\n const summary = `TTL: ${ttlDays} 天 | 候选会话: ${files.length} | 预估回收: ${formatBytes(totalSize)}`\n const detailLines = [\n '会话 ID 大小 最后活跃 标题',\n ...files.map((f) => {\n const id = f.session.id.slice(0, 8)\n const size = formatBytes(f.size).padStart(9)\n const date = f.session.updatedAt\n ? f.session.updatedAt.toISOString().slice(0, 10)\n : '未知'\n const title = (f.session.title ?? '').slice(0, 60)\n return ` ${id} ${size} ${date} ${title}`\n }),\n '',\n 'TTL 基于会话最后一次持久化活动时间(agent 引擎产生新内容的时间),',\n '可能与你最后一次查看/交互该会话的时间不同。',\n '',\n '添加 --apply 参数以实际删除这些 trace 文件。',\n ]\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/trace-gc --dry-run',\n summary,\n detail: detailLines.join('\\n'),\n })\n return\n }\n\n // ── Apply:真实删除 ──\n if (files.length === 0) {\n ctx.tui.info.showToast(\n 'trace-gc',\n `TTL ${ttlDays} 天:没有需要清理的 trace 文件`,\n )\n return\n }\n\n let deleted = 0\n let reclaimedBytes = 0\n const errors: string[] = []\n for (const f of files) {\n try {\n await unlink(f.path)\n deleted++\n reclaimedBytes += f.size\n } catch (err) {\n errors.push(\n `${f.session.id.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n }\n\n const msg = `已清理 ${deleted}/${files.length} 个会话的 trace 文件,回收 ${formatBytes(reclaimedBytes)}`\n if (deleted > 0) {\n ctx.tui.info.showToast('trace-gc', msg + (errors.length > 0 ? `\\n失败:${errors.join('; ')}` : ''))\n } else if (errors.length > 0) {\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/trace-gc --apply',\n status: 'error',\n summary: msg,\n detail: `失败:${errors.join('; ')}`,\n })\n }\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes >= 1_073_741_824) return `${(bytes / 1_073_741_824).toFixed(1)} GB`\n if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)} MB`\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${bytes} B`\n}\n\n/**\n * /session-archive-export — 冷会话归档导出(RFC-172 M4/D4)。\n *\n * 把指定会话完整导出为独立 JSON 归档文件。导出成功且 checksum 校验通过后,\n * 可选 --and-delete 从 DB 中删除该会话(同步清 trace/checkpoint)。\n * 这是本 RFC 唯一会真实删除会话数据的操作。\n *\n * 安全闸门(三道):\n * 1. 导出文件 sha256 checksum 校验(写入前后比对)\n * 2. --and-delete 时写租约检查(另一进程占用则 fail-fast 拒绝)\n * 3. --and-delete 时 TUI 交互式二次确认\n *\n * 用法:/session-archive-export <sessionId> [--out <path>] [--and-delete]\n * sessionId 会话 ID(支持短 ID 前缀匹配)\n * --out 导出目标路径(缺省 = <sessionStorageDir>/archives/<sessionId>.json)\n * --and-delete 导出并校验通过后删除会话(需交互确认 + 写租约检查)\n */\nexport async function handleSessionArchiveExport(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n // ── 参数解析 ──\n const sessionIdArg = args[0]\n if (!sessionIdArg || sessionIdArg.startsWith('--')) {\n ctx.tui.info.showToast(\n 'archive-export',\n '用法: /session-archive-export <sessionId> [--out <path>] [--and-delete]',\n )\n return\n }\n\n const andDelete = args.includes('--and-delete')\n\n // --out <path>\n let outPath: string | undefined\n const outIdx = args.indexOf('--out')\n if (outIdx >= 0 && outIdx + 1 < args.length) {\n const candidate = args[outIdx + 1]!\n if (!candidate.startsWith('--')) outPath = candidate\n }\n\n // ── 会话 ID 前缀匹配 ──\n const allSessions = await ctx.app.listAllSessions()\n const match = allSessions.find(\n (s) => s.id.startsWith(sessionIdArg) && s.status !== 'archived',\n )\n if (!match) {\n ctx.tui.info.showToast('archive-export', `找不到 ID 前缀为 \"${sessionIdArg}\" 的活跃会话`)\n return\n }\n const sessionId = match.id\n const idShort = sessionId.slice(0, 8)\n const title = match.title ?? idShort\n\n // ── 默认导出路径 ──\n const baseDir = ctx.app.sessionStorageDir\n if (!outPath && !baseDir) {\n ctx.tui.info.showToast(\n 'archive-export',\n '当前后端不支持自动选择导出路径,请提供 --out <path>',\n )\n return\n }\n const resolvedOutPath = outPath ?? join(baseDir!, 'archives', `${sessionId}.json`)\n\n // ── 导出 ──\n let exportResult: SessionArchiveExport\n try {\n exportResult = await ctx.app.exportSessionArchive(sessionId, resolvedOutPath)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/session-archive-export',\n status: 'error',\n summary: `导出失败:${msg}`,\n })\n return\n }\n\n // ── 纯导出(不带 --and-delete) ──\n if (!andDelete) {\n ctx.tui.info.showToast(\n 'archive-export',\n `已导出 ${title}(${exportResult.entryCount} entries, sha256=${exportResult.checksum.slice(0, 12)})→ ${exportResult.path}`,\n )\n return\n }\n\n // ── --and-delete:写租约检查 ──\n // 只检查「另一存活进程持有」场景(alive !== false),崩溃残留锁文件不阻塞删除——\n // 死进程不可能正在编辑会话,archive-delete 不应被 crash 后的残留锁永久卡住。\n // alive === undefined(跨 hostname 或旧格式锁文件,无法判断持有者死活)同样拒绝,\n // 保守策略:不确定时不解锁、不删除。\n const leaseInfo = ctx.app.sessionManager.inspectSessionWriteLease(sessionId)\n if (leaseInfo.locked && leaseInfo.alive !== false) {\n const aliveLabel = leaseInfo.alive === true ? '(确认存活)' : '(无法判断存活)'\n const extra = leaseInfo.pid != null\n ? `(pid=${leaseInfo.pid},hostname=${leaseInfo.hostname ?? '?'}${aliveLabel})`\n : ''\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/session-archive-export --and-delete',\n status: 'error',\n summary: `会话 ${idShort} 仍被其他 otto 进程占用${extra},无法归档删除`,\n detail: '仅导出请去掉 --and-delete。',\n })\n return\n }\n\n // ── --and-delete:二次确认 ──\n ctx.tui.modal.openModal({\n kind: 'confirm',\n title: '确认删除会话',\n message: `确认删除会话「${title}」(${idShort})?此操作不可逆,且会同步清理 trace/checkpoint。`,\n confirmLabel: '确认删除',\n cancelLabel: '取消',\n dangerous: true,\n onConfirm: async () => {\n try {\n await ctx.app.removeSession(sessionId)\n ctx.tui.info.showToast(\n 'archive-export',\n `已归档并删除 ${title}(${exportResult.entryCount} entries)→ ${exportResult.path}`,\n )\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/session-archive-export --and-delete',\n status: 'error',\n summary: `删除失败:${msg}`,\n detail: `导出文件仍保存于 ${exportResult.path}`,\n })\n }\n },\n onCancel: () => {\n ctx.tui.info.showToast(\n 'archive-export',\n `已取消删除。导出文件仍保存于 ${exportResult.path}`,\n )\n },\n })\n}\n\n/**\n * /session kill <id> — RFC-234 M3:精确终止一个后台会话。\n *\n * abort + SessionPool 移除 + 后台 Map 移除。\n * 不支持杀掉当前前台会话(先 detach 切走后再杀)。\n * 按 exact → prefix 匹配已加载会话(同 /resume 逻辑)。\n */\nexport async function handleSessionKill(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const id = args[0]\n\n if (!id) {\n ctx.tui.info.showToast('session kill', 'Usage: /session kill <session-id>')\n return\n }\n\n const allSessions = ctx.app.listSessions()\n const target = allSessions.find((s) => s.id === id) ??\n allSessions.find((s) => s.id.startsWith(id)) ??\n allSessions.find((s) => s.id.includes(id))\n\n if (!target) {\n ctx.tui.info.showToast('session kill', i18next.t('cmd.sessionNoMatch', { arg: id }))\n return\n }\n\n if (target.id === ctx.state.session.id) {\n ctx.tui.info.showToast(\n 'session kill',\n 'Cannot kill the current foreground session. /resume to another session first.',\n )\n return\n }\n\n const targetSession = ctx.app.getSession(target.id)\n if (!targetSession) {\n // 不在 pool 但可能在后台 Map——清理 Map 即可\n removeBackgroundSession(target.id)\n ctx.tui.info.showToast('session kill', `Removed stale session ${target.id.slice(0, 8)} from background.`)\n return\n }\n\n targetSession.abort()\n removeBackgroundSession(target.id)\n await ctx.app.removeSession(target.id)\n\n ctx.tui.info.showToast(\n 'session kill',\n `Killed session ${target.id.slice(0, 8)}${target.title ? ` (${target.title})` : ''}.`,\n )\n}\n","/**\n * handlers/attach.ts — RFC-335 T335-07 S4:`/attach <sessionId>`(首期 attach,只读+最小写)。\n *\n * 首期语义:连接 `otto serve`(SERVICE_URL/OTTO_SERVICE_URL)→ fetchSnapshot → 把会话摘要\n * 与最近消息落进当前 TUI(pushMessage),并提示后续经 TUI 直接发消息走 Chat.query 的能力下\n * 一期补;完整写租约 + 实时事件流转(WS subscribe)归 S4 二期(T335-07 里程碑文档。\n * 本入口本期确定性主证明「TUI client 能起、snapshot 能拉、掉线提示明确」。\n */\nimport { ServiceRequestError, ServiceUnreachableError, createServiceClient, type SessionSnapshotDto } from '../service-client'\nimport type { CliContext } from '../../command-dispatcher'\n\nfunction summarizeSnapshot(snapshot: SessionSnapshotDto): string {\n const lines: string[] = [\n `session ${snapshot.session.id}`,\n `status ${snapshot.session.status} · ${snapshot.session.messageCount} messages · cursor ${snapshot.cursor}`,\n `authority host=${snapshot.authority.hostname} pid=${snapshot.authority.pid}`,\n ]\n if (snapshot.session.messages.length > 0) {\n lines.push('recent:')\n for (const m of snapshot.session.messages.slice(-3)) {\n lines.push(` · ${typeof m === 'object' && m && 'role' in m ? String((m as { role: unknown }).role) : 'message'} … ${JSON.stringify(m).slice(0, 120)}`)\n }\n } else {\n lines.push('(无历史消息)')\n }\n return lines.join('\\n')\n}\n\nexport async function handleAttach(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const sessionId = args.join(' ').trim()\n if (!sessionId) {\n ctx.tui.info.showToast('/attach', 'Usage: /attach <sessionId> —— attach to a session running on `otto serve`')\n return\n }\n\n const client = createServiceClient(process.env['OTTO_SERVICE_URL'] ?? 'http://127.0.0.1:8417', globalThis.fetch, {\n token: process.env['OTTO_SERVICE_TOKEN'],\n })\n\n let snapshot: SessionSnapshotDto\n try {\n snapshot = await client.fetchSnapshot(sessionId)\n } catch (err) {\n if (err instanceof ServiceUnreachableError) {\n ctx.tui.info.showToast('/attach', `无法连接 otto serve:${err.message} —— 先 \\`otto serve\\`(或指定 --remote/OTTO_SERVICE_URL)`)\n return\n }\n if (err instanceof ServiceRequestError) {\n ctx.tui.info.showToast('/attach', `拉取 snapshot 失败:${err.message}`)\n return\n }\n ctx.tui.info.showToast('/attach', `attach 失败:${err instanceof Error ? err.message : String(err)}`)\n return\n }\n\n // ChatMessage 无 'system'/'info' role——走 assistant 消息(与现有服务通知型消息惯例一致)。\n ctx.tui.messages.pushMessage({ role: 'assistant', content: [{ type: 'text', text: `[attach]\\n${summarizeSnapshot(snapshot)}` }] })\n ctx.tui.info.showToast('/attach', `已 attach 会话 ${snapshot.session.id}(首期只读预览;发送与 WS 实时同步归 S4 二期)`)\n}\n","/**\n * /fork — Fork current session into a named branch.\n *\n * 流程:\n * 1. forkSession 复制当前会话全量快照(同 /session fork 既有逻辑)\n * 2. 弹输入框让用户命名新会话(预填源会话标题 + \" (fork)\")\n * 3. setTitle(title, 'custom') 持久化为 custom 级(永不被 ai/derived 覆盖)\n * 4. 切换到 fork 会话\n *\n * 与 /session fork 的区别:\n * - /session fork:直接 fork 并切换,会话标题自动从源会话继承(或 derived)\n * - /fork:fork 后先让用户命名,再切换——命名是 fork 的一等公民\n *\n * 不传名字(直接 Enter 或留空)= 用源会话标题 + \" (fork)\" 作默认名。\n * Esc 取消命名 = 仍 fork 并切换,但标题继承源会话(不加 \" (fork)\" 后缀)。\n */\nimport { i18next } from '@x-otto/tui'\nimport type { CliContext } from '../../command-dispatcher'\n\nimport { InteractiveMode } from '../../modes/interactive-mode'\nimport { formatDisplayModel } from '../interactive-utils'\nimport { buildResumeChatMessages } from '../interactive-resume'\nimport { createBackgroundState, createBackgroundSessionHandler } from '../background-session-store'\n\nexport async function handleFork(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n const sourceId = ctx.state.session.id\n const sourceTitle =\n ctx.state.session.session.metadata().title?.trim() ||\n sourceId.slice(0, 8)\n\n const forked = await ctx.app.sessionManager.forkSession(sourceId)\n if (!forked) {\n ctx.tui.info.showToast(i18next.t('cmd.titleSessionFork'), i18next.t('cmd.forkFailed'))\n return\n }\n\n // 命名输入:预填源会话标题 + \" (fork)\",用户可修改或直接 Enter 确认。\n // Esc = 跳过命名(标题继承源会话,不加后缀),仍 fork 并切换。\n const defaultName = `${sourceTitle} (fork)`\n const userInput = await ctx.tui.modal.openModal({\n kind: 'input',\n title: i18next.t('cmd.forkNameTitle'),\n placeholder: defaultName,\n defaultValue: defaultName,\n })\n\n const finalTitle = (userInput ?? '').trim() || defaultName\n forked.renameSession(finalTitle)\n\n // RFC-234 M1 detach:fork 不 abort 源会话(同 /session fork 逻辑)。\n const oldSession = ctx.state.session as unknown as { id: string; session: { messages(): ReadonlyArray<unknown> } }\n if (ctx.state.session.isBusy) {\n const state = createBackgroundState(oldSession.id, oldSession.session.messages(), true)\n const [handler] = createBackgroundSessionHandler(state, () => oldSession.session.messages())\n const unsub = ctx.state.session.subscribe((event) => {\n handler(event)\n if (event.type === 'prompt.end' || event.type === 'error') {\n unsub()\n }\n })\n } else {\n createBackgroundState(oldSession.id, oldSession.session.messages(), false)\n }\n // RFC-338 D9(红线 1b):粘贴缓存随分裂复制到新分区,**nextId 一并继承**。\n // 不继承会让两条时间线的 id 空间重叠——新粘贴复用源会话历史中已存在的 id 后,\n // 共享历史里的同号 chip 会展开成完全不同的内容(静默数据损坏)。\n await ctx.callbacks.pasteBinder?.fork(oldSession.id, forked.id)\n\n ctx.callbacks.sessionUnsub?.()\n ctx.state.session = forked\n ctx.state.interactive = new InteractiveMode(forked, {\n models: ctx.state.models,\n currentModel: forked.model?.id,\n onModelChange: ctx.callbacks.applyModel,\n })\n ctx.state.reg = ctx.state.interactive.getRegistry()\n ctx.callbacks.registerAllTuiCommands()\n ctx.state.slashCommands = ctx.callbacks.refreshSlashCommands()\n ctx.tui.plugins.setCommands(ctx.state.slashCommands)\n ctx.callbacks.attachSessionHandler()\n const newModel = forked.model?.id\n if (newModel) {\n ctx.state.currentModelId = newModel\n ctx.state.modelReady = true\n ctx.tui.stores.tasks.setModelName(formatDisplayModel(ctx.app, newModel, ctx.state.session.thinkingLevel))\n } else {\n ctx.state.currentModelId = undefined\n ctx.state.modelReady = false\n ctx.tui.stores.tasks.setModelName('(no model)')\n }\n ctx.tui.lifecycle.clearSession()\n const switchedChatMsgs = buildResumeChatMessages(forked)\n ctx.tui.messages.pushMessages(switchedChatMsgs)\n ctx.callbacks.restoreSwitchedSession()\n ctx.tui.info.setTopView({ session: forked.id })\n ctx.tui.stores.tasks.setSessionTitle(finalTitle)\n ctx.tui.lifecycle.setTerminalTitle(finalTitle)\n ctx.tui.info.showToast(\n i18next.t('cmd.titleSessionFork'),\n i18next.t('cmd.forkSuccessNamed', { title: finalTitle }),\n )\n}\n","/**\n * /context 命令:上下文占用三段面板(overhead/messages/free)\n */\nimport { i18next } from '@x-otto/tui'\nimport { collectPluginPerfMetrics, resolveSessionTitle } from '@x-otto/coding'\nimport { computeCacheHitRatio } from '@x-otto/ai'\n\nimport type { DataListEntry } from '@x-otto/tui'\nimport type { CliContext } from '../../command-dispatcher'\nimport { computeContextBudget } from '../../context-budget'\n\n/**\n * /status — Show session status.\n * Displays session id, model, message count, token usage, workspace directory.\n *\n * 用户反馈\"改用 DataList\"——同 /about 的分组键值双色渲染(label 弱化/value 正常),\n * 取代纯字符串 `lines` 堆叠。单一分组(无需像 /about 那样按版本/运行时/会话三段拆分),\n * 写权限异常态与暂停态用 warning tone 强调(对齐 InfoDialog lineTones 既有的警示语义)。\n */\nexport async function handleStatus(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n const session = ctx.state.session\n const inner = session.session\n type StatusUsage = {\n inputTokens?: number\n outputTokens?: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n }\n const msgs = inner.messages() as ReadonlyArray<{ role?: string; usage?: StatusUsage }>\n let occupancy = 0\n for (let i = msgs.length - 1; i >= 0; i--) {\n const u = msgs[i]?.usage\n if (msgs[i]?.role === 'assistant' && u) {\n occupancy =\n (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0) + (u.inputTokens ?? 0) + (u.outputTokens ?? 0)\n break\n }\n }\n const window = session.model?.contextWindow as number | undefined\n const pct = window && window > 0 ? Math.round((occupancy / window) * 100) : undefined\n const cumOutput = msgs.reduce((sum, m) => sum + (m.usage?.outputTokens ?? 0), 0)\n // 单一真源回落链(同 interactive.ts 设终端标题、service/resume 同源):\n // meta.title → deriveSessionTitle(首条消息) → id 前 8 位。\n const title = resolveSessionTitle(inner.metadata().title, inner.messages(), session.id)\n const writeLeaseReadOnly = ctx.callbacks.isWriteLeaseReadOnly()\n\n const entries: DataListEntry[] = [\n { kind: 'row', label: i18next.t('cmd.statusRowTitle'), value: title },\n { kind: 'row', label: i18next.t('cmd.statusRowSessionId'), value: session.id.slice(0, 8) },\n { kind: 'row', label: i18next.t('cmd.statusRowModel'), value: session.model?.id ?? i18next.t('cmd.statusModelUnset') },\n { kind: 'row', label: i18next.t('cmd.statusRowMsgs'), value: String(msgs.length) },\n {\n kind: 'row',\n label: i18next.t('cmd.statusRowOccupancy'),\n value: i18next.t('cmd.statusValueOccupancy', { occ: occupancy.toLocaleString(), window: window?.toLocaleString() ?? 'N/A', seg: pct !== undefined ? ` (${pct}%)` : '' }),\n },\n { kind: 'row', label: i18next.t('cmd.statusRowCumOutput'), value: cumOutput.toLocaleString() },\n // 2026-07-14 修复:写权限状态行——用户主动查询详情的入口(三层信息呈现的第 3 层,\n // 首层 toast/次层转录标记均是事件触发的瞬时/流式提示,本行是随时可查的当前状态)。\n {\n kind: 'row',\n label: i18next.t('cmd.statusRowWriteAccess'),\n value: writeLeaseReadOnly ? i18next.t('cmd.statusValueWriteLeaseReadOnly') : i18next.t('cmd.statusValueWriteLeaseOk'),\n tone: writeLeaseReadOnly ? 'warning' : undefined,\n },\n ]\n\n // RFC-180 D7:暂停状态行——只在暂停态显示,避免正常运行时无意义噪音(对齐\"运行\n // 完美时不显示额外行\"的既有克制原则,与 write-lease 行的双态展示不同:那是持久\n // 数据完整性信号需要随时可查,暂停是短暂交互态,非暂停时无信息量)。\n if (ctx.callbacks.isRunPaused()) {\n entries.push({ kind: 'blank' }, { kind: 'text', value: i18next.t('cmd.statusLineRunPaused'), tone: 'warning' })\n }\n\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.titleStatus'), lines: [], dataListEntries: entries })\n}\n\n\n/**\n * /context — 实时上下文用量面板(订阅 budget,每轮 prompt.end 自动刷新)\n */\nexport async function handleContext(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n const session = ctx.state.session\n const model = session.model\n const contextWindow: number = model?.contextWindow ?? 200_000\n const inner = session.session\n const msgs = inner.messages() as ReadonlyArray<{\n role?: string\n usage?: { inputTokens?: number; outputTokens?: number; cacheReadTokens?: number; cacheWriteTokens?: number }\n }>\n\n let lastIn = 0, lastOut = 0, lastCacheRead = 0, lastCacheWrite = 0\n let totIn = 0, totOut = 0, gotLast = false\n for (let i = msgs.length - 1; i >= 0; i--) {\n if (msgs[i]?.role !== 'assistant' || !msgs[i]?.usage) continue\n const u = msgs[i]!.usage!\n totIn += u.inputTokens ?? 0\n totOut += u.outputTokens ?? 0\n if (!gotLast) {\n lastIn = u.inputTokens ?? 0\n lastOut = u.outputTokens ?? 0\n lastCacheRead = u.cacheReadTokens ?? 0\n lastCacheWrite = u.cacheWriteTokens ?? 0\n gotLast = true\n }\n }\n\n const budget = computeContextBudget({\n systemPrompt: session.systemPrompt,\n tools: session.tools,\n lastIn,\n lastOut,\n lastCacheRead,\n lastCacheWrite,\n contextWindow,\n modelName: model?.name ?? model?.id ?? '',\n totIn,\n totOut,\n messageCount: msgs.length,\n })\n\n ctx.tui.stores.budget.setBudget(budget)\n ctx.tui.stores.context.setBudget(budget)\n ctx.tui.modal.openModal({ kind: 'context' })\n}\n\n/**\n * RFC-116 M2:合帧比异常判定阈值(经独立评审 P1-1 补充校准依据 + 可配置出口,\n * 对齐 `MemoryGovernor` 的 `OTTO_MEMORY_WARN_PCT` 模式,不再是无出口的硬编码常量)。\n *\n * `coalesceRatio = coalescedCalls / max(1, coalescedFires)`——越接近 1 表示合帧几乎没有\n * 折叠任何调用(每次流式触发都产生了一次真实 reconcile,RFC-038 想消灭的正是这种情况);\n * 越大表示合帧生效良好(多次调用只触发少量 reconcile)。\n *\n * 校准依据:ratio=1.0 是\"零折叠\"的精确定义(每次 `coalesced()` 调用都触发一次 `fire()`,\n * 合帧机制形同虚设);ratio=2.0 对应 50% 调用被折叠。阈值取二者中点 1.5,即\"折叠掉至少\n * 1/3 的冗余调用\"作为及格线——严格小于(`<`,非 `≤`)此值才判 warning。\n *\n * 已知局限(评审记录,非本里程碑修复范围):`coalescedFires` 统计口径包含 `flush()`\n * 路径触发的 fire(`notify-scheduler.ts` 的 `flush()` 内部调用 `fire()` 累加同一计数器),\n * 小批次流式场景下 flush 触发会拉低比率、可能在批次过小时产生假阳性 warning。修复需要\n * 拆分 `coalescedFires` 的统计口径(区分 timer 触发与 flush 触发),这是 `NotifyStats`\n * 接口本身的扩展,不在本 RFC 范围内,留待后续观察真实数据后决定是否需要。\n */\nconst DEFAULT_NOTIFY_COALESCE_WARN_THRESHOLD = 1.5\n\n/**\n * 每次调用时读取 env(而非模块加载时固化的常量)——对齐 `MemoryGovernor` 的\n * `resolvePctEnv` 模式(`packages/runtime/src/memory-governor.ts`):阈值在\n * 每次实际使用时解析,而非 import 时一次性求值,保证测试/运行时可通过 env 变量\n * 动态覆盖,不受模块缓存影响。\n */\nfunction resolveNotifyWarnThreshold(): number {\n const raw = process.env['OTTO_NOTIFY_COALESCE_WARN_RATIO']\n const parsed = raw ? Number(raw) : NaN\n // 下限用 >= 1(非严格 >1):ratio 恒 ≥ 1(coalescedCalls 不可能小于 coalescedFires),\n // 阈值设为 1.0 是\"几乎禁用告警\"的合理极端配置(只有零折叠时才会告警),不应被拒绝。\n return Number.isFinite(parsed) && parsed >= 1 ? parsed : DEFAULT_NOTIFY_COALESCE_WARN_THRESHOLD\n}\n\n/**\n * `coalescedCalls === 0` 时判定为 `healthy`(不应对\"从未触发过合帧路径\"的场景误报\n * warning——比如会话尚无流式响应到达时)。经独立评审 P2-1 记录:这个 `healthy` 是\n * \"确认无异常\"而非\"确认工作正常\"的语义,`PerfPanel.tsx` 的 `NotifySection` 渲染层\n * 需要用不同的视觉呈现(inactive 色 \"idle\" 而非 accent 色 \"healthy\")区分这两种情况,\n * 本函数的返回值语义(供告警判定用)保持不变,只是渲染表达在组件层做区分。\n */\nfunction deriveNotifyAlert(ratio: number, coalescedCalls: number): 'healthy' | 'warning' {\n if (coalescedCalls === 0) return 'healthy'\n return ratio < resolveNotifyWarnThreshold() ? 'warning' : 'healthy'\n}\n\n/**\n * /perf — 性能监控面板(RFC-116 M1+M2+M5)。\n * 打开面板并读一次当前内存快照 + 渲染健康度快照(合帧统计)+ 插件贡献的采集器\n * 结果(手动刷新,不做自动轮询,见 D7)。三类数据源并发获取,互不阻塞。\n */\nexport async function handlePerf(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n ctx.tui.engine.pushEngineEvent({ kind: 'perf', memory: ctx.app.memoryGovernor.sample() })\n\n const ns = ctx.tui.info.getNotifyStats()\n const coalesceRatio = ns.coalescedCalls / Math.max(1, ns.coalescedFires)\n ctx.tui.engine.pushEngineEvent({\n kind: 'perf',\n notify: {\n coalescedCalls: ns.coalescedCalls,\n coalescedFires: ns.coalescedFires,\n immediateCalls: ns.immediateCalls,\n flushFires: ns.flushFires,\n coalesceRatio,\n alert: deriveNotifyAlert(coalesceRatio, ns.coalescedCalls),\n },\n })\n\n // 插件采集器——collectPluginPerfMetrics 内部已做超时保护+错误归一化+并发调用+\n // 信任门过滤(见 RFC-116 §8/D9),本处失败不应阻塞内置数据源的展示,fail-soft\n // 兜底为空数组(面板不显示插件区块,而非报错阻断整个 /perf 命令)。\n //\n // 经独立评审 P1-1 修正:activePlugins() 移到 try 块外——它是宿主基础设施调用\n // (插件发现/磁盘扫描),若抛异常属于与\"插件采集器执行失败\"完全不同的故障类别,\n // 混在同一个 try/catch 里会让日志把\"插件发现失败\"误标成\"某个采集器执行失败\",\n // 干扰排障。activePlugins() 失败应该让 /perf 命令本身可见地失败,不能静默吞掉。\n const plugins = ctx.app.activePlugins()\n try {\n // RFC-153 D3:代码式工具挂起超时旁路通知 → PluginHealthTracker(供 /extension 面板健康度摘要)。\n const pluginMetrics = await collectPluginPerfMetrics(plugins, undefined, (pluginId) =>\n ctx.app.pluginHealthTracker.recordToolTimeout(pluginId),\n )\n ctx.tui.engine.pushEngineEvent({ kind: 'perf', pluginMetrics })\n } catch (err) {\n ctx.app.logger.warn(\n { err: err instanceof Error ? err.message : String(err) },\n '[perf] collectPluginPerfMetrics failed',\n )\n ctx.tui.engine.pushEngineEvent({ kind: 'perf', pluginMetrics: [] })\n }\n\n ctx.tui.panes.togglePane('perf')\n}\n\n/**\n * /cache-stats — 缓存命中率面板(RFC-019 M2)。\n *\n * 从 `AgentSession.getCostSummary()`(会话级累计用量,按模型拆分,含 api 字段)+\n * `getLastCacheHitRatio()`(最近一回合命中率)组装 CacheView。会话级命中率按 token 量\n * 加权(`Σ cacheRead / Σ totalInput`),而非简单平均——高 token 量的回合对成本影响也大,\n * 加权口径与 `docs/rfc/RFC-019-cache-hit-rate-metrics.md` §5.3 的聚合注意事项一致。\n *\n * 门禁语义与 summary 行完全一致(`computeCacheHitRatio`):某模型的 api 不在缓存报告\n * 白名单内时,该模型行的 hitRatio 为 null,面板据此隐藏百分比/进度条而非展示误导性的 0%。\n */\nexport async function handleCacheStats(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n const summary = ctx.state.session.getCostSummary()\n\n const perModel = summary.perModel.map((m) => {\n const usage = {\n inputTokens: m.inputTokens,\n outputTokens: 0,\n cacheReadTokens: m.cacheReadTokens,\n cacheWriteTokens: m.cacheWriteTokens,\n }\n return {\n modelId: m.modelId,\n modelName: m.modelName,\n cacheReadTokens: m.cacheReadTokens,\n cacheWriteTokens: m.cacheWriteTokens,\n inputTokens: m.inputTokens,\n hitRatio: computeCacheHitRatio(usage, m.api),\n }\n })\n\n // 会话级加权命中率:跨模型累加 token 后统一算一次 ratio(而非对每模型 ratio 取平均,\n // 加权口径见函数头注释)。若全部模型的 api 均不在白名单内(hitRatio 全为 null),\n // 会话级也应隐藏——用\"是否存在任一非 null 模型行\"判定是否有可信数据源;\n // 不传 api 给 computeCacheHitRatio(跳过其内部门禁),因为多模型会话的 api 不唯一,\n // 门禁判定已经在上面逐模型做过。\n const anyReporting = perModel.some((m) => m.hitRatio !== null)\n const sessionUsage = {\n inputTokens: summary.totalInputTokens,\n outputTokens: 0,\n cacheReadTokens: summary.totalCacheReadTokens,\n cacheWriteTokens: summary.totalCacheWriteTokens,\n }\n const sessionHitRatio = anyReporting ? computeCacheHitRatio(sessionUsage) : null\n\n ctx.tui.stores.cache.setView({\n lastTurnHitRatio: ctx.state.session.getLastCacheHitRatio(),\n sessionHitRatio,\n totalCacheReadTokens: summary.totalCacheReadTokens,\n totalCacheWriteTokens: summary.totalCacheWriteTokens,\n totalInputTokens: summary.totalInputTokens,\n totalCostUSD: summary.totalCostUSD > 0 ? summary.totalCostUSD : undefined,\n perModel,\n })\n\n ctx.tui.panes.togglePane('cacheStats')\n}\n\n/**\n * /gallery — 组件画廊(RFC-147 M2,开发指令)。\n * envGate('OTTO_DEV') 只挡 TUI 斜杠菜单注册(见 interactive-commands.ts);直接\n * dispatch 仍可能绕过菜单触达此 handler,故这里二次校验环境变量,未设置时静默\n * no-op + toast 提示,不打开面板(终端用户默认看不到、也摸不到这个开发指令)。\n */\nexport async function handleGallery(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n if (process.env.OTTO_DEV !== '1') {\n ctx.tui.info.showToast('Gallery', 'Set OTTO_DEV=1 to enable the component gallery.')\n return\n }\n ctx.tui.panes.togglePane('gallery')\n}\n","/**\n * /usage 命令:模型用量(总计)面板。\n */\nimport { i18next } from '@x-otto/tui'\nimport { formatProviderUsageLimitTypeFull } from '@x-otto/tui'\nimport type { CliContext } from '../../command-dispatcher'\n\nfunction formatAbsoluteResetTime(resetsAtSec: number): string {\n const d = new Date(resetsAtSec * 1000)\n return d.toLocaleString()\n}\n\n/**\n * /usage — 展示当前模型的供应商 usage 快照(额度类型/状态/重置时间/已用百分比)。\n * 非 Anthropic OAuth 订阅计划(API key/env/未登录)→ 提示不适用。\n * OAuth 订阅计划但本会话尚无快照(还未发起过请求)→ 提示先发消息。\n */\nexport async function handleUsage(_args: string[], ctx: CliContext): Promise<void> {\n const session = ctx.state.session\n const provider = session.model?.provider\n\n const authSource = provider ? await ctx.app.authStore.getAuthSource(provider).catch(() => null) : null\n if (authSource !== 'oauth') {\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.titleUsage'), lines: [i18next.t('cmd.usageNotSubscription')] })\n return\n }\n\n const snapshot = ctx.callbacks.getLastProviderUsage()\n if (!snapshot) {\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.titleUsage'), lines: [i18next.t('cmd.usageNoSnapshotYet')] })\n return\n }\n\n const lines: string[] = []\n const isAnthropic = provider === 'anthropic'\n\n // Anthropic 订阅配额(主要展示)\n if (snapshot.limitType) {\n lines.push(i18next.t('cmd.usageLineType', { type: formatProviderUsageLimitTypeFull(snapshot.limitType) }))\n }\n if (isAnthropic || snapshot.usedPercent !== undefined) {\n // Anthropic 场景:status='allowed' 是正常态,\"没有订阅配额头\"的 OpenAI allowed 不展示(无信息量)\n lines.push(i18next.t('cmd.usageLineStatus', { status: snapshot.status }))\n }\n if (snapshot.usedPercent !== undefined) {\n lines.push(i18next.t('cmd.usageLinePercent', { pct: Math.round(snapshot.usedPercent) }))\n }\n if (snapshot.resetsAt !== undefined) {\n lines.push(i18next.t('cmd.usageLineResetsAt', { when: formatAbsoluteResetTime(snapshot.resetsAt) }))\n }\n\n // 标准 API 限流(x-ratelimit-* 头族,任何 provider+auth 模式都可能返回)\n if (snapshot.standardLimits) {\n const sl = snapshot.standardLimits\n lines.push(\n i18next.t('cmd.usageLineStandardLimits', { reqR: sl.remainingRequests ?? 0, reqL: sl.limitRequests ?? 0, tokRk: ((sl.remainingTokens ?? 0) / 1000) | 0, tokLk: ((sl.limitTokens ?? 0) / 1000) | 0, reset: Math.round(Math.max(sl.resetRequestsMs ?? 0, sl.resetTokensMs ?? 0) / 1000) }),\n )\n }\n\n // OpenAI / 非 Anthropic provider:订阅配额不通过 HTTP 头暴露 → 追加提示\n if (!isAnthropic && snapshot.limitType === undefined) {\n lines.push('')\n lines.push(i18next.t('cmd.usageSubPoolNotInHeaders'))\n }\n\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.titleUsage'), lines })\n}\n","/**\n * RFC-036 §14.3-C:自由 `#` 笔记 → `AutoMemory.record` 入参(MVP,无模型)。\n *\n * `record({name, description, content})`:name 作条目标题(内部 slugify 出文件名),\n * description 作 MEMORY.md 索引 hook,content 是全文。本函数从一条自由笔记派生三者。\n */\n\nexport interface ShapedNote {\n /** 条目标题(AutoMemory 内部 slugify 成文件名)。 */\n name: string\n /** 索引副标题(MEMORY.md 指针的 — hook)。 */\n description: string\n /** 全文。 */\n content: string\n}\n\n/** 首行前若干词作标题,前 N 字作描述,全文作 content。空白折叠。 */\nexport function shapeNote(note: string): ShapedNote {\n const content = note.trim()\n const firstLine = (content.split('\\n', 1)[0] ?? '').replace(/\\s+/g, ' ').trim()\n const name = firstLine.split(' ').slice(0, 6).join(' ').slice(0, 48).trim() || 'note'\n const description = firstLine.slice(0, 60).trim() || name\n return { name, description, content }\n}\n\nconst NAMING_PROMPT = (note: string): string =>\n `把下面的笔记归纳成一个简短标题(≤6 词)和一句话描述。严格只按此格式输出:\\nTITLE: <标题>\\nDESC: <一句话描述>\\n\\n笔记:\\n${note}`\n\n/** 解析模型输出的 TITLE/DESC;缺 TITLE → null(回退 slugify)。 */\nfunction parseNaming(out: string): { name: string; description: string } | null {\n const name = /TITLE:\\s*(.+)/i.exec(out)?.[1]?.trim()\n if (!name) return null\n const desc = /DESC:\\s*(.+)/i.exec(out)?.[1]?.trim()\n return { name: name.slice(0, 60), description: (desc || name).slice(0, 80) }\n}\n\n/**\n * RFC-036 §14.3-C:opt-in 模型辅助命名。`complete` 缺省(开关关/无模型)→ 直接 slugify。\n * 任何失败(无模型/超时/格式不符)**静默回退 slugify**,绝不让 `#` 卡住或报错。content 始终是原文。\n */\nexport async function shapeNoteSmart(\n note: string,\n complete?: (prompt: string, opts?: { maxTokens?: number; signal?: AbortSignal }) => Promise<string>,\n timeoutMs = 8000,\n): Promise<ShapedNote> {\n const base = shapeNote(note)\n if (!complete) return base\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), timeoutMs)\n ;(timer as { unref?: () => void }).unref?.()\n try {\n const out = await complete(NAMING_PROMPT(note), { maxTokens: 64, signal: ac.signal })\n const parsed = parseNaming(out)\n if (parsed) return { name: parsed.name, description: parsed.description, content: base.content }\n } catch {\n } finally {\n clearTimeout(timer)\n }\n return base\n}\n","/**\n * handlers/memory-compact.ts — 记忆管理与强制压缩指令处理\n *\n * /memory — 记忆管理(列出/查看/删除/写入;项目级 + 用户级)\n * /compact — 强制压缩对话上下文\n * /compact log — 打开压缩/裁剪事件日志面板(并入 /compact 子命令,不再是独立命令)\n */\nimport { i18next } from '@x-otto/tui'\nimport type { AutoMemory } from '@x-otto/memory'\nimport type { MemoryEntry } from '@x-otto/tui'\nimport type { CliContext } from '../../command-dispatcher'\nimport { shapeNote } from '../shape-note'\n\n/**\n * MemoryDialog(可导航列表版)的 onViewEntry/onForget 回调构造器——按 entry.source\n * 路由到对应存储(project→proj/AutoMemory,user→user/AutoMemory),两者接口同构\n * (均为 `read(name)`/`forget(name)`)。proj 可能为 null(无 workspace 场景)。\n */\nfunction buildMemoryDialogCallbacks(\n proj: AutoMemory | null,\n user: AutoMemory,\n): { onViewEntry: (entry: MemoryEntry) => Promise<string | null>; onForget: (entry: MemoryEntry) => Promise<void> } {\n return {\n onViewEntry: async (entry: MemoryEntry) => {\n const store = entry.source === 'project' ? proj : user\n if (!store) return null\n return store.read(entry.name)\n },\n onForget: async (entry: MemoryEntry) => {\n const store = entry.source === 'project' ? proj : user\n if (!store) return\n await store.forget(entry.name)\n },\n }\n}\n\n/**\n * /memory [<name> | forget <name>](RFC-036 §14)\n * 无参 → 打开记忆面板(对话框,展示项目+用户记忆索引)。\n * <name> → 显示该条全文。\n * forget <name> → 删除该条。\n * 写入由 `#<内容>` 完成(手动记忆捕获);本命令是配套管理。\n */\nexport async function handleMemory(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const proj = ctx.app.storage.autoMemory\n const user = ctx.app.userMemory\n const sub = (args[0] ?? '').toLowerCase()\n\n const say = (text: string) =>\n ctx.tui.messages.pushMessage({\n role: 'assistant',\n content: [{ type: 'text', text }],\n })\n /** /memory 子命令的操作结果回显(RFC-233 command_echo)——区别于 say():say 用于展示\n * 笔记原文内容本身(语义等价于\"内容展示\",非\"命令反馈\"),echo 用于命令执行的\n * 成功/失败/用量提示。 */\n const echo = (command: string, status: 'ok' | 'warn' | 'error', summary: string) =>\n ctx.tui.messages.pushMessage({ role: 'command_echo', command, status, summary })\n\n // 列出单项:user / project —— 也走对话框(面板视图)\n if (sub === 'user') {\n const rest = args.slice(1).join(' ').trim()\n if (rest) {\n const shaped = shapeNote(rest)\n await user.record(shaped)\n echo('/memory user', 'ok', i18next.t('cmd.memoryUserSaved', { name: shaped.name }))\n return\n }\n const userIdx = user.getInjection().trim()\n ctx.tui.modal.openModal({ kind: 'memory', projectIndex: '', userIndex: userIdx, ...buildMemoryDialogCallbacks(proj, user) })\n return\n }\n\n if (sub === 'project') {\n const projIdx = proj ? proj.getInjection().trim() : ''\n ctx.tui.modal.openModal({ kind: 'memory', projectIndex: projIdx, userIndex: '', ...buildMemoryDialogCallbacks(proj, user) })\n return\n }\n\n if (sub === 'forget') {\n const name = args.slice(1).join(' ').trim()\n if (!name) { echo('/memory forget', 'warn', i18next.t('cmd.memoryForgetUsage')); return }\n let done = false\n if (proj && (await proj.read(name)) !== null) { await proj.forget(name); done = true }\n if ((await user.read(name)) !== null) { await user.forget(name); done = true }\n echo('/memory forget', done ? 'ok' : 'error', done ? i18next.t('cmd.memoryDeleted', { name: name }) : i18next.t('cmd.memoryNotFound', { name: name }))\n return\n }\n\n if (args.length > 0) {\n const name = args.join(' ').trim()\n const content = (proj ? await proj.read(name) : null) ?? (await user.read(name))\n if (content == null) {\n echo(`/memory ${name}`, 'error', i18next.t('cmd.memoryNotFound', { name: name }))\n } else {\n say(content)\n }\n return\n }\n\n // 默认:打开记忆面板(对话框)\n const projIdx = proj ? proj.getInjection().trim() : ''\n const userIdx = user.getInjection().trim()\n ctx.tui.modal.openModal({ kind: 'memory', projectIndex: projIdx, userIndex: userIdx, ...buildMemoryDialogCallbacks(proj, user) })\n}\n\n/**\n * /compact — Force-compact conversation context now.\n * Uses engine summariser; writes back to session tree.\n * Full transcript preserved; only the LLM view shrinks.\n *\n * /compact log — 打开压缩/裁剪事件日志面板(只读展示,数据早已就绪,同 /perf、\n * /cache-stats\"数据早就绪、命令只管开面板\"的既有模式)。子命令置于压缩本体前分派,\n * 不占用/不消耗\"强制压缩\"这个有副作用动作的语义空间——保持在同一斜杠命令下但用\n * 子命令词区分只读查看 vs 有状态改动的操作。\n */\nexport async function handleCompact(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const title = i18next.t('cmd.titleCompact')\n\n if ((args[0] ?? '').toLowerCase() === 'log') {\n ctx.tui.panes.togglePane('compactionLog')\n return\n }\n\n if (!ctx.app.storage.memory) {\n ctx.tui.info.showToast(title, i18next.t('compact.unavailable'))\n return\n }\n\n ctx.tui.info.showToast(title, i18next.t('compact.ing'))\n ctx.tui.engine.pushEngineEvent({ kind: 'agentProcessing', value: true })\n try {\n const result = await ctx.state.session.compactNow()\n if (result.compacted) {\n ctx.tui.info.showToast(title, i18next.t('compact.edResult', { before: result.before, after: result.after }))\n // RFC-156:手动压缩成功——清空上下文占用提醒的区间计数,进入新一轮对话生命周期。\n ctx.callbacks.onContextCompacted()\n } else {\n ctx.tui.info.showToast(title, i18next.t('compact.noop'))\n }\n } catch (e) {\n ctx.tui.info.showToast(title, i18next.t('cmd.compactFailed', { msg: e instanceof Error ? e.message : String(e) }))\n } finally {\n ctx.tui.engine.pushEngineEvent({ kind: 'agentProcessing', value: false })\n }\n}\n","/**\n * /workspace 命令:工作区管理(创建/切换/扫描/导入)。\n */\nimport type { CliHandler, CliContext } from '../../command-dispatcher'\n\nexport const handleWorkspace: CliHandler = async (args: string[], ctx: CliContext): Promise<void> => {\n const sub = args[0]\n switch (sub) {\n case 'memory-sync':\n return handleMemorySync(args.slice(1), ctx)\n default:\n ctx.tui.info.showToast(\n 'Workspace',\n `/workspace ${sub ?? ''} — unknown subcommand. Use: memory-sync [status|enable|disable]`,\n )\n }\n}\n\nasync function handleMemorySync(args: string[], ctx: CliContext): Promise<void> {\n const sub = args[0] ?? 'status'\n const ref = ctx.app.workspaceRef\n\n if (!ref?.root) {\n ctx.tui.info.showToast('Workspace memory-sync', 'No local workspace identity yet — cannot configure memory sync.')\n return\n }\n\n switch (sub) {\n case 'status': {\n const enabled = ref.memorySyncEnabled === true\n ctx.tui.info.showToast(\n 'Workspace memory-sync',\n enabled\n ? 'Enabled — workspace memory (auto-memory/lessons) syncs to the remote store.'\n : 'Disabled — workspace memory stays local (default).',\n )\n return\n }\n case 'enable': {\n if (!ctx.app.storage.remoteMemoryConfigured) {\n ctx.tui.info.showToast(\n 'Workspace memory-sync',\n 'No remote memory service configured (needs --session-url + remote.getAuth) — enabling now would have no effect.',\n )\n return\n }\n const ok = await ctx.app.setWorkspaceMemorySyncEnabled(true)\n ctx.tui.info.showToast(\n 'Workspace memory-sync',\n ok\n ? 'Enabled. Next memory_record/learn calls will sync to the remote store.'\n : 'Failed to enable — no workspace marker to update.',\n )\n return\n }\n case 'disable': {\n const ok = await ctx.app.setWorkspaceMemorySyncEnabled(false)\n ctx.tui.info.showToast(\n 'Workspace memory-sync',\n ok ? 'Disabled. Workspace memory now stays local.' : 'Failed to disable — no workspace marker to update.',\n )\n return\n }\n default:\n ctx.tui.info.showToast('Workspace memory-sync', `/workspace memory-sync ${sub} — unknown subcommand. Use: status|enable|disable`)\n }\n}\n","/**\n * handlers/upgrade-check.ts — /upgrade --check 扩展版本检查\n *\n * /upgrade --check — 扫描已安装扩展,对比版本更新\n */\nimport { i18next } from '@x-otto/tui'\nimport { scanInstalledVersions, checkUpgrades, findWorkspaceRoot } from '@x-otto/coding'\nimport type { CliContext } from '../../command-dispatcher'\n\nexport async function handleUpgradeCheck(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n ctx.tui.info.showToast(i18next.t('cmd.titleUpgrade'), i18next.t('cmd.upgradeCheckScanning'))\n\n try {\n const installed = scanInstalledVersions({\n workspaceDir: ctx.app.workspaceDir,\n // RFC-130:'all' 已含 repository 分支(仅当 workspaceRoot 被找到时才实际扫描)。\n workspaceRoot: findWorkspaceRoot(ctx.app.workspaceDir),\n scope: 'all',\n })\n\n if (installed.length === 0) {\n ctx.tui.info.showToast(i18next.t('cmd.titleUpgrade'), i18next.t('cmd.upgradeCheckNoVersioned'))\n return\n }\n\n const results = await checkUpgrades(installed)\n\n const lines: string[] = []\n let updateCount = 0\n\n for (const r of results) {\n const tag = `[${r.type}]`\n if (r.error) {\n lines.push(`${tag} ${r.name} v${r.installed} — ${r.error}`)\n } else if (r.newer) {\n updateCount++\n lines.push(i18next.t('cmd.upgradeCheckLineUpdate', { tag: tag, name: r.name, installed: r.installed, latest: r.latest ?? '' }))\n } else {\n lines.push(i18next.t('cmd.upgradeCheckLineLatest', { tag: tag, name: r.name, installed: r.installed }))\n }\n }\n\n if (updateCount > 0) {\n lines.push('')\n lines.push(i18next.t('cmd.upgradeCheckSummary', { count: updateCount }))\n }\n\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.titleUpgradeCheck'), lines })\n } catch (err) {\n ctx.tui.info.showToast(\n i18next.t('cmd.titleUpgrade'),\n i18next.t('cmd.upgradeCheckFailed', { err: err instanceof Error ? err.message : String(err) }),\n )\n }\n}\n","import { i18next } from '@x-otto/tui'\nimport { arch, cpus, platform, release, totalmem, freemem } from 'node:os'\n\nimport { SDK_VERSION, BUILD_ID } from '@x-otto/env'\nimport { BUILD_BRANCH, BUILD_HASH } from '../../build-id'\n\nimport type { DataListEntry } from '@x-otto/tui'\nimport type { CliContext } from '../../command-dispatcher'\n\n/**\n * /about — 用 `DataList`(分组 + label 弱化/value 正常前景双色)渲染,取代此前\n * `formatKeyValueLines` 纯文本堆叠版本。三段分组:版本信息 / 运行环境 / 会话状态——\n * 运行环境全部字段(Node/CPU/内存/Shell/终端)是纯背景信息,统一走 `muted` tone 弱化;\n * 会话状态的「当前模型」是用户最关心的字段,走 `secondary` tone 强调。\n */\nexport async function handleAbout(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n const session = ctx.state.session\n const modelId = session.model?.id ?? i18next.t('cmd.aboutModelNotSet')\n const cpuCount = cpus().length\n const memTotal = Math.round(totalmem() / (1024 * 1024 * 1024))\n const memFree = Math.round(freemem() / (1024 * 1024))\n\n const entries: DataListEntry[] = [\n { kind: 'group', title: i18next.t('cmd.aboutGroupVersion') },\n { kind: 'row', label: i18next.t('cmd.aboutVersion'), value: `${SDK_VERSION} (build ${BUILD_ID})` },\n { kind: 'row', label: i18next.t('cmd.aboutBranch'), value: BUILD_BRANCH },\n { kind: 'row', label: i18next.t('cmd.aboutCommit'), value: BUILD_HASH },\n\n { kind: 'group', title: i18next.t('cmd.aboutGroupRuntime') },\n { kind: 'row', label: i18next.t('cmd.aboutRuntime'), value: `Node.js ${process.version} · ${platform()} ${arch()} · ${release()}`, tone: 'muted' },\n { kind: 'row', label: i18next.t('cmd.aboutCPU'), value: `${cpuCount} cores`, tone: 'muted' },\n { kind: 'row', label: i18next.t('cmd.aboutMemory'), value: `${memTotal} GB total / ${Math.round(memFree)} MB free`, tone: 'muted' },\n { kind: 'row', label: i18next.t('cmd.aboutShell'), value: process.env['SHELL'] ?? '(unknown)', tone: 'muted' },\n { kind: 'row', label: i18next.t('cmd.aboutTerminal'), value: process.env['TERM'] ?? '(unknown)', tone: 'muted' },\n\n { kind: 'group', title: i18next.t('cmd.aboutGroupSession') },\n { kind: 'row', label: i18next.t('cmd.aboutModel'), value: modelId, tone: 'secondary' },\n ]\n\n if (session.id) {\n entries.push({ kind: 'row', label: i18next.t('cmd.aboutSession'), value: session.id.slice(0, 8) })\n }\n\n entries.push({ kind: 'blank' }, { kind: 'text', value: i18next.t('cmd.aboutUpdateHint'), tone: 'muted' })\n\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.titleAbout'), lines: [], dataListEntries: entries })\n}\n","/**\n * /feedback 命令:用户满意度反馈(L1: 本地落盘 + 上传 + 显示统计)。\n */\nimport { i18next } from '@x-otto/tui'\nimport { openBrowser } from '@x-otto/shared'\nimport { resolveFeedbackRepo, buildFeedbackEnvInfo } from '../feedback'\nimport type { CliContext } from '../../command-dispatcher'\n\n/** openModal 'input' 分支的 Promise 包装——本仓既有 handler(feishu-setup.ts)已用同名手写包装,\n * 但 openModal(kind:'input') 本身已是 Promise 签名,这里直接透传不需要额外包装函数。 */\nasync function askInput(ctx: CliContext, opts: { title: string; description?: string[]; placeholder?: string }): Promise<string | null> {\n return ctx.tui.modal.openModal({ kind: 'input', ...opts })\n}\n\n/** openModal 'confirm' 分支的 Promise 包装(同 feishu-setup.ts promiseConfirm 模式)。 */\nfunction askConfirm(ctx: CliContext, title: string, message: string | string[]): Promise<boolean> {\n return new Promise((resolve) => {\n ctx.tui.modal.openModal({\n kind: 'confirm',\n title,\n message,\n onConfirm: () => resolve(true),\n onCancel: () => resolve(false),\n })\n })\n}\n\nexport async function handleFeedback(args: string[], ctx: CliContext): Promise<void> {\n\n const provider = ctx.app.feedbackRegistry.get()\n if (!provider) {\n ctx.tui.info.showToast(i18next.t('cmd.titleFeedback'), i18next.t('cmd.feedbackUnavailable'))\n return\n }\n\n // --repo <owner/repo> 命令行参数(RFC-207 §3.4 解析优先级的第三档)。\n const repoArgIndex = args.indexOf('--repo')\n const cliRepoArg = repoArgIndex >= 0 ? args[repoArgIndex + 1] : undefined\n\n const configuredRepo = ctx.app.settings.get('feedback_repo') as string | undefined\n const repo = await resolveFeedbackRepo({ configuredRepo, cliRepoArg, cwd: ctx.app.workspaceDir })\n if (!repo) {\n ctx.tui.info.showToast(i18next.t('cmd.titleFeedback'), i18next.t('cmd.feedbackRepoUnresolved'))\n return\n }\n\n const title = await askInput(ctx, { title: i18next.t('cmd.feedbackStepTitle') })\n if (!title || title.trim().length === 0) return // 标题必填,取消/空输入即中止\n\n const reproduce = (await askInput(ctx, { title: i18next.t('cmd.feedbackStepRepro') })) ?? undefined\n const expected = (await askInput(ctx, { title: i18next.t('cmd.feedbackStepExpected') })) ?? undefined\n const actual = (await askInput(ctx, { title: i18next.t('cmd.feedbackStepActual') })) ?? undefined\n\n const envInfo = buildFeedbackEnvInfo(ctx.state.session.model?.id)\n const input = { title: title.trim(), reproduce, expected, actual, envInfo, repo }\n\n const bodyPreview = [reproduce, expected, actual].filter(Boolean).join('\\n').slice(0, 300)\n const confirmed = await askConfirm(ctx, i18next.t('cmd.feedbackConfirmTitle'), i18next.t('cmd.feedbackConfirmMessage', { title: input.title, bodyPreview: bodyPreview }))\n if (!confirmed) return\n\n ctx.tui.info.showToast(i18next.t('cmd.titleFeedback'), i18next.t('cmd.feedbackChannelSubmitting'))\n\n // 通道选择(RFC-207 §2.7/RFC-209 §2.4):优先 submitIssueViaGh,失败或方法不存在\n // (插件未实现该方法)时降级 buildIssueUrl + openBrowser。\n if (provider.submitIssueViaGh) {\n const result = await provider.submitIssueViaGh(input)\n if (result.ok) {\n ctx.tui.info.showToast(i18next.t('cmd.titleFeedback'), i18next.t('cmd.feedbackCreated', { url: result.url }))\n return\n }\n // gh 失败——降级浏览器通道兜底完成本次提交(复用已拼装好的 input,不需要用户重新填写)。\n }\n\n const url = provider.buildIssueUrl?.(input)\n if (!url) {\n ctx.tui.info.showToast(i18next.t('cmd.titleFeedback'), i18next.t('cmd.feedbackUnavailable'))\n return\n }\n openBrowser(url)\n ctx.tui.info.showToast(i18next.t('cmd.titleFeedback'), i18next.t('cmd.feedbackCreated', { url: url }))\n}\n","/**\n * provider-auth-options.ts — RFC-274 M2:Provider 授权选项动态派生。\n *\n * 取代 setup-wizard.ts 硬编码的 `providerOAuthSupports` 白名单——从\n * `OAuthRegistry`(已装载插件的 OAuth provider,含声明式 `contributes.oauth`\n * 与代码式 `oauthProviderFactories` 两种轴,如 Cursor 用后者、无法通过 manifest\n * 静态判定)动态派生该 provider 是否有 OAuth 授权方式。\n *\n * 权威源裁定(终局核实修正):查 `OAuthRegistry` 而非 manifest 磁盘扫描。理由:\n * ① `OAuthRegistry` 覆盖代码式 OAuth(Cursor 的 `oauthProviderFactories`,无\n * `contributes.oauth`)——manifest-only 会静默丢失这些 provider 的 OAuth 选项,\n * 是真实回归;\n * ② setup 向导安装 provider 后的调用链\n * `WizardDialog.handleInstall → request.onInstallProvider → runPluginInstall`\n * 内部已 `await ctx.app.reloadCapabilities()`(`extension-plugin.ts:304`),\n * 该调用同步跑完 `PluginProviderManager.reload()` → `loadOAuthAxis` 把新装插件\n * 的 OAuth provider 注册进 `OAuthRegistry`——`onInstallProvider` resolve 时\n * registry 已完整,不存在\"刚装完但未注册\"的空窗;\n * ③ 已核实全部内置 provider 插件(anthropic/github-copilot/openai-codex,通过\n * `codeProviderIds` 声明)在 `OAuthRegistry` 里均以**裸 provider id**注册\n * (`buildPreservedOAuthRefs` 的 `codeProviderIds` 分支命中),与\n * `app.authStore.listProviders()` 返回的裸 id 同一命名空间——可直接用\n * provider id 查 registry,无需重算 `<pluginId>:<id>` 命名空间前缀。\n *\n * 不区分 pkce/device-flow 两种选项(设计纠正,与初版 RFC 草案不同):`OAuthProvider`\n * 运行时对象没有可靠字段暴露 kind(`usesCallbackServer` 的真实语义是\"loopback 自动\n * 回填 vs 手动粘贴\",与 pkce/device-flow 区分无关——PKCE 模板本身也写 `false`,两种\n * 模板在这一点上不可区分)。这个区分本就不该在\"选项列表\"这一层做:`OAuthInfo\n * {url, code?, instructions?}` 已经是运行时自适应的——`code` 存在与否由 WizardDialog\n * 的等待态渲染分支处理(同一个 'oauth' 选项,等待态里有 code 就展示设备码 UI,没有就\n * 展示纯浏览器 UI),不需要在选项级别预先分叉出 'device-flow' 这个 value。\n */\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { parsePluginManifest, PLUGIN_MANIFEST_FILENAME, resolvePackageDir } from '@x-otto/coding'\n\nexport interface ProviderAuthOption {\n label: string\n value: 'oauth' | 'key'\n detail?: string\n}\n\nexport interface ProviderAuthOAuthRegistry {\n get(id: string): { id: string } | undefined\n}\n\n/**\n * 从一个已安装 provider 插件的 npm 包名解析出裸 provider id(终局审核修正,取代原\n * `ALIAS_TO_PROVIDER_ID` 静态映射表)。\n *\n * 独立审核发现的真实缺陷:静态映射表只覆盖 `EXTENSION_ALIASES` 里的 6 个官方 provider\n * alias——用户通过 `otto extension install <第三方 npm 包名>` 装非官方 provider 插件时,\n * 该值查不到映射,`WizardDialog` 会 fallback 回 alias/包名本身去查 `OAuthRegistry`,\n * 必然 miss(即使该插件真声明了 `contributes.oauth`),永久只能看到 API key 选项。\n *\n * 通用解法:装完后直接读该插件包目录的 `otto-plugin.json`,按 `loadOAuthAxis`/\n * `buildPreservedOAuthRefs` 同款优先级解析裸 id——`codeProviderIds[0]`(代码式主 id)\n * 优先,否则取 `contributes.providers[0].id`(声明式主 id),否则取\n * `contributes.oauth[0].id`(纯 OAuth 无 provider 条目的边界情况)。解析失败返回\n * `undefined`(调用方 fallback 回 alias,行为不劣于修复前)。\n */\nexport function resolveInstalledProviderId(pkgName: string, cwd?: string): string | undefined {\n try {\n const pkgDir = resolvePackageDir(pkgName, cwd)\n if (!pkgDir) return undefined\n const manifestPath = join(pkgDir, PLUGIN_MANIFEST_FILENAME)\n const manifest = parsePluginManifest(readFileSync(manifestPath, 'utf-8'), manifestPath)\n if (!manifest) return undefined\n return (\n manifest.codeProviderIds?.[0] ??\n manifest.contributes?.providers?.[0]?.id ??\n manifest.contributes?.oauth?.[0]?.id\n )\n } catch {\n return undefined\n }\n}\n\n/**\n * 从 `OAuthRegistry` 派生某 provider 的授权方式列表。\n * @param providerId Step 1 选中/已认证的 provider 裸 id(空 = 未选/多 provider 场景)。\n * @param oauthRegistry `app.authStore.oauth`(唯一权威源,见上方文件头注释)。\n * @param labels i18n 化的选项文案(由调用方注入,避免本模块直接依赖 i18next)。\n */\nexport function deriveProviderAuthOptions(\n providerId: string,\n oauthRegistry: ProviderAuthOAuthRegistry,\n labels: {\n oauth: { label: string; detail: string }\n apiKey: { label: string; detail: string }\n },\n): ProviderAuthOption[] {\n // providerId 为空(多 provider 已装、Step 0 已完成但未跟踪选中项)→ 保守展示 oauth+key,\n // 保持与旧硬编码分支一致的向后兼容行为(无法判定具体 provider 时不猜测)。\n const supportsOAuth = providerId ? !!oauthRegistry.get(providerId) : true\n if (supportsOAuth) {\n return [\n { label: labels.oauth.label, value: 'oauth', detail: labels.oauth.detail },\n { label: labels.apiKey.label, value: 'key', detail: labels.apiKey.detail },\n ]\n }\n return [{ label: labels.apiKey.label, value: 'key', detail: labels.apiKey.detail }]\n}\n\n","/**\n * ftue-snapshot.ts — RFC-326 M1-01:新用户首次体验(FTUE)的**单一派生判定**。\n *\n * 终局原则(RFC-326 §4 原则 3「状态是事实,不是浏览历史」):\n * 「能不能用」永远从 provider / 凭据 / 模型可用性**实时重算**,绝不由「用户是否看过\n * 或关闭过某个 UI」的历史 flag 决定。`dismissed` 只影响**要不要自动抢焦点**,不影响\n * `stage`——这是本模块最重要的不变式,也是三条突变门禁守护的对象。\n *\n * 为什么是纯函数(零 I/O、零时钟、零全局读):\n * - 判定逻辑可被组合矩阵单测穷举,不需要构造真实 AuthStore / ModelRegistry;\n * - 与 `createModelUsabilityGate` 的异步探查解耦——凭据判定由调用方(controller)\n * 用**权威端口**先算好,本模块只消费其结论。这样\"可用性判据\"在全仓只有一处实现,\n * 不会重蹈 `modelReady` / `availableModelIds` / `has_completed_wizard` 各自为政的覆辙。\n *\n * 边界(本模块**不**做的事):\n * - 不查询凭据、不读 settings、不触网、不读 Date.now()——全部经入参传入;\n * - 不决定 UI 如何渲染,只给出「当前卡在哪一步」+「唯一推荐动作」;\n * - 不持久化任何东西。持久化只保留用户意图(dismissedAt 等),见 M1-02。\n */\n\n/**\n * FTUE 阶段——按「用户距离可发出第一条 prompt 还差什么」排序的单调阶梯。\n *\n * provider → authentication → model → ready → first-prompt\n *\n * 语义要点:`ready` 表示\"可以发 prompt 了\",`first-prompt` 表示\"已经发过至少一条\"。\n * 二者分开是因为首 prompt 建议卡只在 ready 显示、发过之后自然消失(RFC-326 §6.5)。\n */\nexport type FtueStage =\n | 'provider'\n | 'authentication'\n | 'model'\n | 'ready'\n | 'first-prompt'\n\n/** 推荐动作的机器可读标识——UI 据此渲染唯一主 CTA(RFC-326 §4 原则 4)。 */\nexport type FtueAction =\n | 'install-provider'\n | 'connect-provider'\n | 'choose-model'\n | 'send-first-prompt'\n | 'none'\n\n/** 可安装的 provider 候选(纯数据,排序由调用方按「最快可用」决定)。 */\nexport interface ProviderCandidate {\n /** 安装标识(EXTENSION_ALIASES 的 key,或第三方 npm 包名)。 */\n id: string\n /** 展示名。 */\n label: string\n /** 简短能力/计费/认证方式提示。 */\n detail?: string\n}\n\n/**\n * 单一主行动建议。`action` 是机器判据,`stage` 冗余携带便于 UI 直接分派;\n * 文案不在此处硬编码——i18n 由消费方按 action 取,避免纯逻辑层耦合语言资源。\n */\nexport interface FtueRecommendation {\n action: FtueAction\n stage: FtueStage\n /**\n * 是否应当**自动**抢占焦点弹出设置旅程。\n * dismissed 或已 ready 时恒为 false——「不自动弹」不等于「不需要配置」,\n * 后者仍由 stage 表达,Home 的常驻恢复卡据此显示(RFC-326 §6.7)。\n */\n autoOpen: boolean\n}\n\n/** `deriveFtueSnapshot` 的输入端口(全部显式传入,无隐式 I/O)。 */\nexport interface FtueInput {\n /** 是否已安装任一 provider 插件(权威源:ModelRegistry.hasAnyPluginSourcedModel())。 */\n providerInstalled: boolean\n /** 可安装的 provider 候选清单。 */\n providerCandidates?: readonly ProviderCandidate[]\n /** 已连接(存在任意凭据)的 provider id 列表。 */\n connectedProviderIds?: readonly string[]\n /**\n * 经 `createModelUsabilityGate` 判定为**真正可用**的模型 id。\n *\n * 唯一合法来源是该 gate 的结论。禁止传入 registry 全量 id、`availableModelIds`\n * 这类陈旧 session 缓存,或 `applyModel()` 的乐观写入——RFC-326 §5.1 明文约束。\n */\n usableModelIds?: readonly string[]\n /** 当前选中的模型 id(session.model?.id)。 */\n selectedModelId?: string\n /** 用户是否已发出过第一条 prompt(持久化的用户意图,非派生)。 */\n firstPromptSubmitted?: boolean\n /**\n * 用户是否主动关闭过设置 UI(旧 `has_completed_wizard` / `wizard_dont_show_again`\n * 的新语义)。**只影响 autoOpen,不影响 stage**——这是 RFC-326 §4 原则 3 的核心。\n */\n dismissed?: boolean\n}\n\nexport interface FtueSnapshot {\n stage: FtueStage\n provider: { installed: boolean; candidates: readonly ProviderCandidate[] }\n authentication: { connectedProviderIds: readonly string[] }\n model: { selectedId?: string; usableIds: readonly string[] }\n firstPrompt: { submitted: boolean }\n recommendation: FtueRecommendation\n}\n\nconst STAGE_ACTION: Record<FtueStage, FtueAction> = {\n provider: 'install-provider',\n authentication: 'connect-provider',\n model: 'choose-model',\n ready: 'send-first-prompt',\n 'first-prompt': 'none',\n}\n\n/**\n * 纯派生:由客观事实推出当前阶段。\n *\n * 判定顺序即依赖顺序,逐级短路:\n * 1. 没有 provider 插件 → `provider`\n * 2. 没有任何已连接凭据 → `authentication`\n * 3. 没有任何**可用**模型 → `model`\n * (注意判据是 usableModelIds 非空,不是\"registry 里有模型\"——\n * 插件装了但凭据无效时 registry 有模型而 usableIds 为空,必须停在这一步)\n * 4. 选中的模型不在可用集合里 → `model`\n * (覆盖\"凭据被外部删除/过期导致既有选择失效\",RFC-326 §7.1 要求可退化)\n * 5. 尚未发过首 prompt → `ready`;发过 → `first-prompt`\n */\nfunction deriveStage(input: FtueInput): FtueStage {\n if (!input.providerInstalled) return 'provider'\n\n const connected = input.connectedProviderIds ?? []\n if (connected.length === 0) return 'authentication'\n\n const usable = input.usableModelIds ?? []\n if (usable.length === 0) return 'model'\n\n // 已选模型必须仍在可用集合内;否则退回 model 步重选。\n // selectedModelId 缺省(从未选过)同样停在 model 步。\n const selected = input.selectedModelId\n if (!selected || !usable.includes(selected)) return 'model'\n\n return input.firstPromptSubmitted ? 'first-prompt' : 'ready'\n}\n\n/**\n * 由 FTUE 事实推导只读快照。纯函数:同输入恒同输出,无副作用。\n */\nexport function deriveFtueSnapshot(input: FtueInput): FtueSnapshot {\n const stage = deriveStage(input)\n const candidates = input.providerCandidates ?? []\n const connectedProviderIds = input.connectedProviderIds ?? []\n const usableIds = input.usableModelIds ?? []\n const submitted = input.firstPromptSubmitted ?? false\n\n // autoOpen 只在「确实还不可用」且「用户没主动关过」时为真。\n // ready / first-prompt 阶段永不自动弹窗——已经能用了就不该打扰。\n const needsSetup = stage === 'provider' || stage === 'authentication' || stage === 'model'\n const autoOpen = needsSetup && !(input.dismissed ?? false)\n\n return {\n stage,\n provider: { installed: input.providerInstalled, candidates },\n authentication: { connectedProviderIds },\n model: {\n ...(input.selectedModelId !== undefined ? { selectedId: input.selectedModelId } : {}),\n usableIds,\n },\n firstPrompt: { submitted },\n recommendation: {\n action: STAGE_ACTION[stage],\n stage,\n autoOpen,\n },\n }\n}\n\n/**\n * 是否处于「可以发普通 prompt」的状态。\n *\n * 供输入门(PromptInput / interactive 提交路径)消费,替代散落的 `modelReady`\n * 布尔。注意 `dismissed` **不**参与判断——用户关掉向导不代表模型就能用了。\n */\nexport function isFtueReady(snapshot: FtueSnapshot): boolean {\n return snapshot.stage === 'ready' || snapshot.stage === 'first-prompt'\n}\n","/**\n * ftue-settings.ts — RFC-326 M1-02:旧 FTUE flag 的兼容读取与语义迁移。\n *\n * 背景:首次体验状态历史上散落在三个布尔 flag 里,语义重叠且被混用——\n * `has_completed_onboarding` 昵称/语言引导走完\n * `has_completed_wizard` setup 向导走完(或被 Esc 逐步跳过)\n * `wizard_dont_show_again` 欢迎页勾了「不再提示」\n *\n * RFC-326 §5.1 给出的新语义:**它们全部只表达「用户意图/浏览历史」,一律不参与\n * readiness 判定**。「能不能用」永远由 provider/凭据/模型可用性实时重算\n * (见 `ftue-snapshot.ts` 的 `deriveFtueSnapshot`)。\n *\n * 本模块因此只负责一件事:把三个旧 flag 归一成三个语义清晰的用户意图字段。\n * 它**不导出任何形如 `isReady` 的判断**——这是有意的 API 设计:让「旧 flag 推出\n * ready」在类型层就无从表达,而不是靠调用方自觉。\n *\n * 纯函数:零 I/O、零时钟。settings 读取经 `FtueSettingsSource` 端口注入。\n *\n * 关于 schema:本里程碑**不新增** `ftue.dismissedAt` 等持久化字段。理由是当前\n * 零写入方(真正的写入发生在 M1-03 controller),提前加字段会产生死配置,且需\n * 连带同步 `types.ts` / `remote-sync-keys.ts` / `config-merge-strategy.ts` 四处。\n * 待 M1-03 有真实写入需求时再一并添加,届时本模块的 `readFtuePreferences`\n * 增加「优先读新字段、回退旧 flag」的分支即可,调用方无感。\n */\n\n/**\n * settings 的最小只读切面——只要一个按 key 取值的能力,便于单测直接传字面量对象,\n * 不必构造真实 SettingsManager。与 `SettingsManager.get()` 签名兼容。\n */\nexport interface FtueSettingsSource {\n get(key: string): unknown\n}\n\n/**\n * 归一后的用户意图(**不含任何可用性结论**)。\n *\n * 三个字段彼此正交:\n * - `personalizationSeen` 只影响要不要再问昵称/语言;\n * - `dismissed` 只影响要不要自动抢焦点(`/setup` 始终可手动打开);\n * - `firstPromptSubmitted` 只影响首 prompt 建议卡是否还需展示。\n */\nexport interface FtuePreferences {\n /** 个性化引导(昵称/语言)已看过。映射自 `has_completed_onboarding`。 */\n personalizationSeen: boolean\n /**\n * 用户曾主动结束过设置 UI(走完或勾选不再提示)。\n * 语义严格限定为「不要自动弹窗」,**不代表配置已完成**。\n */\n dismissed: boolean\n /** 已发出过第一条 prompt。当前无持久化来源,恒 false(M1-03 落字段后接入)。 */\n firstPromptSubmitted: boolean\n}\n\n/** 把 settings 里的任意值收窄为布尔(缺省/类型不符一律 false,fail-safe)。 */\nfunction readBool(source: FtueSettingsSource, key: string): boolean {\n return source.get(key) === true\n}\n\n/**\n * 读取并迁移旧 flag 为新语义偏好。\n *\n * 迁移映射(RFC-326 §5.1 表):\n * has_completed_onboarding → personalizationSeen\n * has_completed_wizard → dismissed(\"曾结束过设置 UI\")\n * wizard_dont_show_again → dismissed(\"不自动抢焦点\",与上者取并集)\n *\n * 两个旧 flag 都并入 `dismissed` 是因为它们对**当前行为**的影响完全一致:\n * 都只意味着「不要自动弹」。区分二者的历史动机(走完 vs 主动跳过)在新模型下\n * 没有任何消费方——真正要回答的「配好了没有」已改由事实重算。\n */\nexport function readFtuePreferences(source: FtueSettingsSource): FtuePreferences {\n return {\n personalizationSeen: readBool(source, 'has_completed_onboarding'),\n dismissed:\n readBool(source, 'has_completed_wizard') ||\n readBool(source, 'wizard_dont_show_again'),\n // 尚无持久化字段(见文件头「关于 schema」)。恒 false 意味着首 prompt 建议卡\n // 在每个新进程都会出现一次,直到 M1-03 落 firstPromptSubmittedAt——这是刻意\n // 选择的保守缺省:宁可多显示一次建议,也不谎称用户已上手。\n firstPromptSubmitted: false,\n }\n}\n\n/**\n * 是否应当在启动时**自动**弹出设置旅程。\n *\n * 注意入参:只接受 `dismissed` 与「当前是否真的还需要配置」两个显式布尔,\n * 不接受整个 snapshot——避免本模块反向依赖判定层,也避免调用方误传旧 flag。\n *\n * 真值表刻意保持最小:`needsSetup && !dismissed`。\n * 「已 dismiss 但仍不可用」返回 false(不自动弹),但调用方仍应据 stage 显示\n * Home 常驻恢复卡——不打扰 ≠ 假装已就绪(RFC-326 §6.7)。\n */\nexport function shouldAutoOpenSetup(params: {\n needsSetup: boolean\n dismissed: boolean\n}): boolean {\n return params.needsSetup && !params.dismissed\n}\n","/**\n * ftue-controller.ts — RFC-326 M1-03a:FTUE 事实采集与快照编排。\n *\n * 职责边界(三层分工,本文件是中间那层):\n * ftue-snapshot.ts 纯判定:事实 → stage/CTA,零 I/O\n * ftue-controller.ts 采集编排:向权威服务问事实 → 交给判定层 ← 本文件\n * interactive.ts 接线:把 controller 挂到 TUI ready 回调(M1-03b)\n *\n * 为什么采集要单独成层:`deriveFtueSnapshot` 必须保持纯函数以便穷举单测,但\n * \"有没有 provider 插件\"\"哪些模型真能用\"这些事实只能异步问 ModelRegistry /\n * AuthStore。本层把异步 I/O 收在一处,判定仍是纯的,两边都可测。\n *\n * 最重要的不变式:**`usableModelIds` 只能由 `createModelUsabilityGate` 产出**。\n * 本层不自己判断\"有凭据就算可用\"——那正是 RFC-326 §5.1 点名禁止的退化\n * (registry 里存在 ≠ 可用;有 auth 条目 ≠ 凭据有效;OAuth 订阅令牌会 429)。\n * 全部 provider/auth/model 依赖经端口注入,不 import 具体实现,便于用桩替身单测。\n */\nimport { deriveFtueSnapshot } from './ftue-snapshot'\nimport { readFtuePreferences } from './ftue-settings'\n\nimport type { FtueSnapshot, ProviderCandidate } from './ftue-snapshot'\nimport type { FtueSettingsSource } from './ftue-settings'\n\n/** 模型的最小形状——usability gate 按 `apiCredentialId(api, provider)` 取凭证槽,二者缺一不可。 */\nexport interface FtueModelRef {\n id: string\n provider: string\n api: string\n}\n\n/** 可用性判定端口。唯一合法实参是 `createModelUsabilityGate(authStore)` 的返回值。 */\nexport type FtueUsabilityGate = (\n model: Pick<FtueModelRef, 'provider' | 'api'>,\n) => Promise<{ usable: boolean; reason?: string }>\n\n/** controller 的依赖端口(全部注入,无隐式全局读)。 */\nexport interface FtueControllerDeps {\n /** 是否已安装任一 provider 插件(ModelRegistry.hasAnyPluginSourcedModel)。 */\n hasProviderPlugin(): boolean\n /** 已登记凭据的 provider id(AuthStore.listProviders)。 */\n listProviders(): readonly string[]\n /** 某 provider 的凭据来源;null = 无凭据(AuthStore.getAuthSource)。 */\n getAuthSource(provider: string): Promise<string | null>\n /** registry 全量模型(含不可用者;筛选由本层经 gate 完成)。 */\n listModels(): readonly FtueModelRef[]\n /** 权威可用性闸。 */\n isModelUsable: FtueUsabilityGate\n /** 当前会话选中的模型 id。 */\n getSelectedModelId(): string | undefined\n /** settings 只读切面(旧 flag 迁移)。 */\n settings: FtueSettingsSource\n /** 可安装的 provider 候选(展示用,排序由调用方决定)。 */\n providerCandidates?: readonly ProviderCandidate[]\n}\n\n/**\n * 占位模型 id——无 provider 时用于桥接启动,永远不是真实可用模型。\n * 与 `interactive.ts` 的 `SETUP_PLACEHOLDER_ID` 同值;此处独立声明避免\n * 为一个字符串常量反向依赖 interactive 模块(后者是巨型入口文件)。\n */\nconst SETUP_PLACEHOLDER_ID = '__otto_setup__'\n\n/**\n * 采集当前 FTUE 事实并派生快照。\n *\n * 采集顺序刻意与判定的短路顺序**解耦**:即便 provider 未装,也照常查凭据与模型\n * (代价极小,且让 snapshot 的 `authentication`/`model` 字段始终反映真实情况,\n * 便于诊断\"插件没了但凭据还在\"这类错位状态——正是 setup-wizard 跳步 bug 的成因)。\n */\nexport async function collectFtueSnapshot(deps: FtueControllerDeps): Promise<FtueSnapshot> {\n const prefs = readFtuePreferences(deps.settings)\n\n // 已连接 provider:有任意凭据来源即算已连接(不判断凭据是否有效——\n // 那是模型级 gate 的职责,避免两处各自判定产生分歧)。\n const providers = deps.listProviders()\n const authSources = await Promise.all(\n providers.map(async (p) => ({ provider: p, source: await deps.getAuthSource(p).catch(() => null) })),\n )\n const connectedProviderIds = authSources\n .filter((entry) => entry.source != null)\n .map((entry) => entry.provider)\n\n // 可用模型:逐个过权威 gate。占位模型先行剔除——它在 registry 里存在,\n // 但被选为活跃模型是非法的(applyModel 有对应 fail-loud 分支)。\n const candidates = deps.listModels().filter((m) => m.id !== SETUP_PLACEHOLDER_ID)\n const verdicts = await Promise.all(\n candidates.map(async (model) => {\n try {\n const { usable } = await deps.isModelUsable({ provider: model.provider, api: model.api })\n return usable ? model.id : null\n } catch {\n // gate 异常按不可用处理(fail-closed)——宁可多引导一步,\n // 也不能因一次探查失败就谎称模型可用。\n return null\n }\n }),\n )\n const usableModelIds = verdicts.filter((id): id is string => id !== null)\n\n const selected = deps.getSelectedModelId()\n\n return deriveFtueSnapshot({\n providerInstalled: deps.hasProviderPlugin(),\n ...(deps.providerCandidates ? { providerCandidates: deps.providerCandidates } : {}),\n connectedProviderIds,\n usableModelIds,\n ...(selected !== undefined && selected !== SETUP_PLACEHOLDER_ID ? { selectedModelId: selected } : {}),\n firstPromptSubmitted: prefs.firstPromptSubmitted,\n dismissed: prefs.dismissed,\n })\n}\n\n/**\n * 从 CLI 运行态装配 controller 依赖(RFC-326 M1-05:消除重复探测的单一装配点)。\n *\n * `interactive.ts`(启动自动唤起)与 `setup-wizard.ts`(`/setup` 打开向导)此前\n * 各自内联重算 provider/auth/model 三项事实,是两份可独立漂移的实现——其中\n * wizard 侧还把 `ctx.state.modelReady`(乐观布尔)当作\"有可用模型\"。统一走本\n * 工厂后,两处消费同一套判据,`hasModel` 恒由 usability gate 决定。\n *\n * 入参用结构化类型而非 `CliContext`,避免 ftue 模块反向依赖 command-dispatcher\n * (后者体量大且含大量与 FTUE 无关的面),同时让测试可传最小替身。\n */\nexport function buildFtueDeps(runtime: {\n modelRegistry: {\n hasAnyPluginSourcedModel(): boolean\n getAll(): ReadonlyArray<{ id: string; provider: string; api: string }>\n }\n authStore: {\n listProviders(): string[]\n getAuthSource(provider: string): Promise<string | null>\n }\n settings: { get(key: string): unknown }\n /** 当前会话选中的模型 id(wizard 侧同样应看会话真实选择,而非 settings 里的期望值)。 */\n selectedModelId: () => string | undefined\n /** usability gate;由调用方注入 `createModelUsabilityGate(authStore)`,本模块不 import @x-otto/ai。 */\n isModelUsable: FtueUsabilityGate\n providerCandidates?: readonly ProviderCandidate[]\n}): FtueControllerDeps {\n return {\n hasProviderPlugin: () => runtime.modelRegistry.hasAnyPluginSourcedModel(),\n listProviders: () => runtime.authStore.listProviders(),\n getAuthSource: (provider) => runtime.authStore.getAuthSource(provider),\n listModels: () =>\n runtime.modelRegistry.getAll().map((m) => ({ id: m.id, provider: m.provider, api: m.api })),\n isModelUsable: runtime.isModelUsable,\n getSelectedModelId: runtime.selectedModelId,\n settings: { get: (key: string) => runtime.settings.get(key) },\n ...(runtime.providerCandidates ? { providerCandidates: runtime.providerCandidates } : {}),\n }\n}\n\n/**\n * FTUE 编排器:持有最近一次快照,提供刷新与订阅。\n *\n * 设计取舍——**不在构造时自动采集**。启动时序必须由调用方在 TUI ready 后显式\n * 触发 `refresh()`,这正是 RFC-326 §5.2 要替换掉 `setTimeout(500)` 的原因:\n * 用明确的就绪回调代替猜测性延迟。构造函数里偷偷发起异步采集会把时序竞争\n * 重新引进来。\n */\nexport class FtueController {\n private snapshot: FtueSnapshot | null = null\n private readonly listeners = new Set<(s: FtueSnapshot) => void>()\n\n constructor(private readonly deps: FtueControllerDeps) {}\n\n /** 最近一次快照;从未 refresh 过则为 null(调用方据此判断\"尚未采集\")。 */\n current(): FtueSnapshot | null {\n return this.snapshot\n }\n\n /**\n * 重新采集事实并广播。\n *\n * 应在以下时机调用:TUI ready、provider 安装成功、授权成功、模型选定、\n * `/model refresh` 之后。每次都是**全量重算**——不做增量推断,因为\n * \"外部变化后必须能退化\"(§7.1)依赖的正是无状态重算。\n */\n async refresh(): Promise<FtueSnapshot> {\n const next = await collectFtueSnapshot(this.deps)\n this.snapshot = next\n for (const listener of this.listeners) {\n try {\n listener(next)\n } catch {\n // 单个订阅者异常不影响其余订阅者与主流程。\n }\n }\n return next\n }\n\n /** 订阅快照更新;返回取消订阅函数。 */\n subscribe(listener: (s: FtueSnapshot) => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n}\n","/**\n * provider-ranking.ts — RFC-326 §6.2:provider 选项按「最快可用」排序。\n *\n * 问题:向导的 provider 列表此前是 `EXTENSION_ALIASES` 的**声明顺序**——一个纯粹\n * 由代码书写次序决定的排列,与\"哪个能让用户最快用上\"毫无关系。新用户面对 6 个\n * 名字,既不知道哪个要付费、哪个能一键登录,也不知道自己环境里其实已经有现成\n * 凭据了。\n *\n * §6.2 规定的优先级(本模块实现):\n * 1. 已检测到环境凭据 —— 装完即可用,一步到位\n * 2. OAuth 一键连接 —— 浏览器点一下,无需去后台找 key\n * 3. 其余(需手动粘贴 API key)\n *\n * 关键设计:**信号来自 manifest 而非硬编码名单**。`contributes.oauth` 是否存在\n * 决定第 2 档,`contributes.providers[].envKey` 配合当前环境变量决定第 1 档。\n * 这样第三方 provider 插件天然参与排序,无需回来改这张表——RFC-326 §10 明确\n * 要求\"third-party plugin 不被硬编码名单排除\"。\n *\n * 纯函数:环境变量经 `env` 入参注入,不读 `process.env`;manifest 信号由调用方\n * 读盘后传入。便于穷举单测,也让\"读盘失败\"退化为\"按第 3 档排\"而非抛错。\n */\n\n/** 单个 provider 候选的排序输入(信号全部来自 manifest + 环境,不含硬编码偏好)。 */\nexport interface ProviderRankingInput {\n /** 安装标识(EXTENSION_ALIASES 的 key,或第三方 npm 包名)。 */\n id: string\n /** manifest 声明了 `contributes.oauth` —— 支持浏览器一键授权。 */\n supportsOAuth: boolean\n /**\n * manifest 中该 provider 声明的环境变量名(`contributes.providers[].envKey`)。\n * 可能多个(如 oxygen 的两个 provider 共用一个 key);任一命中即算\"已有凭据\"。\n */\n envKeys?: readonly string[]\n}\n\n/** 排序档位——数字越小越靠前;导出供测试与 UI 标注复用。 */\nexport const RANK_ENV_CREDENTIAL = 0\nexport const RANK_OAUTH = 1\nexport const RANK_MANUAL_KEY = 2\n\nexport type ProviderRank =\n | typeof RANK_ENV_CREDENTIAL\n | typeof RANK_OAUTH\n | typeof RANK_MANUAL_KEY\n\n/**\n * 判定单个 provider 的档位。\n *\n * 环境凭据优先于 OAuth:前者装完**零交互**即可用,后者还要过一次浏览器往返。\n * 空字符串视为未设置——CI/容器里常见 `FOO=` 占位,把它当\"已有凭据\"会把用户\n * 送进一个必然失败的路径。\n */\nexport function rankProvider(\n input: ProviderRankingInput,\n env: Readonly<Record<string, string | undefined>>,\n): ProviderRank {\n const hasEnvCredential = (input.envKeys ?? []).some((key) => {\n const value = env[key]\n return typeof value === 'string' && value.trim().length > 0\n })\n if (hasEnvCredential) return RANK_ENV_CREDENTIAL\n if (input.supportsOAuth) return RANK_OAUTH\n return RANK_MANUAL_KEY\n}\n\n/**\n * 按「最快可用」排序,同档保持输入顺序(稳定排序)。\n *\n * 稳定性是有意的:同档内不再引入第二重偏好(字母序会让 Anthropic 恒排 DeepSeek\n * 前面,看着像产品背书)。同档时沿用调用方给的顺序,把\"谁在前\"的责任留在\n * EXTENSION_ALIASES 的声明处,本模块只负责跨档提升。\n */\nexport function sortProvidersByReadiness<T extends ProviderRankingInput>(\n providers: readonly T[],\n env: Readonly<Record<string, string | undefined>>,\n): T[] {\n return providers\n .map((provider, index) => ({ provider, index, rank: rankProvider(provider, env) }))\n .sort((a, b) => a.rank - b.rank || a.index - b.index)\n .map((entry) => entry.provider)\n}\n\n/**\n * 从插件包目录读取排序所需的 manifest 信号(`contributes.oauth` / `providers[].envKey`)。\n *\n * 面向**尚未安装**的候选:向导列出的是可安装 provider,此时插件不在 registry 里,\n * 只能直接读包目录的 `otto-plugin.json`。读不到/解析失败一律退化为\"无信号\"——\n * 排序降级到末档,绝不因为一个坏 manifest 让整个 provider 列表构建失败。\n *\n * 与 `readHighRiskCapabilities` 同源思路(都是安装前读包 manifest),但那个函数只\n * 关心高危能力,这里要的是 oauth/envKey 两个排序信号,故独立实现而非扩其返回值。\n */\nexport function readProviderManifestSignals(\n pkgName: string,\n): { supportsOAuth: boolean; envKeys: readonly string[] } {\n try {\n // 延迟 require:本模块的纯排序逻辑不应因读盘依赖而变得不可单测。\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { createRequire } = require('node:module') as typeof import('node:module')\n const { readFileSync } = require('node:fs') as typeof import('node:fs')\n const { dirname, join } = require('node:path') as typeof import('node:path')\n\n const requireFrom = createRequire(import.meta.url)\n const pkgJsonPath = requireFrom.resolve(`${pkgName}/package.json`)\n const manifestPath = join(dirname(pkgJsonPath), 'otto-plugin.json')\n const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as {\n contributes?: {\n oauth?: unknown\n providers?: Array<{ envKey?: string }>\n }\n }\n const contributes = manifest.contributes ?? {}\n const envKeys = (contributes.providers ?? [])\n .map((provider) => provider.envKey)\n .filter((key): key is string => typeof key === 'string' && key.length > 0)\n return { supportsOAuth: Boolean(contributes.oauth), envKeys }\n } catch {\n return { supportsOAuth: false, envKeys: [] }\n }\n}\n","/**\n * model-ranking.ts — RFC-326 §6.4:模型步的推荐与可用性标注。\n *\n * 问题:模型步此前是 `modelRegistry.getAll()` 的原始顺序——一张\"无排序的技术清单\"\n * (§6.4 首句就点名要消灭它)。新用户面对十几个形如 `anthropic/claude-x` 的 id,\n * 既不知道哪个能用、哪个缺凭据,也不知道该选哪个;更糟的是**不可用的模型和可用的\n * 混在一起**,选中一个不可用的才会在下一步撞墙。\n *\n * 本模块按 §6.4 产出三件事:\n * 1. **Recommended** —— 可用模型中的首选(置顶并标注)。\n * 2. **可用优先排序** —— 可用的排前面,不可用的沉底。\n * 3. **不可用原因** —— 缺凭据 / 订阅 429 风险,逐条标注而非笼统灰掉。\n *\n * 推荐策略刻意保守:**只在可用集合内选,且不引入\"模型好坏\"的主观排名**。\n * 取可用集合的第一个(保持 registry 顺序)——registry 顺序本身来自 provider 插件\n * 的声明,比在 CLI 层硬编码\"哪个模型更好\"更中立,也避免每次有新模型发布就要回来\n * 改一张偏好表。§6.4 只要求\"先显示推荐项,仍可打开完整列表\",未要求智能选型。\n *\n * 纯函数:不查凭据、不读 registry,全部经入参注入(可用集合与原因由\n * `collectFtueSnapshot` 用权威 gate 算好后传入)。\n */\n\n/** 不可用原因——与 `@x-otto/ai` 的 `UnusableReason` 同构,此处独立声明避免 ftue 反向依赖。 */\nexport type ModelUnusableReason = 'unauthenticated' | 'oauth-429-risk'\n\n/** 排序输入:一个模型的展示信息 + 可用性判定结果。 */\nexport interface ModelRankingInput {\n id: string\n label: string\n /** provider/能力等次要信息,原样透传到 UI 的 detail 行。 */\n detail?: string\n /** 是否经 usability gate 判定为可用。 */\n usable: boolean\n /** 仅在 usable=false 时有意义。 */\n reason?: ModelUnusableReason\n}\n\n/** 排序产物:附带 UI 需要的推荐标记。 */\nexport interface RankedModel extends ModelRankingInput {\n /** 该项是否为推荐模型(整个列表至多一个)。 */\n recommended: boolean\n}\n\n/**\n * 按可用性分组排序,并标出推荐项。\n *\n * - 可用模型在前、不可用在后;两组**内部各自保持输入顺序**(稳定排序)。\n * 同组内不再引入第二重偏好——按名称或 provider 排会变成隐性产品背书。\n * - 推荐项 = 排序后第一个可用模型;全不可用时无推荐(不硬推一个用不了的)。\n */\nexport function rankModels(models: readonly ModelRankingInput[]): RankedModel[] {\n const ordered = models\n .map((model, index) => ({ model, index }))\n .sort((a, b) => Number(b.model.usable) - Number(a.model.usable) || a.index - b.index)\n .map((entry) => entry.model)\n\n const recommendedId = pickRecommendedId(ordered)\n return ordered.map((model) => ({\n ...model,\n // `model.usable &&` 是防御纵深(即便 pickRecommendedId 出错也不会把不可用项\n // 标成推荐)。注意它会**掩盖** pickRecommendedId 的缺陷,使黑盒断言观察不到\n // ——故该函数单独导出并直测,见 model-ranking.test.ts 的\"选取逻辑\"一节。\n recommended: model.usable && model.id === recommendedId,\n }))\n}\n\n/**\n * 从(已排序的)候选中选出推荐项 id:第一个**可用**模型;全不可用时返回 undefined。\n *\n * 单独导出是为了可直测:`rankModels` 的输出里有 `model.usable &&` 兜底,\n * 本函数返回错值时黑盒看不出来(实测:把它换成 `ordered[0]?.id` 后 14 例全绿)。\n */\nexport function pickRecommendedId(models: readonly ModelRankingInput[]): string | undefined {\n return models.find((model) => model.usable)?.id\n}\n\n/**\n * 是否存在任何可用模型——供调用方决定要不要显示\"无可用模型\"的空态引导。\n * 单列一个函数而非让调用方 `.some()`,是为了让\"空态\"这个判断只有一处定义。\n */\nexport function hasUsableModel(models: readonly ModelRankingInput[]): boolean {\n return models.some((model) => model.usable)\n}\n","/**\n * setup-wizard.ts — 五步线性设置向导 CLI 编排层。\n *\n * 单次 `openModal({kind:'wizard',...})` 打开,WizardDialog 内部管理步骤状态。\n * CLI 层只负责:状态探测 + 选项构建 + 异步操作回调。\n *\n * RFC-274:setup 向导只做 provider 授权(connect),不纳入 Otto/Aniwei 账号登录——\n * `/login` 保留为账号语义(见 RFC-274 §0.1)。历史命名 `loginOptions`/`onOAuthStart`/\n * `onSubmitApiKey` 已改名为 `providerAuthOptions`/`onProviderOAuthStart`/\n * `onSubmitProviderApiKey`,避免与账号登录语义混淆。\n */\nimport { i18next } from '@x-otto/tui'\nimport type { CliContext } from '../command-dispatcher'\nimport { EXTENSION_ALIASES } from './extension'\nimport { deriveProviderAuthOptions, resolveInstalledProviderId } from './provider-auth-options'\nimport { buildFtueDeps, collectFtueSnapshot } from './ftue/ftue-controller'\nimport { sortProvidersByReadiness, readProviderManifestSignals } from './ftue/provider-ranking'\nimport { rankModels } from './ftue/model-ranking'\nimport { createModelUsabilityGate } from '@x-otto/ai'\n\nconst OXYGEN_PROVIDER_ALIAS = 'oxygen-provider'\nconst OXYGEN_PROVIDER_PACKAGE = '@x-otto/plugin-oxygen'\nconst OXYGEN_PROVIDER_REGISTRY_IDS = new Set(['plugin-oxygen', OXYGEN_PROVIDER_ALIAS])\n\nasync function registryHasOxygenProvider(ctx: CliContext): Promise<boolean> {\n try {\n const { makeRegistryClient } = await import('./handlers/registry')\n const { registries } = await makeRegistryClient(ctx.app).loadAll()\n return registries.some((registry) =>\n registry.entries.some((entry) =>\n OXYGEN_PROVIDER_REGISTRY_IDS.has(entry.id) ||\n entry.source === OXYGEN_PROVIDER_PACKAGE ||\n entry.source.includes('/plugin-oxygen') ||\n entry.subPath?.endsWith('plugin-oxygen'),\n ),\n )\n } catch {\n return false\n }\n}\n\nexport async function runSetupWizard(ctx: CliContext): Promise<void> {\n const tui = ctx.tui\n const app = ctx.app\n\n // ── 状态探测(RFC-326 M1-05:统一走 FtueSnapshot,不再内联重算)──\n //\n // 此前这里独立重算 hasProvider/hasAuth/hasModel,与 interactive.ts 启动路径的\n // 同名探测是两份可独立漂移的实现;其中 hasModel 取 `ctx.state.modelReady`——\n // 一个\"有人调用过 applyModel\"的乐观布尔,凭据无效/订阅 429 风险的模型照样算数。\n // 现改为消费同一份快照:`stage` 与各步完成度都由 deriveFtueSnapshot 派生,\n // 可用性恒由 createModelUsabilityGate 判定。\n //\n // 原先用于\"强制从第 0 步体验完整流程\"的临时调试常量(恒 false)随之删除——\n // RFC-326 M1 验收要求其全仓零命中。同等效果现可用一个全新 OTTO_HOME 达成,\n // 不必在产品代码里长期保留一个永假分支。\n const snapshot = await collectFtueSnapshot(\n buildFtueDeps({\n modelRegistry: app.modelRegistry,\n authStore: app.authStore as never,\n settings: app.settings,\n selectedModelId: () => ctx.state.session?.model?.id,\n isModelUsable: createModelUsabilityGate(app.authStore as never),\n }),\n )\n\n // 注册在 authStore 的 provider 列表——供下方授权回调选择默认 provider。\n // 与 snapshot.authentication.connectedProviderIds 语义不同:后者只含**已有凭据**\n // 的 provider(用于判定 auth 步是否完成),这里要的是\"可供授权的候选\"(包括\n // 尚未登录的),故仍读原始列表,不能用 connectedProviderIds 替代。\n const providers = app.authStore.listProviders()\n\n // 各步完成度取 snapshot 的**分项事实**,而非从 `stage` 反推。\n //\n // 二者在\"状态有空洞\"时并不等价:卸载 provider 但凭据与模型仍在时 stage 是\n // `provider`(卡在最靠前的缺口),按 stage 反推会得出 completed=[],进度条便把\n // 已满足的 Login/Model 显示成未完成——那是撒谎。分项事实能如实标记 [1,2] 已完成、\n // 仅 Provider 待办;这与 `initialCurrent` 回退到 0 并不矛盾:「当前该做哪一步」\n // 与「哪些步已满足」本就是正交的两个维度(进度条读后者,起始步读前者)。\n const completed: number[] = []\n if (snapshot.provider.installed) completed.push(0)\n if (snapshot.authentication.connectedProviderIds.length > 0) completed.push(1)\n // 模型步以\"存在真实可用模型\"为准(经 usability gate),不再是乐观的 modelReady。\n if (snapshot.model.usableIds.length > 0) completed.push(2)\n\n /**\n * 起始步 = **第一个未满足**的步骤,而非 `completed.length`(RFC-326 M1 前置清障)。\n *\n * 旧写法 `initialCurrent: completed.length` 只在三项条件「从前往后连续满足」时\n * 才恰好等于下一个待办步;一旦中间有空洞就会错位跳步。真实反例:用户卸载了\n * provider 插件但 `auth.json` 里凭据还在 → hasProvider=false / hasAuth=true →\n * `completed=[1]`、`length=1` → 向导**从 Login 步开始、跳过 Provider 安装**。\n * 而此时 provider 插件并不存在,`providerAuthOptions()` 查 OAuthRegistry 得到\n * 空数组,用户卡在一个没有任何选项的授权步——无法前进也不知道该退到哪。\n *\n * 改为取首个未完成步的索引:状态有空洞时回到最靠前的缺口(上例回到 Provider\n * 步),与 `deriveFtueSnapshot` 的短路顺序语义一致(都遵循「多项缺失时只报最\n * 靠前一步」,见 ftue-snapshot.ts)。三项全满足时返回 3(Start 步)。\n */\n const STEP_COUNT = 4\n const firstIncompleteStep = (() => {\n for (let step = 0; step < STEP_COUNT - 1; step++) {\n if (!completed.includes(step)) return step\n }\n return STEP_COUNT - 1\n })()\n\n // 注意:这里**没有**\"全部就绪就早退\"的分支,是刻意的。\n //\n // `/setup` 是用户主动查看上手进度,命令描述明确「无论当前处于哪个步骤都重新\n // 打开,自动跳过已满足步骤」——若在 ready 时静默 return,用户敲 `/setup` 会\n // 毫无反应,比多开一个窗口糟得多。启动时的自动弹出另由 interactive.ts 依据\n // `snapshot.recommendation.autoOpen` 判断,不依赖此处兜底。\n //\n // (历史:此处曾有 `completed.length >= 4`,因 completed 最多 3 项而恒不成立,\n // 实际语义就是\"永远打开\"。现改为显式不早退,语义与产品定义一致且不再需要\n // 一行永假条件来表达。)\n\n // ── 构建选项 ──\n const showOxygenProvider = await registryHasOxygenProvider(ctx)\n const providerAliases = Object.entries(EXTENSION_ALIASES)\n .filter(([alias, entry]) =>\n entry.capabilities.includes('provider') &&\n (alias !== OXYGEN_PROVIDER_ALIAS || showOxygenProvider),\n )\n const PROVIDER_DESCRIPTIONS: Record<string, string> = {\n 'anthropic-provider': 'Claude 全系列 · OAuth 一键登录',\n 'deepseek-provider': '高性价比 · API-key 接入 · 中文友好',\n 'openai-codex-provider': 'GPT / Codex 系列 · 需要 API key 或 OAuth',\n 'github-copilot-provider': '订阅即用 · GitHub 账号授权',\n 'cursor-provider': 'Cursor IDE 内置 · 需 Cursor 订阅',\n 'oxygen-provider': '企业级 · 按组织授权',\n }\n // RFC-326 §6.2:按「最快可用」排序,而非 EXTENSION_ALIASES 的声明顺序(后者纯由\n // 代码书写次序决定,与\"哪个能让用户最快用上\"无关)。信号取自各插件 manifest\n // (contributes.oauth / providers[].envKey)+ 当前环境变量,故第三方 provider\n // 无需回来改硬编码名单即可参与排序(§10 明确要求)。读盘失败按末档处理。\n const rankingInputs = providerAliases.map(([alias, entry]) => ({\n id: alias,\n ...readProviderManifestSignals(entry.pkg),\n }))\n const orderedAliases = sortProvidersByReadiness(rankingInputs, process.env)\n const providerOptions = orderedAliases.map(({ id: alias }) => ({\n label: alias,\n value: alias,\n detail: PROVIDER_DESCRIPTIONS[alias] ?? EXTENSION_ALIASES[alias]?.pkg ?? alias,\n }))\n\n // RFC-274 M2:从硬编码白名单改为查 OAuthRegistry(权威源裁定见 RFC-274 §4\"实施修正\n // 记录\"——manifest-only 会丢失 Cursor 这类代码式 OAuth provider 的选项)。\n const providerAuthOptions = (providerId: string) =>\n deriveProviderAuthOptions(providerId, app.authStore.oauth, {\n oauth: { label: i18next.t('setup.loginOAuth'), detail: 'Open browser, complete authorization — no copy-paste needed.' },\n apiKey: { label: i18next.t('setup.loginApiKey'), detail: 'Generate a key on your provider dashboard and paste it here.' },\n })\n\n // RFC-326 §6.4:模型步不能是\"无排序的技术清单\"——可用优先 + 推荐置顶 +\n // 不可用标原因。可用性判定复用 snapshot 的 usableIds(同一权威 gate 的结论,\n // 不在此二次判定);原因则按凭据来源现算:snapshot 只保留了可用集合,\n // 不可用原因需要逐个模型问 gate。\n const modelUsabilityGate = createModelUsabilityGate(app.authStore as never)\n const usableIdSet = new Set(snapshot.model.usableIds)\n const registryModels = app.modelRegistry.getAll().filter(m => m.id !== '__otto_setup__')\n const modelVerdicts = await Promise.all(\n registryModels.map(async (m) => {\n const usable = usableIdSet.has(m.id)\n if (usable) return { model: m, usable, reason: undefined }\n // 只对不可用者问原因(可用者无需 reason),失败按\"缺凭据\"归类——\n // 比留空更有指导性,且与 gate 的默认失败语义一致。\n const verdict = await modelUsabilityGate({ provider: m.provider, api: m.api })\n .catch(() => ({ usable: false, reason: 'unauthenticated' as const }))\n return { model: m, usable: false, reason: verdict.reason ?? 'unauthenticated' }\n }),\n )\n const modelOpts = rankModels(\n modelVerdicts.map(({ model, usable, reason }) => ({\n id: model.id,\n label: model.name,\n detail: `${model.provider} · ${model.strengths?.join(', ') ?? ''}`,\n usable,\n ...(reason ? { reason: reason as 'unauthenticated' | 'oauth-429-risk' } : {}),\n })),\n ).map((ranked) => ({\n label: ranked.label,\n value: ranked.id,\n detail: ranked.detail,\n recommended: ranked.recommended,\n ...(ranked.reason ? { unusableReason: ranked.reason } : {}),\n }))\n\n const authProviderName = providers.length === 1 ? providers[0] : undefined\n\n // Step 5 示例提示\n const examplePrompts: readonly string[] = [\n 'Explain what\\'s in this project',\n 'Fix the failing test in src/x.ts',\n 'Add a health check endpoint',\n ]\n\n // ── 打开向导 ──\n tui.modal.openModal({\n kind: 'wizard',\n title: i18next.t('setup.title'),\n // Welcome 页文案\n welcomeTitle: i18next.t('setup.welcomeTitle'),\n // 欢迎页无 greeting 行(setup.welcomeGreeting 有意为空)——传空串而非 t(),\n // 避免 i18next returnEmptyString:false 把空 key 回退成字面量 \"setup.welcomeGreeting\"(RFC-264 迁移回归)。\n welcomeGreeting: '',\n welcomeDescription: i18next.t('setup.welcomeDescription'),\n welcomeDontShowAgainLabel: i18next.t('setup.welcomeDontShowAgain'),\n welcomeStartLabel: i18next.t('setup.welcomeStart'),\n welcomeCancelLabel: i18next.t('setup.welcomeCancel'),\n stepNames: [\n i18next.t('setup.stepNameProvider'),\n i18next.t('setup.stepNameLogin'),\n i18next.t('setup.stepNameModel'),\n i18next.t('setup.stepNameStart'),\n ],\n stepSubtitles: [\n i18next.t('setup.step1Subtitle'),\n i18next.t('setup.step3Subtitle'),\n i18next.t('setup.step4Subtitle'),\n i18next.t('setup.step5Done'),\n ],\n initialCompleted: completed,\n initialCurrent: firstIncompleteStep,\n providerOptions,\n providerAuthOptions,\n authProviderName,\n modelOptions: modelOpts,\n examplePrompts,\n workspacePath: app.workspaceDir,\n\n async onInstallProvider(value: string, onProgress: (phase: string) => void) {\n onProgress('Resolving package…')\n try {\n const { runPluginInstall } = await import('./handlers/extension-plugin')\n // RFC-326 M2-05:捕获\"装上了但能力未授权\"——向导压制了 toast,只能靠这个\n // 回调把它带到成功态显示,否则用户会在模型步面对一个空列表且不知原因。\n let pendingCapabilities = false\n const ok = await runPluginInstall(value, ctx, {\n autoTrust: true,\n onProgress: (phase) => onProgress(`${phase}…`),\n onPendingCapabilities: () => { pendingCapabilities = true },\n })\n // 独立审核修正(终局审核 finding 3):不再用静态 alias→provider id 表(只覆盖\n // EXTENSION_ALIASES 内 6 个官方 provider,用户经 `otto extension install\n // <第三方npm包名>` 装非官方 provider 插件时会查不到映射,永久丢失 OAuth 选项)。\n // 改为装完后直接读该插件包目录的 otto-plugin.json 解析裸 provider id——覆盖任意\n // 插件,不限于已知白名单。pkgName 优先取 EXTENSION_ALIASES 映射(alias 场景),\n // 否则 value 本身就是 npm 包名(第三方直装场景)。\n const pkgName = EXTENSION_ALIASES[value]?.pkg ?? value\n if (!ok) {\n return { ok: false as const, error: i18next.t('setup.installFailed', { reason: value }) }\n }\n // RFC-326:向导走 autoTrust 跳过了确认弹窗,但 trustPlugin +\n // grantPluginCapabilities 照常执行——把**实际授予**的高危能力回传给成功终态\n // 披露,消除\"用户毫无感知地授出 provider 级权限\"的信任缺口(CLI 路径\n // `otto extension install` 一直有等价的文字披露,向导此前因压制 toast 而缺失)。\n // 读盘失败不阻断安装:披露缺失优于流程中断,返回空数组即不展示该行。\n let capabilities: readonly string[] = []\n try {\n const { readHighRiskCapabilities, resolvePackageDir } = await import('@x-otto/coding')\n const dir = resolvePackageDir(pkgName)\n if (dir) capabilities = readHighRiskCapabilities(dir)\n } catch {\n /* 披露信息缺失不影响安装结果本身 */\n }\n return { ok: true as const, providerId: resolveInstalledProviderId(pkgName), capabilities, pendingCapabilities }\n } catch (e) {\n return { ok: false as const, error: e instanceof Error ? e.message : String(e) }\n }\n },\n\n // ── 内联 provider 授权回调(向导内嵌 OAuth/API Key,不弹出独立面板)──\n async onProviderOAuthStart(providerId: string) {\n const provider = providerId || providers[0] || app.authStore.listProviders()[0]\n if (!provider) return null\n const controller = new AbortController()\n try {\n const { openBrowser } = await import('@x-otto/shared')\n let authUrl: string | undefined\n let authCode: string | undefined\n let authInstructions: string | undefined\n const loginPromise = app.authStore.login(provider, {\n signal: controller.signal,\n onAuth: (info) => {\n authCode = info.code\n authInstructions = info.instructions\n if (info.url) {\n authUrl = info.url\n // device-flow 场景(info.code 存在)不自动打开浏览器——用户需要先看到 code\n // 再决定何时跳转;pkce 场景(无 code)维持既有自动打开行为。\n if (!info.code) openBrowser(info.url)\n }\n },\n onProgress: () => {},\n onPrompt: async () => '',\n })\n // 防止 cancel() abort signal 导致 loginPromise reject 后无人 await → unhandled rejection\n loginPromise.catch(() => {})\n await new Promise<void>((resolve) => setTimeout(resolve, 200))\n return {\n authorizationUrl: authUrl ?? '',\n code: authCode,\n instructions: authInstructions,\n // RFC-326 §6.3 \"Open again\":重新打开同一个授权 URL。\n // 不重新发起 login(那会作废当前 pending 授权、换一个新 URL/code),\n // 只是把已有链接再送一次给浏览器——覆盖\"没弹出来/误关了标签页\"这类\n // 最常见的失败形态。device-flow 场景 authUrl 同样存在,一并可用。\n openAgain: () => {\n if (authUrl) openBrowser(authUrl)\n },\n poll: async () => {\n try {\n const result = await Promise.race([\n loginPromise.then(() => 'done' as const),\n new Promise<'pending'>((resolve) => setTimeout(() => resolve('pending'), 500)),\n ])\n if (result === 'done') {\n await ctx.callbacks.refreshModelsNow()\n return 'done'\n }\n return 'pending'\n } catch (e: unknown) {\n // RFC-326 §6.3:失败必须分类,UI 才能给对症的下一步。\n // 只回传受限枚举,绝不把 error/stack 原文交给 UI(§6.3 明确禁止\n // 把 raw stack 放在第一屏)。\n const err = e instanceof Error ? e : undefined\n if ((err as (Error & { code?: string }) | undefined)?.code === 'OAUTH_CANCELLED') {\n return { status: 'error' as const, reason: 'cancelled' as const }\n }\n // 网络类:连不上授权端点(含代理不通)。Node/undici 的网络错误\n // code 集中在这几个;识别不了的一律归 rejected,宁可少引导也不误导。\n const NETWORK_CODES = new Set([\n 'ENOTFOUND', 'ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT',\n 'EAI_AGAIN', 'EHOSTUNREACH', 'ENETUNREACH', 'UND_ERR_CONNECT_TIMEOUT',\n ])\n const code = (err as (Error & { code?: string }) | undefined)?.code\n const cause = (err as (Error & { cause?: { code?: string } }) | undefined)?.cause?.code\n if ((code && NETWORK_CODES.has(code)) || (cause && NETWORK_CODES.has(cause))) {\n return { status: 'error' as const, reason: 'network' as const }\n }\n return { status: 'error' as const, reason: 'rejected' as const }\n }\n },\n cancel: () => { controller.abort() },\n }\n } catch (e) {\n return null\n }\n },\n\n async onSubmitProviderApiKey(providerId: string, key: string) {\n const provider = providerId || providers[0] || app.authStore.listProviders()[0]\n if (!provider) return { ok: false as const, error: 'No provider selected.' }\n try {\n await app.authStore.setCredentialKey(provider, key)\n await app.authStore.save()\n await ctx.callbacks.refreshModelsNow()\n return { ok: true as const }\n } catch (e) {\n return { ok: false as const, error: e instanceof Error ? e.message : String(e) }\n }\n },\n\n // RFC-326 §6.3:网络/代理错误的主 CTA —— 复用既有 /proxy 命令面板,不另造入口。\n onOpenProxySettings() {\n // 复用既有 /proxy 面板(handlers/proxy.ts),不另造入口——代理配置的真源\n // 只该有一处。延迟 import 避免向导模块常驻加载代理面板依赖。\n void import('./handlers/proxy').then(({ handleProxy }) => handleProxy([], ctx)).catch(() => {})\n },\n\n async onSelectModel(modelId: string) {\n await ctx.callbacks.applyModel(modelId)\n // 验证模型切换生效——applyModel 成功不代表 modelReady 翻为 true\n // (如凭据无效时模型注册了但不可用)。\n if (!ctx.state.modelReady) {\n throw new Error('Model selected but not ready. Check your credentials and try /model refresh.')\n }\n },\n\n onComplete() {\n // RFC-326 M1-04:不再硬编码 `setNeedsSetup(false)`。\n //\n // 走完向导 UI ≠ 配置成功——用户可能在 Model 步选了个凭据无效的模型,或中途\n // 各步都被跳过。此前无条件宣布\"已配置\",与 §4 原则 3(状态是事实,不是浏览\n // 历史)直接冲突:把\"看完了流程\"当成了\"能用了\"。\n //\n // 改为按真实可用性重算(refreshModelsNow 内部据 availableModelIds 调\n // setNeedsSetup)。真配好时同样会翻 false,只是不再凭\"走完\"下结论。\n void ctx.callbacks.refreshModelsNow().catch(() => {})\n // RFC-274 M1:完成标记是 user/设备级状态(remote-sync-keys.ts 已声明跨设备同步、\n // deviceBound:false),落 'global' 层(~/.otto/config.json),不落项目层——否则换\n // 项目目录会因项目层查不到标记而重复弹出向导。\n app.settings.persist({ has_completed_wizard: true }, 'global').catch(() => {})\n },\n\n onDismiss() {\n // wizard dismissed via Esc\n },\n\n onDontShowAgain(value: boolean) {\n // RFC-274 M1:同 onComplete,落 global 层。\n app.settings.persist({ wizard_dont_show_again: value }, 'global').catch(() => {})\n },\n })\n}\n","/**\n * /setup 命令:启动五步线性设置向导。\n *\n * 重新打开引导(无论当前处于哪个步骤),自动跳过已满足步骤。\n */\nimport type { CliContext } from '../../command-dispatcher'\nimport { runSetupWizard } from '../setup-wizard'\n\nexport async function handleSetup(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n await runSetupWizard(ctx)\n}\n","/**\n * model-effort-record.ts — model/effort 切换的三层一致记录(终局,2026-07-31)。\n *\n * 用户需求:model/effort 切换要在对话流中可见、持久化(resume 后仍可回溯)、进 trace。\n * 本工具是唯一入口,切换发生时三层同时写入,保证三层数据一致:\n *\n * 1. 实时对话流 —— tui.messages.pushMessage({ role:'model_switch'|'effort_switch', label })\n * 2. L1 会话 entries —— session.session.append(SystemAgentMessage)(role:'system',\n * subtype:'model_switch'|'effort_switch')——resume 时经 view-model-adapter 转回\n * ChatMessage 渲染,对话流跨会话可见\n * 3. trace 审计 —— app.storage.traceStore.append(kind:'lifecycle',\n * node:'model.switch'|'effort.change',payload:{from,to,label})\n *\n * 安全边界(M40-B0-04,agent.ts defaultConvertToLLM):role:'system' 消息被过滤,\n * 永不进 provider 请求——持久化层只服务转录渲染与审计,不污染模型上下文。\n *\n * trace append 是异步的,fire-and-forget + fail-soft(切换主流程不被 trace 失败阻断)。\n */\nimport type { AgentSession } from '@x-otto/runtime'\nimport type { ChatMessage, TuiHandle } from '@x-otto/tui'\nimport type { TraceEvent } from '@x-otto/agent'\nimport type { AppendLog } from '@x-otto/persistence'\n\nexport interface RecordSwitchDeps {\n tui: Pick<TuiHandle, 'messages'>\n session: AgentSession\n /** trace 写入通道(App.storage.traceStore)。 */\n traceStore: AppendLog<TraceEvent>\n}\n\n/** trace-only 记录依赖(retry/pause 等动作审计,无需对话流/L1 写入)。 */\nexport interface RecordTraceDeps {\n traceStore: AppendLog<TraceEvent>\n sessionId: string\n /** 可选的当前回合号(缺省不写 turn 字段)。 */\n turn?: number\n}\n\n/** 底层 trace 写入:lifecycle 事件 fire-and-forget + fail-soft(不阻断主流程)。 */\nexport function recordTraceLifecycle(deps: RecordTraceDeps, node: string, payload: Record<string, unknown>): void {\n const event: TraceEvent = {\n ts: Date.now(),\n sessionId: deps.sessionId,\n ...(deps.turn !== undefined ? { turn: deps.turn } : {}),\n kind: 'lifecycle',\n node,\n payload,\n }\n void deps.traceStore\n .append(deps.sessionId, event)\n .catch(() => { /* trace 写入失败不阻断主流程 */ })\n}\n\ninterface SwitchInfo {\n from: string | undefined\n to: string\n label: string\n}\n\n/** 三层写入公共实现——role/subtype/node 由调用方按领域指定。 */\nfunction recordSwitch(\n deps: RecordSwitchDeps,\n kind: 'model' | 'effort',\n info: SwitchInfo,\n): void {\n const { tui, session, traceStore } = deps\n const role = kind === 'model' ? 'model_switch' : 'effort_switch'\n const node = kind === 'model' ? 'model.switch' : 'effort.change'\n\n // 1. 实时对话流(单行标记)\n tui.messages.pushMessage({ role, label: info.label } as ChatMessage)\n\n // 2. L1 会话 entries 持久化(resume 后转录可见;role:'system' 不进 LLM 上下文)\n session.session.append({\n role: 'system',\n subtype: role,\n content: info.label,\n level: 'info',\n timestamp: Date.now(),\n })\n\n // 3. trace 审计(fire-and-forget + fail-soft)\n const turn = session.session.getTurnCount()\n const event: TraceEvent = {\n ts: Date.now(),\n sessionId: session.id,\n ...(turn !== undefined ? { turn } : {}),\n kind: 'lifecycle',\n node,\n payload: { from: info.from ?? null, to: info.to, label: info.label },\n }\n void traceStore\n .append(session.id, event)\n .catch(() => { /* trace 写入失败不阻断切换主流程 */ })\n}\n\n/** 记录一次模型切换(from=旧模型 id,to=新模型 id,label=格式化显示串)。 */\nexport function recordModelSwitch(\n deps: RecordSwitchDeps,\n info: SwitchInfo,\n): void {\n recordSwitch(deps, 'model', info)\n}\n\n/** 记录一次 effort 切换(from=旧档位,to=新档位,label=格式化档位显示串)。 */\nexport function recordEffortSwitch(\n deps: RecordSwitchDeps,\n info: SwitchInfo,\n): void {\n recordSwitch(deps, 'effort', info)\n}\n\n// ── 对话重试/流程暂停动作审计(终局 2026-07-31)─────────────────────────\n// 引擎侧已有 stream.retry(自动退避重试)/stream.retry.accelerated(加速)/\n// rate.limit.exhausted 的 trace;本节补 **CLI 交互动作层**(ErrorRetryCard 的回合级\n// 重投、PauseDialog 流程暂停)——此前这些动作只活在内存/EventBus,重启后不可审计。\n\n/** 回合级重投动作(ErrorRetryCard retryQueued:per-prompt 第 attempt/max 次重投)。 */\nexport function recordRetryQueued(\n deps: RecordTraceDeps,\n info: { attempt: number; max: number; mode?: string; reason?: string },\n): void {\n recordTraceLifecycle(deps, 'retry.queued', {\n attempt: info.attempt,\n max: info.max,\n ...(info.mode !== undefined ? { mode: info.mode } : {}),\n ...(info.reason !== undefined ? { reason: info.reason } : {}),\n })\n}\n\n/** 用户取消重试卡(ErrorRetryCard cancelRetry)。 */\nexport function recordRetryCancelled(deps: RecordTraceDeps, info: { attempt: number }): void {\n recordTraceLifecycle(deps, 'retry.cancelled', { attempt: info.attempt })\n}\n\n/** 配额耗尽后手动重置重投计数(ErrorRetryCard resetAndRetry)。 */\nexport function recordRetryReset(deps: RecordTraceDeps): void {\n recordTraceLifecycle(deps, 'retry.reset', {})\n}\n\n/** 流程暂停四态(PauseDialog / turn 边界暂停):requested→paused→resumed / cancelled。\n * source 标注触发入口便于审计。 */\nexport function recordPauseState(\n deps: RecordTraceDeps,\n state: 'requested' | 'paused' | 'cancelled' | 'resumed',\n source?: string,\n): void {\n recordTraceLifecycle(deps, `pause.${state}`, {\n ...(source !== undefined ? { source } : {}),\n })\n}\n","/**\n * handlers/misc.ts — 界面/兼容/升级/帮助等杂项指令处理\n *\n * /lang — 语言切换\n * /compat — .claude 可执行面信任管理(含 /trust plugin <id> 项目级插件信任)\n * /upgrade — 热升级检查/执行\n * /help — 命令列表\n */\nimport { i18next, createBuiltinThemeRegistry } from '@x-otto/tui'\nimport { homedir } from 'node:os'\nimport {\n evaluateClaudeTrust,\n trustProject,\n untrustProject,\n detectProjectConfigGating,\n evaluatePluginTrust,\n untrustPlugin,\n INDUCTOR_PLUGIN_ID,\n DEFAULT_WORKSPACE_KEY,\n readSkillLoopStatus,\n} from '@x-otto/coding'\nimport { discoverPluginsWithBundled } from '../../discover-with-bundled'\n\nimport { promptPluginTrust } from './extension-plugin'\nimport { setLanguage as setCliLanguage } from '../../i18n'\nimport { FIGURES } from '@x-otto/tui'\nimport type { CliContext, CliCommandEntry, HelpGroup } from '../../command-dispatcher'\nimport type { LangMode, Lang, SelectOption } from '@x-otto/tui'\nimport { HELP_GROUP_ORDER, dispatchSlashCommand } from '../../command-dispatcher'\nimport { fetchLatestVersion, upgradeInstall } from '../../hot-upgrade'\nimport { getVersion } from '../../version'\nimport { formatDisplayModel, formatEffortLabel, formatSwitch } from '../interactive-utils'\nimport { recordEffortSwitch } from '../model-effort-record'\n\n/** 可选的推理投入档(reasoning effort)——与 @x-otto/ai 的 ThinkingLevel 联动。 */\nconst EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'] as const\ntype EffortLevel = (typeof EFFORT_LEVELS)[number]\n\n/** Gauge 条宽度(字符数)。 */\nconst GAUGE_W = 8\n\n/** 每个精力档的展示元数据(label/描述/intensity)。 */\nconst EFFORT_META: Record<string, { label: string; desc: string; intensity: number }> = {\n low: { label: 'Low', desc: '节约 Token,快速响应', intensity: 0.2 },\n medium: { label: 'Medium', desc: '平衡速度与推理质量', intensity: 0.4 },\n high: { label: 'High', desc: '深度推理,延迟较高', intensity: 0.6 },\n xhigh: { label: 'XHigh', desc: '更长推理链,更全面思考', intensity: 0.8 },\n max: { label: 'Max', desc: '全力推理,最慢但最深入', intensity: 1.0 },\n}\n\n/** 构建视觉 gauge 条字符串(━ 已消耗 / ╌ 剩余)。 */\nfunction gaugeBar(intensity: number): string {\n const filled = Math.round(intensity * GAUGE_W)\n return '\\u2501'.repeat(Math.max(0, filled)) + '\\u254C'.repeat(Math.max(0, GAUGE_W - filled))\n}\n\n/**\n * /effort [low|medium|high|xhigh|max] — 切换本会话的推理投入档并持久化,下一回合生效。\n * 无参数 → 弹出视觉 gauge 选择面板。\n * <level> → 直接切换。\n */\nexport async function handleEffort(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const arg = args[0]?.toLowerCase()\n\n // Direct switch with arg\n if (arg && (EFFORT_LEVELS as readonly string[]).includes(arg)) {\n applyEffort(ctx, arg as EffortLevel)\n return\n }\n\n if (arg) {\n ctx.tui.info.showToast(i18next.t('cmd.titleEffort'), i18next.t('cmd.effortUnknown', { arg: arg, levels: EFFORT_LEVELS.join(' / ') }))\n return\n }\n\n // Interactive picker: visual gauge panel\n const current = ctx.state.session.thinkingLevel\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.selectEffortTitle'),\n options: EFFORT_LEVELS.map((lvl) => {\n const meta = EFFORT_META[lvl]!\n const cur = lvl === current\n const gauge = gaugeBar(meta.intensity)\n return {\n label: meta.label,\n value: lvl,\n detail: `${gauge} ${meta.desc}${cur ? ' · 当前' : ''}`,\n }\n }),\n onSelect: (v: string) => applyEffort(ctx, v as EffortLevel),\n onCancel: () => {},\n })\n}\n\n/** 应用并持久化推理投入档:写 session(下一回合 prompt 读取)+ config.json + toast。 */\nexport function applyEffort(ctx: CliContext, level: EffortLevel): void {\n const session = ctx.state.session\n const prev = session.thinkingLevel\n session.thinkingLevel = level\n void ctx.app.settings.persist({ thinking_level: level })\n // 三层一致记录(终局 2026-07-31):对话流标记 + L1 会话 entries 持久化(resume 后\n // 转录可见)+ trace 审计。仅档位真实变化时记录(与 applyModel 的 oldModelId 门控对齐)。\n if (prev !== level) {\n recordEffortSwitch(\n {\n tui: ctx.tui,\n session,\n traceStore: ctx.app.storage.traceStore,\n },\n {\n from: prev,\n to: level,\n label: formatEffortLabel(level),\n },\n )\n }\n if (session.model?.id) {\n ctx.tui.stores.tasks.setModelName(formatDisplayModel(ctx.app, session.model.id, level))\n }\n ctx.tui.info.showToast(i18next.t('cmd.titleEffort'), level)\n}\n\n/**\n * /preference [general|lang|theme] — 偏好设置入口,收敛 general/lang/theme 为子命令。\n * 无参:弹出一级菜单让用户选择 常规 / 语言 / 主题(每项带第二行左对齐说明)。\n * general:常规面板(昵称/完成通知/提示音/启动检查更新)。\n * lang [en|zh|auto]:切换语言。\n * theme [dark|light|system]:切换主题。\n */\nexport async function handlePreference(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const sub = args[0]?.toLowerCase()\n\n if (sub === 'general' || sub === 'gen') {\n handlePreferenceGeneral(ctx)\n return\n }\n\n if (sub === 'lang' || sub === 'language') {\n handlePreferenceLang(args.slice(1), ctx)\n return\n }\n\n if (sub === 'theme') {\n handlePreferenceTheme(args.slice(1), ctx)\n return\n }\n\n if (sub) {\n ctx.tui.info.showToast(i18next.t('cmd.titlePreference'), i18next.t('cmd.preferenceUsage'))\n return\n }\n\n // 无参数:弹出一级选择菜单(常规置顶,每项带第二行说明)\n openPreferenceMenu(ctx)\n}\n\n/** 一级偏好菜单——常规 / 语言 / 主题(detail 为标签下方左对齐说明行)。 */\nfunction openPreferenceMenu(ctx: CliContext, restoreValue?: string): void {\n const settings = ctx.app.settings\n // 读取当前语言值显示在「语言」项右侧,方便用户一眼看到当前态。\n // UI/UX:**每一项都显示当前值**。此前只有「语言」带 trailingLabel,主题与常规\n // 都得点进去才知道现在是什么状态——一级菜单的价值就在于一眼看全,缺了当前态\n // 就退化成纯目录。\n const currentLang = settings.get('language')\n // 当前值——显示在 label 左侧(用户要求:「值放说明左边」)。\n const langValue =\n currentLang === 'zh' ? '中文 (zh)' : currentLang === 'en' ? 'English (en)' : 'Auto'\n const registry = ctx.state.themeRegistry ?? createBuiltinThemeRegistry()\n const presetSel = settings.get('theme_preset') ?? {}\n const themeValue = registry.get(presetSel.dark ?? 'dark')?.label ?? (presetSel.dark ?? 'dark')\n\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.titlePreference'),\n detailPlacement: 'inline',\n options: [\n {\n label: i18next.t('cmd.prefOptionGeneral'),\n value: 'general',\n detail: i18next.t('cmd.prefDescGeneral'),\n },\n {\n label: i18next.t('cmd.prefOptionLang'),\n trailingLabel: langValue,\n value: 'lang',\n detail: i18next.t('cmd.prefDescLang'),\n },\n {\n label: i18next.t('cmd.prefOptionTheme'),\n trailingLabel: themeValue,\n value: 'theme',\n detail: i18next.t('cmd.prefDescTheme'),\n },\n ],\n defaultSelectedValue: restoreValue,\n onSelect: (value: string) => {\n if (value === 'general') handlePreferenceGeneral(ctx)\n else if (value === 'lang') handlePreferenceLang([], ctx)\n else if (value === 'theme') handlePreferenceTheme([], ctx)\n },\n onCancel: () => {},\n })\n}\n\n/**\n * /preference general — 常规偏好面板。\n * 昵称:回车打开文本输入框;\n * 完成通知 / 提示音 / 启动检查更新:回车就地翻转布尔并持久化,面板原地重开反映新值。\n * Esc 返回一级菜单。\n */\nlet lastGeneralHighlight: string | undefined\n\nfunction handlePreferenceGeneral(ctx: CliContext, subtitle?: string, restoreValue?: string, replace?: boolean): void {\n const settings = ctx.app.settings\n const notification = settings.get('notification')\n const nickname = settings.get('nickname') ?? ''\n const completionOn = notification?.on_completion !== false\n const soundOn = notification?.sound === true\n const updateCheckOn = settings.get('auto_update_check') !== false\n // RFC-318 技能回路:默认关(未配置即关),与 memory_auto_extract 同纹理。\n const skillLoopOn = settings.get('skill_loop')?.enabled === true\n // 观测由 plugin-skill-inductor 插件承担——没装的话开了也不会有任何反应,\n // 面板必须如实显示这个状态,而不是让用户对着一个\"已开启\"却毫无动静的开关。\n const inductorInstalled = ctx.app\n .activePlugins()\n .some((p) => p.id === INDUCTOR_PLUGIN_ID)\n // 开关态文案统一走 formatSwitch(符号 + 文字,如 ◉ 开启 / ○ 关闭)。\n const glyphs = { on: FIGURES.toggleOn, off: FIGURES.toggleOff }\n const labels = { on: i18next.t('cmd.toggleOn'), off: i18next.t('cmd.toggleOff') }\n const onOff = (v: boolean): string => formatSwitch(v, glyphs, labels)\n\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.selectGeneralTitle'),\n detailPlacement: 'inline',\n subtitle,\n // 布尔项就地翻转时 replace=true:更新当前弹窗 state 而非重开(消除整屏闪动,见\n // toggleGeneralBool)。初次打开/昵称保存等仍是正常 push(replace 未传即 false)。\n replace,\n options: [\n {\n label: i18next.t('cmd.genNickname'),\n trailingLabel: nickname || '—',\n value: 'nickname',\n detail: i18next.t('cmd.genNicknameDesc'),\n },\n {\n label: i18next.t('cmd.genCompletionNotice'),\n trailingLabel: onOff(completionOn),\n value: 'completion',\n detail: i18next.t('cmd.genCompletionNoticeDesc'),\n },\n {\n label: i18next.t('cmd.genSound'),\n trailingLabel: onOff(soundOn),\n value: 'sound',\n detail: i18next.t('cmd.genSoundDesc'),\n },\n {\n label: i18next.t('cmd.genUpdateCheck'),\n trailingLabel: onOff(updateCheckOn),\n value: 'updateCheck',\n detail: i18next.t('cmd.genUpdateCheckDesc'),\n },\n {\n label: i18next.t('cmd.genSkillLoop'),\n trailingLabel: inductorInstalled ? onOff(skillLoopOn) : i18next.t('cmd.genSkillLoopMissing'),\n value: 'skillLoopPanel',\n detail: i18next.t('cmd.genSkillLoopEntryDesc'),\n },\n ],\n defaultSelectedValue: restoreValue ?? lastGeneralHighlight,\n onHighlight: (v: string) => { lastGeneralHighlight = v },\n onSelect: (value: string) => {\n if (value === 'nickname') {\n void editNickname(ctx)\n return\n }\n if (value === 'skillLoopPanel') {\n openSkillLoopPanel(ctx)\n return\n }\n void toggleGeneralBool(ctx, value)\n },\n onCancel: () => { openPreferenceMenu(ctx, 'general') },\n })\n}\n\n/** 昵称文本输入:resolve 非 null 即持久化并重开常规面板(保留其余项当前值)。 */\nasync function editNickname(ctx: CliContext): Promise<void> {\n const next = await ctx.tui.modal.openModal({\n kind: 'input',\n title: i18next.t('cmd.genNicknameInputTitle'),\n placeholder: i18next.t('cmd.genNicknamePlaceholder'),\n })\n if (next == null) {\n // 用户取消输入——重开面板不改动。\n handlePreferenceGeneral(ctx)\n return\n }\n const trimmed = next.trim()\n await ctx.app.settings.persist({ nickname: trimmed })\n handlePreferenceGeneral(ctx, i18next.t('cmd.genSaved'))\n}\n\n/** 就地翻转常规布尔项并持久化,然后原地重开面板反映新值。 */\n/**\n * 技能沉淀的灵敏度档位。\n *\n * 给档位而不是让用户填数字:这个值的含义是\"攒够多少条新消息才分析一次\",用户很难\n * 凭直觉给出合适的数——但能判断\"太吵了/太少了\"。档位把它变成可判断的选择,\n * 每档带一句\"这样会怎样\"。6 是缺省(与 lesson 抽取同口径)。\n */\nconst SKILL_LOOP_THRESHOLDS = [4, 6, 12, 20] as const\n\n/** 当前阈值的显示文案——未配置时显示缺省档。 */\nfunction skillLoopThresholdLabel(value: number | undefined): string {\n const v = typeof value === 'number' ? value : 6\n const key = (SKILL_LOOP_THRESHOLDS as readonly number[]).includes(v) ? `genSkillLoopThr${v}` : ''\n // 用户手改 config 写了个非档位值——如实显示数字,不假装它是某一档。\n return key ? i18next.t(`cmd.${key}`) : String(v)\n}\n\n/** 灵敏度选择面板。 */\nfunction openSkillLoopThreshold(ctx: CliContext): void {\n const settings = ctx.app.settings\n const current = settings.get('skill_loop')?.min_messages ?? 6\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.genSkillLoopThresholdTitle'),\n detailPlacement: 'inline',\n options: SKILL_LOOP_THRESHOLDS.map((n) => ({\n label: i18next.t(`cmd.genSkillLoopThr${n}`),\n value: String(n),\n detail: i18next.t(`cmd.genSkillLoopThr${n}Desc`),\n trailingLabel: n === current ? FIGURES.toggleOn : undefined,\n })),\n onSelect: (value: string) => {\n void (async () => {\n // 嵌套对象读-改-写(persist 是浅合并,直接写会抹掉 enabled)。\n const cur = { ...(settings.get('skill_loop') ?? {}) }\n cur.min_messages = Number(value)\n await settings.persist({ skill_loop: cur })\n openSkillLoopPanel(ctx, i18next.t('cmd.genSaved'), 'threshold')\n })()\n },\n // Esc 与选中某档回到同一个地方(二级面板)——同一面板两个出口去不同层级是 UX 缺陷。\n onCancel: () => { openSkillLoopPanel(ctx, undefined, 'threshold') },\n })\n}\n\n/**\n * 拒绝后的静默期档位(天)。\n *\n * 同灵敏度的理由:用户判断不了\"该配几天\",但判断得了\"太快又问了/再也没问过\"。\n * 365 档的语义是\"基本不再问\"——比让用户去找关闭开关更贴合真实意图(他往往只是\n * 不想要**这一个**候选,而不是关掉整个功能)。\n */\nconst SKILL_LOOP_COOLDOWNS = [3, 14, 30, 365] as const\n\n/** 当前静默期的显示文案——未配置时显示缺省档。 */\nfunction skillLoopCooldownLabel(value: number | undefined): string {\n const v = typeof value === 'number' ? value : 14\n return (SKILL_LOOP_COOLDOWNS as readonly number[]).includes(v)\n ? i18next.t(`cmd.genSkillLoopCd${v}`)\n : i18next.t('cmd.genSkillLoopCdDays', { n: v })\n}\n\n/** 静默期选择面板。 */\nfunction openSkillLoopCooldown(ctx: CliContext): void {\n const settings = ctx.app.settings\n const current = settings.get('skill_loop')?.dismiss_cooldown_days ?? 14\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.genSkillLoopCooldownTitle'),\n detailPlacement: 'inline',\n options: SKILL_LOOP_COOLDOWNS.map((n) => ({\n label: i18next.t(`cmd.genSkillLoopCd${n}`),\n value: String(n),\n detail: i18next.t(`cmd.genSkillLoopCd${n}Desc`),\n trailingLabel: n === current ? FIGURES.toggleOn : undefined,\n })),\n onSelect: (value: string) => {\n void (async () => {\n const cur = { ...(settings.get('skill_loop') ?? {}) }\n cur.dismiss_cooldown_days = Number(value)\n await settings.persist({ skill_loop: cur })\n openSkillLoopPanel(ctx, i18next.t('cmd.genSaved'), 'cooldown')\n })()\n },\n // Esc 与选中某档回到同一个地方(二级面板)——同一面板两个出口去不同层级是 UX 缺陷。\n onCancel: () => { openSkillLoopPanel(ctx, undefined, 'cooldown') },\n })\n}\n\n/**\n * 技能沉淀二级面板——启用开关 + 两个旋钮 + 状态入口。\n *\n * 为什么收成二级:这一个功能有 4 个可操作项,铺在常规面板会把它从 4 项撑到 8 项,\n * 昵称/通知这些更常用的设置被挤成配角。一级只留\"技能沉淀 ◉开启\"一行。\n *\n * 未装插件时只显示安装指引——摆一排调不出效果的旋钮没有意义。\n */\nfunction openSkillLoopPanel(ctx: CliContext, subtitle?: string, restoreValue?: string): void {\n const settings = ctx.app.settings\n const on = settings.get('skill_loop')?.enabled === true\n const installed = ctx.app.activePlugins().some((p) => p.id === INDUCTOR_PLUGIN_ID)\n const glyphs = { on: FIGURES.toggleOn, off: FIGURES.toggleOff }\n const labels = { on: i18next.t('cmd.toggleOn'), off: i18next.t('cmd.toggleOff') }\n\n if (!installed) {\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.skillLoopPanelTitle'),\n detailPlacement: 'inline',\n subtitle: i18next.t('cmd.genSkillLoopMissingHint'),\n options: [{ label: i18next.t('cmd.genSkillLoopMissing'), value: 'noop', detail: '' }],\n onSelect: () => { handlePreferenceGeneral(ctx, undefined, 'skillLoopPanel') },\n onCancel: () => { handlePreferenceGeneral(ctx, undefined, 'skillLoopPanel') },\n })\n return\n }\n\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.skillLoopPanelTitle'),\n detailPlacement: 'inline',\n subtitle,\n defaultSelectedValue: restoreValue,\n options: [\n {\n label: i18next.t('cmd.skillLoopOnOff'),\n trailingLabel: formatSwitch(on, glyphs, labels),\n value: 'toggle',\n detail: i18next.t('cmd.genSkillLoopEntryDesc'),\n },\n // 关着时不显示旋钮与状态——它们此刻都无意义。\n ...(on\n ? [\n {\n label: i18next.t('cmd.genSkillLoopThreshold'),\n trailingLabel: skillLoopThresholdLabel(settings.get('skill_loop')?.min_messages),\n value: 'threshold',\n detail: i18next.t('cmd.genSkillLoopThresholdDesc'),\n },\n {\n label: i18next.t('cmd.genSkillLoopCooldown'),\n trailingLabel: skillLoopCooldownLabel(settings.get('skill_loop')?.dismiss_cooldown_days),\n value: 'cooldown',\n detail: i18next.t('cmd.genSkillLoopCooldownDesc'),\n },\n {\n label: i18next.t('cmd.genSkillLoopStatus'),\n value: 'status',\n detail: i18next.t('cmd.genSkillLoopStatusDesc'),\n },\n ]\n : []),\n ],\n onSelect: (value: string) => {\n if (value === 'threshold') { openSkillLoopThreshold(ctx); return }\n if (value === 'cooldown') { openSkillLoopCooldown(ctx); return }\n if (value === 'status') { openSkillLoopStatus(ctx); return }\n void (async () => {\n const cur = { ...(settings.get('skill_loop') ?? {}) }\n const next = cur.enabled !== true\n cur.enabled = next\n await settings.persist({ skill_loop: cur })\n openSkillLoopPanel(\n ctx,\n next ? i18next.t('cmd.genSkillLoopOn') : i18next.t('cmd.genSkillLoopOff'),\n 'toggle',\n )\n })()\n },\n onCancel: () => { handlePreferenceGeneral(ctx, undefined, 'skillLoopPanel') },\n })\n}\n\n/**\n * 技能沉淀状态面板。\n *\n * 为什么放在 /preference 而不是单独一个 /skill-loop status 命令:用户是在设置里遇到\n * 这个功能的,\"它到底攒了什么\"是紧接着的疑问。另起一个命令等于让人先知道它存在。\n *\n * 一个默认关、开了之后可能长时间没动静的功能,如果还看不见内部状态,用户无从判断\n * \"是没触发条件,还是根本坏了\"——那是让人放弃它最快的方式。\n */\nfunction openSkillLoopStatus(ctx: CliContext): void {\n const settings = ctx.app.settings\n const installed = ctx.app.activePlugins().some((p) => p.id === INDUCTOR_PLUGIN_ID)\n const status = readSkillLoopStatus({\n workspaceKey: ctx.app.workspaceRef?.key ?? DEFAULT_WORKSPACE_KEY,\n now: Date.now(),\n cooldownDays: settings.get('skill_loop')?.dismiss_cooldown_days,\n pluginInstalled: installed,\n })\n\n const count = (n: number): string => i18next.t('cmd.skillLoopStatusCount', { n })\n // 用 select 而非 info modal 呈现:info 没有 onCancel 通道,关掉会把用户弹回聊天界面,\n // 得重新 /preference 进三层才能回来。select 天然有返回路径,也与其余面板一致。\n const options: Array<{ label: string; value: string; detail?: string; trailingLabel?: string }> = []\n\n if (!installed) {\n options.push({\n label: i18next.t('cmd.skillLoopStatusNotInstalled'),\n value: 'noop',\n detail: i18next.t('cmd.genSkillLoopMissingHint'),\n })\n } else {\n options.push(\n {\n label: i18next.t('cmd.skillLoopStatusEpisodes'),\n trailingLabel: count(status.episodeCount),\n value: 'noop',\n detail: i18next.t('cmd.skillLoopStatusSuccess') + ' ' + count(status.successCount),\n },\n {\n label: i18next.t('cmd.skillLoopStatusPending'),\n trailingLabel: count(status.pendingCount),\n value: 'noop',\n detail:\n count(status.candidateCount) + ' ' + i18next.t('cmd.skillLoopStatusCandidates') + ' · ' +\n count(status.coolingCount) + ' ' + i18next.t('cmd.skillLoopStatusCooling'),\n },\n )\n\n // 待提案的候选——用户最关心\"接下来它会问我什么\"。\n for (const p of status.pending) {\n options.push({\n label: p.taskSignature,\n trailingLabel: i18next.t('cmd.skillLoopStatusTimes', { n: p.size }),\n value: 'noop',\n detail: i18next.t('cmd.skillLoopStatusGroupPending'),\n })\n }\n\n // 最近观测——给\"它确实在看\"的实感。\n for (const sig of status.recentSignatures) {\n options.push({\n label: sig,\n value: 'noop',\n detail: i18next.t('cmd.skillLoopStatusGroupRecent'),\n })\n }\n }\n\n // 空状态/进度提示——空面板会让人以为坏了。\n const hintKey = status.episodeCount === 0\n ? 'cmd.skillLoopStatusEmpty'\n : {\n 'no-episodes': 'cmd.skillLoopStatusHintNoEpisodes',\n 'not-enough-successes': 'cmd.skillLoopStatusHintNotEnough',\n 'no-cluster-yet': 'cmd.skillLoopStatusHintNoCluster',\n }[status.nextProposalHint ?? ''] ?? ''\n\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.skillLoopStatusTitle'),\n detailPlacement: 'inline',\n subtitle: hintKey ? i18next.t(hintKey) : undefined,\n options,\n // 全是只读行——选中任何一行都回二级面板(不做无意义的动作)。\n onSelect: () => { openSkillLoopPanel(ctx, undefined, 'status') },\n onCancel: () => { openSkillLoopPanel(ctx, undefined, 'status') },\n })\n}\n\nasync function toggleGeneralBool(ctx: CliContext, value: string): Promise<void> {\n const settings = ctx.app.settings\n\n if (value === 'updateCheck') {\n const cur = settings.get('auto_update_check') !== false\n await settings.persist({ auto_update_check: !cur })\n // 就地翻转:replace=true 更新当前弹窗(不重开、不闪),restoreValue=当前项保持游标不动。\n handlePreferenceGeneral(ctx, i18next.t('cmd.genSaved'), value, true)\n return\n }\n\n // notification 是嵌套对象——persist 为浅合并,需读-改-写整体回写。\n const notification = { ...(settings.get('notification') ?? {}) }\n if (value === 'completion') {\n const cur = notification.on_completion !== false\n notification.on_completion = !cur\n } else if (value === 'sound') {\n const cur = notification.sound === true\n notification.sound = !cur\n }\n await settings.persist({ notification })\n // 同上:就地翻转,游标停在被翻转项。\n handlePreferenceGeneral(ctx, i18next.t('cmd.genSaved'), value, true)\n}\n\nfunction handlePreferenceLang(args: string[], ctx: CliContext): void {\n const arg = args[0]?.toLowerCase()\n\n if (arg === 'en' || arg === 'zh' || arg === 'auto') {\n applyLanguage(ctx, arg)\n return\n }\n\n if (arg) {\n ctx.tui.info.showToast(i18next.t('cmd.titlePreference'), i18next.t('pref.languageUsage'))\n return\n }\n\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.selectLanguageTitle'),\n detailPlacement: 'inline',\n options: [\n { label: '中文 (zh)', value: 'zh', detail: i18next.t('cmd.langDescZh') },\n { label: 'English (en)', value: 'en', detail: i18next.t('cmd.langDescEn') },\n { label: 'Auto', value: 'auto', detail: i18next.t('cmd.langDescAuto') },\n ],\n onSelect: (value: string) => applyLanguage(ctx, value as LangMode),\n // Esc = 返回上一级(与常规/技能沉淀一致)。此前是关掉整个设置并弹一句已取消——\n // 同一个 Esc 在同一功能里两种语义,用户按下去不知道会回到哪。\n onCancel: () => { openPreferenceMenu(ctx, 'lang') },\n })\n}\n\nfunction handlePreferenceTheme(_args: string[], ctx: CliContext): void {\n // RFC-283:删除 dark/light/system mode 选择——主题深浅始终自动跟随终端背景色。\n // 只保留 preset 选择入口(用户可选配色方案,不选深浅)。\n openThemeMenu(ctx)\n}\n\n/**\n * 主题菜单(RFC-283):只保留 preset 选择入口——深浅自动跟随终端背景色,用户不再可选。\n * trailingLabel 显示当前生效的 preset 名称。\n */\nfunction openThemeMenu(ctx: CliContext): void {\n const registry = ctx.state.themeRegistry ?? createBuiltinThemeRegistry()\n const selection = ctx.app.settings.get('theme_preset') ?? {}\n const darkPreset = registry.get(selection.dark ?? 'dark') ?? registry.get('dark')\n const lightPreset = registry.get(selection.light ?? 'light') ?? registry.get('light')\n\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.prefOptionTheme'),\n detailPlacement: 'inline',\n options: [\n {\n label: i18next.t('cmd.themeDarkPresetOption'),\n value: 'preset-dark',\n detail: i18next.t('cmd.themeDarkPresetDesc'),\n trailingLabel: darkPreset?.label,\n },\n {\n label: i18next.t('cmd.themeLightPresetOption'),\n value: 'preset-light',\n detail: i18next.t('cmd.themeLightPresetDesc'),\n trailingLabel: lightPreset?.label,\n },\n ],\n onSelect: (value: string) => {\n if (value === 'preset-dark') { openThemePresetPicker(ctx, 'dark'); return }\n if (value === 'preset-light') { openThemePresetPicker(ctx, 'light'); return }\n },\n onCancel: () => { openPreferenceMenu(ctx, 'theme') },\n })\n}\n\n/**\n * 按 appearance 选择 preset(RFC-277 M6):列出 registry 中该 appearance 的全部\n * built-in/extension preset + 一个\"恢复默认\"逃生项。高亮移动即经 onHighlight →\n * ctx.tui.lifecycle.setPresetSelection 实时预览整个 TUI 换色;Esc 取消时把预览态还原为进入前的\n * 选择(不落盘);Enter 确认时持久化 `theme_preset.<appearance>` 并 toast。\n */\nfunction openThemePresetPicker(ctx: CliContext, appearance: 'dark' | 'light'): void {\n const registry = ctx.state.themeRegistry ?? createBuiltinThemeRegistry()\n // 进入前的真实选择态(用于 Esc 取消时精确还原,而非笼统清空)。\n const originalSelection = ctx.app.settings.get('theme_preset') ?? {}\n const currentId = originalSelection[appearance] ?? appearance\n\n const presets = registry.list().filter((p) => p.appearance === appearance)\n const options = [\n ...presets.map((p) => ({\n label: p.label,\n value: p.id,\n detail: p.source.kind === 'builtin'\n ? i18next.t('cmd.themePresetBuiltin')\n : i18next.t('cmd.themePresetFromExtension', { id: p.source.extensionId }),\n trailingLabel: p.id === currentId ? '✓' : undefined,\n })),\n { label: i18next.t('cmd.themePresetReset'), value: '__reset__', detail: i18next.t('cmd.themePresetResetDesc') },\n ]\n\n const previewSelection = (presetId: string | undefined): void => {\n const next = { ...originalSelection }\n if (presetId === undefined) delete next[appearance]\n else next[appearance] = presetId\n ctx.tui.lifecycle.setPresetSelection?.(next)\n }\n const restoreOriginal = (): void => { ctx.tui.lifecycle.setPresetSelection?.(originalSelection) }\n\n ctx.tui.modal.openModal({\n kind: 'select',\n title: appearance === 'dark' ? i18next.t('cmd.themePresetTitleDark') : i18next.t('cmd.themePresetTitleLight'),\n options,\n // 移动即预览:高亮到某个 preset(或\"恢复默认\")立即整树换色,尚未确认。\n onHighlight: (value: string) => { previewSelection(value === '__reset__' ? undefined : value) },\n onSelect: (value: string) => {\n const presetId = value === '__reset__' ? undefined : value\n const next = { ...originalSelection }\n if (presetId === undefined) delete next[appearance]\n else next[appearance] = presetId\n // setPresetSelection 已在 onHighlight 阶段把预览态设为与 next 等价的值——这里显式\n // 再调一次是为了 disabled 场景/边界稳态,不依赖 onHighlight 一定先于 onSelect 触发。\n ctx.tui.lifecycle.setPresetSelection?.(next)\n void ctx.app.settings.persist({ theme_preset: next }, 'global')\n const savedLabel = presetId === undefined\n ? (appearance === 'dark' ? registry.get('dark')?.label : registry.get('light')?.label)\n : registry.get(presetId)?.label\n ctx.tui.info.showToast(i18next.t('cmd.titlePreference'), i18next.t('cmd.themePresetSaved', { label: savedLabel ?? presetId ?? appearance }))\n },\n onCancel: () => {\n // 取消:预览态还原为进入前的真实选择(不落盘)。\n restoreOriginal()\n ctx.tui.info.showToast(i18next.t('cmd.titlePreference'), i18next.t('cmd.selectCancelled'))\n },\n })\n}\n\nexport function applyLanguage(ctx: CliContext, target: LangMode): void {\n ctx.state.currentLang = ctx.tui.lifecycle.setLanguage(target)\n // RFC-161 M3a:cli i18n 层与 TUI 层双真源同步切换(cli 层文案 live binding 生效)。\n setCliLanguage(target)\n ctx.tui.plugins.setCommands(ctx.callbacks.refreshSlashCommands())\n void ctx.app.settings.persist({ language: target })\n ctx.tui.info.showToast(\n i18next.t('cmd.titleLanguage'),\n i18next.t('pref.languageSwitched', { l: ctx.state.currentLang as Lang, name: (ctx.state.currentLang as Lang) === 'zh' ? '中文' : 'English' }),\n )\n}\n\n/**\n * /trust(= /compat)[status|trust|untrust|plugin <id>|plugin untrust <id>]\n * —— 项目可执行面信任的**统一入口**:`.claude/` 兼容面 + 项目级插件。\n * status(默认):列出本项目的 .claude 可执行面 + 各项目级插件的信任状态。\n * trust / untrust:信任 / 撤销本项目的 .claude 面(热重载)。\n * plugin <id>:信任某项目级插件的可执行面(与 /extension plugin trust 共用 promptPluginTrust)。\n * plugin untrust <id>:撤销某项目级插件信任。\n * (用户级插件 `~/.otto/plugins/` 永远受信任,不入此门。)\n */\nexport async function handleCompat(args: string[], ctx: CliContext): Promise<void> {\n const ws: string = ctx.app.workspaceDir ?? process.cwd()\n const sub = args[0]?.toLowerCase() ?? 'status'\n\n // `/trust plugin <id>` / `/trust plugin untrust <id>` —— 统一入口下的项目级插件信任\n // (与 /extension plugin trust 共用 promptPluginTrust)。用户级插件永远受信任,此处只涉项目级。\n if (sub === 'plugin') {\n const untrust = args[1]?.toLowerCase() === 'untrust'\n const id = untrust ? args[2] : args[1]\n if (!id) {\n ctx.tui.info.showToast(i18next.t('cmd.titlePlugin'), i18next.t('cmd.pluginUsage', { seg: (untrust ? 'untrust' : 'trust') === 'untrust' ? 'untrust ' : '' }))\n return\n }\n const p = discoverPluginsWithBundled({ cwd: ws, homedir: homedir() }).find((x) => x.id === id)\n if (!p) {\n ctx.tui.info.showToast(i18next.t('cmd.titlePlugin'), i18next.t('cmd.pluginNotFound', { id: id }))\n return\n }\n if (untrust) {\n untrustPlugin(p.dir)\n ctx.tui.info.showToast(i18next.t('cmd.titlePlugin'), i18next.t('cmd.pluginUntrusted', { id: id }))\n } else {\n promptPluginTrust(ctx, p)\n }\n return\n }\n\n if (sub === 'trust') {\n trustProject(ws)\n ctx.tui.info.showToast(i18next.t('cmd.titleCompat'), i18next.t('cmd.compatTrusting'))\n void ctx.app.reloadCapabilities().then(\n (r) => {\n ctx.tui.plugins.setPluginRegistrations(ctx.app.getPluginRegistrations())\n ctx.tui.info.showToast(i18next.t('cmd.titleCompat'), i18next.t('cmd.compatTrusted', { count: r.mcpTools }))\n },\n (e: unknown) => ctx.tui.info.showToast(i18next.t('cmd.titleCompat'), i18next.t('cmd.compatReloadFailed', { err: e instanceof Error ? e.message : String(e) })),\n )\n return\n }\n\n if (sub === 'untrust') {\n untrustProject(ws)\n ctx.tui.info.showToast(i18next.t('cmd.titleCompat'), i18next.t('cmd.compatUntrusted'))\n return\n }\n\n const v = evaluateClaudeTrust(ws)\n // /compat 是交互态命令 → interactive:true。严格模式关时 detect 返空,不呈现噪声。\n const config = detectProjectConfigGating(ws, { interactive: true })\n\n const lines: string[] = []\n if (v.surface.any) {\n const surfaces = [\n v.surface.mcpJson ? '.mcp.json' : null,\n v.surface.commands ? '.claude/commands' : null,\n ].filter(Boolean)\n const trustLine = v.trusted ? i18next.t('cmd.compatTrustedLabel') : i18next.t('cmd.compatUntrustedLabel')\n lines.push(`${surfaces.join(i18next.t('cmd.listSep'))} ${trustLine}`)\n }\n if (config.highRiskKeys.length > 0) {\n const keys = config.highRiskKeys.join(i18next.t('cmd.listSep'))\n lines.push(config.gated ? i18next.t('cmd.compatConfigGated', { keys: keys }) : i18next.t('cmd.compatConfigActive', { keys: keys }))\n }\n\n // 项目级/仓库级插件的信任状态也在此统一呈现(用户级永受信任,不入门)。未信任者提示用\n // `/trust plugin <id>` 放行——把「Browser Use project · untrusted」这类项收进同一 trust 入口。\n const projectPlugins = discoverPluginsWithBundled({ cwd: ws, homedir: homedir() }).filter(\n (p) => p.scope === 'project' || p.scope === 'repository',\n )\n for (const p of projectPlugins) {\n const t = evaluatePluginTrust(p)\n if (!t.surface.any) continue // 无可执行面 → 无所谓信任,不列噪声\n const label = t.gated ? i18next.t('cmd.compatUntrustedLabel') : i18next.t('cmd.compatTrustedLabel')\n lines.push(`plugin:${p.id} ${label}${t.gated ? ` → /trust plugin ${p.id}` : ''}`)\n }\n\n if (lines.length === 0) {\n ctx.tui.info.showToast(i18next.t('cmd.titleCompat'), i18next.t('cmd.compatNone'))\n return\n }\n\n // 高危键被门控时用 dialog 提示(更醒目,用户必须确认后再操作)\n if (config.gated) {\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.titleCompat'), lines })\n } else {\n ctx.tui.info.showToast(i18next.t('cmd.titleCompat'), lines.join('\\n'))\n }\n}\n\n/**\n * /permissions [clear | add <entry> | remove <entry>] | /perms …\n * 无参:查看会话级 + 项目级「始终允许」列表。\n * clear:清空会话级列表。\n * add <entry>:往项目级基线加一条(工具名或 `bash(git status)` 命令前缀,持久化到 config.json)。\n * remove <entry>:从项目级基线移除一条。\n */\nexport async function handlePermissions(\n args: string[],\n ctx: CliContext,\n): Promise<void> {\n const sub = args[0]\n if (sub === 'clear') {\n ctx.app.clearSessionAlwaysAllow(ctx.state.session.id)\n ctx.tui.info.showToast(i18next.t('cmd.titlePermissions'), i18next.t('cmd.permsCleared'))\n return\n }\n if (sub === 'add' || sub === 'remove') {\n const entry = args.slice(1).join(' ').trim()\n if (!entry) {\n ctx.tui.info.showToast(i18next.t('cmd.titlePermissions'), i18next.t('cmd.permsManageHint'))\n return\n }\n if (sub === 'add') {\n // RFC-073 P3:拒绝空括号 `bash()`(塌缩为整工具陷阱)/ 垃圾条目静默持久化。\n if (!ctx.app.isWellFormedAllowEntry(entry)) {\n ctx.tui.info.showToast(i18next.t('cmd.titlePermissions'), i18next.t('cmd.permsInvalidEntry', { entry: entry }))\n return\n }\n await ctx.app.allowToolAlways(entry)\n ctx.tui.info.showToast(i18next.t('cmd.titlePermissions'), i18next.t('cmd.permsAdded', { entry: entry }))\n } else {\n const hit = await ctx.app.disallowToolAlways(entry)\n ctx.tui.info.showToast(i18next.t('cmd.titlePermissions'), hit ? i18next.t('cmd.permsRemoved', { entry: entry }) : i18next.t('cmd.permsNotFound', { entry: entry }))\n }\n return\n }\n const sessionList = ctx.app.getSessionAlwaysAllow(ctx.state.session.id)\n const projectList = ctx.app.settings.get('permission_always_allow') ?? []\n const fmt = (label: string, xs: string[]) =>\n `${label} ${xs.length ? xs.join(', ') : i18next.t('cmd.permsEmpty')}`\n ctx.tui.modal.openModal({\n kind: 'permissions',\n lines: [\n fmt(i18next.t('cmd.permsSessionLabel'), sessionList),\n fmt(i18next.t('cmd.permsProjectLabel'), projectList),\n '',\n i18next.t('cmd.permsClearHint'),\n i18next.t('cmd.permsManageHint'),\n ],\n })\n}\n\n/**\n * /upgrade — Hot upgrade: check for latest version + install + re-spawn.\n */\nexport async function handleUpgrade(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n const current = getVersion()\n ctx.tui.info.showToast(i18next.t('cmd.titleUpgrade'), i18next.t('cmd.upgradeChecking', { current: current }))\n\n const info = await fetchLatestVersion(current)\n if (!info) {\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.titleUpgrade'), lines: [i18next.t('cmd.upgradeNoNetwork')] })\n return\n }\n if (!info.newer) {\n ctx.tui.info.showToast(i18next.t('cmd.titleUpgrade'), i18next.t('cmd.upgradeLatest', { current: current }))\n return\n }\n\n ctx.tui.modal.openModal({ kind: 'info', title: i18next.t('cmd.titleUpgrade'), lines: (i18next.t('cmd.upgradeAvailableLines', { returnObjects: true, current: current, latest: info.latest }) as string[]) })\n}\n\n/**\n * /upgrade --force — 执行热升级\n *\n * npm install 后走正常 shutdown 流程(restartRequested → tui.lifecycle.stop → app.stop → cli.ts 陪跑 spawn),\n * 不再在 handler 内直接 process.exit/re-spawn——避免截断 saveAll/killAll/dispose。\n */\nexport async function forceUpgrade(args: string[], ctx: CliContext): Promise<boolean> {\n if (!args.includes('--force')) return false\n ctx.tui.info.showToast(i18next.t('cmd.titleUpgrade'), i18next.t('cmd.upgrading'))\n const { ok, error } = await upgradeInstall()\n if (!ok) {\n ctx.tui.info.showToast(i18next.t('cmd.titleUpgrade'), i18next.t('cmd.upgradeFailed', { err: error ?? i18next.t('cmd.unknownError') }))\n return true\n }\n // 安装成功:标记重启并触发正常关停\n ctx.state.restartRequested = true\n ctx.tui.lifecycle.stop()\n return true\n}\n\n/**\n * /help — 命令面板\n */\nexport async function handleHelp(\n _args: string[],\n ctx: CliContext,\n cliCommands: CliCommandEntry[],\n): Promise<void> {\n const cmdDescMap = i18next.t('cmdDesc', { returnObjects: true }) as Record<string, string>\n\n // RFC-074 D2:分组与成员从命令目录 `group` 字段派生(不再手抄名单);组顺序 = HELP_GROUP_ORDER。\n const groupLabel: Record<HelpGroup, string> = {\n session: i18next.t('cmd.helpGroupSession'),\n model: i18next.t('cmd.helpGroupModel'),\n agent: i18next.t('cmd.helpGroupAgent'),\n debug: i18next.t('cmd.helpGroupDebug'),\n ui: i18next.t('cmd.helpGroupUi'),\n other: i18next.t('cmd.helpGroupOther'),\n }\n\n // RFC-294 D4:/help 从只读文本升级为**可选中命令列表**——此前走 helpViewer\n // (FileContentViewer 纯文本),有命令清单却不能选、Enter 不执行,用户只能记住名字\n // 关掉面板重新手打。改用 SelectDialog 后白得模糊搜索 + 分组渲染 + 全仓一致的键盘行为。\n //\n // 排除 tuiHidden 命令(exit/abort/copy):它们有 group(会进本列表)但 **handler 为\n // undefined**——由 TUI 命令解析器/onSubmit 快路径处理,不经 dispatchSlashCommand\n // (command-dispatcher.ts `if (!entry?.handler) return false`)。做成可选中项会导致\n // 选中后静默无反应。用现成的 tuiHidden 标记筛除,与斜杠 typeahead 的既有口径一致。\n const options: SelectOption[] = []\n for (const group of HELP_GROUP_ORDER) {\n const entries = cliCommands.filter((c) => c.group === group && !c.tuiHidden)\n if (entries.length === 0) continue\n for (const entry of entries) {\n const label = entry.aliases?.length\n ? [entry.name, ...entry.aliases].map((a) => `/${a}`).join(', ')\n : `/${entry.name}`\n options.push({\n label,\n value: entry.name,\n detail: cmdDescMap[entry.name] ?? entry.description ?? undefined,\n group: groupLabel[group],\n // RFC-125 同款:模糊搜索评分只用命令名,避免 `/` 前缀与别名逗号干扰短 query。\n searchText: [entry.name, ...(entry.aliases ?? [])].join(' '),\n })\n }\n }\n\n // 前缀提示(`!<cmd>` / `@<file>`)不是命令、无法作为可选中项——移入 subtitle 保留信息。\n const subtitle = `${i18next.t('cmd.helpPrefixes')} !<cmd> ${i18next.t('cmd.helpPrefixShell')} · @<file> ${i18next.t('cmd.helpPrefixFile')}`\n\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.titleCommands'),\n subtitle,\n searchable: true,\n // 命令描述是变长多段文本(如 /compact 那条近百字符),用 'inline' 会被挤压折行、\n // 列宽也无法对齐(实测可读性差于改造前的纯文本版);改用缺省 'below'——\n // detail 独占条目下方一行,与 /model 的多段 detail 同款排布。\n options,\n onSelect: (name: string) => {\n void dispatchSlashCommand(name, [], ctx)\n },\n onCancel: () => {},\n })\n}\n\n/**\n * /history — 打开历史搜索面板(与 Ctrl+R 同一面板:SigilPane history 模式)。\n */\nexport async function handleHistory(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n ctx.tui.panes.togglePane('history')\n}\n\n/**\n * /notification — 切换通知中心面板(等价于 ctrl+n)。\n */\nexport async function handleNotification(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n ctx.tui.panes.togglePane('notifications')\n}\n\n/** /prompts — 查看 Prompt 记录(本会话已提交的 prompt 列表)。 */\nexport async function handlePrompts(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n ctx.tui.panes.togglePane('prompts')\n}\n\n/** /services — 打开进程服务列表面板(RFC-223)。 */\nexport async function handleServices(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n ctx.tui.panes.setServices(true)\n}\n","/**\n * /schedule 命令:周期性 Agent Job 管理(增删改查/手动执行)。\n */\n\nimport { readdirSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\n// RFC-161 M2:SchedulerService 已迁至 @x-otto/schedule,直连新真源。\nimport type { SchedulerService } from '@x-otto/schedule'\nimport { OTTO_HOME } from '@x-otto/env'\n\nimport type { CliHandler, CliContext } from '../../command-dispatcher'\n\n// ── Module-level scheduler ref ──\n\nlet schedulerRef: SchedulerService | undefined\n\nexport function setScheduleService(s: SchedulerService | undefined): void {\n schedulerRef = s\n}\n\n// ── Main handler ──\n\nexport const handleSchedule: CliHandler = async (args: string[], ctx: CliContext): Promise<void> => {\n const scheduler = schedulerRef\n if (!scheduler) {\n ctx.tui.info.showToast('Schedule', 'Schedule system not available')\n return\n }\n\n const sub = args[0]\n switch (sub) {\n case undefined:\n // RFC-229 M2: open the schedule plugin panel\n return handleOpenPanel(ctx)\n case 'list':\n return handleList(scheduler, ctx)\n case 'add':\n return handleAdd(args.slice(1), scheduler, ctx)\n case 'remove':\n return handleRemove(args[1], scheduler, ctx)\n case 'run':\n return handleRun(args[1], scheduler, ctx)\n case 'enable':\n return handleToggle(args[1], true, scheduler, ctx)\n case 'disable':\n return handleToggle(args[1], false, scheduler, ctx)\n case 'logs':\n return handleLogs(args[1], ctx)\n case 'cancel':\n return handleCancel(args[1], ctx)\n default:\n ctx.tui.info.showToast('Schedule', `/schedule ${sub} — unknown subcommand. Use: add|list|remove|run|enable|disable|cancel|logs`)\n }\n}\n\n// ── Subcommand handlers ──\n\n// open panel (RFC-229 M2: when /schedule is called without subcommand)\n//\n// 2026-07-25 终局修复:不再用 resolveOttoRepoRoot() 猜测 extensions/plugin-schedule/panels-dist/\n// 的磁盘路径——该函数向上找 `.git` 定位仓库根,只在本仓开发环境有效,`@x-otto/cli` 一旦真正\n// 发布到 npm(生产分发只含 `dist/`,无 `.git`),该函数恒返回 undefined,面板永远打不开。\n// schedule 现已物化为独立 npm 包 `@x-otto/plugin-schedule-panel`(对齐 browser-use/mobile-use 等既有\n// 内置扩展形态),走 `otto extension install schedule` 标准安装流程软链进 `~/.otto/plugins/\n// schedule`,面板路径改为查已注册的 `PanelRegistration`(`app.getPluginPanelRegistrations()`,\n// 内部用真实插件目录 `p.dir` 拼接,不依赖任何路径猜测)。\nasync function handleOpenPanel(ctx: CliContext): Promise<void> {\n const panel = (ctx.app.getPluginPanelRegistrations?.() ?? []).find(\n (p) => p.pluginId === 'plugin-schedule',\n )\n if (!panel) {\n ctx.tui.info.showToast(\n 'Schedule',\n 'Schedule panel not installed — run \"otto extension install plugin-schedule\"',\n )\n return\n }\n ctx.tui.modal.openModal({\n kind: 'pluginPanel',\n panelInfo: {\n title: panel.title,\n modulePath: panel.modulePath,\n exportName: panel.exportName,\n pluginId: panel.pluginId,\n },\n onClose: () => {},\n } as any)\n}\n\n// list\nasync function handleList(scheduler: SchedulerService, ctx: CliContext): Promise<void> {\n const tasks = scheduler.list()\n // RFC-169 D3d: surface read-only state — the user can see the shared task list but\n // this process isn't the one that will fire them (another terminal holds the lease).\n // F2 (RFC-169 终局 review 2026-07-15): distinguish \"another terminal genuinely holds the\n // lease\" from \"we couldn't reach the remote lease service at all\" — showing the wrong\n // specific reason is worse than a vague one; a network-error hint tells the user to check\n // connectivity instead of hunting for a nonexistent second terminal.\n const readOnlyHint = scheduler.isFireOwner()\n ? []\n : scheduler.fireOwnerFailureReason() === 'network-error'\n ? ['[read-only — could not reach the remote schedule service; check network/persistenced connectivity]']\n : ['[read-only — another terminal holds the fire-ownership lease for this workspace]']\n\n if (tasks.length === 0) {\n ctx.tui.info.showToast('Schedule', readOnlyHint.length > 0 ? `No scheduled tasks. ${readOnlyHint[0]}` : 'No scheduled tasks')\n return\n }\n const lines: string[] = []\n for (const t of tasks) {\n const status = t.lastFireFailed ? '✕' : t.enabled ? '◉' : '○'\n const cron = t.recurring ? t.cronExpression : 'one-shot'\n const next = new Date(t.nextFireAt).toLocaleString()\n const failedHint = t.lastFireFailed && t.lastFireError ? ` (failed: ${t.lastFireError})` : ''\n lines.push(`${status} ${t.id} ${t.name}${failedHint}`)\n lines.push(` ${cron} next: ${next}`)\n }\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/schedule',\n status: readOnlyHint.length > 0 ? 'warn' : 'ok',\n // readOnlyHint 优先展示(降级功能提醒,比\"N 个已排定任务\"更需要用户先看到)。\n summary: readOnlyHint.length > 0 ? readOnlyHint[0] : `${tasks.length} 个已排定任务`,\n detail: lines.join('\\n'),\n })\n // Open the schedule panel in TUI\n ctx.tui.panes.togglePane?.('schedulePane')\n}\n\n// add\nasync function handleAdd(args: string[], scheduler: SchedulerService, ctx: CliContext): Promise<void> {\n if (args.length < 2) {\n ctx.tui.info.showToast('Schedule', '/schedule add [--once] <cron> <prompt>')\n return\n }\n\n let once = false\n const positional = [...args]\n const onceIdx = positional.indexOf('--once')\n if (onceIdx !== -1) {\n once = true\n positional.splice(onceIdx, 1)\n }\n\n if (positional.length < 2) {\n // RFC-229 M4: open the panel in create view mode\n ctx.tui.info.showToast('Schedule', 'Opening schedule panel in create mode')\n void handleOpenPanel(ctx)\n return\n }\n\n const cronExpr = positional[0]!\n const prompt = positional.slice(1).join(' ')\n\n try {\n const task = scheduler.add({\n name: prompt.slice(0, 40),\n prompt,\n cronExpression: cronExpr,\n recurring: !once,\n origin: 'user',\n })\n const kind = once ? 'one-shot' : 'recurring'\n const next = new Date(task.nextFireAt).toLocaleString()\n ctx.tui.info.showToast('Schedule', `${task.id}: ${task.name} (${kind}) next: ${next}`)\n } catch (err) {\n ctx.tui.info.showToast('Schedule', `Failed: ${err instanceof Error ? err.message : String(err)}`)\n }\n}\n\n// remove\nasync function handleRemove(id: string | undefined, scheduler: SchedulerService, ctx: CliContext): Promise<void> {\n if (!id) {\n ctx.tui.info.showToast('Schedule', '/schedule remove <id>')\n return\n }\n const removed = scheduler.remove(id)\n ctx.tui.info.showToast('Schedule', removed ? `Removed ${id}` : `Schedule \"${id}\" not found`)\n}\n\n// run\nasync function handleRun(id: string | undefined, scheduler: SchedulerService, ctx: CliContext): Promise<void> {\n if (!id) {\n ctx.tui.info.showToast('Schedule', '/schedule run <id>')\n return\n }\n try {\n scheduler.runNow(id)\n ctx.tui.info.showToast('Schedule', `Triggering ${id}...`)\n } catch (err) {\n ctx.tui.info.showToast('Schedule', `Failed: ${err instanceof Error ? err.message : String(err)}`)\n }\n}\n\n// cancel\nasync function handleCancel(id: string | undefined, ctx: CliContext): Promise<void> {\n const scheduler = schedulerRef\n if (!scheduler) {\n ctx.tui.info.showToast('Schedule', 'Schedule system not available')\n return\n }\n if (!id) {\n ctx.tui.info.showToast('Schedule', '/schedule cancel <id>')\n return\n }\n const result = scheduler.cancel(id)\n if (result.ok) {\n ctx.tui.info.showToast('Schedule', `Canceled \"${id}\"`)\n } else {\n ctx.tui.info.showToast('Schedule', `Cannot cancel \"${id}\": ${result.reason ?? 'unknown'}`)\n }\n}\n\n// enable / disable\nasync function handleToggle(\n id: string | undefined,\n enabled: boolean,\n scheduler: SchedulerService,\n ctx: CliContext,\n): Promise<void> {\n if (!id) {\n ctx.tui.info.showToast('Schedule', `/schedule ${enabled ? 'enable' : 'disable'} <id>`)\n return\n }\n const ok = scheduler.update(id, { enabled })\n ctx.tui.info.showToast('Schedule', ok ? `Schedule ${id} ${enabled ? 'enabled' : 'disabled'}` : `Schedule \"${id}\" not found`)\n}\n\n// logs\nasync function handleLogs(taskId: string | undefined, ctx: CliContext): Promise<void> {\n const logDir = resolve(OTTO_HOME, 'schedule-logs')\n\n let logFiles: string[]\n try {\n logFiles = readdirSync(logDir)\n } catch {\n ctx.tui.info.showToast('Schedule', 'No schedule logs found')\n return\n }\n\n if (taskId) {\n logFiles = logFiles.filter(f => f.startsWith(taskId + '-'))\n }\n\n if (logFiles.length === 0) {\n ctx.tui.info.showToast('Schedule', taskId ? `No logs for schedule \"${taskId}\"` : 'No schedule logs found')\n return\n }\n\n logFiles.sort().reverse()\n\n const recent = logFiles.slice(0, 5)\n const lines: string[] = []\n for (const filename of recent) {\n try {\n const content = readFileSync(resolve(logDir, filename), 'utf-8').trim()\n const dashIdx = filename.indexOf('-')\n const tid = dashIdx > 0 ? filename.slice(0, dashIdx) : filename\n lines.push(`${tid} ${content}`)\n } catch {\n lines.push(`${filename} (unreadable)`)\n }\n }\n\n const remaining = logFiles.length - recent.length\n if (remaining > 0) {\n lines.push(`... and ${remaining} more entries`)\n }\n\n ctx.tui.messages.pushMessage({\n role: 'command_echo',\n command: '/schedule logs',\n summary: `${logFiles.length} 条日志文件`,\n detail: lines.join('\\n'),\n })\n}\n","/**\n * /draft 命令:草稿列表与管理(草稿列表/选中后可回填 PromptInput)。\n */\nimport type { CliHandler, CliContext } from '../../command-dispatcher'\nimport { type DraftItem, sortDrafts, makeDraftItem, capDrafts } from '../draft-entries'\n\n/** 按 savedAt 降序(最新在前)读取当前会话的全部草稿。 */\nfunction listDrafts(ctx: CliContext): DraftItem[] {\n return sortDrafts(ctx.state.session.session.getDrafts() ?? [])\n}\n\n/** 写回 + 立即落盘(fail-soft:session 层已吞错,这里只需转发到 TUI)。 */\nfunction persistAndPush(ctx: CliContext, entries: DraftItem[]): void {\n ctx.state.session.session.setDrafts(entries)\n void ctx.app.sessionManager.save(ctx.state.session.id).catch(() => {})\n ctx.tui.stores.tasks.setDrafts(listDrafts(ctx))\n}\n\n// ── Main handler ──\n\nexport const handleDraft: CliHandler = async (args: string[], ctx: CliContext): Promise<void> => {\n const sub = args[0]\n switch (sub) {\n case undefined:\n case 'list':\n return handleList(args.slice(1), ctx)\n case 'save':\n return handleSave(args.slice(1), ctx)\n case 'load':\n return handleLoad(args.slice(1), ctx)\n case 'delete':\n case 'remove':\n return handleDelete(args.slice(1), ctx)\n default:\n ctx.tui.info.showToast('Draft', `/draft ${sub} — unknown subcommand. Use: save|list|load|delete`)\n }\n}\n\n// ── Subcommand handlers ──\n\n// list\nasync function handleList(_args: string[], ctx: CliContext): Promise<void> {\n const drafts = listDrafts(ctx)\n ctx.tui.stores.tasks.setDrafts(drafts)\n ctx.tui.panes.togglePane('draftPane', true)\n if (drafts.length === 0) {\n ctx.tui.info.showToast('Draft', 'No drafts. Save one with Ctrl+S')\n }\n}\n\n// save <name?> <text...>\nasync function handleSave(args: string[], ctx: CliContext): Promise<void> {\n if (args.length === 0) {\n ctx.tui.info.showToast('Draft', '/draft save <name?> <text>')\n return\n }\n // 第一个 token 若不含空格且后面还有内容,视为显式命名;否则整段作正文、自动命名。\n const [first, ...rest] = args\n const hasExplicitName = rest.length > 0\n const name = hasExplicitName ? first : undefined\n const text = hasExplicitName ? rest.join(' ') : args.join(' ')\n\n const existing = listDrafts(ctx)\n const item = makeDraftItem(name, text, existing)\n const next = capDrafts([...existing, item])\n try {\n persistAndPush(ctx, next)\n } catch (err) {\n ctx.tui.info.showToast('Draft', `Save failed: ${err instanceof Error ? err.message : String(err)}`)\n return\n }\n ctx.tui.info.showToast('Draft', `Saved \"${item.name}\"`)\n}\n\n// load <name>\nasync function handleLoad(args: string[], ctx: CliContext): Promise<void> {\n const name = args.join(' ')\n if (!name) {\n ctx.tui.info.showToast('Draft', '/draft load <name>')\n return\n }\n const item = listDrafts(ctx).find((e) => e.name === name)\n if (!item) {\n ctx.tui.info.showToast('Draft', `Draft \"${name}\" not found`)\n return\n }\n ctx.tui.messages.pushMessage({ role: 'assistant', content: [{ type: 'text', text: item.text }] })\n}\n\n// delete <name>\nasync function handleDelete(args: string[], ctx: CliContext): Promise<void> {\n const name = args.join(' ')\n if (!name) {\n ctx.tui.info.showToast('Draft', '/draft delete <name>')\n return\n }\n const existing = listDrafts(ctx)\n const item = existing.find((e) => e.name === name)\n if (!item) {\n ctx.tui.info.showToast('Draft', `Draft \"${name}\" not found`)\n return\n }\n const next = existing.filter((e) => e.id !== item.id)\n try {\n persistAndPush(ctx, next)\n } catch (err) {\n ctx.tui.info.showToast('Draft', `Delete failed: ${err instanceof Error ? err.message : String(err)}`)\n return\n }\n ctx.tui.info.showToast('Draft', `Removed \"${item.name}\"`)\n}\n","/**\n * /queue 命令:消息发送队列管理(预览/编辑/移除/提交)\n */\nimport type { CliHandler, CliContext } from '../../command-dispatcher'\n\nexport const handleQueue: CliHandler = async (args: string[], ctx: CliContext): Promise<void> => {\n const sub = args[0]\n switch (sub) {\n case undefined:\n case 'list':\n return handleList(ctx)\n case 'remove':\n return handleRemove(args[1], ctx)\n case 'move':\n return handleMove(args[1], args[2], ctx)\n case 'clear':\n return handleClear(ctx)\n default:\n ctx.tui.info.showToast('Queue', `/queue ${sub} — unknown subcommand. Use: list|remove|move|clear`)\n }\n}\n\n// list\nasync function handleList(ctx: CliContext): Promise<void> {\n ctx.tui.panes.togglePane('queuePane', true)\n}\n\n// remove <1-based index>\nasync function handleRemove(indexArg: string | undefined, ctx: CliContext): Promise<void> {\n const n = indexArg ? Number.parseInt(indexArg, 10) : NaN\n if (!Number.isInteger(n) || n < 1) {\n ctx.tui.info.showToast('Queue', '/queue remove <index> (1-based)')\n return\n }\n ctx.tui.panes.removeQueuedAt(n - 1)\n ctx.tui.info.showToast('Queue', `Removed item ${n}`)\n}\n\n// move <1-based index> <up|down>\nasync function handleMove(indexArg: string | undefined, dirArg: string | undefined, ctx: CliContext): Promise<void> {\n const n = indexArg ? Number.parseInt(indexArg, 10) : NaN\n if (!Number.isInteger(n) || n < 1 || (dirArg !== 'up' && dirArg !== 'down')) {\n ctx.tui.info.showToast('Queue', '/queue move <index> <up|down> (1-based)')\n return\n }\n const delta = dirArg === 'down' ? 1 : -1\n ctx.tui.panes.moveQueuedItem(n - 1, delta)\n ctx.tui.info.showToast('Queue', `Moved item ${n} ${dirArg}`)\n}\n\n// clear\nasync function handleClear(ctx: CliContext): Promise<void> {\n ctx.tui.panes.clearQueue()\n ctx.tui.info.showToast('Queue', 'Queue cleared')\n}\n","/**\n * /pause 与 /continue 命令:agent 流式控制。\n */\nimport type { CliHandler, CliContext } from '../../command-dispatcher'\nimport { i18next } from '@x-otto/tui'\n\nexport const handlePauseContinue: CliHandler = async (\n _args: string[],\n ctx: CliContext,\n): Promise<void> => {\n const { session, agentProcessing } = ctx.state\n\n // 分支 1:已暂停 → 继续运行\n if (session.isPaused && !session.isBusy) {\n session.resumeRun()\n ctx.tui.info.showToast(i18next.t('cmd.titleInfo'), i18next.t('cmd.runResumedByCommand'))\n refreshPauseCommandPanel(ctx)\n return\n }\n\n // 分支 2:暂停等待中 → 取消等待\n if (session.isPaused && session.isBusy) {\n const ok = session.cancelPauseRun()\n if (ok) {\n ctx.tui.info.showToast(i18next.t('cmd.titleInfo'), i18next.t('cmd.pauseCancelled'))\n } else {\n // 竞态:turn 恰好在判断 isPaused 后结束,cancelPauseRun 返回 false\n // 回退到 resumeRun\n session.resumeRun()\n ctx.tui.info.showToast(i18next.t('cmd.titleInfo'), i18next.t('cmd.runResumedByCommand'))\n }\n refreshPauseCommandPanel(ctx)\n return\n }\n\n // 分支 3:运行中 → 请求暂停\n if (agentProcessing) {\n const ok = session.pauseRun()\n if (ok) {\n ctx.tui.info.showToast(i18next.t('cmd.titlePauseRequested'), i18next.t('cmd.runWillPauseAfterTurn'))\n } else {\n ctx.tui.info.showToast(i18next.t('cmd.titleInfo'), i18next.t('cmd.noRunningTaskToPause'))\n }\n refreshPauseCommandPanel(ctx)\n return\n }\n\n // 分支 4:空闲\n ctx.tui.info.showToast(i18next.t('cmd.titleInfo'), i18next.t('cmd.nothingToPause'))\n}\n\n/** 刷新命令面板,使其根据最新 isPaused 状态切换 /pause ↔ /continue 展示名。 */\nfunction refreshPauseCommandPanel(ctx: CliContext): void {\n const updated = ctx.callbacks.refreshSlashCommands?.()\n if (updated) ctx.tui.plugins.setCommands(updated)\n}\n","/**\n * handleKeybindings — /keybindings 命令(RFC-174 M5 D5)。\n *\n * 打开快捷键面板(独立 hard modal,Band A),用户可在面板内浏览全部命令、\n * 搜索、录制新键位、重置单条/全部为默认值、选择写入目标(全局/项目配置)。\n * 面板内部维护自己的临时状态(PanelState),onSave/onReset 等回调负责异步\n * 持久化到 settings,无需在每次 save 后重新 openModal 刷新。\n */\nimport { i18next } from '@x-otto/tui'\n\nimport type { CliContext } from '../../command-dispatcher'\n\n/**\n * 键位谓词覆盖类型(与 @x-otto/keybindings 的 KeyBindingPredicate 同构,\n * 避免 CLI 包依赖 keybindings 包——CLI 只关心持久化形态,不消费匹配逻辑)。\n *\n * `key` 枚举值对齐 packages/setting/src/schema.ts 的 `keybinding_overrides` schema\n * (`z.enum(['tab', 'escape', 'return'])`),面板录制态通过 predicateToLabel 生成标签,\n * 不在此枚举内的 key 值终端通常不会产生。\n */\ninterface KeyBindingPredicate {\n ctrl?: boolean\n shift?: boolean\n meta?: boolean\n input?: string\n key?: 'tab' | 'escape' | 'return'\n upArrow?: boolean\n downArrow?: boolean\n}\n\nexport async function handleKeybindings(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n // 读取当前 keybinding_overrides(Record<commandId, KeyBindingPredicate>)\n const overrides = ctx.app.settings.get('keybinding_overrides') as\n | Record<string, KeyBindingPredicate>\n | undefined\n\n /**\n * 覆盖表是 TUI 的**启动期配置**(经 TuiOptions.keybindingOverrides 传入,见该字段注释),\n * 改绑后当前会话内不生效。没有这条提示,用户\"存了却没动静\"的观感与修复前\"改了永远不\n * 生效\"完全相同——反而会以为功能仍然是坏的。\n */\n const notifyRestartNeeded = (): void => {\n ctx.tui.info.showToast(\n i18next.t('cmd.keybindTitle'),\n i18next.t('cmd.keybindSavedRestartHint'),\n )\n }\n\n const onSave = (\n commandId: string,\n predicate: KeyBindingPredicate,\n target: 'global' | 'project',\n ): void => {\n const current = (ctx.app.settings.get('keybinding_overrides') as\n | Record<string, KeyBindingPredicate>\n | undefined) ?? {}\n void ctx.app.settings.persist(\n { keybinding_overrides: { ...current, [commandId]: predicate } } as never,\n target,\n )\n notifyRestartNeeded()\n }\n\n const onSaveReplace = (\n actions: Array<{ commandId: string; predicate: KeyBindingPredicate }>,\n target: 'global' | 'project',\n ): void => {\n const current = (ctx.app.settings.get('keybinding_overrides') as\n | Record<string, KeyBindingPredicate>\n | undefined) ?? {}\n const next = { ...current }\n for (const a of actions) {\n next[a.commandId] = a.predicate\n }\n void ctx.app.settings.persist({ keybinding_overrides: next } as never, target)\n notifyRestartNeeded()\n }\n\n const onReset = (commandId: string): void => {\n const current = ctx.app.settings.get('keybinding_overrides') as\n | Record<string, KeyBindingPredicate>\n | undefined\n if (!current || !(commandId in current)) return\n const { [commandId]: _removed, ...rest } = current\n void ctx.app.settings.persist({ keybinding_overrides: rest } as never)\n notifyRestartNeeded()\n }\n\n const onResetAll = (): void => {\n void ctx.app.settings.persist({ keybinding_overrides: {} } as never)\n notifyRestartNeeded()\n }\n\n // openModal 重载签名要求 types 与 @x-otto/keybindings KeyBindingPredicate 精确匹配;\n // handler 内用本地同构 interface 避免 CLI→keybindings 依赖,经 `as never` 桥接。\n ctx.tui.modal.openModal({\n kind: 'keybindingPanel',\n overrides,\n onSave,\n onSaveReplace,\n onReset,\n onResetAll,\n } as never)\n}\n","/**\n * interactive-input-setup.ts — @ sigil/上下文 chip 注册。\n *\n * 启动期一次性建立的 TUI 辅助接线。\n *\n * RFC-335 M6:原先并存的 devtools 断点控制(`createDebuggerControls`)已随 TUI 内嵌\n * 调试轴一并下线——断点/暂停改由 Web 服务承接(service `DebugBridge` + `otto debug`)。\n */\n\n/**\n * 把 agent/skill/mcp 扩展注册为 @ sigil 条目(来源 'extensions',\n * 可整体清理重注册)。value 用约定式 chip 格式 [@kind:name],提交时原样保留给 LLM。\n * 返回 refreshSigilExtensions 供后续(如 /reload)重刷。\n */\nexport function registerSigilEntries(app: import('@x-otto/coding').App): { refreshSigilExtensions: () => void } {\n const escChip = (s: string) => s.replace(/]/g, '\\\\]')\n const refreshSigilExtensions = () => {\n app.pluginInput.unregisterBySource('extensions')\n const items = app.extensions.list()\n const entries = items\n .filter((e) => e.category === 'agent' || e.category === 'skill' || e.category === 'mcp')\n .map((e) => ({\n label: `@${e.name}`,\n value: `[@${e.category}:${escChip(e.name)}]`,\n description: e.description || e.category,\n kind: e.category as 'agent' | 'skill' | 'mcp',\n source: 'extensions',\n }))\n if (entries.length) app.pluginInput.register(entries)\n }\n refreshSigilExtensions()\n\n return { refreshSigilExtensions }\n}\n\n/** 快捷方式 description 首行截断预览(对齐既有 clipboardPreview 首行截断约定)。 */\nfunction shortcutPreview(text: string): string {\n const firstLine = text.trim().split('\\n')[0]!.replace(/\\s+/g, ' ').trim()\n return firstLine.length > 40 ? `${firstLine.slice(0, 39)}…` : firstLine\n}\n\n/**\n * RFC-233 D6:注册用户自定义 `[@shortcut:name]` 文本快捷方式为 @ sigil 条目(来源\n * 'shortcut',可整体清理重注册)。管理面板(`/shortcuts`)增删改后调用本函数重刷,\n * 使 @ 面板搜索立即反映最新内容,无需重启进程。参数收窄为 `pluginInput`/`settings`\n * 两个字段(非完整 `App`)——handlers/shortcuts.ts 只能拿到 `AppBridge`(收窄接口),\n * 用完整 `App` 类型会导致该 handler 无法调用本函数(类型不兼容)。\n */\nexport function refreshShortcutSigilEntries(\n app: Pick<import('@x-otto/coding').App, 'pluginInput' | 'settings'>,\n): void {\n app.pluginInput.unregisterBySource('shortcut')\n const list = app.settings.get('shortcuts') ?? []\n const entries = list.map((s) => ({\n label: `@${s.name}`,\n value: `[@shortcut:${s.name.replace(/]/g, '\\\\]')}]`,\n description: shortcutPreview(s.text),\n kind: 'shortcut' as const,\n source: 'shortcut',\n }))\n if (entries.length) app.pluginInput.register(entries)\n}\n\n/**\n * 把可用模型注册为 @model sigil 条目(kind='model',source='builtin')。\n * chip value 使用 [@model:id] 格式,提交时由 onSubmit 剥离并转为模型指令。\n * 模型按名称排序;仅注册 availableModelIds 中的模型(已通过可用性门禁)。\n */\nexport function registerModelSigil(\n app: import('@x-otto/coding').App,\n availableModelIds: ReadonlySet<string>,\n): void {\n const models = app.modelRegistry.getAll()\n if (models.length === 0) return\n const fmtWindow = (cw: number) =>\n cw >= 1_000_000\n ? `${(cw / 1_000_000).toFixed(1)}M`\n : cw >= 1000\n ? `${Math.round(cw / 1000)}k`\n : ''\n const entries = models\n .filter((m) => availableModelIds.has(m.id))\n .sort((a, b) => a.name.localeCompare(b.name))\n .map((m) => {\n const window = fmtWindow(m.contextWindow)\n const tags: string[] = []\n if (m.strengths && m.strengths.length > 0) tags.push(...m.strengths)\n if (window) tags.push(window)\n const desc = tags.length > 0 ? `${m.provider} · ${tags.join(' · ')}` : m.provider\n return {\n label: m.name,\n value: `[@model:${m.id.replace(/]/g, '\\\\]')}]`,\n description: desc,\n kind: 'model' as const,\n source: 'builtin' as const,\n }\n })\n app.pluginInput.unregisterBySource('builtin-model')\n app.pluginInput.register(entries)\n}\n\n","/**\n * handleShortcuts — /shortcuts 命令(RFC-233)。\n *\n * 打开快捷方式管理面板(独立 hard modal,Band A),用户可在面板内浏览全部\n * 自定义 `[@shortcut:name]` 文本快捷方式、新建/编辑/删除。持久化与校验的权威\n * 实现在 App.getShortcuts/saveShortcut/deleteShortcut(同一套逻辑供 TUI 与\n * web-ui 复用,见 packages/coding/src/app/app.ts),本 handler 只是接线:\n * 面板回调 → App 方法 → 成功后刷新 @ sigil 条目,使 @ 面板搜索立即反映最新内容。\n */\nimport type { CliContext } from '../../command-dispatcher'\nimport { refreshShortcutSigilEntries } from '../interactive-input-setup'\n\nexport async function handleShortcuts(\n _args: string[],\n ctx: CliContext,\n): Promise<void> {\n const onSave = (name: string, text: string, previousName?: string): void => {\n ctx.app\n .saveShortcut(name, text, previousName)\n .then(() => refreshShortcutSigilEntries(ctx.app))\n .catch((err: unknown) => {\n ctx.tui.info.showToast(\n 'Shortcuts',\n `Save failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n })\n }\n\n const onDelete = (name: string): void => {\n ctx.app\n .deleteShortcut(name)\n .then(() => refreshShortcutSigilEntries(ctx.app))\n .catch((err: unknown) => {\n ctx.tui.info.showToast(\n 'Shortcuts',\n `Delete failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n })\n }\n\n ctx.tui.modal.openModal({\n kind: 'shortcutsPanel',\n shortcuts: ctx.app.getShortcuts(),\n onSave,\n onDelete,\n } as never)\n}\n","/**\n * /open 命令:打开本地文件/目录(FileContentViewer)。\n * Supports mode flags: --tree, --diff, --recent, --workspace <id>.\n */\nimport { i18next } from '@x-otto/tui'\nimport { existsSync, openSync, readSync, closeSync, readdirSync, statSync } from 'node:fs'\nimport { resolve, join, relative, dirname } from 'node:path'\nimport { readFileCapped } from '@x-otto/shared'\nimport type { CliContext } from '../../command-dispatcher'\n\n/**\n * 二进制文件探测(RFC-189 R5):读取前 512 字节,含 null 字节或不可打印字符占比过高\n * 即判定为二进制内容,拒绝渲染——避免 `readFile(..., 'utf-8')` 对二进制文件产出的\n * 乱码字符串(可能含终端控制字符)破坏 TUI 渲染状态,比\"文件太大\"的降级体验更差。\n */\nexport function isBinaryContent(path: string): boolean {\n let buf: Buffer\n try {\n const fd = openSync(path, 'r')\n try {\n buf = Buffer.alloc(512)\n const bytesRead = readSync(fd, buf, 0, 512, 0)\n buf = buf.subarray(0, bytesRead)\n } finally {\n closeSync(fd)\n }\n } catch {\n return false // 读取失败交给后续 readFileSync 处理并报告真实错误\n }\n if (buf.length === 0) return false\n let suspicious = 0\n for (const byte of buf) {\n if (byte === 0) return true // null 字节 → 恒判定二进制\n // 可打印 ASCII(含常见空白)之外、且非 UTF-8 多字节序列起始字节的字节计为可疑。\n const isPrintableAscii = byte >= 0x20 && byte < 0x7f\n const isCommonWhitespace = byte === 0x09 || byte === 0x0a || byte === 0x0d\n const isUtf8MultibyteLead = byte >= 0xc0 && byte <= 0xfd\n const isUtf8Continuation = byte >= 0x80 && byte <= 0xbf\n if (!isPrintableAscii && !isCommonWhitespace && !isUtf8MultibyteLead && !isUtf8Continuation) {\n suspicious++\n }\n }\n return suspicious / buf.length > 0.3\n}\n\n/** `1234` → `1.2 KB` 这类简短人类可读大小(目录浏览器 detail 列用,无需 formatBytes 全套单位)。 */\nfunction formatSize(bytes: number): string {\n if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)} MB`\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${bytes} B`\n}\n\ninterface DirEntryInfo {\n name: string\n isDir: boolean\n size?: number\n}\n\n/** 列出目录直接子项(不递归),静默跳过读取失败的条目(权限/损坏符号链接等)。 */\nfunction listDirEntries(dir: string): DirEntryInfo[] {\n let names: string[]\n try {\n names = readdirSync(dir)\n } catch {\n return []\n }\n const entries: DirEntryInfo[] = []\n for (const name of names) {\n try {\n const st = statSync(join(dir, name))\n entries.push({ name, isDir: st.isDirectory(), size: st.isDirectory() ? undefined : st.size })\n } catch {\n // 权限拒绝/损坏符号链接等——静默跳过,不让单个坏条目挡掉整个目录浏览\n }\n }\n return entries\n}\n\n/** 目录排前(按名称)、文件排后(按名称)——类 `ls -F` 惯例。 */\nfunction sortDirEntries(entries: DirEntryInfo[]): DirEntryInfo[] {\n return [...entries].sort((a, b) => {\n if (a.isDir !== b.isDir) return a.isDir ? -1 : 1\n return a.name.localeCompare(b.name)\n })\n}\n\n/**\n * 打开目录浏览器(可搜索 select 面板)。`root` 用于展示相对路径 subtitle;`dir` 是当前\n * 浏览层的绝对路径。递归:选中目录 → 以子目录重入本函数;选中文件 → 走 openFileViewer。\n *\n * `filterMemory`:按目录绝对路径记忆过滤词。目录浏览器的 select modal 是\"一次性\"的\n * ——选中文件会弹栈打开查看器、Esc 回来时 modal 已被销毁并重新 navigate 重建,组件本地\n * 的过滤 useState 随之丢失(真实用户反馈:\"输入 filter 后打开文件再返回,filter 消失\")。\n * 用一个跨重建存活的 Map 记住每个目录的过滤词,重建时经 initialFilter 回灌,即可保持状态。\n */\nfunction openDirectoryBrowser(\n ctx: CliContext,\n root: string,\n dir: string,\n filterMemory: Map<string, string>,\n): void {\n const entries = sortDirEntries(listDirEntries(dir))\n const relDir = relative(root, dir)\n const subtitle = relDir === '' ? i18next.t('cmd.openBrowserSubtitleRoot') : i18next.t('cmd.openBrowserSubtitleDir', { relDir: relDir })\n const canGoUp = dir !== root\n\n const options = entries.map((e) => ({\n label: e.isDir ? `${e.name}/` : e.name,\n value: e.name,\n detail: e.isDir ? undefined : formatSize(e.size ?? 0),\n }))\n\n // Esc:当前在子目录 → 返回上级目录浏览器;顶层(root)→ 关闭浏览器回到 prompt。\n // 不再显示 [返回上一级] subtitle action——按键(Enter 选中 Esc 返回)强于列表内混合交互入口。\n\n ctx.tui.modal.openModal({\n kind: 'select',\n title: i18next.t('cmd.titleOpen'),\n subtitle,\n searchable: true,\n options,\n // 本目录上次输入的过滤词(modal 重建后恢复),无记忆则空串。\n initialFilter: filterMemory.get(dir) ?? '',\n // 用户编辑过滤框时持久化到本目录键——下次重建(文件查看器 Esc 返回)时回灌。\n onFilterChange: (text: string) => { filterMemory.set(dir, text) },\n onSelect: (value: string) => {\n const abs = join(dir, value)\n let isDir: boolean\n try {\n isDir = statSync(abs).isDirectory()\n } catch {\n ctx.tui.info.showToast(i18next.t('cmd.titleOpen'), i18next.t('cmd.openNotFound', { path: value }))\n return\n }\n if (isDir) {\n openDirectoryBrowser(ctx, root, abs, filterMemory)\n return\n }\n // SelectDialog onSelect 签名是 fire-and-forget `(value: string) => void`,openFileViewer\n // 是 async——必须 .catch()(独立复核发现):TOCTOU 窗口内文件可能在上面 statSync 探测\n // 之后、readFileCapped 真正读取之前被删除/移动,readFileCapped 内部 fs.promises.stat\n // 会抛 ENOENT,未捕获的 rejection 在 Node 15+ 会终止整个进程——必须兜底成用户可见的\n // toast,而不是让其变成未处理拒绝。\n void openFileViewer(ctx, abs, value, () => {\n openDirectoryBrowser(ctx, root, dir, filterMemory)\n }).catch(() => {\n ctx.tui.info.showToast(i18next.t('cmd.titleOpen'), i18next.t('cmd.openNotFound', { path: value }))\n })\n },\n // Esc:当前在子目录 → 返回上级目录浏览器;顶层(root)→ 关闭浏览器回到 prompt。\n onCancel: canGoUp ? () => openDirectoryBrowser(ctx, root, dirname(dir), filterMemory) : () => {},\n })\n}\n\n/** 二进制探测 + 熔断读取 + FileViewerDialog(精确路径直开与目录浏览器选中文件共用)。 */\nasync function openFileViewer(ctx: CliContext, abs: string, displayPath: string, onDismiss?: () => void): Promise<void> {\n if (isBinaryContent(abs)) {\n ctx.tui.info.showToast(i18next.t('cmd.titleOpen'), i18next.t('cmd.openBinaryRejected', { path: displayPath }))\n return\n }\n const { content, truncated } = await readFileCapped(abs)\n ctx.tui.modal.openModal({ kind: 'fileViewer', path: displayPath, content, preTruncated: truncated, onDismiss: onDismiss ?? (() => {}) })\n}\n\nexport async function handleOpen(args: string[], ctx: CliContext): Promise<void> {\n const root = ctx.app.workspaceDir ?? process.cwd()\n const pathArg = args.join(' ').trim()\n\n // 目录→过滤词记忆:贯穿本次 `/open` 的整个浏览会话(跨 modal 重建存活),供目录浏览器\n // 在\"选中文件→查看器→Esc 返回\"重建时恢复过滤状态。每次 `/open` 命令新建一份(不跨命令\n // 泄漏,语义上一次浏览会话一份记忆)。\n const filterMemory = new Map<string, string>()\n\n // 无参数 → 目录浏览器从工作区根起始。\n if (!pathArg) {\n openDirectoryBrowser(ctx, root, root, filterMemory)\n return\n }\n\n const abs = resolve(root, pathArg)\n // 路径穿越防护(独立复核发现):`resolve(root, '../../../etc/passwd')` 之类构造可产生\n // root 之外的 abs——精确路径分支下游 existsSync/isBinaryContent/readFileCapped 只作用于\n // 该单一路径,越权读取风险等同用户直接给了一个绝对路径(RFC-189 遗留、已知可接受范围)。\n // 但下面的\"祖先回退\"逻辑如果对 root 外的 abs 也生效,会把\"读一个路径\"放大成\"自动枚举\n // 暴露任意可达祖先目录\"(如 /open ../../../etc/x 回退列出 /etc 内容),风险量级不同,\n // 必须先判定 abs 是否仍在 root 内,root 外一律不做祖先回退,直接报错。\n const escapesRoot = relative(root, abs).startsWith('..')\n if (!existsSync(abs)) {\n if (escapesRoot) {\n ctx.tui.info.showToast(i18next.t('cmd.titleOpen'), i18next.t('cmd.openNotFound', { path: pathArg }))\n return\n }\n // 不存在的路径——此前直接报错;改为尝试把最深一层存在的祖先目录当浏览起点\n // (对齐\"打字打到一半/记错路径\"场景,不再一律拒绝)。找不到任何存在祖先则报错。\n let probe = dirname(abs)\n while (probe !== root && probe !== dirname(probe) && !existsSync(probe)) {\n probe = dirname(probe)\n }\n if (existsSync(probe) && statSync(probe).isDirectory()) {\n openDirectoryBrowser(ctx, root, probe, filterMemory)\n return\n }\n ctx.tui.info.showToast(i18next.t('cmd.titleOpen'), i18next.t('cmd.openNotFound', { path: pathArg }))\n return\n }\n\n if (statSync(abs).isDirectory()) {\n // 目录浏览器(可枚举导航,含\"返回上一级\")与\"精确文件路径直开\"(单文件读取,不可\n // 枚举)风险量级不同——后者允许 root 外绝对路径是 RFC-189 遗留且可接受的行为(用户\n // 显式给了一条路径,无法借此枚举到其他内容);前者一旦打开即可通过 Enter/Go-up 在\n // 该目录树内自由导航,root 外的目录(如 `/open /etc`)会让浏览器脱离 root 边界、\n // \"Go up\" 可一路走到文件系统根——必须拒绝,不越权打开可枚举的浏览视图。\n if (escapesRoot) {\n ctx.tui.info.showToast(i18next.t('cmd.titleOpen'), i18next.t('cmd.openNotFound', { path: pathArg }))\n return\n }\n openDirectoryBrowser(ctx, root, abs, filterMemory)\n return\n }\n\n await openFileViewer(ctx, abs, pathArg)\n}\n","/**\n * command-dispatcher.ts — CLI 指令分发中枢\n *\n * 类型桥接(TuiBridge / AppBridge / CliContext)+ 指令注册表 + 分发入口。\n * 各 handler 函数已按领域拆分至 commands/handlers/*.ts。\n */\nimport type { AgentSession, App } from '@x-otto/coding'\nimport type { InteractiveMode } from './modes/interactive-mode'\nimport type { CommandEntry, TuiHandle } from '@x-otto/tui'\nimport type { TaskItem } from '@x-otto/session-contract'\n\n// ── handler imports ──\nimport { handleClear, handleResume, handleReload, handleDiagnose, handleArchive, handlePin, handleRename, handleA2uiAction, handleSessionUnlock, handleTraceGc, handleSessionArchiveExport } from './commands/handlers/session'\nimport { handleAttach } from './commands/handlers/attach'\nimport { handleFork } from './commands/handlers/fork'\nimport { handleModel, handleProviderAuth, handleLogout } from './commands/handlers/model-auth'\nimport { handleProxy } from './commands/handlers/proxy'\nimport { handleStatus, handleContext, handlePerf, handleCacheStats, handleGallery } from './commands/handlers/inspect-context'\nimport { handleUsage } from './commands/handlers/usage'\nimport { handleMemory, handleCompact } from './commands/handlers/memory-compact'\nimport { handleWorkspace } from './commands/handlers/workspace'\n// 重导出供测试/外部从本 barrel 消费(handle-memory.test 等;重构拆分到 handlers/ 后遗漏了再导出)。\nexport { handleMemory, handleCompact }\nimport { handleDelegatesToggle, handleJobStart, handleExtension } from './commands/handlers/extension-plugin'\nimport { handleInstall } from './commands/handlers/install'\nimport { handleRegistry, handleSearch } from './commands/handlers/registry'\nimport { handleUpgradeCheck } from './commands/handlers/upgrade-check'\nimport { handleAbout } from './commands/handlers/about'\nimport { handleFeedback } from './commands/handlers/feedback'\nimport { handleSetup } from './commands/handlers/setup'\nimport { handleEffort, handlePreference, handleCompat, handleUpgrade, forceUpgrade, handleHelp, handlePermissions, handleHistory, handleNotification, handleServices, handlePrompts } from './commands/handlers/misc'\nimport { handleSchedule } from './commands/handlers/schedule'\nimport { handleDraft } from './commands/handlers/draft'\nimport { handleQueue } from './commands/handlers/queue'\nimport { handlePauseContinue } from './commands/handlers/pause-continue'\nimport { handleKeybindings } from './commands/handlers/keybindings'\nimport { handleShortcuts } from './commands/handlers/shortcuts'\nimport { handleOpen } from './commands/handlers/open-file'\n\n// ── Types ──\n\n/**\n * tui 桥接(RFC-074 D4)——`Pick<TuiHandle,…>` 派生而非手抄影子接口。\n * 此前是手写的 TuiHandle 子集(~24 方法签名各自重声明,与真 TuiHandle 静默漂移:\n * 如 setLanguage 形参曾宽成 string、openSelect/openInput opts 形状曾内联重写)。\n * 改 Pick 后:签名取真 TuiHandle,漂移即编译错;新方法增删自动传导。\n */\ntype TuiBridge = Pick<\n TuiHandle,\n | 'modal'\n | 'messages'\n | 'engine'\n | 'pending'\n | 'lifecycle'\n | 'panes'\n | 'plugins'\n | 'info'\n | 'stores'\n> & {\n /** RFC-277 M6 / RFC-283:运行时切换/预览主题 preset 选择(深浅自动跟随终端背景色)。 */\n setPresetSelection?: (selection: { dark?: string; light?: string } | undefined) => void\n}\n\n/**\n * i18n 字符串桥接(RFC-074 D4)——`Pick<Strings,…>` 派生而非手抄。\n * 此前是手写影子 interface + `[key:string]:unknown` 类型侵蚀(任意键访问都过编译,i18n key\n * 拼错不报)。改为真 `Strings` 的 `Pick` 后:键拼错=编译错、Strings 改签名自动传导到此处。\n */\n/**\n * app 桥接(RFC-074 M2-03)——`Pick<App,…>` 派生而非手抄影子接口。\n * cli 早已直接传真 App 实例(interactive.ts,旧手抄字面量 facade 已撤),故桥的真源是 App 类。\n * 此前 AppBridge 是手写的 App 子集(~23 成员各自重声明窄签名,与真 App 静默漂移:漏同步即运行时\n * undefined 而 typecheck 抓不到)。改 `Pick<App,…>` 后签名取真 App,漂移即编译错、成员增删自动传导。\n * 注:Pick 给的是 App 的**完整**成员类型(比旧手抄窄签名宽),消费方按子集访问照常;\n * 真不兼容处(旧窄签名与 App 实型相左)会被编译器揪出。\n */\ntype AppBridge = Pick<\n App,\n | 'workspaceDir'\n | 'reloadCapabilities'\n | 'listSessions'\n | 'listAllSessions'\n | 'getSession'\n | 'archiveSession'\n | 'removeSession'\n | 'exportSessionArchive'\n | 'sessionManager'\n | 'sessionStorageDir'\n | 'diagnoser'\n | 'agentJobs'\n | 'devtools'\n | 'storage'\n | 'userMemory'\n | 'settings'\n | 'authStore'\n | 'modelRegistry'\n | 'extensions'\n | 'mcpManager'\n | 'getSessionAlwaysAllow'\n | 'clearSessionAlwaysAllow'\n | 'allowToolAlways'\n | 'disallowToolAlways'\n // 终局架构 review 2026-07-10 P1-5:门面转发 @x-otto/hooks 权限纯函数,cli 不再直连引擎权限模块。\n | 'bashAlwaysAllowPrefix'\n | 'isWellFormedAllowEntry'\n | 'refreshModels'\n | 'getPluginPanelRegistrations'\n | 'getPluginStatusItems'\n | 'getGlobalMenuItems'\n | 'getPluginRenderers'\n | 'getPluginFileViewers'\n | 'getPluginRegistrations'\n | 'firePluginActivationEvent'\n | 'memoryGovernor'\n | 'activePlugins'\n | 'logger'\n // RFC-125 D5:/model 据此呈现\"N 个 provider 待授权\"逃生口(因 pendingCapabilities 被跳过的插件 id 列表)。\n | 'pendingProviderCapabilities'\n // RFC-124 P0-B:/extension 详情页按插件 source 查询其贡献的 provider/tools(R11 权限审计基线)。\n | 'providerRegistry'\n | 'toolRegistry'\n // 插件 i18n 字符串注册表(RFC-138/141)——/extension 详情页动作标题按 i18n key 查询,\n // 跟随当前语言(RFC-138-A 已接线 setLocale,见 app.ts settings.change 回调)。\n | 'i18nRegistry'\n // RFC-153 D3/D4:插件健康度追踪(挂起/错误计数)——/perf 采集回调写入、/extension 详情页读取。\n | 'pluginHealthTracker'\n // RFC-303 D4:monitor 事件派发通道(安装完成等宿主事件广播给 monitor 插件)。\n | 'pluginModuleManager'\n // RFC-164 M164-2:/workspace memory-sync 读取当前 opt-in 状态(workspaceRef.memorySyncEnabled)\n // + 调用 setWorkspaceMemorySyncEnabled 切换。\n | 'workspaceRef'\n | 'setWorkspaceMemorySyncEnabled'\n // /extension 详情页 skill 类别对齐 plugin 类别的富展示(DataList:来源/触发方式/依赖/标签/错误)。\n | 'skillRegistry'\n // /extension 详情页 agent 类别对齐 skill 类别的富展示(DataList:任务类型/能力/工具白名单\n // 黑名单/执行配置 + 查看 systemPromptTemplate 正文)。\n | 'declarativeAgentProfiles'\n // RFC-207 §2.4:`/feedback` 指令通道选择——优先 submitIssueViaGh,失败/不可用降级\n // buildIssueUrl + openBrowser(通道选择编排留 CLI 核心层,见 RFC-209 §2.4 重要事项规则7)。\n | 'feedbackRegistry'\n // RFC-233:`/shortcuts` 面板增删改用户自定义 @ 快捷方式(权威实现见 App 方法注释)。\n | 'getShortcuts'\n | 'saveShortcut'\n | 'deleteShortcut'\n | 'pluginInput'\n>\n\nexport interface CliContext {\n tui: TuiBridge\n app: AppBridge\n state: {\n session: AgentSession\n interactive: InteractiveMode\n reg: import('@x-otto/tui').CommandRegistry\n slashCommands: CommandEntry[]\n modelReady: boolean\n currentModelId: string | undefined\n currentLang: string\n todoList: TaskItem[]\n agentProcessing: boolean\n models: Array<{\n id: string\n name: string\n provider: string\n api?: string\n strengths: string[]\n contextWindow?: number\n }>\n availableModelIds: Set<string>\n /** /exit --restart 或 /upgrade --force 置位;runInteractiveCommand 据此返回 restart 信息。 */\n restartRequested?: boolean\n /**\n * RFC-277 M6:启动时装配的主题预设 registry(内置 + 全部活跃插件贡献,见\n * `theme-preset-wiring.ts buildThemeRegistry`)——与运行中 `ThemeProvider` 使用的是\n * 同一实例(构造于 startTui 之前并原样传入),`/preference theme` 据此列出可选 preset。\n * 独立审核 finding:本仓 ~25 个既有测试文件用 `as unknown as CliContext` 构造 mock\n * context(不含此字段)——设为 optional 避免这类 mock 因缺少必填字段而在未来新增的\n * 类型检查中报错;消费方(misc.ts)已加运行时兜底,undefined 时降级到纯 builtin 主题。\n */\n themeRegistry?: import('@x-otto/tui').ThemeRegistry\n }\n callbacks: {\n applyModel: (modelId: string) => Promise<string>\n persistAvailable: () => void\n refreshModelsNow: (say?: (msg: string) => void) => Promise<void>\n registerAllTuiCommands: () => void\n refreshSlashCommands: () => CommandEntry[]\n attachSessionHandler: () => void\n /**\n * B6:/resume 切到已存在会话后,重置投影/归约状态(projState/redState 在工厂里按引用持有,\n * 必须原地重置而非重建)并恢复目标会话的面板态(tasks/turnBoundaries/editedFiles/subagents)。\n * 不调则上一会话的 todoList/currentTurn/turnBoundaries 残留,新会话 write_todos 并进旧列表、回合号错位。\n */\n restoreSwitchedSession: () => void\n /**\n * RFC-338 D7/D9:粘贴缓存的会话归属绑定器。`/resume` 切会话时 `switchTo`\n * (旧会话落盘 → 新会话 hydrate)、`/fork` 时 `fork`(复制含 nextId 继承)。\n * TUI 未挂载(非交互场景)时为 undefined,调用方按可选处理。\n */\n pasteBinder?: {\n load: (sessionId: string) => Promise<void>\n save: (sessionId: string) => Promise<void>\n switchTo: (from: string | undefined, to: string) => Promise<void>\n fork: (from: string, to: string) => Promise<void>\n }\n /**\n * M81 已知限制的收口:/clear 需把 projState.currentTurn 归零(projState 不在\n * handleClear 的 ctx.state 作用域内,故经 callback 桥接)。不调则 clear 后回合号\n * 继续递增而非归 1(docs/milestones/M81-task-turn-resume-alignment.md 已知限制项)。\n */\n resetCurrentTurn: () => void\n /** RFC-156:/compact 手动压缩成功后调用,清空上下文占用提醒的区间计数。 */\n onContextCompacted: () => void\n sessionUnsub: (() => void) | undefined\n /** 最近一次已确认展示的供应商 usage 快照(/usage 命令数据源,见 handlers/usage.ts)。 */\n getLastProviderUsage: () => import('@x-otto/interchange').ProviderUsageSnapshot | null\n /** 2026-07-14 修复:当前会话是否处于写租约只读降级态(/status 面板数据源)。 */\n isWriteLeaseReadOnly: () => boolean\n /** RFC-180 D7:当前会话是否处于 turn 边界暂停态(/status 面板数据源)。 */\n isRunPaused: () => boolean\n agentMessagesToChatMessages: (msgs: readonly unknown[]) => import('@x-otto/tui').ChatMessage[]\n PROVIDER_DISPLAY_NAMES: Record<string, string>\n apiCredentialId: (api: string, provider: string) => string\n // 派生而非手抄(RFC-077 §4 规则1/规则2):类型取真 fn 签名,erased `entry: unknown`\n // 旧手抄镜像被 interactive.ts 的 `as unknown as` 强行桥接(逆变不兼容)。改 typeof 派生后\n // 真签名(`entry: TraceLike`)直接结构匹配 → 赋值无需强转、drift 即编译错。\n }\n}\n\nexport type CliHandler = (args: string[], ctx: CliContext) => Promise<void>\n\n/** /help 面板分组。GROUP 顺序即 /help 渲染顺序。 */\nexport type HelpGroup = 'session' | 'model' | 'agent' | 'debug' | 'ui' | 'other'\nexport const HELP_GROUP_ORDER: readonly HelpGroup[] = [\n 'session',\n 'model',\n 'agent',\n 'debug',\n 'ui',\n 'other',\n]\n\n/**\n * 单一命令目录条目(RFC-074 D2 单源)。\n *\n * 此表是 cli 命令名的**唯一真相源**:dispatch、SAFE_COMMAND_NAMES、ENGINE_BUILTIN_COMMANDS、\n * TUI 斜杠菜单注册、/help 分组全部从这里 derive,取代原先 6 处手抄集合/表(漏一处即静默腐烂)。\n */\nexport interface CliCommandEntry {\n name: string\n aliases?: string[]\n /**\n * CLI dispatch 处理器。缺省 = engine/TUI 核内置(exit/quit/abort/copy/guide):\n * 由 TUI 命令解析器或 onSubmit 快路径处理,不经 dispatchSlashCommand;列在此处仅为让\n * safe/engineBuiltin/help/注册 投影保持完整(单源不漏)。\n */\n handler?: CliHandler\n /** agent 运行中仍可立即执行(只读/UI 类)。→ SAFE_COMMAND_NAMES(含 aliases)。 */\n safe?: boolean\n /** engine builtin(非 response 结果类型):onSubmit 快路径跳过 model 校验。→ ENGINE_BUILTIN_COMMANDS(含 aliases)。 */\n engineBuiltin?: boolean\n /** /help 面板分组;缺省 = 不在 /help 列出。 */\n group?: HelpGroup\n /** 不注册进 TUI 斜杠菜单(exit/abort/copy 由 TUI 核解析,不入菜单)。 */\n tuiHidden?: boolean\n /** 仅当该 env 变量 === '1' 才注册进 TUI 菜单(如 gallery,OTTO_DEV)。 */\n envGate?: string\n /** 英文兜底描述(运行时被 i18n STR.cmdDesc[name] 覆盖;TUI 注册 + /help 用)。 */\n description?: string\n}\n\n// ── Helpers ──\n// Re-export 供 handler 模块使用。handler 内联调用 ctx.tui.messages.pushMessage / ctx.tui.info.showToast\n// 保持独立文件自包含(clarity > DRY for trivial one-liners)。\n\nexport const say = (ctx: CliContext, msg: string) =>\n ctx.tui.messages.pushMessage({\n role: 'assistant',\n content: [{ type: 'text', text: msg }],\n })\n\nexport const toast = (ctx: CliContext, title: string, content: string) =>\n ctx.tui.info.showToast(title, content)\n\n// ── Command Registry ──\n\n/**\n * 命令目录 — cli 命令名单一真相源(RFC-074 D2)。条目按 /help 分组顺序排列,组内顺序即 /help 展示顺序。\n * 末尾无 group 的条目(archive/unarchive/history/guide)只进 TUI 菜单 + 投影集,不在 /help 列出。\n */\nexport const SLASH_COMMANDS: CliCommandEntry[] = [\n // ── session ──\n { name: 'clear', handler: handleClear, engineBuiltin: true, group: 'session',\n description: 'Clear screen and reset interface state' },\n { name: 'compact', handler: handleCompact, group: 'session',\n description: 'Compact context now, or /compact log for recent compaction/prune event details' },\n { name: 'memory', aliases: ['mem'], handler: handleMemory, group: 'session',\n description: 'Project + user memory: /memory [user|project|<name>|forget <name>]' },\n { name: 'workspace', handler: handleWorkspace, safe: true, group: 'session',\n description: 'Manage workspace settings: /workspace memory-sync [status|enable|disable] (RFC-164 R-form remote memory sync opt-in)' },\n { name: 'status', handler: handleStatus, safe: true, engineBuiltin: true, group: 'session',\n description: 'Show session status (/status)' },\n { name: 'open', handler: handleOpen, safe: true, group: 'session',\n description: 'Open a file, or browse the current directory to find one: /open [path] (RFC-189)' },\n { name: 'attach', handler: handleAttach, safe: true, group: 'session',\n description: 'Attach to a session running on `otto serve`: /attach <sessionId> (RFC-335 T335-07; 首期只读预览)' },\n\n // ── model ──\n { name: 'model', aliases: ['m'], handler: handleModel, group: 'model',\n description: 'Switch model; /model [<id>|refresh]' },\n { name: 'auth', aliases: ['login'], handler: handleProviderAuth, group: 'model',\n description: 'Connect a model provider (OAuth or API key); /auth [<provider>]' },\n { name: 'usage', handler: handleUsage, safe: true, engineBuiltin: true, group: 'model',\n description: 'Show provider usage quota snapshot (Anthropic OAuth subscription plans only)' },\n { name: 'logout', handler: handleLogout, group: 'model',\n description: 'Remove stored credentials for a provider; /logout [<provider>]' },\n\n // ── agent ──\n { name: 'job', handler: handleJobStart, safe: true, group: 'agent',\n description: 'Start a background agent job: /job <directive> (worktree-isolated, full tools incl. MCP)' },\n { name: 'schedule', handler: handleSchedule, safe: true, group: 'agent',\n description: 'Manage scheduled tasks: /schedule add|list|remove|run|enable|disable <args>' },\n { name: 'delegates', handler: handleDelegatesToggle, safe: true, group: 'agent',\n description: 'Toggle the Delegates panel (↑↓ select · k kill · o output · a apply)' },\n\n // ── debug ──\n // RFC-335 M6:`/inspect`(含 devtools 别名)与 `/replay` 已下线——TUI 内嵌调试轴退役,\n // 调试改由 Web 服务承接(service `DebugBridge` + `otto debug` REST 子命令仍在)。\n { name: 'diagnose', handler: handleDiagnose, safe: true, group: 'debug',\n description: 'Surface improvement proposals from recurring hard failures (L1: suggest-only)' },\n { name: 'context', handler: handleContext, safe: true, group: 'debug',\n description: 'Show context usage breakdown (system/tools/messages/cache/free bar)' },\n { name: 'perf', handler: handlePerf, safe: true, group: 'debug',\n description: 'Open performance panel (memory pressure, rendering health)' },\n { name: 'cache-stats', handler: handleCacheStats, safe: true, group: 'debug',\n description: 'Open cache hit rate panel (session/last-turn hit ratio, per-model breakdown)' },\n { name: 'gallery', handler: handleGallery, safe: true, group: 'debug', envGate: 'OTTO_DEV',\n description: 'Open the TUI component gallery (all component examples; dev only, OTTO_DEV=1)' },\n { name: 'trace-gc', handler: handleTraceGc, group: 'debug',\n description: 'Trace TTL lifecycle GC: /trace-gc [--ttl-days N] [--apply]. Cleans stale session trace files (not session data). TTL is based on the session\\'s last agent engine activity time, not your last view/interaction time.' },\n { name: 'session-archive-export', handler: handleSessionArchiveExport, group: 'debug',\n description: 'Archive-export a cold session: /session-archive-export <sessionId> [--out <path>] [--and-delete]. Exports full session entries as JSON; optional --and-delete removes from DB after checksum verification + confirmation.' },\n\n // ── ui ──\n { name: 'effort', handler: handleEffort, group: 'ui',\n description: 'Switch reasoning effort (/effort low|medium|high|xhigh|max)' },\n { name: 'preference', aliases: ['pref'], handler: handlePreference, safe: true, group: 'ui',\n description: 'Preferences: /preference [general|lang|theme]' },\n { name: 'trust', handler: handleCompat, safe: true, group: 'ui',\n description: 'Trust this project — apply its .otto/config.json high-risk keys + .claude executable config (/trust [status|trust|untrust])' },\n { name: 'notification', aliases: ['notif'], handler: handleNotification, group: 'ui',\n description: 'Toggle the notification center panel (≡ ctrl+n)' },\n { name: 'services', handler: handleServices, safe: true, group: 'other',\n description: 'Show detected services from delegated background processes (/services)' },\n { name: 'draft', handler: handleDraft, safe: true, group: 'ui',\n description: 'Manage saved drafts (≡ Ctrl+S save, Ctrl+O open panel): /draft [list|save <name?> <text>|load <name>|delete <name>]' },\n { name: 'queue', handler: handleQueue, safe: true, group: 'ui',\n description: 'Open the queued-commands panel (≡ ctrl+q): /queue [list|remove <index>|move <index> <up|down>|clear]' },\n { name: 'keybindings', aliases: ['kb'], handler: handleKeybindings, safe: true, group: 'ui',\n description: 'Open the keybinding panel: browse, search, remap, reset shortcuts (≡ default Ctrl+/)' },\n { name: 'shortcuts', handler: handleShortcuts, safe: true, group: 'ui',\n description: 'Manage custom @ text shortcuts: [@shortcut:name] expands to predefined text on submit' },\n { name: 'proxy', handler: handleProxy, group: 'ui',\n description: 'Configure HTTP proxy: /proxy [<url>|on|off|status|clear]' },\n\n // ── other ──\n { name: 'help', aliases: ['h', '?'], safe: true, engineBuiltin: true, group: 'other',\n description: 'Show available commands',\n handler: async (args, ctx) => handleHelp(args, ctx, SLASH_COMMANDS) },\n // engine/TUI 核内置(无 CLI handler):由 TUI 命令解析器/onSubmit 快路径处理,列此仅为投影完整。\n { name: 'exit', aliases: ['quit', 'q'], safe: true, engineBuiltin: true, group: 'other', tuiHidden: true,\n description: 'Exit the TUI; /exit --restart to restart into the same session' },\n { name: 'abort', engineBuiltin: true, group: 'other', tuiHidden: true,\n description: 'Abort the current agent run' },\n // RFC-180 D8:显式继续入口——真实 CLI handler(非 engineBuiltin),agent 忙判定期间\n // 也需可执行(safe: true):暂停态下 AgentSession.isBusy 已因暂停外观空闲,命令本身\n // 又只读检查 isPaused/写会话状态,不撞 session.prompt() 的忙闸。\n { name: 'pause', aliases: ['continue'], handler: handlePauseContinue, safe: true, group: 'other',\n description: 'Pause after current turn finishes (toggle with /continue)' },\n { name: 'copy', engineBuiltin: true, group: 'other', tuiHidden: true,\n description: 'Copy last assistant response' },\n { name: 'resume', handler: handleResume, group: 'other',\n description: 'List and switch to an existing session (--workspace <id>, --archived, --all)' },\n { name: 'rename', handler: handleRename, safe: true, group: 'session',\n description: 'Set a custom title for the current session (/rename <new title>)' },\n { name: 'a2ui-action', handler: handleA2uiAction, safe: false, group: 'session',\n description: 'Trigger A2UI interactive component action (/a2ui-action btn1 click)' },\n { name: 'reload', handler: handleReload, group: 'other',\n description: 'Hot-reload capabilities (MCP / providers / declarative agents) without restart' },\n { name: 'upgrade', group: 'other',\n description: 'Check for updates (/upgrade --check for extensions, /upgrade for otto CLI)',\n handler: async (args, ctx) => {\n if (args.includes('--check')) {\n await handleUpgradeCheck(args, ctx)\n return\n }\n if (await forceUpgrade(args, ctx)) return\n await handleUpgrade(args, ctx)\n } },\n { name: 'about', handler: handleAbout, safe: true, group: 'other',\n description: 'Show version, build, and environment info (/about)' },\n { name: 'setup', handler: handleSetup, safe: true, group: 'other',\n description: 'Show onboarding progress (provider → login → model) with a jump-to-next-step button (/setup)' },\n { name: 'extension', aliases: ['extensions', 'ext'], handler: handleExtension, group: 'other',\n description: 'Manage pluggable extensions (MCP / skills / agents / plugins / panels): ↑↓ select · Enter toggle/open · Esc close' },\n { name: 'install', handler: handleInstall, safe: true, group: 'other',\n description: 'Install a skill/plugin/MCP from a local archive/dir/URL (/install [--global] [--force] [--dry-run] [--sub-path <p>] [--ref <tag|branch>] --as <type> <source>)' },\n { name: 'registry', handler: handleRegistry, safe: true, group: 'other',\n description: 'Manage plugin discovery registries (/registry [list|add <name> <url>|remove <name>]) — RFC-201' },\n { name: 'search', handler: handleSearch, safe: true, group: 'other',\n description: 'Search plugins/skills/MCP across registered registries (/search <keyword>) — RFC-201' },\n { name: 'permissions', aliases: ['perms'], handler: handlePermissions, safe: true, group: 'other',\n description: 'View / clear the always-allow list (/permissions [clear])' },\n { name: 'feedback', handler: handleFeedback, group: 'other',\n description: 'Report an issue or send feedback via GitHub (/feedback [--repo <owner/repo>])' },\n\n // ── 无 /help 分组:仅进 TUI 菜单 + 投影集 ──\n { name: 'archive', handler: handleArchive, safe: true,\n description: 'Toggle archive state (archived disables further input); /archive [<id>]' },\n // RFC-171 D4:写租约诊断/强制解锁。不入 safe——`--force` 分支直接 unlinkSync 锁文件,\n // 属于写磁盘操作,agent 处理中不应允许触发(同 lesson_33「写磁盘/重载 MCP 的不加 safe」)。\n { name: 'session', handler: handleSessionUnlock,\n description: 'Session management: /session fork | /session unlock <id> [--force] | /session kill <id>' },\n // /fork:fork 当前会话并命名——/session fork 的快捷版,fork 后弹输入框给新会话命名。\n { name: 'fork', handler: handleFork, safe: true,\n description: 'Fork current session into a named branch' },\n // RFC-158 D5:aliases: ['unpin'] 使 /unpin 也能路由到同一 handlePin(CommandRegistry.find\n // 与 dispatchSlashCommand 均已原生支持按 aliases 查找);面板展示名的 pin⇄unpin 动态切换见\n // interactive-commands.ts 的 resolvePinCommandLabel。\n { name: 'pin', aliases: ['unpin'], handler: handlePin, safe: true,\n description: 'Toggle pin state (pinned sessions sort to top of /resume); /pin [<id>...] [--group <name>]' },\n { name: 'history', aliases: ['hist'], handler: handleHistory, safe: true,\n description: 'Open history search panel (same panel as Ctrl+R)' },\n { name: 'prompts', handler: handlePrompts, safe: true,\n description: 'View prompt log (list of submitted prompts this session)' },\n]\n\n/**\n * 投影:可在 agent 运行中立即执行的命令名集(含 aliases)。从 SLASH_COMMANDS `safe` 派生(RFC-074 D2)。\n */\nexport const SAFE_COMMAND_NAMES: ReadonlySet<string> = new Set(\n SLASH_COMMANDS.filter((c) => c.safe).flatMap((c) => [c.name, ...(c.aliases ?? [])]),\n)\n\n/**\n * 投影:engine builtin(非 response 结果)命令名集(含 aliases)。从 SLASH_COMMANDS `engineBuiltin` 派生。\n * onSubmit 快路径据此跳过 model 校验与 agent-processing toggle。\n */\nexport const ENGINE_BUILTIN_COMMANDS: ReadonlySet<string> = new Set(\n SLASH_COMMANDS.filter((c) => c.engineBuiltin).flatMap((c) => [c.name, ...(c.aliases ?? [])]),\n)\n\n/** 投影:所有具 CLI handler 的命令名(含 aliases)——not-busy 时可立即执行的集合。 */\nexport const DISPATCHABLE_COMMAND_NAMES: ReadonlySet<string> = new Set(\n SLASH_COMMANDS.filter((c) => c.handler).flatMap((c) => [c.name, ...(c.aliases ?? [])]),\n)\n\n// ── Dispatch ──\n\n/**\n * Dispatch a CLI command by name to its registered handler.\n *\n * @param command The command name (without leading `/`).\n * @param args Tokenized arguments from the command-parser.\n * @param ctx The shared CliContext bridging tui, app services, mutable state, and callbacks.\n * @returns `true` if the command was dispatched, `false` if no handler matched.\n */\nexport async function dispatchSlashCommand(\n command: string,\n args: string[],\n ctx: CliContext,\n): Promise<boolean> {\n let entry = SLASH_COMMANDS.find((c) => c.name === command)\n if (!entry)\n entry = SLASH_COMMANDS.find((c) => c.aliases?.includes(command))\n // handler 缺省 = engine/TUI 核内置(exit/abort/copy/guide),不经此处派发 → 视为未匹配。\n if (!entry?.handler) return false\n await entry.handler(args, ctx)\n return true\n}\n"],"mappings":"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"}