@ai-agent-forge/agent-forge 0.88.4 → 0.88.6
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.
- package/CHANGELOG.md +30 -0
- package/bundled-plugins/agent-loop-policy/agent-forge.json +1 -1
- package/bundled-plugins/agent-loop-policy/plugin.json +1 -1
- package/bundled-plugins/alignment/agent-forge.json +19 -0
- package/bundled-plugins/alignment/dist/baseline.d.ts +12 -0
- package/bundled-plugins/alignment/dist/baseline.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/baseline.js +68 -0
- package/bundled-plugins/alignment/dist/baseline.js.map +1 -0
- package/bundled-plugins/alignment/dist/engine.d.ts +102 -0
- package/bundled-plugins/alignment/dist/engine.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/engine.js +654 -0
- package/bundled-plugins/alignment/dist/engine.js.map +1 -0
- package/bundled-plugins/alignment/dist/entry.d.ts +14 -0
- package/bundled-plugins/alignment/dist/entry.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/entry.js +23 -0
- package/bundled-plugins/alignment/dist/entry.js.map +1 -0
- package/bundled-plugins/alignment/dist/export.d.ts +15 -0
- package/bundled-plugins/alignment/dist/export.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/export.js +122 -0
- package/bundled-plugins/alignment/dist/export.js.map +1 -0
- package/bundled-plugins/alignment/dist/http-server.d.ts +33 -0
- package/bundled-plugins/alignment/dist/http-server.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/http-server.js +119 -0
- package/bundled-plugins/alignment/dist/http-server.js.map +1 -0
- package/bundled-plugins/alignment/dist/index.d.ts +24 -0
- package/bundled-plugins/alignment/dist/index.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/index.js +719 -0
- package/bundled-plugins/alignment/dist/index.js.map +1 -0
- package/bundled-plugins/alignment/dist/page.d.ts +15 -0
- package/bundled-plugins/alignment/dist/page.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/page.js +832 -0
- package/bundled-plugins/alignment/dist/page.js.map +1 -0
- package/bundled-plugins/alignment/dist/render.d.ts +40 -0
- package/bundled-plugins/alignment/dist/render.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/render.js +194 -0
- package/bundled-plugins/alignment/dist/render.js.map +1 -0
- package/bundled-plugins/alignment/dist/types.d.ts +217 -0
- package/bundled-plugins/alignment/dist/types.d.ts.map +1 -0
- package/bundled-plugins/alignment/dist/types.js +30 -0
- package/bundled-plugins/alignment/dist/types.js.map +1 -0
- package/bundled-plugins/alignment/plugin.json +12 -0
- package/bundled-plugins/browser-playwright/MANUAL.md +60 -0
- package/bundled-plugins/browser-playwright/agent-forge.json +25 -0
- package/bundled-plugins/browser-playwright/dist/capability.d.ts +25 -0
- package/bundled-plugins/browser-playwright/dist/capability.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/capability.js +612 -0
- package/bundled-plugins/browser-playwright/dist/capability.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/entry.d.ts +13 -0
- package/bundled-plugins/browser-playwright/dist/entry.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/entry.js +14 -0
- package/bundled-plugins/browser-playwright/dist/entry.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/index.d.ts +7 -0
- package/bundled-plugins/browser-playwright/dist/index.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/index.js +5 -0
- package/bundled-plugins/browser-playwright/dist/index.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/operations.d.ts +69 -0
- package/bundled-plugins/browser-playwright/dist/operations.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/operations.js +149 -0
- package/bundled-plugins/browser-playwright/dist/operations.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/playwright-driver.d.ts +275 -0
- package/bundled-plugins/browser-playwright/dist/playwright-driver.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/playwright-driver.js +953 -0
- package/bundled-plugins/browser-playwright/dist/playwright-driver.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/setup.d.ts +88 -0
- package/bundled-plugins/browser-playwright/dist/setup.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/setup.js +252 -0
- package/bundled-plugins/browser-playwright/dist/setup.js.map +1 -0
- package/bundled-plugins/browser-playwright/dist/types.d.ts +270 -0
- package/bundled-plugins/browser-playwright/dist/types.d.ts.map +1 -0
- package/bundled-plugins/browser-playwright/dist/types.js +2 -0
- package/bundled-plugins/browser-playwright/dist/types.js.map +1 -0
- package/bundled-plugins/browser-playwright/plugin.json +20 -0
- package/bundled-plugins/coding-tools/agent-forge.json +1 -1
- package/bundled-plugins/coding-tools/dist/bash.d.ts +2 -7
- package/bundled-plugins/coding-tools/dist/bash.d.ts.map +1 -1
- package/bundled-plugins/coding-tools/dist/bash.js +2 -34
- package/bundled-plugins/coding-tools/dist/bash.js.map +1 -1
- package/bundled-plugins/coding-tools/plugin.json +1 -1
- package/bundled-plugins/compaction-policy/agent-forge.json +1 -1
- package/bundled-plugins/compaction-policy/plugin.json +1 -1
- package/bundled-plugins/mcp-http/dist/mcp-http.d.ts.map +1 -1
- package/bundled-plugins/mcp-http/dist/mcp-http.js +84 -31
- package/bundled-plugins/mcp-http/dist/mcp-http.js.map +1 -1
- package/bundled-plugins/overflow-policy/agent-forge.json +1 -1
- package/bundled-plugins/overflow-policy/plugin.json +1 -1
- package/bundled-plugins/provider-failover/agent-forge.json +1 -1
- package/bundled-plugins/provider-failover/plugin.json +1 -1
- package/bundled-plugins/retry-policy/agent-forge.json +1 -1
- package/bundled-plugins/retry-policy/plugin.json +1 -1
- package/dist/bundle/.build-manifest.json +60 -58
- package/dist/bundle/chunks/{anthropic-messages-3FJHRAJL.js → anthropic-messages-NL4Y6WPB.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-GCL47IDS.js → azure-openai-responses-CPXC42HO.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/chunk-5LRQFZFE.js +2 -0
- package/dist/bundle/chunks/{chunk-HTEVUY7F.js → chunk-7HSJPQKN.js} +1 -1
- package/dist/bundle/chunks/{chunk-AC2FIS4G.js → chunk-EDO7Q6XR.js} +1 -1
- package/dist/bundle/chunks/{chunk-755VNWIF.js → chunk-ISLHFRER.js} +6 -6
- package/dist/bundle/chunks/{chunk-HV64ASMT.js → chunk-K3S6GTL7.js} +1 -1
- package/dist/bundle/chunks/{chunk-HEL6TAWE.js → chunk-POLDAUYA.js} +37 -36
- package/dist/bundle/chunks/{chunk-V3ACW7VD.js → chunk-WJSKB3FC.js} +1 -1
- package/dist/bundle/chunks/{chunk-32YDZI4J.js → chunk-ZS5JEI76.js} +1 -1
- package/dist/bundle/chunks/{google-generative-ai-6BMXWA44.js → google-generative-ai-UMZ53EFA.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-6H7NLRTK.js → google-vertex-ZNR5NS6H.js} +1 -1
- package/dist/bundle/chunks/image-resize-worker.js +1 -1
- package/dist/bundle/chunks/{mistral-conversations-IXUHSFSJ.js → mistral-conversations-IT66WUNN.js} +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-YHUAEVCM.js → openai-codex-responses-FOVCVG6I.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-ULQCFC72.js → openai-completions-FLQ7CBOL.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-NLLAJDPS.js → openai-responses-ABGYHS2T.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-EJTI2EJ3.js → pi-messages-6K5W6LUN.js} +1 -1
- package/dist/bundle/chunks/{virtual-modules-TQWEGNNZ.js → virtual-modules-2QGILFV7.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/client.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/client/remote-session.d.ts.map +1 -1
- package/dist/client/remote-session.js +65 -6
- package/dist/client/remote-session.js.map +1 -1
- package/dist/core/agent-session-store.d.ts.map +1 -1
- package/dist/core/agent-session-store.js +20 -3
- package/dist/core/agent-session-store.js.map +1 -1
- package/dist/core/extensions/agent-composition.d.ts.map +1 -1
- package/dist/core/extensions/agent-composition.js +20 -1
- package/dist/core/extensions/agent-composition.js.map +1 -1
- package/dist/core/i18n/locales/zh-cn/core-errors.d.ts.map +1 -1
- package/dist/core/i18n/locales/zh-cn/core-errors.js +1 -0
- package/dist/core/i18n/locales/zh-cn/core-errors.js.map +1 -1
- package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts.map +1 -1
- package/dist/core/i18n/locales/zh-cn/interactive-mode.js +10 -3
- package/dist/core/i18n/locales/zh-cn/interactive-mode.js.map +1 -1
- package/dist/core/keybindings.d.ts +35 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +34 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +62 -15
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +11 -3
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +2 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/capability-mount.d.ts.map +1 -1
- package/dist/core/tools/capability-mount.js +4 -21
- package/dist/core/tools/capability-mount.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +2 -2
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +2 -0
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +25 -0
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/changelog-pager.d.ts +53 -0
- package/dist/modes/interactive/components/changelog-pager.d.ts.map +1 -0
- package/dist/modes/interactive/components/changelog-pager.js +239 -0
- package/dist/modes/interactive/components/changelog-pager.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts +6 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-run-group.d.ts +41 -11
- package/dist/modes/interactive/components/tool-run-group.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-run-group.js +126 -14
- package/dist/modes/interactive/components/tool-run-group.js.map +1 -1
- package/dist/modes/interactive/interactive-commands.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-commands.js +23 -13
- package/dist/modes/interactive/interactive-commands.js.map +1 -1
- package/dist/modes/interactive/interactive-events.d.ts +14 -4
- package/dist/modes/interactive/interactive-events.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-events.js +94 -12
- package/dist/modes/interactive/interactive-events.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +6 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +21 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/plugins/ecosystem/node-local-package-source.d.ts +2 -1
- package/dist/plugins/ecosystem/node-local-package-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-local-package-source.js +110 -4
- package/dist/plugins/ecosystem/node-local-package-source.js.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.js +106 -39
- package/dist/plugins/ecosystem/node-package-install-host.js.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-bootstrap.js +59 -2
- package/dist/plugins/ecosystem/node-plugin-bootstrap.js.map +1 -1
- package/dist/suites/builtin-suites.d.ts.map +1 -1
- package/dist/suites/builtin-suites.js +2 -2
- package/dist/suites/builtin-suites.js.map +1 -1
- package/dist/utils/changelog.d.ts +27 -0
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js +110 -0
- package/dist/utils/changelog.js.map +1 -1
- package/docs/keybindings.md +14 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/extensions/with-deps/plugin.json +1 -1
- package/package.json +1 -1
- package/dist/bundle/chunks/chunk-A3JYRWB6.js +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactive-commands.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/interactive-commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Slash-command dispatch table for the interactive mode editor submit handler,\n * plus the command-handler implementations behind it (拆分 slice 1).\n *\n * The dispatch table was extracted from InteractiveMode.setupEditorSubmitHandler;\n * entry order mirrors the original if-chain and each run body mirrors its\n * branch verbatim (await/no-await and editor clearing order included). The\n * handler functions below were moved verbatim out of InteractiveMode (拆分\n * slice 1, docs/plans/interactive-mode拆分计划.md); the mode keeps same-name\n * delegate methods so prototype-level tests and the dispatch table keep\n * working.\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Component, EditorComponent, Keybinding, MarkdownTheme, TUI } from \"@agent-forge/tui\";\nimport { Container, Markdown, Spacer, Text, TuiAltScreen, visibleWidth } from \"@agent-forge/tui\";\nimport { getDebugLogPath } from \"../../config.ts\";\nimport { SessionImportFileNotFoundError } from \"../../core/agent-session-runtime.ts\";\nimport { computeCacheWaste } from \"../../core/cache-stats.ts\";\nimport type { CodingAgentFacadeV1, CodingAgentRuntimeFacadeV1 } from \"../../core/facade/index.ts\";\nimport { t } from \"../../core/i18n/index.ts\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../core/keybindings.ts\";\nimport type { ThemeMetaV1 } from \"../../core/resource-loader.ts\";\nimport { runCleanSessions } from \"../../core/session-cleanup.ts\";\nimport { MissingSessionCwdError } from \"../../core/session-cwd.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { BUILTIN_SLASH_COMMANDS } from \"../../core/slash-commands.ts\";\nimport type { TruncationResult } from \"../../core/tools/truncate.ts\";\nimport { getUsageCostBreakdown } from \"../../core/usage-totals.ts\";\nimport type { MetricsSnapshotV1, ObservabilityMetricsOptions } from \"../../plugins/observability-types.ts\";\nimport { loadThemeFromPath, setRegisteredThemes, type Theme, theme } from \"../../theme/theme.ts\";\nimport type { InteractiveThemeController } from \"../../theme/theme-controller.ts\";\nimport { getChangelogPath, normalizeChangelogLinks, parseChangelog } from \"../../utils/changelog.ts\";\nimport { copyToClipboard } from \"../../utils/clipboard.ts\";\nimport { ArminComponent } from \"./components/armin.ts\";\nimport { BashExecutionComponent } from \"./components/bash-execution.ts\";\nimport { DaxnutsComponent } from \"./components/daxnuts.ts\";\nimport { DynamicBorder } from \"./components/dynamic-border.ts\";\nimport { formatTokens } from \"./components/footer.ts\";\nimport { keyDisplayText } from \"./components/keybinding-hints.ts\";\nimport { isExpandable } from \"./interactive-dialog-ui.ts\";\nimport { formatMetricsSnapshot } from \"./interactive-format.ts\";\n\n/**\n * Narrow facade InteractiveMode exposes to the dispatch table. Entries resolve\n * handlers through this interface at dispatch time, so instance-level\n * overrides keep working.\n */\nexport interface CommandDispatchHost {\n\tsetEditorText(text: string): void;\n\tshowSettingsSelector(): void;\n\tshowModelsSelector(): void;\n\thandleModelCommand(searchTerm?: string): Promise<void>;\n\thandleThinkingCommand(searchTerm?: string): void;\n\thandleSuiteCommand(suiteId?: string): void;\n\thandleExportCommand(text: string): Promise<void>;\n\thandleImportCommand(text: string): Promise<void>;\n\thandleCopyCommand(options?: { flashConfirmation?: boolean; preferSelection?: boolean }): Promise<void>;\n\thandleNameCommand(text: string): void;\n\thandleSessionCommand(): void;\n\t/** Interrupt grading (§4.2): terminate every background subagent; the\n\t * /stop-agents command's single execution face. */\n\thandleStopAgentsCommand(): void;\n\thandleMetricsCommand(turnsArg: string | undefined): void;\n\thandleChangelogCommand(): void;\n\thandleHelpCommand(): void;\n\thandleHotkeysCommand(): void;\n\tshowUserMessageSelector(): void;\n\thandleCloneCommand(): Promise<void>;\n\tshowTreeSelector(initialSelectedId?: string): void;\n\thandleLoginCommand(providerRef?: string): Promise<void>;\n\tshowOAuthSelector(mode: \"login\" | \"logout\"): Promise<void>;\n\thandleClearCommand(): Promise<void>;\n\thandleCompactCommand(customInstructions?: string): Promise<void>;\n\t/** /clean-sessions:会话清理两阶段流程(预览 → 宿主确认 → 执行,设计 §7)。 */\n\thandleCleanSessionsCommand(arg: string | undefined): Promise<void>;\n\thandleReloadCommand(): Promise<void>;\n\thandleDebugCommand(): void;\n\thandleArminSaysHi(): void;\n\tshowSessionSelector(): void;\n\tshowAgentsView(): void;\n\thandleQueueCommand(text: string | undefined): Promise<void>;\n\thandleDequeueCommand(): void;\n\thandleQueueListCommand(): void;\n\thandleEditorCommand(): Promise<void>;\n\tshutdown(options?: { fromSignal?: boolean }): Promise<void>;\n\tshowWorkflowView(): void;\n}\n\n/**\n * Host surface for the command-handler implementations in this module (拆分\n * slice 1): everything the handler bodies touch on the mode beyond the\n * dispatch signatures CommandDispatchHost already declares. InteractiveMode\n * implements this interface; its same-name delegate methods keep\n * prototype-level test bindings and the dispatch table working.\n *\n * Internal collaboration interface (same pattern as CommandDispatchHost): not\n * a plugin API and not exported from the package root.\n */\nexport interface CommandHandlersHost extends CommandDispatchHost {\n\t/** Stable facade (阶段 1 slice 1). */\n\tfacade: CodingAgentFacadeV1;\n\t/** Stable runtime facade (阶段 4 slice 3). */\n\truntimeFacade: CodingAgentRuntimeFacadeV1;\n\t/** Constructor-injected settings (D-047). */\n\tsettingsManager: SettingsManager;\n\t/** Mode options; /metrics reads the observability plugin source here. */\n\toptions: { getMetrics?: (options?: ObservabilityMetricsOptions) => MetricsSnapshotV1 | undefined };\n\tkeybindings: KeybindingsManager;\n\tthemeController: InteractiveThemeController;\n\n\t// UI surfaces.\n\tui: TUI;\n\teditor: EditorComponent;\n\teditorContainer: Container;\n\tchatContainer: Container;\n\tpendingMessagesContainer: Container;\n\n\t// Mutable mode state the handler bodies read or write.\n\tbuiltInHeader: Component | undefined;\n\ttoolOutputExpanded: boolean;\n\thideThinkingBlock: boolean;\n\toutputPad: number;\n\tbashComponent: BashExecutionComponent | undefined;\n\tpendingBashComponents: BashExecutionComponent[];\n\n\t// Status, dialog, and rebuild helpers.\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\tshowWarning(message: string): void;\n\tclearStatusIndicator(): void;\n\tresetDialogUI(): void;\n\trebuildChatFromMessages(): void;\n\tapplyRuntimeSettings(): void;\n\tsetupAutocompleteProvider(): void;\n\tshowLoadedResources(options?: { force?: boolean; showDiagnosticsWhenQuiet?: boolean }): void;\n\tshowDialogConfirm(title: string, message: string): Promise<boolean>;\n\tpromptForMissingSessionCwd(error: MissingSessionCwdError): Promise<string | undefined>;\n\thandleFatalRuntimeError(title: string, error: unknown): Promise<void>;\n\tgetMarkdownThemeWithSettings(): MarkdownTheme;\n\t/** Daxnuts easter-egg render; resolved through the host so instance-level\n\t * overrides keep working (original called this.handleDaxnuts()). */\n\thandleDaxnuts(): void;\n}\n\nexport interface CommandDispatchEntry {\n\t/** Command word including the leading slash, e.g. \"/model\". */\n\tcommand: string;\n\t/**\n\t * Slice offset of the optional argument for prefix commands\n\t * (\"/model claude\" -> text.slice(7).trim()); omit for exact-match commands.\n\t */\n\targOffset?: number;\n\t/** Report a blank (empty after trim) argument as undefined. */\n\tblankArgToUndefined?: boolean;\n\t/** Clear the editor text before running the handler instead of after. */\n\tclearEditorBeforeRun?: boolean;\n\t/** Handler; receives the full submitted text and the extracted argument. */\n\trun: (host: CommandDispatchHost, text: string, arg: string | undefined) => Promise<void> | void;\n}\n\nexport interface CommandDispatchMatch {\n\tentry: CommandDispatchEntry;\n\targ: string | undefined;\n}\n\nexport const COMMAND_DISPATCH_ENTRIES: readonly CommandDispatchEntry[] = [\n\t{\n\t\tcommand: \"/settings\",\n\t\trun: (host) => {\n\t\t\thost.showSettingsSelector();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/scoped-models\",\n\t\tclearEditorBeforeRun: true,\n\t\trun: async (host) => {\n\t\t\tawait host.showModelsSelector();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/model\",\n\t\targOffset: 7,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => host.handleModelCommand(arg),\n\t},\n\t{\n\t\tcommand: \"/thinking\",\n\t\targOffset: 10,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => {\n\t\t\thost.handleThinkingCommand(arg);\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/suite\",\n\t\targOffset: 7,\n\t\tblankArgToUndefined: true,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => {\n\t\t\thost.handleSuiteCommand(arg);\n\t\t},\n\t},\n\t// /export, /import and /name are prefix commands whose handlers parse the\n\t// full text themselves; argOffset only enables prefix matching here.\n\t{ command: \"/export\", argOffset: 8, run: (host, text) => host.handleExportCommand(text) },\n\t{ command: \"/import\", argOffset: 8, run: (host, text) => host.handleImportCommand(text) },\n\t{ command: \"/copy\", run: (host) => host.handleCopyCommand() },\n\t{\n\t\tcommand: \"/name\",\n\t\targOffset: 6,\n\t\trun: (host, text) => {\n\t\t\thost.handleNameCommand(text);\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/session\",\n\t\trun: (host) => {\n\t\t\thost.handleSessionCommand();\n\t\t},\n\t},\n\t// Interrupt grading (设计 子代理运行时 §4.2, 2026-10-02): the only explicit\n\t// face for terminating background subagents — Esc never cancels them.\n\t{\n\t\tcommand: \"/stop-agents\",\n\t\trun: (host) => {\n\t\t\thost.handleStopAgentsCommand();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/metrics\",\n\t\targOffset: 9,\n\t\tblankArgToUndefined: true,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => {\n\t\t\thost.handleMetricsCommand(arg);\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/changelog\",\n\t\trun: (host) => {\n\t\t\thost.handleChangelogCommand();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/help\",\n\t\trun: (host) => {\n\t\t\thost.handleHelpCommand();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/hotkeys\",\n\t\trun: (host) => {\n\t\t\thost.handleHotkeysCommand();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/fork\",\n\t\trun: (host) => {\n\t\t\thost.showUserMessageSelector();\n\t\t},\n\t},\n\t{ command: \"/clone\", clearEditorBeforeRun: true, run: (host) => host.handleCloneCommand() },\n\t{\n\t\tcommand: \"/tree\",\n\t\trun: (host) => {\n\t\t\thost.showTreeSelector();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/login\",\n\t\targOffset: 7,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => host.handleLoginCommand(arg),\n\t},\n\t// Original branch does not await showOAuthSelector; keep it fire-and-forget.\n\t{\n\t\tcommand: \"/logout\",\n\t\trun: (host) => {\n\t\t\tvoid host.showOAuthSelector(\"logout\");\n\t\t},\n\t},\n\t{ command: \"/new\", clearEditorBeforeRun: true, run: (host) => host.handleClearCommand() },\n\t{\n\t\tcommand: \"/compact\",\n\t\targOffset: 9,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => host.handleCompactCommand(arg),\n\t},\n\t{\n\t\tcommand: \"/clean-sessions\",\n\t\targOffset: 16,\n\t\tblankArgToUndefined: true,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => host.handleCleanSessionsCommand(arg),\n\t},\n\t{ command: \"/reload\", clearEditorBeforeRun: true, run: (host) => host.handleReloadCommand() },\n\t{\n\t\tcommand: \"/debug\",\n\t\trun: (host) => {\n\t\t\thost.handleDebugCommand();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/arminsayshi\",\n\t\trun: (host) => {\n\t\t\thost.handleArminSaysHi();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/resume\",\n\t\trun: (host) => {\n\t\t\thost.showSessionSelector();\n\t\t},\n\t},\n\t// Read-only subagent session browser (view-family command: sync, no editor\n\t// clearing), grouped with /resume ahead of the /workflow view overlay.\n\t{\n\t\tcommand: \"/agents\",\n\t\trun: (host) => {\n\t\t\thost.showAgentsView();\n\t\t},\n\t},\n\t// Follow-up queue face (single-entry iron law: /queue replaces the shipped\n\t// ctrl+q default). A bare /queue prints usage — the editor buffer is\n\t// cleared on submit before the command runs, so there is no editor\n\t// content to queue (the configurable keybinding keeps that behavior).\n\t{\n\t\tcommand: \"/queue\",\n\t\targOffset: 7,\n\t\tblankArgToUndefined: true,\n\t\trun: (host, _text, arg) => host.handleQueueCommand(arg),\n\t},\n\t{ command: \"/dequeue\", run: (host) => host.handleDequeueCommand() },\n\t// Queue manager overlay (single entry point: the /queue-list command;\n\t// reorder/remove/restore are in-component keys of the overlay itself).\n\t{ command: \"/queue-list\", run: (host) => host.handleQueueListCommand() },\n\t{ command: \"/editor\", run: (host) => host.handleEditorCommand() },\n\t{ command: \"/quit\", clearEditorBeforeRun: true, run: (host) => host.shutdown() },\n\t// Exact-match only: every other /workflow subcommand (why/log/...) keeps\n\t// flowing to the plugin's text command path untouched (§9 L2).\n\t{\n\t\tcommand: \"/workflow view\",\n\t\trun: (host) => {\n\t\t\thost.showWorkflowView();\n\t\t},\n\t},\n];\n\n/** Find the entry matching text: exact command, or command prefix plus argument. */\nexport function matchCommandDispatchEntry(\n\tentries: readonly CommandDispatchEntry[],\n\ttext: string,\n): CommandDispatchMatch | undefined {\n\tfor (const entry of entries) {\n\t\tif (text === entry.command) {\n\t\t\treturn { entry, arg: undefined };\n\t\t}\n\t\tif (entry.argOffset === undefined || !text.startsWith(`${entry.command} `)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst arg = text.slice(entry.argOffset).trim();\n\t\treturn { entry, arg: entry.blankArgToUndefined && arg.length === 0 ? undefined : arg };\n\t}\n\treturn undefined;\n}\n\n/** Minimal command shape the completion filter works on (mirrors SlashCommand). */\nexport interface CompletionCommandInput {\n\treadonly name: string;\n\treadonly description: string;\n\treadonly argumentHint?: string;\n}\n\n/**\n * Capability commands that collide with a builtin, prompt-template, or skill\n * command name are rejected by AgentSession at invocation time rather than\n * silently resolving to one source, so the colliding capability command is\n * withheld from the completion list entirely (both sides stay executable via\n * their own paths; neither is offered as an ambiguous completion).\n */\nexport function filterCapabilityCommandsForCompletions(\n\tcapabilityCommands: readonly CompletionCommandInput[],\n\treservedNames: readonly Iterable<string>[],\n): CompletionCommandInput[] {\n\tconst reserved = new Set<string>();\n\tfor (const names of reservedNames) for (const name of names) reserved.add(name);\n\treturn capabilityCommands\n\t\t.filter((command) => !reserved.has(command.name))\n\t\t.map((command) => ({\n\t\t\tname: command.name,\n\t\t\tdescription: command.description,\n\t\t\t...(command.argumentHint !== undefined ? { argumentHint: command.argumentHint } : {}),\n\t\t}));\n}\n\n/**\n * Rebuilds real Theme instances from the resource loader's ThemeMetaV1\n * projection for the theme registry (B-S2b v1). Registration points\n * (constructor, runtime session start, reload) need loadable instances —\n * setRegisteredThemes stores what it is given and getThemeByName returns the\n * registered instance first — while the loader surface now projects only\n * metadata. Re-loading is synchronous and deterministic per sourcePath;\n * theme names come from the same JSON the loader parsed, so meta.name ===\n * Theme.name and the registry keeps its own name-filter/dedupe semantics.\n *\n * Contract: a meta without a sourcePath (or one whose file no longer parses)\n * throws — themesOverride host inputs must always carry a loadable\n * sourcePath. This adds one extra parse per theme at registration time\n * (constructor / session start / reload, not per turn).\n *\n * Re-exported from ./interactive-mode.ts for existing consumers of that module.\n */\nexport function buildRegisteredThemes(themes: readonly ThemeMetaV1[]): Theme[] {\n\treturn themes.map((meta) => loadThemeFromPath(meta.sourcePath));\n}\n\nexport async function handleReloadCommand(host: CommandHandlersHost): Promise<void> {\n\tif (host.facade.isStreaming()) {\n\t\thost.showWarning(t(\"Wait for the current response to finish before reloading.\"));\n\t\treturn;\n\t}\n\tif (host.facade.isCompacting()) {\n\t\thost.showWarning(t(\"Wait for compaction to finish before reloading.\"));\n\t\treturn;\n\t}\n\n\thost.resetDialogUI();\n\n\tconst reloadBox = new Container();\n\tconst borderColor = (s: string) => theme.fg(\"border\", s);\n\treloadBox.addChild(new DynamicBorder(borderColor));\n\treloadBox.addChild(new Spacer(1));\n\treloadBox.addChild(\n\t\tnew Text(theme.fg(\"muted\", t(\"Reloading keybindings, skills, prompts, themes, and context files...\")), 1, 0),\n\t);\n\treloadBox.addChild(new Spacer(1));\n\treloadBox.addChild(new DynamicBorder(borderColor));\n\n\tconst previousEditor = host.editor;\n\thost.editorContainer.clear();\n\thost.editorContainer.addChild(reloadBox);\n\thost.ui.setFocus(reloadBox);\n\thost.ui.requestRender(true);\n\tawait new Promise((resolve) => process.nextTick(resolve));\n\n\tconst dismissReloadBox = (editor: Component) => {\n\t\thost.editorContainer.clear();\n\t\thost.editorContainer.addChild(editor);\n\t\thost.ui.setFocus(editor);\n\t\thost.ui.requestRender();\n\t};\n\n\tlet chatRestoredBeforeSessionStart = false;\n\tlet reloadBoxDismissed = false;\n\tconst restoreChatBeforeSessionStart = () => {\n\t\tif (chatRestoredBeforeSessionStart) {\n\t\t\treturn;\n\t\t}\n\t\thost.hideThinkingBlock = host.settingsManager.getHideThinkingBlock();\n\t\thost.outputPad = host.settingsManager.getOutputPad();\n\t\thost.rebuildChatFromMessages();\n\t\tchatRestoredBeforeSessionStart = true;\n\t};\n\n\ttry {\n\t\tawait host.facade.reload({ beforeSessionStart: restoreChatBeforeSessionStart });\n\t\trestoreChatBeforeSessionStart();\n\t\thost.keybindings.reload();\n\t\tconst activeHeader = host.builtInHeader;\n\t\tif (isExpandable(activeHeader)) {\n\t\t\tactiveHeader.setExpanded(host.toolOutputExpanded);\n\t\t}\n\t\tsetRegisteredThemes(buildRegisteredThemes(host.facade.getThemes().themes));\n\t\thost.applyRuntimeSettings();\n\t\tawait host.themeController.applyFromSettings();\n\t\thost.setupAutocompleteProvider();\n\t\thost.showLoadedResources({\n\t\t\tforce: false,\n\t\t\tshowDiagnosticsWhenQuiet: true,\n\t\t});\n\t\tconst modelsJsonError = host.facade.getModelsError();\n\t\tif (modelsJsonError) {\n\t\t\thost.showError(t(\"models.json error: {error}\", { error: modelsJsonError }));\n\t\t}\n\t\thost.showStatus(t(\"Reloaded keybindings, skills, prompts, themes, and context files\"));\n\t\tdismissReloadBox(host.editor as Component);\n\t\treloadBoxDismissed = true;\n\t} catch (error) {\n\t\t// v8 ignore next -- reloadBoxDismissed 置位后 catch 前无可抛出点,恒走 dismiss 分支\n\t\tif (!reloadBoxDismissed) {\n\t\t\tdismissReloadBox(previousEditor as Component);\n\t\t}\n\t\thost.showError(t(\"Reload failed: {error}\", { error: error instanceof Error ? error.message : String(error) }));\n\t}\n}\n\nexport async function handleExportCommand(host: CommandHandlersHost, text: string): Promise<void> {\n\tconst outputPath = getPathCommandArgument(text, \"/export\");\n\n\ttry {\n\t\tif (outputPath?.endsWith(\".jsonl\")) {\n\t\t\tconst filePath = host.facade.exportToJsonl(outputPath);\n\t\t\thost.showStatus(t(\"Session exported to: {path}\", { path: filePath }));\n\t\t} else {\n\t\t\tconst filePath = await host.facade.exportToHtml(outputPath, {\n\t\t\t\tthemeName: theme.name,\n\t\t\t});\n\t\t\thost.showStatus(t(\"Session exported to: {path}\", { path: filePath }));\n\t\t}\n\t} catch (error: unknown) {\n\t\thost.showError(\n\t\t\tt(\"Failed to export session: {error}\", {\n\t\t\t\terror: error instanceof Error ? error.message : t(\"Unknown error\"),\n\t\t\t}),\n\t\t);\n\t}\n}\n\nexport function getPathCommandArgument(text: string, command: \"/export\" | \"/import\"): string | undefined {\n\tif (text === command) {\n\t\treturn undefined;\n\t}\n\tif (!text.startsWith(`${command} `)) {\n\t\treturn undefined;\n\t}\n\n\tconst argsString = text.slice(command.length + 1).trimStart();\n\tif (!argsString) {\n\t\treturn undefined;\n\t}\n\n\tconst firstChar = argsString[0];\n\tif (firstChar === '\"' || firstChar === \"'\") {\n\t\tconst closingQuoteIndex = argsString.indexOf(firstChar, 1);\n\t\tif (closingQuoteIndex < 0) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn argsString.slice(1, closingQuoteIndex);\n\t}\n\n\tconst firstWhitespaceIndex = argsString.search(/\\s/);\n\tif (firstWhitespaceIndex < 0) {\n\t\treturn argsString;\n\t}\n\treturn argsString.slice(0, firstWhitespaceIndex);\n}\n\nexport async function handleImportCommand(host: CommandHandlersHost, text: string): Promise<void> {\n\tconst inputPath = getPathCommandArgument(text, \"/import\");\n\tif (!inputPath) {\n\t\thost.showError(t(\"Usage: /import <path.jsonl>\"));\n\t\treturn;\n\t}\n\n\tconst confirmed = await host.showDialogConfirm(\n\t\tt(\"Import session\"),\n\t\tt(\"Replace current session with {path}?\", { path: inputPath }),\n\t);\n\tif (!confirmed) {\n\t\thost.showStatus(t(\"Import cancelled\"));\n\t\treturn;\n\t}\n\n\ttry {\n\t\thost.clearStatusIndicator();\n\t\tconst result = await host.runtimeFacade.importFromJsonl(inputPath);\n\t\tif (result.cancelled) {\n\t\t\thost.showStatus(t(\"Import cancelled\"));\n\t\t\treturn;\n\t\t}\n\t\thost.showStatus(t(\"Session imported from: {path}\", { path: inputPath }));\n\t} catch (error: unknown) {\n\t\tif (error instanceof MissingSessionCwdError) {\n\t\t\tconst selectedCwd = await host.promptForMissingSessionCwd(error);\n\t\t\tif (!selectedCwd) {\n\t\t\t\thost.showStatus(t(\"Import cancelled\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst result = await host.runtimeFacade.importFromJsonl(inputPath, selectedCwd);\n\t\t\tif (result.cancelled) {\n\t\t\t\thost.showStatus(t(\"Import cancelled\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.showStatus(t(\"Session imported from: {path}\", { path: inputPath }));\n\t\t\treturn;\n\t\t}\n\t\tif (error instanceof SessionImportFileNotFoundError) {\n\t\t\thost.showError(t(\"Failed to import session: {error}\", { error: error.message }));\n\t\t\treturn;\n\t\t}\n\t\tawait host.handleFatalRuntimeError(t(\"Failed to import session\"), error);\n\t}\n}\n\nexport async function handleCopyCommand(\n\thost: CommandHandlersHost,\n\toptions: { flashConfirmation?: boolean; preferSelection?: boolean } = {},\n): Promise<void> {\n\tif (\n\t\toptions.preferSelection &&\n\t\thost.ui instanceof TuiAltScreen &&\n\t\t!host.ui.getCopyOnSelect() &&\n\t\thost.ui.hasActiveSelection()\n\t) {\n\t\tawait host.ui.copyActiveSelectionToClipboard();\n\t\treturn;\n\t}\n\n\tconst text = host.facade.getLastAssistantText();\n\tif (!text) {\n\t\thost.showError(t(\"No agent messages to copy yet.\"));\n\t\treturn;\n\t}\n\n\ttry {\n\t\tawait copyToClipboard(text);\n\t\tif (options.flashConfirmation && host.ui instanceof TuiAltScreen) {\n\t\t\thost.ui.flash(t(\"Copied!\"));\n\t\t} else {\n\t\t\thost.showStatus(t(\"Copied last agent message to clipboard\"));\n\t\t}\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport function handleNameCommand(host: CommandHandlersHost, text: string): void {\n\tconst name = text.replace(/^\\/name\\s*/, \"\").trim();\n\tif (!name) {\n\t\tconst currentName = host.facade.getSessionName();\n\t\tif (currentName) {\n\t\t\thost.chatContainer.addChild(new Spacer(1));\n\t\t\thost.chatContainer.addChild(new Text(theme.fg(\"dim\", t(\"Session name: {name}\", { name: currentName })), 1, 0));\n\t\t} else {\n\t\t\thost.showWarning(t(\"Usage: /name <name>\"));\n\t\t}\n\t\thost.ui.requestRender();\n\t\treturn;\n\t}\n\n\thost.facade.setSessionName(name);\n\tconst sessionName = host.facade.getSessionName();\n\tif (sessionName !== name) {\n\t\thost.showWarning(\n\t\t\tt(\"Session name was normalized from {from} to {to}\", {\n\t\t\t\tfrom: JSON.stringify(name),\n\t\t\t\tto: JSON.stringify(sessionName),\n\t\t\t}),\n\t\t);\n\t}\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(\n\t\tnew Text(theme.fg(\"dim\", t(\"Session name set: {name}\", { name: sessionName ?? name })), 1, 0),\n\t);\n\thost.ui.requestRender();\n}\n\n/**\n * 中断分级(设计 子代理运行时 §4.2,2026-10-02):/stop-agents 的唯一执行\n * 面——终止全部后台子代理(不触当前回合)。Esc 从不做这件事。\n */\nexport function handleStopAgentsCommand(host: CommandHandlersHost): void {\n\tconst active = host.facade.getActiveAgentTaskCount();\n\tif (active === 0) {\n\t\thost.showStatus(t(\"No background subagents running\"));\n\t\treturn;\n\t}\n\thost.facade.abortAgentTasks();\n\thost.showStatus(\n\t\tactive === 1\n\t\t\t? t(\"Terminated {count} background subagent\", { count: active })\n\t\t\t: t(\"Terminated {count} background subagents\", { count: active }),\n\t);\n}\n\nexport function handleSessionCommand(host: CommandHandlersHost): void {\n\tconst stats = host.facade.getSessionStats();\n\tconst sessionName = host.facade.getSessionName();\n\tconst entries = host.facade.getEntries();\n\tconst cacheWaste = computeCacheWaste(entries, host.facade.getModelPriceSource());\n\n\t// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`\n\t// resolves to a concrete responseModel). Usage without model attribution is\n\t// grouped separately so the breakdown reconciles with the session total.\n\tconst usageBreakdown = getUsageCostBreakdown(entries);\n\n\tlet info = `${theme.bold(t(\"Session Info\"))}\\n\\n`;\n\tif (sessionName) {\n\t\tinfo += `${theme.fg(\"dim\", t(\"Name:\"))} ${sessionName}\\n`;\n\t}\n\tinfo += `${theme.fg(\"dim\", t(\"File:\"))} ${stats.sessionFile ?? t(\"In-memory\")}\\n`;\n\tinfo += `${theme.fg(\"dim\", t(\"ID:\"))} ${stats.sessionId}\\n\\n`;\n\tinfo += `${theme.bold(t(\"Messages\"))}\\n`;\n\tinfo += `${theme.fg(\"dim\", t(\"Total:\"))} ${stats.totalMessages}\\n`;\n\tinfo += `${theme.fg(\"dim\", t(\"User:\"))} ${stats.userMessages}\\n`;\n\tinfo += `${theme.fg(\"dim\", t(\"Assistant:\"))} ${stats.assistantMessages}\\n`;\n\tinfo += `${theme.fg(\"dim\", t(\"Tools:\"))} ${t(\"{calls} calls, {results} results\", {\n\t\tcalls: stats.toolCalls,\n\t\tresults: stats.toolResults,\n\t})}\\n\\n`;\n\tinfo += `${theme.bold(t(\"Tokens\"))}\\n`;\n\t// \"Input\" is the full prompt volume. With cache activity, split it into\n\t// cached (served from cache) vs uncached (everything else) - the only\n\t// provider-independent split. Cache writes, where reported, are a detail\n\t// of the uncached portion.\n\tconst { input, cacheRead, cacheWrite } = stats.tokens;\n\tconst promptTokens = input + cacheRead + cacheWrite;\n\tinfo += `${theme.fg(\"dim\", t(\"Input:\"))} ${promptTokens.toLocaleString()}\\n`;\n\tif (promptTokens > 0 && (cacheRead > 0 || cacheWrite > 0)) {\n\t\tconst hitRate = theme.fg(\"dim\", `(${((cacheRead / promptTokens) * 100).toFixed(1)}%)`);\n\t\tinfo += ` ${theme.fg(\"dim\", t(\"Cached:\"))} ${cacheRead.toLocaleString()} ${hitRate}\\n`;\n\t\tconst written =\n\t\t\tcacheWrite > 0\n\t\t\t\t? ` ${theme.fg(\"dim\", t(\"({count} written to cache)\", { count: cacheWrite.toLocaleString() }))}`\n\t\t\t\t: \"\";\n\t\tinfo += ` ${theme.fg(\"dim\", t(\"Uncached:\"))} ${(input + cacheWrite).toLocaleString()}${written}\\n`;\n\t}\n\tinfo += `${theme.fg(\"dim\", t(\"Output:\"))} ${stats.tokens.output.toLocaleString()}\\n`;\n\tinfo += `${theme.fg(\"dim\", t(\"Total:\"))} ${stats.tokens.total.toLocaleString()}\\n`;\n\n\t// 双维度统计(用户裁决 2026-09-25):总计之外按供应商/模型分桶——缓存读/写、\n\t// 命中率、费用逐桶可见。此前该拆分门控在 cost>0 且多桶才渲染,单价为 0 的\n\t// 自定义供应商(如自建代理)永远看不到。\n\tif (usageBreakdown.length > 0) {\n\t\tinfo += `\\n${theme.bold(t(\"By Provider/Model\"))}\\n`;\n\t\tfor (const entry of usageBreakdown) {\n\t\t\tconst bucket = entry.totals;\n\t\t\tconst promptBucket = bucket.input + bucket.cacheRead + bucket.cacheWrite;\n\t\t\tlet line = ` ${theme.bold(entry.key)}`;\n\t\t\tif (promptBucket > 0) {\n\t\t\t\tline += `\\n ${theme.fg(\"dim\", t(\"Input:\"))} ${promptBucket.toLocaleString()}`;\n\t\t\t\tif (bucket.cacheRead > 0 || bucket.cacheWrite > 0) {\n\t\t\t\t\tconst rate = promptBucket > 0 ? ((bucket.cacheRead / promptBucket) * 100).toFixed(1) : \"0.0\";\n\t\t\t\t\tline += ` ${theme.fg(\"dim\", t(\"(cache {rate}%, W {written})\", { rate, written: bucket.cacheWrite.toLocaleString() }))}`;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (bucket.output > 0) line += `\\n ${theme.fg(\"dim\", t(\"Output:\"))} ${bucket.output.toLocaleString()}`;\n\t\t\tif (entry.cost > 0) line += ` ${theme.fg(\"dim\", `$${entry.cost.toFixed(3)}`)}`;\n\t\t\tinfo += `${line}\\n`;\n\t\t}\n\t}\n\n\tif (stats.cost > 0 || cacheWaste.missedTokens > 0) {\n\t\tinfo += `\\n${theme.bold(t(\"Cost\"))}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", t(\"Total:\"))} $${stats.cost.toFixed(3)}`;\n\t\tif (usageBreakdown.length > 1) {\n\t\t\tfor (const entry of usageBreakdown) {\n\t\t\t\tinfo += `\\n ${theme.fg(\"dim\", `${entry.key}:`)} $${entry.cost.toFixed(3)} ${theme.fg(\"dim\", t(\"({tokens} tokens)\", { tokens: formatTokens(entry.tokens) }))}`;\n\t\t\t}\n\t\t}\n\t\tif (cacheWaste.missedTokens > 0) {\n\t\t\tconst missLabel =\n\t\t\t\tcacheWaste.missCount === 1\n\t\t\t\t\t? t(\"{count} miss\", { count: cacheWaste.missCount })\n\t\t\t\t\t: t(\"{count} misses\", { count: cacheWaste.missCount });\n\t\t\tconst detail = t(\"{tokens} tokens, {misses}\", {\n\t\t\t\ttokens: cacheWaste.missedTokens.toLocaleString(),\n\t\t\t\tmisses: missLabel,\n\t\t\t});\n\t\t\tinfo +=\n\t\t\t\tcacheWaste.missedCost >= 0.0001\n\t\t\t\t\t? `\\n${theme.fg(\"dim\", t(\"Cache Re-billed:\"))} $${cacheWaste.missedCost.toFixed(3)} ${theme.fg(\"dim\", `(${detail})`)}`\n\t\t\t\t\t: `\\n${theme.fg(\"dim\", t(\"Cache Re-billed:\"))} ${detail}`;\n\t\t}\n\t}\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new Text(info, 1, 0));\n\thost.ui.requestRender();\n}\n\nexport function handleMetricsCommand(host: CommandHandlersHost, turnsArg: string | undefined): void {\n\tconst provider = host.options.getMetrics;\n\tif (!provider) {\n\t\thost.showWarning(t(\"Metrics are not available: the observability plugin is not mounted in this session\"));\n\t\treturn;\n\t}\n\n\tlet windowTurns: number | undefined;\n\tif (turnsArg !== undefined) {\n\t\tconst parsed = Number.parseInt(turnsArg, 10);\n\t\tif (!Number.isSafeInteger(parsed) || parsed < 1) {\n\t\t\thost.showWarning(t(\"Invalid turns: {turns}. Usage: /metrics [turns]\", { turns: turnsArg }));\n\t\t\treturn;\n\t\t}\n\t\twindowTurns = parsed;\n\t}\n\n\tlet snapshot: MetricsSnapshotV1;\n\ttry {\n\t\tconst result = provider({ windowTurns });\n\t\tif (result === undefined) {\n\t\t\thost.showWarning(t(\"Metrics are not available: the observability plugin is not mounted in this session\"));\n\t\t\treturn;\n\t\t}\n\t\tsnapshot = result;\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\treturn;\n\t}\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new Text(formatMetricsSnapshot(snapshot), 1, 0));\n\thost.ui.requestRender();\n}\n\nexport function handleChangelogCommand(host: CommandHandlersHost): void {\n\tconst changelogPath = getChangelogPath();\n\tconst allEntries = parseChangelog(changelogPath);\n\n\tconst changelogMarkdown =\n\t\tallEntries.length > 0\n\t\t\t? allEntries\n\t\t\t\t\t.reverse()\n\t\t\t\t\t.map((e) => normalizeChangelogLinks(e.content, e))\n\t\t\t\t\t.join(\"\\n\\n\")\n\t\t\t: t(\"No changelog entries found.\");\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new DynamicBorder());\n\thost.chatContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", t(\"What's New\"))), 1, 0));\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new Markdown(changelogMarkdown, 1, 1, host.getMarkdownThemeWithSettings()));\n\thost.chatContainer.addChild(new DynamicBorder());\n\thost.ui.requestRender();\n}\n\n/**\n * Get capitalized display string for an app keybinding action.\n */\nexport function getAppKeyDisplay(action: AppKeybinding): string {\n\treturn keyDisplayText(action);\n}\n\n/**\n * Get capitalized display string for an editor keybinding action.\n */\nexport function getEditorKeyDisplay(action: Keybinding): string {\n\treturn keyDisplayText(action);\n}\n\nexport function handleHelpCommand(host: CommandHandlersHost): void {\n\tconst section = (title: string, rows: Array<{ name: string; hint?: string; description?: string }>) => {\n\t\tif (rows.length === 0) return \"\";\n\t\tlet table = `\\n**${title}**\\n| ${t(\"Command\")} | ${t(\"Description\")} |\\n|---------|-------------|\\n`;\n\t\tfor (const row of rows) {\n\t\t\tconst command = row.hint ? `\\`/${row.name} ${row.hint}\\`` : `\\`/${row.name}\\``;\n\t\t\ttable += `| ${command} | ${row.description ?? \"\"} |\\n`;\n\t\t}\n\t\treturn table;\n\t};\n\n\tconst builtinRows = BUILTIN_SLASH_COMMANDS.map((command) => ({\n\t\tname: command.name,\n\t\thint: command.argumentHint,\n\t\tdescription: command.description,\n\t}));\n\tconst promptRows = host.facade.getPromptTemplates().map((template) => ({\n\t\tname: template.name,\n\t\thint: template.argumentHint,\n\t\tdescription: template.description,\n\t}));\n\tconst skillRows = host.facade.getSkills().skills.map((skill) => ({\n\t\tname: `skill:${skill.name}`,\n\t\tdescription: skill.description,\n\t}));\n\n\tconst help =\n\t\tsection(t(\"Built-in commands\"), builtinRows) +\n\t\tsection(t(\"Prompt templates\"), promptRows) +\n\t\tsection(t(\"Skills\"), skillRows) +\n\t\t`\\n${t(\"Type `/` for autocomplete. Use `/hotkeys` for keyboard shortcuts.\")}`;\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new DynamicBorder());\n\thost.chatContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", t(\"Available Commands\"))), 1, 0));\n\thost.chatContainer.addChild(new Markdown(help.trim(), 1, 1, host.getMarkdownThemeWithSettings()));\n\thost.chatContainer.addChild(new DynamicBorder());\n\thost.ui.requestRender();\n}\n\nexport function handleHotkeysCommand(host: CommandHandlersHost): void {\n\t// Navigation keybindings\n\tconst cursorUp = getEditorKeyDisplay(\"tui.editor.cursorUp\");\n\tconst cursorDown = getEditorKeyDisplay(\"tui.editor.cursorDown\");\n\tconst cursorLeft = getEditorKeyDisplay(\"tui.editor.cursorLeft\");\n\tconst cursorRight = getEditorKeyDisplay(\"tui.editor.cursorRight\");\n\tconst cursorWordLeft = getEditorKeyDisplay(\"tui.editor.cursorWordLeft\");\n\tconst cursorWordRight = getEditorKeyDisplay(\"tui.editor.cursorWordRight\");\n\tconst cursorLineStart = getEditorKeyDisplay(\"tui.editor.cursorLineStart\");\n\tconst cursorLineEnd = getEditorKeyDisplay(\"tui.editor.cursorLineEnd\");\n\tconst jumpForward = getEditorKeyDisplay(\"tui.editor.jumpForward\");\n\tconst jumpBackward = getEditorKeyDisplay(\"tui.editor.jumpBackward\");\n\tconst pageUp = getEditorKeyDisplay(\"tui.editor.pageUp\");\n\tconst pageDown = getEditorKeyDisplay(\"tui.editor.pageDown\");\n\n\t// Editing keybindings\n\tconst submit = getEditorKeyDisplay(\"tui.input.submit\");\n\tconst newLine = getEditorKeyDisplay(\"tui.input.newLine\");\n\tconst deleteWordBackward = getEditorKeyDisplay(\"tui.editor.deleteWordBackward\");\n\tconst deleteWordForward = getEditorKeyDisplay(\"tui.editor.deleteWordForward\");\n\tconst deleteToLineStart = getEditorKeyDisplay(\"tui.editor.deleteToLineStart\");\n\tconst deleteToLineEnd = getEditorKeyDisplay(\"tui.editor.deleteToLineEnd\");\n\tconst yank = getEditorKeyDisplay(\"tui.editor.yank\");\n\tconst yankPop = getEditorKeyDisplay(\"tui.editor.yankPop\");\n\tconst undo = getEditorKeyDisplay(\"tui.editor.undo\");\n\tconst tab = getEditorKeyDisplay(\"tui.input.tab\");\n\n\t// App keybindings\n\tconst interrupt = getAppKeyDisplay(\"app.interrupt\");\n\tconst clear = getAppKeyDisplay(\"app.clear\");\n\tconst exit = getAppKeyDisplay(\"app.exit\");\n\tconst suspend = getAppKeyDisplay(\"app.suspend\");\n\tconst cycleThinkingLevel = getAppKeyDisplay(\"app.thinking.cycle\");\n\tconst cycleModelForward = getAppKeyDisplay(\"app.model.cycleForward\");\n\tconst selectModel = getAppKeyDisplay(\"app.model.select\");\n\tconst expandTools = getAppKeyDisplay(\"app.tools.expand\");\n\tconst toggleThinking = getAppKeyDisplay(\"app.thinking.toggle\");\n\tconst externalEditor = getAppKeyDisplay(\"app.editor.external\");\n\tconst cycleModelBackward = getAppKeyDisplay(\"app.model.cycleBackward\");\n\tconst copyMessage = getAppKeyDisplay(\"app.message.copy\");\n\tconst followUp = getAppKeyDisplay(\"app.message.followUp\");\n\tconst dequeue = getAppKeyDisplay(\"app.message.dequeue\");\n\tconst pasteImage = getAppKeyDisplay(\"app.clipboard.pasteImage\");\n\t// Iron law (2026-10-01): these shipped defaults were cleared in favor of\n\t// the /model command; the row only appears when a user re-configures a key.\n\tconst selectModelRow = selectModel ? `| \\`${selectModel}\\` | ${t(\"Open model selector\")} |\\n` : \"\";\n\t// Iron law (2026-10-02 复审): ctrl+g/ctrl+x stay bound only for the\n\t// components without a command channel — the main editor ignores them\n\t// (/editor and /copy are its single entries), so the rows say where\n\t// each key actually acts.\n\tconst copyMessageRow = copyMessage ? `| \\`${copyMessage}\\` | ${t(\"Copy selection in /tree\")} |\\n` : \"\";\n\tconst externalEditorRow = externalEditor\n\t\t? `| \\`${externalEditor}\\` | ${t(\"External editor (inside dialog inputs)\")} |\\n`\n\t\t: \"\";\n\t// Iron law (2026-10-01): the message queue is the /queue and /dequeue\n\t// commands' feature; rows only appear when a user re-configures a key.\n\tconst followUpRow = followUp ? `| \\`${followUp}\\` | ${t(\"Queue follow-up message\")} |\\n` : \"\";\n\tconst dequeueRow = dequeue ? `| \\`${dequeue}\\` | ${t(\"Restore queued messages\")} |\\n` : \"\";\n\tconst cycleThinkingRow = cycleThinkingLevel ? `| \\`${cycleThinkingLevel}\\` | ${t(\"Cycle thinking level\")} |\\n` : \"\";\n\tconst cycleModelsRow =\n\t\tcycleModelForward || cycleModelBackward\n\t\t\t? `| \\`${cycleModelForward}\\` / \\`${cycleModelBackward}\\` | ${t(\"Cycle models\")} |\\n`\n\t\t\t: \"\";\n\t// Suspend has no default key on native Windows; hide the row there.\n\tconst suspendRow = suspend ? `| \\`${suspend}\\` | ${t(\"Suspend to background\")} |\\n` : \"\";\n\n\tconst hotkeys = `\n**${t(\"Navigation\")}**\n| ${t(\"Key\")} | ${t(\"Action\")} |\n|-----|--------|\n| \\`${cursorUp}\\` / \\`${cursorDown}\\` / \\`${cursorLeft}\\` / \\`${cursorRight}\\` | ${t(\"Move cursor / browse history\")} |\n| \\`${cursorWordLeft}\\` / \\`${cursorWordRight}\\` | ${t(\"Move by word\")} |\n| \\`${cursorLineStart}\\` | ${t(\"Start of line\")} |\n| \\`${cursorLineEnd}\\` | ${t(\"End of line\")} |\n| \\`${jumpForward}\\` | ${t(\"Jump forward to character\")} |\n| \\`${jumpBackward}\\` | ${t(\"Jump backward to character\")} |\n| \\`${pageUp}\\` / \\`${pageDown}\\` | ${t(\"Scroll by page\")} |\n\n**${t(\"Editing\")}**\n| ${t(\"Key\")} | ${t(\"Action\")} |\n|-----|--------|\n| \\`${submit}\\` | ${t(\"Send message\")} |\n| \\`${newLine}\\` | ${process.platform === \"win32\" ? t(\"New line (Ctrl+Enter on Windows Terminal)\") : t(\"New line\")} |\n| \\`${deleteWordBackward}\\` | ${t(\"Delete word backwards\")} |\n| \\`${deleteWordForward}\\` | ${t(\"Delete word forwards\")} |\n| \\`${deleteToLineStart}\\` | ${t(\"Delete to start of line\")} |\n| \\`${deleteToLineEnd}\\` | ${t(\"Delete to end of line\")} |\n| \\`${yank}\\` | ${t(\"Paste the most-recently-deleted text\")} |\n| \\`${yankPop}\\` | ${t(\"Cycle through the deleted text after pasting\")} |\n| \\`${undo}\\` | ${t(\"Undo\")} |\n\n**${t(\"Other\")}**\n| ${t(\"Key\")} | ${t(\"Action\")} |\n|-----|--------|\n| \\`${tab}\\` | ${t(\"Path completion / accept autocomplete\")} |\n| \\`${interrupt}\\` | ${t(\"Cancel autocomplete / abort streaming\")} |\n| \\`${clear}\\` | ${t(\"Clear editor (first) / exit (second)\")} |\n| \\`${exit}\\` | ${t(\"Exit (when editor is empty)\")} |\n${suspendRow}${cycleThinkingRow}${cycleModelsRow}${selectModelRow}| \\`${expandTools}\\` | ${t(\"Toggle tool output expansion\")} |\n| \\`${toggleThinking}\\` | ${t(\"Toggle thinking block visibility\")} |\n${externalEditorRow}${copyMessageRow}${followUpRow}${dequeueRow}\n| \\`${pasteImage}\\` | ${t(\"Paste image or text from clipboard\")} |\n| \\`/\\` | ${t(\"Slash commands\")} |\n| \\`!\\` | ${t(\"Run bash command\")} |\n| \\`!!\\` | ${t(\"Run bash command (excluded from context)\")} |\n`;\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new DynamicBorder());\n\thost.chatContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", t(\"Keyboard Shortcuts\"))), 1, 0));\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new Markdown(hotkeys.trim(), 1, 1, host.getMarkdownThemeWithSettings()));\n\thost.chatContainer.addChild(new DynamicBorder());\n\thost.ui.requestRender();\n}\n\nexport async function handleClearCommand(host: CommandHandlersHost): Promise<void> {\n\thost.clearStatusIndicator();\n\ttry {\n\t\tconst result = await host.runtimeFacade.newSession();\n\t\tif (result.cancelled) {\n\t\t\treturn;\n\t\t}\n\t\thost.chatContainer.addChild(new Spacer(1));\n\t\thost.chatContainer.addChild(new Text(`${theme.fg(\"accent\", t(\"✓ New session started\"))}`, 1, 1));\n\t\thost.ui.requestRender();\n\t} catch (error: unknown) {\n\t\tawait host.handleFatalRuntimeError(\"Failed to create session\", error);\n\t}\n}\n\nexport function handleDebugCommand(host: CommandHandlersHost): void {\n\tconst width = host.ui.terminal.columns;\n\tconst height = host.ui.terminal.rows;\n\tconst allLines = host.ui.render(width);\n\n\tconst debugLogPath = getDebugLogPath();\n\tconst debugData = [\n\t\tt(\"Debug output at {date}\", { date: new Date().toISOString() }),\n\t\tt(\"Terminal: {columns}x{rows}\", { columns: width, rows: height }),\n\t\tt(\"Total lines: {count}\", { count: allLines.length }),\n\t\t\"\",\n\t\tt(\"=== All rendered lines with visible widths ===\"),\n\t\t...allLines.map((line, idx) => {\n\t\t\tconst vw = visibleWidth(line);\n\t\t\tconst escaped = JSON.stringify(line);\n\t\t\treturn `[${idx}] (w=${vw}) ${escaped}`;\n\t\t}),\n\t\t\"\",\n\t\tt(\"=== Agent messages (JSONL) ===\"),\n\t\t...host.facade\n\t\t\t.getSessionAPI()\n\t\t\t.getMessages()\n\t\t\t.map((msg) => JSON.stringify(msg)),\n\t\t\"\",\n\t].join(\"\\n\");\n\n\tfs.mkdirSync(path.dirname(debugLogPath), { recursive: true });\n\tfs.writeFileSync(debugLogPath, debugData);\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(\n\t\tnew Text(`${theme.fg(\"accent\", t(\"✓ Debug log written\"))}\\n${theme.fg(\"muted\", debugLogPath)}`, 1, 1),\n\t);\n\thost.ui.requestRender();\n}\n\nexport function handleArminSaysHi(host: CommandHandlersHost): void {\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new ArminComponent(host.ui));\n\thost.ui.requestRender();\n}\n\nexport function handleDaxnuts(host: CommandHandlersHost): void {\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new DaxnutsComponent(host.ui));\n\thost.ui.requestRender();\n}\n\n/** Host-visible for ./interactive-auth.ts (S5 slice 3). */\nexport function checkDaxnutsEasterEgg(host: CommandHandlersHost, model: { provider: string; id: string }): void {\n\tif (model.provider === \"opencode\" && model.id.toLowerCase().includes(\"kimi-k2.5\")) {\n\t\thost.handleDaxnuts();\n\t}\n}\n\nexport async function handleBashCommand(\n\thost: CommandHandlersHost,\n\tcommand: string,\n\texcludeFromContext = false,\n): Promise<void> {\n\t// Dispatch the user.bash lifecycle facts, then execute locally.\n\thost.facade.emitUserBash(excludeFromContext);\n\n\t// Normal execution path\n\tconst isDeferred = host.facade.isStreaming();\n\thost.bashComponent = new BashExecutionComponent(command, host.ui, excludeFromContext);\n\n\tif (isDeferred) {\n\t\t// Show in pending area when agent is streaming\n\t\thost.pendingMessagesContainer.addChild(host.bashComponent);\n\t\thost.pendingBashComponents.push(host.bashComponent);\n\t} else {\n\t\t// Show in chat immediately when agent is idle\n\t\thost.chatContainer.addChild(host.bashComponent);\n\t}\n\thost.ui.requestRender();\n\n\ttry {\n\t\tconst result = await host.facade.executeBash(\n\t\t\tcommand,\n\t\t\t(chunk) => {\n\t\t\t\tif (host.bashComponent) {\n\t\t\t\t\thost.bashComponent.appendOutput(chunk);\n\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ excludeFromContext },\n\t\t);\n\n\t\tif (host.bashComponent) {\n\t\t\thost.bashComponent.setComplete(\n\t\t\t\tresult.exitCode,\n\t\t\t\tresult.cancelled,\n\t\t\t\tresult.truncated ? ({ truncated: true, content: result.output } as TruncationResult) : undefined,\n\t\t\t\tresult.fullOutputPath,\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\tif (host.bashComponent) {\n\t\t\thost.bashComponent.setComplete(undefined, false);\n\t\t}\n\t\thost.showError(\n\t\t\tt(\"Bash command failed: {error}\", {\n\t\t\t\terror: error instanceof Error ? error.message : t(\"Unknown error\"),\n\t\t\t}),\n\t\t);\n\t}\n\n\thost.bashComponent = undefined;\n\thost.ui.requestRender();\n}\n\nexport async function handleCompactCommand(host: CommandHandlersHost, customInstructions?: string): Promise<void> {\n\thost.clearStatusIndicator();\n\n\ttry {\n\t\tawait host.facade.compact(customInstructions);\n\t} catch {\n\t\t// Ignore, will be emitted as an event\n\t}\n}\n\n/** /clean-sessions:会话清理两阶段流程;输出/确认走宿主既有机制(设计 §7)。 */\nexport async function handleCleanSessionsCommand(host: CommandHandlersHost, arg: string | undefined): Promise<void> {\n\tawait runCleanSessions(\n\t\t{\n\t\t\tshowMessage: (text) => host.showStatus(text),\n\t\t\tshowError: (text) => host.showError(text),\n\t\t\tconfirm: (title, message) => host.showDialogConfirm(title, message),\n\t\t},\n\t\targ,\n\t);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"interactive-commands.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/interactive-commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Slash-command dispatch table for the interactive mode editor submit handler,\n * plus the command-handler implementations behind it (拆分 slice 1).\n *\n * The dispatch table was extracted from InteractiveMode.setupEditorSubmitHandler;\n * entry order mirrors the original if-chain and each run body mirrors its\n * branch verbatim (await/no-await and editor clearing order included). The\n * handler functions below were moved verbatim out of InteractiveMode (拆分\n * slice 1, docs/plans/interactive-mode拆分计划.md); the mode keeps same-name\n * delegate methods so prototype-level tests and the dispatch table keep\n * working.\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Component, EditorComponent, Keybinding, MarkdownTheme, TUI } from \"@agent-forge/tui\";\nimport { Container, Markdown, Spacer, Text, TuiAltScreen, visibleWidth } from \"@agent-forge/tui\";\nimport { getDebugLogPath } from \"../../config.ts\";\nimport { SessionImportFileNotFoundError } from \"../../core/agent-session-runtime.ts\";\nimport { computeCacheWaste } from \"../../core/cache-stats.ts\";\nimport type { CodingAgentFacadeV1, CodingAgentRuntimeFacadeV1 } from \"../../core/facade/index.ts\";\nimport { t } from \"../../core/i18n/index.ts\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../core/keybindings.ts\";\nimport type { ThemeMetaV1 } from \"../../core/resource-loader.ts\";\nimport { runCleanSessions } from \"../../core/session-cleanup.ts\";\nimport { MissingSessionCwdError } from \"../../core/session-cwd.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { BUILTIN_SLASH_COMMANDS } from \"../../core/slash-commands.ts\";\nimport type { TruncationResult } from \"../../core/tools/truncate.ts\";\nimport { getUsageCostBreakdown } from \"../../core/usage-totals.ts\";\nimport type { MetricsSnapshotV1, ObservabilityMetricsOptions } from \"../../plugins/observability-types.ts\";\nimport { loadThemeFromPath, setRegisteredThemes, type Theme, theme } from \"../../theme/theme.ts\";\nimport type { InteractiveThemeController } from \"../../theme/theme-controller.ts\";\nimport { getChangelogPath, parseChangelog, splitChangelogPagerBlocksV1 } from \"../../utils/changelog.ts\";\nimport { copyToClipboard } from \"../../utils/clipboard.ts\";\nimport { ArminComponent } from \"./components/armin.ts\";\nimport { BashExecutionComponent } from \"./components/bash-execution.ts\";\nimport { ChangelogPager } from \"./components/changelog-pager.ts\";\nimport { DaxnutsComponent } from \"./components/daxnuts.ts\";\nimport { DynamicBorder } from \"./components/dynamic-border.ts\";\nimport { formatTokens } from \"./components/footer.ts\";\nimport { keyDisplayText } from \"./components/keybinding-hints.ts\";\nimport { isExpandable } from \"./interactive-dialog-ui.ts\";\nimport { formatMetricsSnapshot } from \"./interactive-format.ts\";\n\n/**\n * Narrow facade InteractiveMode exposes to the dispatch table. Entries resolve\n * handlers through this interface at dispatch time, so instance-level\n * overrides keep working.\n */\nexport interface CommandDispatchHost {\n\tsetEditorText(text: string): void;\n\tshowSettingsSelector(): void;\n\tshowModelsSelector(): void;\n\thandleModelCommand(searchTerm?: string): Promise<void>;\n\thandleThinkingCommand(searchTerm?: string): void;\n\thandleSuiteCommand(suiteId?: string): void;\n\thandleExportCommand(text: string): Promise<void>;\n\thandleImportCommand(text: string): Promise<void>;\n\thandleCopyCommand(options?: { flashConfirmation?: boolean; preferSelection?: boolean }): Promise<void>;\n\thandleNameCommand(text: string): void;\n\thandleSessionCommand(): void;\n\t/** Interrupt grading (§4.2): terminate every background subagent; the\n\t * /stop-agents command's single execution face. */\n\thandleStopAgentsCommand(): void;\n\thandleMetricsCommand(turnsArg: string | undefined): void;\n\thandleChangelogCommand(): void;\n\thandleHelpCommand(): void;\n\thandleHotkeysCommand(): void;\n\tshowUserMessageSelector(): void;\n\thandleCloneCommand(): Promise<void>;\n\tshowTreeSelector(initialSelectedId?: string): void;\n\thandleLoginCommand(providerRef?: string): Promise<void>;\n\tshowOAuthSelector(mode: \"login\" | \"logout\"): Promise<void>;\n\thandleClearCommand(): Promise<void>;\n\thandleCompactCommand(customInstructions?: string): Promise<void>;\n\t/** /clean-sessions:会话清理两阶段流程(预览 → 宿主确认 → 执行,设计 §7)。 */\n\thandleCleanSessionsCommand(arg: string | undefined): Promise<void>;\n\thandleReloadCommand(): Promise<void>;\n\thandleDebugCommand(): void;\n\thandleArminSaysHi(): void;\n\tshowSessionSelector(): void;\n\tshowAgentsView(): void;\n\thandleQueueCommand(text: string | undefined): Promise<void>;\n\thandleDequeueCommand(): void;\n\thandleQueueListCommand(): void;\n\thandleEditorCommand(): Promise<void>;\n\tshutdown(options?: { fromSignal?: boolean }): Promise<void>;\n\tshowWorkflowView(): void;\n}\n\n/**\n * Host surface for the command-handler implementations in this module (拆分\n * slice 1): everything the handler bodies touch on the mode beyond the\n * dispatch signatures CommandDispatchHost already declares. InteractiveMode\n * implements this interface; its same-name delegate methods keep\n * prototype-level test bindings and the dispatch table working.\n *\n * Internal collaboration interface (same pattern as CommandDispatchHost): not\n * a plugin API and not exported from the package root.\n */\nexport interface CommandHandlersHost extends CommandDispatchHost {\n\t/** Stable facade (阶段 1 slice 1). */\n\tfacade: CodingAgentFacadeV1;\n\t/** Stable runtime facade (阶段 4 slice 3). */\n\truntimeFacade: CodingAgentRuntimeFacadeV1;\n\t/** Constructor-injected settings (D-047). */\n\tsettingsManager: SettingsManager;\n\t/** Mode options; /metrics reads the observability plugin source here. */\n\toptions: { getMetrics?: (options?: ObservabilityMetricsOptions) => MetricsSnapshotV1 | undefined };\n\tkeybindings: KeybindingsManager;\n\tthemeController: InteractiveThemeController;\n\n\t// UI surfaces.\n\tui: TUI;\n\teditor: EditorComponent;\n\teditorContainer: Container;\n\tchatContainer: Container;\n\tpendingMessagesContainer: Container;\n\n\t// Mutable mode state the handler bodies read or write.\n\tbuiltInHeader: Component | undefined;\n\ttoolOutputExpanded: boolean;\n\thideThinkingBlock: boolean;\n\toutputPad: number;\n\tbashComponent: BashExecutionComponent | undefined;\n\tpendingBashComponents: BashExecutionComponent[];\n\n\t// Status, dialog, and rebuild helpers.\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\tshowWarning(message: string): void;\n\tclearStatusIndicator(): void;\n\tresetDialogUI(): void;\n\trebuildChatFromMessages(): void;\n\tapplyRuntimeSettings(): void;\n\tsetupAutocompleteProvider(): void;\n\tshowLoadedResources(options?: { force?: boolean; showDiagnosticsWhenQuiet?: boolean }): void;\n\tshowDialogConfirm(title: string, message: string): Promise<boolean>;\n\tpromptForMissingSessionCwd(error: MissingSessionCwdError): Promise<string | undefined>;\n\thandleFatalRuntimeError(title: string, error: unknown): Promise<void>;\n\tgetMarkdownThemeWithSettings(): MarkdownTheme;\n\t/** Daxnuts easter-egg render; resolved through the host so instance-level\n\t * overrides keep working (original called this.handleDaxnuts()). */\n\thandleDaxnuts(): void;\n}\n\nexport interface CommandDispatchEntry {\n\t/** Command word including the leading slash, e.g. \"/model\". */\n\tcommand: string;\n\t/**\n\t * Slice offset of the optional argument for prefix commands\n\t * (\"/model claude\" -> text.slice(7).trim()); omit for exact-match commands.\n\t */\n\targOffset?: number;\n\t/** Report a blank (empty after trim) argument as undefined. */\n\tblankArgToUndefined?: boolean;\n\t/** Clear the editor text before running the handler instead of after. */\n\tclearEditorBeforeRun?: boolean;\n\t/** Handler; receives the full submitted text and the extracted argument. */\n\trun: (host: CommandDispatchHost, text: string, arg: string | undefined) => Promise<void> | void;\n}\n\nexport interface CommandDispatchMatch {\n\tentry: CommandDispatchEntry;\n\targ: string | undefined;\n}\n\nexport const COMMAND_DISPATCH_ENTRIES: readonly CommandDispatchEntry[] = [\n\t{\n\t\tcommand: \"/settings\",\n\t\trun: (host) => {\n\t\t\thost.showSettingsSelector();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/scoped-models\",\n\t\tclearEditorBeforeRun: true,\n\t\trun: async (host) => {\n\t\t\tawait host.showModelsSelector();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/model\",\n\t\targOffset: 7,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => host.handleModelCommand(arg),\n\t},\n\t{\n\t\tcommand: \"/thinking\",\n\t\targOffset: 10,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => {\n\t\t\thost.handleThinkingCommand(arg);\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/suite\",\n\t\targOffset: 7,\n\t\tblankArgToUndefined: true,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => {\n\t\t\thost.handleSuiteCommand(arg);\n\t\t},\n\t},\n\t// /export, /import and /name are prefix commands whose handlers parse the\n\t// full text themselves; argOffset only enables prefix matching here.\n\t{ command: \"/export\", argOffset: 8, run: (host, text) => host.handleExportCommand(text) },\n\t{ command: \"/import\", argOffset: 8, run: (host, text) => host.handleImportCommand(text) },\n\t{ command: \"/copy\", run: (host) => host.handleCopyCommand() },\n\t{\n\t\tcommand: \"/name\",\n\t\targOffset: 6,\n\t\trun: (host, text) => {\n\t\t\thost.handleNameCommand(text);\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/session\",\n\t\trun: (host) => {\n\t\t\thost.handleSessionCommand();\n\t\t},\n\t},\n\t// Interrupt grading (设计 子代理运行时 §4.2, 2026-10-02): the only explicit\n\t// face for terminating background subagents — Esc never cancels them.\n\t{\n\t\tcommand: \"/stop-agents\",\n\t\trun: (host) => {\n\t\t\thost.handleStopAgentsCommand();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/metrics\",\n\t\targOffset: 9,\n\t\tblankArgToUndefined: true,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => {\n\t\t\thost.handleMetricsCommand(arg);\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/changelog\",\n\t\trun: (host) => {\n\t\t\thost.handleChangelogCommand();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/help\",\n\t\trun: (host) => {\n\t\t\thost.handleHelpCommand();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/hotkeys\",\n\t\trun: (host) => {\n\t\t\thost.handleHotkeysCommand();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/fork\",\n\t\trun: (host) => {\n\t\t\thost.showUserMessageSelector();\n\t\t},\n\t},\n\t{ command: \"/clone\", clearEditorBeforeRun: true, run: (host) => host.handleCloneCommand() },\n\t{\n\t\tcommand: \"/tree\",\n\t\trun: (host) => {\n\t\t\thost.showTreeSelector();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/login\",\n\t\targOffset: 7,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => host.handleLoginCommand(arg),\n\t},\n\t// Original branch does not await showOAuthSelector; keep it fire-and-forget.\n\t{\n\t\tcommand: \"/logout\",\n\t\trun: (host) => {\n\t\t\tvoid host.showOAuthSelector(\"logout\");\n\t\t},\n\t},\n\t{ command: \"/new\", clearEditorBeforeRun: true, run: (host) => host.handleClearCommand() },\n\t{\n\t\tcommand: \"/compact\",\n\t\targOffset: 9,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => host.handleCompactCommand(arg),\n\t},\n\t{\n\t\tcommand: \"/clean-sessions\",\n\t\targOffset: 16,\n\t\tblankArgToUndefined: true,\n\t\tclearEditorBeforeRun: true,\n\t\trun: (host, _text, arg) => host.handleCleanSessionsCommand(arg),\n\t},\n\t{ command: \"/reload\", clearEditorBeforeRun: true, run: (host) => host.handleReloadCommand() },\n\t{\n\t\tcommand: \"/debug\",\n\t\trun: (host) => {\n\t\t\thost.handleDebugCommand();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/arminsayshi\",\n\t\trun: (host) => {\n\t\t\thost.handleArminSaysHi();\n\t\t},\n\t},\n\t{\n\t\tcommand: \"/resume\",\n\t\trun: (host) => {\n\t\t\thost.showSessionSelector();\n\t\t},\n\t},\n\t// Read-only subagent session browser (view-family command: sync, no editor\n\t// clearing), grouped with /resume ahead of the /workflow view overlay.\n\t{\n\t\tcommand: \"/agents\",\n\t\trun: (host) => {\n\t\t\thost.showAgentsView();\n\t\t},\n\t},\n\t// Follow-up queue face (single-entry iron law: /queue replaces the shipped\n\t// ctrl+q default). A bare /queue prints usage — the editor buffer is\n\t// cleared on submit before the command runs, so there is no editor\n\t// content to queue (the configurable keybinding keeps that behavior).\n\t{\n\t\tcommand: \"/queue\",\n\t\targOffset: 7,\n\t\tblankArgToUndefined: true,\n\t\trun: (host, _text, arg) => host.handleQueueCommand(arg),\n\t},\n\t{ command: \"/dequeue\", run: (host) => host.handleDequeueCommand() },\n\t// Queue manager overlay (single entry point: the /queue-list command;\n\t// reorder/remove/restore are in-component keys of the overlay itself).\n\t{ command: \"/queue-list\", run: (host) => host.handleQueueListCommand() },\n\t{ command: \"/editor\", run: (host) => host.handleEditorCommand() },\n\t{ command: \"/quit\", clearEditorBeforeRun: true, run: (host) => host.shutdown() },\n\t// Exact-match only: every other /workflow subcommand (why/log/...) keeps\n\t// flowing to the plugin's text command path untouched (§9 L2).\n\t{\n\t\tcommand: \"/workflow view\",\n\t\trun: (host) => {\n\t\t\thost.showWorkflowView();\n\t\t},\n\t},\n];\n\n/** Find the entry matching text: exact command, or command prefix plus argument. */\nexport function matchCommandDispatchEntry(\n\tentries: readonly CommandDispatchEntry[],\n\ttext: string,\n): CommandDispatchMatch | undefined {\n\tfor (const entry of entries) {\n\t\tif (text === entry.command) {\n\t\t\treturn { entry, arg: undefined };\n\t\t}\n\t\tif (entry.argOffset === undefined || !text.startsWith(`${entry.command} `)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst arg = text.slice(entry.argOffset).trim();\n\t\treturn { entry, arg: entry.blankArgToUndefined && arg.length === 0 ? undefined : arg };\n\t}\n\treturn undefined;\n}\n\n/** Minimal command shape the completion filter works on (mirrors SlashCommand). */\nexport interface CompletionCommandInput {\n\treadonly name: string;\n\treadonly description: string;\n\treadonly argumentHint?: string;\n}\n\n/**\n * Capability commands that collide with a builtin, prompt-template, or skill\n * command name are rejected by AgentSession at invocation time rather than\n * silently resolving to one source, so the colliding capability command is\n * withheld from the completion list entirely (both sides stay executable via\n * their own paths; neither is offered as an ambiguous completion).\n */\nexport function filterCapabilityCommandsForCompletions(\n\tcapabilityCommands: readonly CompletionCommandInput[],\n\treservedNames: readonly Iterable<string>[],\n): CompletionCommandInput[] {\n\tconst reserved = new Set<string>();\n\tfor (const names of reservedNames) for (const name of names) reserved.add(name);\n\treturn capabilityCommands\n\t\t.filter((command) => !reserved.has(command.name))\n\t\t.map((command) => ({\n\t\t\tname: command.name,\n\t\t\tdescription: command.description,\n\t\t\t...(command.argumentHint !== undefined ? { argumentHint: command.argumentHint } : {}),\n\t\t}));\n}\n\n/**\n * Rebuilds real Theme instances from the resource loader's ThemeMetaV1\n * projection for the theme registry (B-S2b v1). Registration points\n * (constructor, runtime session start, reload) need loadable instances —\n * setRegisteredThemes stores what it is given and getThemeByName returns the\n * registered instance first — while the loader surface now projects only\n * metadata. Re-loading is synchronous and deterministic per sourcePath;\n * theme names come from the same JSON the loader parsed, so meta.name ===\n * Theme.name and the registry keeps its own name-filter/dedupe semantics.\n *\n * Contract: a meta without a sourcePath (or one whose file no longer parses)\n * throws — themesOverride host inputs must always carry a loadable\n * sourcePath. This adds one extra parse per theme at registration time\n * (constructor / session start / reload, not per turn).\n *\n * Re-exported from ./interactive-mode.ts for existing consumers of that module.\n */\nexport function buildRegisteredThemes(themes: readonly ThemeMetaV1[]): Theme[] {\n\treturn themes.map((meta) => loadThemeFromPath(meta.sourcePath));\n}\n\nexport async function handleReloadCommand(host: CommandHandlersHost): Promise<void> {\n\tif (host.facade.isStreaming()) {\n\t\thost.showWarning(t(\"Wait for the current response to finish before reloading.\"));\n\t\treturn;\n\t}\n\tif (host.facade.isCompacting()) {\n\t\thost.showWarning(t(\"Wait for compaction to finish before reloading.\"));\n\t\treturn;\n\t}\n\n\thost.resetDialogUI();\n\n\tconst reloadBox = new Container();\n\tconst borderColor = (s: string) => theme.fg(\"border\", s);\n\treloadBox.addChild(new DynamicBorder(borderColor));\n\treloadBox.addChild(new Spacer(1));\n\treloadBox.addChild(\n\t\tnew Text(theme.fg(\"muted\", t(\"Reloading keybindings, skills, prompts, themes, and context files...\")), 1, 0),\n\t);\n\treloadBox.addChild(new Spacer(1));\n\treloadBox.addChild(new DynamicBorder(borderColor));\n\n\tconst previousEditor = host.editor;\n\thost.editorContainer.clear();\n\thost.editorContainer.addChild(reloadBox);\n\thost.ui.setFocus(reloadBox);\n\thost.ui.requestRender(true);\n\tawait new Promise((resolve) => process.nextTick(resolve));\n\n\tconst dismissReloadBox = (editor: Component) => {\n\t\thost.editorContainer.clear();\n\t\thost.editorContainer.addChild(editor);\n\t\thost.ui.setFocus(editor);\n\t\thost.ui.requestRender();\n\t};\n\n\tlet chatRestoredBeforeSessionStart = false;\n\tlet reloadBoxDismissed = false;\n\tconst restoreChatBeforeSessionStart = () => {\n\t\tif (chatRestoredBeforeSessionStart) {\n\t\t\treturn;\n\t\t}\n\t\thost.hideThinkingBlock = host.settingsManager.getHideThinkingBlock();\n\t\thost.outputPad = host.settingsManager.getOutputPad();\n\t\thost.rebuildChatFromMessages();\n\t\tchatRestoredBeforeSessionStart = true;\n\t};\n\n\ttry {\n\t\tawait host.facade.reload({ beforeSessionStart: restoreChatBeforeSessionStart });\n\t\trestoreChatBeforeSessionStart();\n\t\thost.keybindings.reload();\n\t\tconst activeHeader = host.builtInHeader;\n\t\tif (isExpandable(activeHeader)) {\n\t\t\tactiveHeader.setExpanded(host.toolOutputExpanded);\n\t\t}\n\t\tsetRegisteredThemes(buildRegisteredThemes(host.facade.getThemes().themes));\n\t\thost.applyRuntimeSettings();\n\t\tawait host.themeController.applyFromSettings();\n\t\thost.setupAutocompleteProvider();\n\t\thost.showLoadedResources({\n\t\t\tforce: false,\n\t\t\tshowDiagnosticsWhenQuiet: true,\n\t\t});\n\t\tconst modelsJsonError = host.facade.getModelsError();\n\t\tif (modelsJsonError) {\n\t\t\thost.showError(t(\"models.json error: {error}\", { error: modelsJsonError }));\n\t\t}\n\t\thost.showStatus(t(\"Reloaded keybindings, skills, prompts, themes, and context files\"));\n\t\tdismissReloadBox(host.editor as Component);\n\t\treloadBoxDismissed = true;\n\t} catch (error) {\n\t\t// v8 ignore next -- reloadBoxDismissed 置位后 catch 前无可抛出点,恒走 dismiss 分支\n\t\tif (!reloadBoxDismissed) {\n\t\t\tdismissReloadBox(previousEditor as Component);\n\t\t}\n\t\thost.showError(t(\"Reload failed: {error}\", { error: error instanceof Error ? error.message : String(error) }));\n\t}\n}\n\nexport async function handleExportCommand(host: CommandHandlersHost, text: string): Promise<void> {\n\tconst outputPath = getPathCommandArgument(text, \"/export\");\n\n\ttry {\n\t\tif (outputPath?.endsWith(\".jsonl\")) {\n\t\t\tconst filePath = host.facade.exportToJsonl(outputPath);\n\t\t\thost.showStatus(t(\"Session exported to: {path}\", { path: filePath }));\n\t\t} else {\n\t\t\tconst filePath = await host.facade.exportToHtml(outputPath, {\n\t\t\t\tthemeName: theme.name,\n\t\t\t});\n\t\t\thost.showStatus(t(\"Session exported to: {path}\", { path: filePath }));\n\t\t}\n\t} catch (error: unknown) {\n\t\thost.showError(\n\t\t\tt(\"Failed to export session: {error}\", {\n\t\t\t\terror: error instanceof Error ? error.message : t(\"Unknown error\"),\n\t\t\t}),\n\t\t);\n\t}\n}\n\nexport function getPathCommandArgument(text: string, command: \"/export\" | \"/import\"): string | undefined {\n\tif (text === command) {\n\t\treturn undefined;\n\t}\n\tif (!text.startsWith(`${command} `)) {\n\t\treturn undefined;\n\t}\n\n\tconst argsString = text.slice(command.length + 1).trimStart();\n\tif (!argsString) {\n\t\treturn undefined;\n\t}\n\n\tconst firstChar = argsString[0];\n\tif (firstChar === '\"' || firstChar === \"'\") {\n\t\tconst closingQuoteIndex = argsString.indexOf(firstChar, 1);\n\t\tif (closingQuoteIndex < 0) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn argsString.slice(1, closingQuoteIndex);\n\t}\n\n\tconst firstWhitespaceIndex = argsString.search(/\\s/);\n\tif (firstWhitespaceIndex < 0) {\n\t\treturn argsString;\n\t}\n\treturn argsString.slice(0, firstWhitespaceIndex);\n}\n\nexport async function handleImportCommand(host: CommandHandlersHost, text: string): Promise<void> {\n\tconst inputPath = getPathCommandArgument(text, \"/import\");\n\tif (!inputPath) {\n\t\thost.showError(t(\"Usage: /import <path.jsonl>\"));\n\t\treturn;\n\t}\n\n\tconst confirmed = await host.showDialogConfirm(\n\t\tt(\"Import session\"),\n\t\tt(\"Replace current session with {path}?\", { path: inputPath }),\n\t);\n\tif (!confirmed) {\n\t\thost.showStatus(t(\"Import cancelled\"));\n\t\treturn;\n\t}\n\n\ttry {\n\t\thost.clearStatusIndicator();\n\t\tconst result = await host.runtimeFacade.importFromJsonl(inputPath);\n\t\tif (result.cancelled) {\n\t\t\thost.showStatus(t(\"Import cancelled\"));\n\t\t\treturn;\n\t\t}\n\t\thost.showStatus(t(\"Session imported from: {path}\", { path: inputPath }));\n\t} catch (error: unknown) {\n\t\tif (error instanceof MissingSessionCwdError) {\n\t\t\tconst selectedCwd = await host.promptForMissingSessionCwd(error);\n\t\t\tif (!selectedCwd) {\n\t\t\t\thost.showStatus(t(\"Import cancelled\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst result = await host.runtimeFacade.importFromJsonl(inputPath, selectedCwd);\n\t\t\tif (result.cancelled) {\n\t\t\t\thost.showStatus(t(\"Import cancelled\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.showStatus(t(\"Session imported from: {path}\", { path: inputPath }));\n\t\t\treturn;\n\t\t}\n\t\tif (error instanceof SessionImportFileNotFoundError) {\n\t\t\thost.showError(t(\"Failed to import session: {error}\", { error: error.message }));\n\t\t\treturn;\n\t\t}\n\t\tawait host.handleFatalRuntimeError(t(\"Failed to import session\"), error);\n\t}\n}\n\nexport async function handleCopyCommand(\n\thost: CommandHandlersHost,\n\toptions: { flashConfirmation?: boolean; preferSelection?: boolean } = {},\n): Promise<void> {\n\tif (\n\t\toptions.preferSelection &&\n\t\thost.ui instanceof TuiAltScreen &&\n\t\t!host.ui.getCopyOnSelect() &&\n\t\thost.ui.hasActiveSelection()\n\t) {\n\t\tawait host.ui.copyActiveSelectionToClipboard();\n\t\treturn;\n\t}\n\n\tconst text = host.facade.getLastAssistantText();\n\tif (!text) {\n\t\thost.showError(t(\"No agent messages to copy yet.\"));\n\t\treturn;\n\t}\n\n\ttry {\n\t\tawait copyToClipboard(text);\n\t\tif (options.flashConfirmation && host.ui instanceof TuiAltScreen) {\n\t\t\thost.ui.flash(t(\"Copied!\"));\n\t\t} else {\n\t\t\thost.showStatus(t(\"Copied last agent message to clipboard\"));\n\t\t}\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport function handleNameCommand(host: CommandHandlersHost, text: string): void {\n\tconst name = text.replace(/^\\/name\\s*/, \"\").trim();\n\tif (!name) {\n\t\tconst currentName = host.facade.getSessionName();\n\t\tif (currentName) {\n\t\t\thost.chatContainer.addChild(new Spacer(1));\n\t\t\thost.chatContainer.addChild(new Text(theme.fg(\"dim\", t(\"Session name: {name}\", { name: currentName })), 1, 0));\n\t\t} else {\n\t\t\thost.showWarning(t(\"Usage: /name <name>\"));\n\t\t}\n\t\thost.ui.requestRender();\n\t\treturn;\n\t}\n\n\thost.facade.setSessionName(name);\n\tconst sessionName = host.facade.getSessionName();\n\tif (sessionName !== name) {\n\t\thost.showWarning(\n\t\t\tt(\"Session name was normalized from {from} to {to}\", {\n\t\t\t\tfrom: JSON.stringify(name),\n\t\t\t\tto: JSON.stringify(sessionName),\n\t\t\t}),\n\t\t);\n\t}\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(\n\t\tnew Text(theme.fg(\"dim\", t(\"Session name set: {name}\", { name: sessionName ?? name })), 1, 0),\n\t);\n\thost.ui.requestRender();\n}\n\n/**\n * 中断分级(设计 子代理运行时 §4.2,2026-10-02):/stop-agents 的唯一执行\n * 面——终止全部后台子代理(不触当前回合)。Esc 从不做这件事。\n */\nexport function handleStopAgentsCommand(host: CommandHandlersHost): void {\n\tconst active = host.facade.getActiveAgentTaskCount();\n\tif (active === 0) {\n\t\thost.showStatus(t(\"No background subagents running\"));\n\t\treturn;\n\t}\n\thost.facade.abortAgentTasks();\n\thost.showStatus(\n\t\tactive === 1\n\t\t\t? t(\"Terminated {count} background subagent\", { count: active })\n\t\t\t: t(\"Terminated {count} background subagents\", { count: active }),\n\t);\n}\n\nexport function handleSessionCommand(host: CommandHandlersHost): void {\n\tconst stats = host.facade.getSessionStats();\n\tconst sessionName = host.facade.getSessionName();\n\tconst entries = host.facade.getEntries();\n\tconst cacheWaste = computeCacheWaste(entries, host.facade.getModelPriceSource());\n\n\t// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`\n\t// resolves to a concrete responseModel). Usage without model attribution is\n\t// grouped separately so the breakdown reconciles with the session total.\n\tconst usageBreakdown = getUsageCostBreakdown(entries);\n\n\tlet info = `${theme.bold(t(\"Session Info\"))}\\n\\n`;\n\tif (sessionName) {\n\t\tinfo += `${theme.fg(\"dim\", t(\"Name:\"))} ${sessionName}\\n`;\n\t}\n\tinfo += `${theme.fg(\"dim\", t(\"File:\"))} ${stats.sessionFile ?? t(\"In-memory\")}\\n`;\n\tinfo += `${theme.fg(\"dim\", t(\"ID:\"))} ${stats.sessionId}\\n\\n`;\n\tinfo += `${theme.bold(t(\"Messages\"))}\\n`;\n\tinfo += `${theme.fg(\"dim\", t(\"Total:\"))} ${stats.totalMessages}\\n`;\n\tinfo += `${theme.fg(\"dim\", t(\"User:\"))} ${stats.userMessages}\\n`;\n\tinfo += `${theme.fg(\"dim\", t(\"Assistant:\"))} ${stats.assistantMessages}\\n`;\n\tinfo += `${theme.fg(\"dim\", t(\"Tools:\"))} ${t(\"{calls} calls, {results} results\", {\n\t\tcalls: stats.toolCalls,\n\t\tresults: stats.toolResults,\n\t})}\\n\\n`;\n\tinfo += `${theme.bold(t(\"Tokens\"))}\\n`;\n\t// \"Input\" is the full prompt volume. With cache activity, split it into\n\t// cached (served from cache) vs uncached (everything else) - the only\n\t// provider-independent split. Cache writes, where reported, are a detail\n\t// of the uncached portion.\n\tconst { input, cacheRead, cacheWrite } = stats.tokens;\n\tconst promptTokens = input + cacheRead + cacheWrite;\n\tinfo += `${theme.fg(\"dim\", t(\"Input:\"))} ${promptTokens.toLocaleString()}\\n`;\n\tif (promptTokens > 0 && (cacheRead > 0 || cacheWrite > 0)) {\n\t\tconst hitRate = theme.fg(\"dim\", `(${((cacheRead / promptTokens) * 100).toFixed(1)}%)`);\n\t\tinfo += ` ${theme.fg(\"dim\", t(\"Cached:\"))} ${cacheRead.toLocaleString()} ${hitRate}\\n`;\n\t\tconst written =\n\t\t\tcacheWrite > 0\n\t\t\t\t? ` ${theme.fg(\"dim\", t(\"({count} written to cache)\", { count: cacheWrite.toLocaleString() }))}`\n\t\t\t\t: \"\";\n\t\tinfo += ` ${theme.fg(\"dim\", t(\"Uncached:\"))} ${(input + cacheWrite).toLocaleString()}${written}\\n`;\n\t}\n\tinfo += `${theme.fg(\"dim\", t(\"Output:\"))} ${stats.tokens.output.toLocaleString()}\\n`;\n\tinfo += `${theme.fg(\"dim\", t(\"Total:\"))} ${stats.tokens.total.toLocaleString()}\\n`;\n\n\t// 双维度统计(用户裁决 2026-09-25):总计之外按供应商/模型分桶——缓存读/写、\n\t// 命中率、费用逐桶可见。此前该拆分门控在 cost>0 且多桶才渲染,单价为 0 的\n\t// 自定义供应商(如自建代理)永远看不到。\n\tif (usageBreakdown.length > 0) {\n\t\tinfo += `\\n${theme.bold(t(\"By Provider/Model\"))}\\n`;\n\t\tfor (const entry of usageBreakdown) {\n\t\t\tconst bucket = entry.totals;\n\t\t\tconst promptBucket = bucket.input + bucket.cacheRead + bucket.cacheWrite;\n\t\t\tlet line = ` ${theme.bold(entry.key)}`;\n\t\t\tif (promptBucket > 0) {\n\t\t\t\tline += `\\n ${theme.fg(\"dim\", t(\"Input:\"))} ${promptBucket.toLocaleString()}`;\n\t\t\t\tif (bucket.cacheRead > 0 || bucket.cacheWrite > 0) {\n\t\t\t\t\tconst rate = promptBucket > 0 ? ((bucket.cacheRead / promptBucket) * 100).toFixed(1) : \"0.0\";\n\t\t\t\t\tline += ` ${theme.fg(\"dim\", t(\"(cache {rate}%, W {written})\", { rate, written: bucket.cacheWrite.toLocaleString() }))}`;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (bucket.output > 0) line += `\\n ${theme.fg(\"dim\", t(\"Output:\"))} ${bucket.output.toLocaleString()}`;\n\t\t\tif (entry.cost > 0) line += ` ${theme.fg(\"dim\", `$${entry.cost.toFixed(3)}`)}`;\n\t\t\tinfo += `${line}\\n`;\n\t\t}\n\t}\n\n\tif (stats.cost > 0 || cacheWaste.missedTokens > 0) {\n\t\tinfo += `\\n${theme.bold(t(\"Cost\"))}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", t(\"Total:\"))} $${stats.cost.toFixed(3)}`;\n\t\tif (usageBreakdown.length > 1) {\n\t\t\tfor (const entry of usageBreakdown) {\n\t\t\t\tinfo += `\\n ${theme.fg(\"dim\", `${entry.key}:`)} $${entry.cost.toFixed(3)} ${theme.fg(\"dim\", t(\"({tokens} tokens)\", { tokens: formatTokens(entry.tokens) }))}`;\n\t\t\t}\n\t\t}\n\t\tif (cacheWaste.missedTokens > 0) {\n\t\t\tconst missLabel =\n\t\t\t\tcacheWaste.missCount === 1\n\t\t\t\t\t? t(\"{count} miss\", { count: cacheWaste.missCount })\n\t\t\t\t\t: t(\"{count} misses\", { count: cacheWaste.missCount });\n\t\t\tconst detail = t(\"{tokens} tokens, {misses}\", {\n\t\t\t\ttokens: cacheWaste.missedTokens.toLocaleString(),\n\t\t\t\tmisses: missLabel,\n\t\t\t});\n\t\t\tinfo +=\n\t\t\t\tcacheWaste.missedCost >= 0.0001\n\t\t\t\t\t? `\\n${theme.fg(\"dim\", t(\"Cache Re-billed:\"))} $${cacheWaste.missedCost.toFixed(3)} ${theme.fg(\"dim\", `(${detail})`)}`\n\t\t\t\t\t: `\\n${theme.fg(\"dim\", t(\"Cache Re-billed:\"))} ${detail}`;\n\t\t}\n\t}\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new Text(info, 1, 0));\n\thost.ui.requestRender();\n}\n\nexport function handleMetricsCommand(host: CommandHandlersHost, turnsArg: string | undefined): void {\n\tconst provider = host.options.getMetrics;\n\tif (!provider) {\n\t\thost.showWarning(t(\"Metrics are not available: the observability plugin is not mounted in this session\"));\n\t\treturn;\n\t}\n\n\tlet windowTurns: number | undefined;\n\tif (turnsArg !== undefined) {\n\t\tconst parsed = Number.parseInt(turnsArg, 10);\n\t\tif (!Number.isSafeInteger(parsed) || parsed < 1) {\n\t\t\thost.showWarning(t(\"Invalid turns: {turns}. Usage: /metrics [turns]\", { turns: turnsArg }));\n\t\t\treturn;\n\t\t}\n\t\twindowTurns = parsed;\n\t}\n\n\tlet snapshot: MetricsSnapshotV1;\n\ttry {\n\t\tconst result = provider({ windowTurns });\n\t\tif (result === undefined) {\n\t\t\thost.showWarning(t(\"Metrics are not available: the observability plugin is not mounted in this session\"));\n\t\t\treturn;\n\t\t}\n\t\tsnapshot = result;\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\treturn;\n\t}\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new Text(formatMetricsSnapshot(snapshot), 1, 0));\n\thost.ui.requestRender();\n}\n\nexport function handleChangelogCommand(host: CommandHandlersHost): void {\n\tconst changelogPath = getChangelogPath();\n\tconst allEntries = parseChangelog(changelogPath);\n\tconst blocks = splitChangelogPagerBlocksV1(allEntries);\n\n\tif (blocks.length === 0) {\n\t\thost.chatContainer.addChild(new Spacer(1));\n\t\thost.chatContainer.addChild(new Text(t(\"No changelog entries found.\"), 1, 0));\n\t\thost.ui.requestRender();\n\t\treturn;\n\t}\n\n\t// Pager overlay instead of a chat dump (设计 docs/design/版本新特性面板与\n\t// Changelog分页.md): full history stays out of the scrollback, blocks render\n\t// lazily, closing removes everything. rows - 7 keeps title + blank + status\n\t// inside the overlay's 90% maxHeight on short terminals.\n\tconst viewportRows = Math.max(5, Math.min(40, host.ui.terminal.rows - 7));\n\tlet handle: ReturnType<TUI[\"showOverlay\"]> | undefined;\n\tconst pager = new ChangelogPager({\n\t\ttitle: t(\"Changelog\"),\n\t\tblocks,\n\t\tviewportRows,\n\t\tmarkdownTheme: host.getMarkdownThemeWithSettings(),\n\t\tonClose: () => handle?.hide(),\n\t});\n\thandle = host.ui.showOverlay(pager, { width: \"80%\", minWidth: 40, maxHeight: \"90%\" });\n\thost.ui.requestRender();\n}\n\n/**\n * Get capitalized display string for an app keybinding action.\n */\nexport function getAppKeyDisplay(action: AppKeybinding): string {\n\treturn keyDisplayText(action);\n}\n\n/**\n * Get capitalized display string for an editor keybinding action.\n */\nexport function getEditorKeyDisplay(action: Keybinding): string {\n\treturn keyDisplayText(action);\n}\n\nexport function handleHelpCommand(host: CommandHandlersHost): void {\n\tconst section = (title: string, rows: Array<{ name: string; hint?: string; description?: string }>) => {\n\t\tif (rows.length === 0) return \"\";\n\t\tlet table = `\\n**${title}**\\n| ${t(\"Command\")} | ${t(\"Description\")} |\\n|---------|-------------|\\n`;\n\t\tfor (const row of rows) {\n\t\t\tconst command = row.hint ? `\\`/${row.name} ${row.hint}\\`` : `\\`/${row.name}\\``;\n\t\t\ttable += `| ${command} | ${row.description ?? \"\"} |\\n`;\n\t\t}\n\t\treturn table;\n\t};\n\n\tconst builtinRows = BUILTIN_SLASH_COMMANDS.map((command) => ({\n\t\tname: command.name,\n\t\thint: command.argumentHint,\n\t\tdescription: command.description,\n\t}));\n\tconst promptRows = host.facade.getPromptTemplates().map((template) => ({\n\t\tname: template.name,\n\t\thint: template.argumentHint,\n\t\tdescription: template.description,\n\t}));\n\tconst skillRows = host.facade.getSkills().skills.map((skill) => ({\n\t\tname: `skill:${skill.name}`,\n\t\tdescription: skill.description,\n\t}));\n\n\tconst help =\n\t\tsection(t(\"Built-in commands\"), builtinRows) +\n\t\tsection(t(\"Prompt templates\"), promptRows) +\n\t\tsection(t(\"Skills\"), skillRows) +\n\t\t`\\n${t(\"Type `/` for autocomplete. Use `/hotkeys` for keyboard shortcuts.\")}`;\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new DynamicBorder());\n\thost.chatContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", t(\"Available Commands\"))), 1, 0));\n\thost.chatContainer.addChild(new Markdown(help.trim(), 1, 1, host.getMarkdownThemeWithSettings()));\n\thost.chatContainer.addChild(new DynamicBorder());\n\thost.ui.requestRender();\n}\n\nexport function handleHotkeysCommand(host: CommandHandlersHost): void {\n\t// Navigation keybindings\n\tconst cursorUp = getEditorKeyDisplay(\"tui.editor.cursorUp\");\n\tconst cursorDown = getEditorKeyDisplay(\"tui.editor.cursorDown\");\n\tconst cursorLeft = getEditorKeyDisplay(\"tui.editor.cursorLeft\");\n\tconst cursorRight = getEditorKeyDisplay(\"tui.editor.cursorRight\");\n\tconst cursorWordLeft = getEditorKeyDisplay(\"tui.editor.cursorWordLeft\");\n\tconst cursorWordRight = getEditorKeyDisplay(\"tui.editor.cursorWordRight\");\n\tconst cursorLineStart = getEditorKeyDisplay(\"tui.editor.cursorLineStart\");\n\tconst cursorLineEnd = getEditorKeyDisplay(\"tui.editor.cursorLineEnd\");\n\tconst jumpForward = getEditorKeyDisplay(\"tui.editor.jumpForward\");\n\tconst jumpBackward = getEditorKeyDisplay(\"tui.editor.jumpBackward\");\n\tconst pageUp = getEditorKeyDisplay(\"tui.editor.pageUp\");\n\tconst pageDown = getEditorKeyDisplay(\"tui.editor.pageDown\");\n\n\t// Editing keybindings\n\tconst submit = getEditorKeyDisplay(\"tui.input.submit\");\n\tconst newLine = getEditorKeyDisplay(\"tui.input.newLine\");\n\tconst deleteWordBackward = getEditorKeyDisplay(\"tui.editor.deleteWordBackward\");\n\tconst deleteWordForward = getEditorKeyDisplay(\"tui.editor.deleteWordForward\");\n\tconst deleteToLineStart = getEditorKeyDisplay(\"tui.editor.deleteToLineStart\");\n\tconst deleteToLineEnd = getEditorKeyDisplay(\"tui.editor.deleteToLineEnd\");\n\tconst yank = getEditorKeyDisplay(\"tui.editor.yank\");\n\tconst yankPop = getEditorKeyDisplay(\"tui.editor.yankPop\");\n\tconst undo = getEditorKeyDisplay(\"tui.editor.undo\");\n\tconst tab = getEditorKeyDisplay(\"tui.input.tab\");\n\n\t// App keybindings\n\tconst interrupt = getAppKeyDisplay(\"app.interrupt\");\n\tconst clear = getAppKeyDisplay(\"app.clear\");\n\tconst exit = getAppKeyDisplay(\"app.exit\");\n\tconst suspend = getAppKeyDisplay(\"app.suspend\");\n\tconst cycleThinkingLevel = getAppKeyDisplay(\"app.thinking.cycle\");\n\tconst cycleModelForward = getAppKeyDisplay(\"app.model.cycleForward\");\n\tconst selectModel = getAppKeyDisplay(\"app.model.select\");\n\tconst expandTools = getAppKeyDisplay(\"app.tools.expand\");\n\tconst toggleThinking = getAppKeyDisplay(\"app.thinking.toggle\");\n\tconst externalEditor = getAppKeyDisplay(\"app.editor.external\");\n\tconst cycleModelBackward = getAppKeyDisplay(\"app.model.cycleBackward\");\n\tconst copyMessage = getAppKeyDisplay(\"app.message.copy\");\n\tconst followUp = getAppKeyDisplay(\"app.message.followUp\");\n\tconst dequeue = getAppKeyDisplay(\"app.message.dequeue\");\n\tconst pasteImage = getAppKeyDisplay(\"app.clipboard.pasteImage\");\n\t// Iron law (2026-10-01): these shipped defaults were cleared in favor of\n\t// the /model command; the row only appears when a user re-configures a key.\n\tconst selectModelRow = selectModel ? `| \\`${selectModel}\\` | ${t(\"Open model selector\")} |\\n` : \"\";\n\t// Iron law (2026-10-02 复审): ctrl+g/ctrl+x stay bound only for the\n\t// components without a command channel — the main editor ignores them\n\t// (/editor and /copy are its single entries), so the rows say where\n\t// each key actually acts.\n\tconst copyMessageRow = copyMessage ? `| \\`${copyMessage}\\` | ${t(\"Copy selection in /tree\")} |\\n` : \"\";\n\tconst externalEditorRow = externalEditor\n\t\t? `| \\`${externalEditor}\\` | ${t(\"External editor (inside dialog inputs)\")} |\\n`\n\t\t: \"\";\n\t// Iron law (2026-10-01): the message queue is the /queue and /dequeue\n\t// commands' feature; rows only appear when a user re-configures a key.\n\tconst followUpRow = followUp ? `| \\`${followUp}\\` | ${t(\"Queue follow-up message\")} |\\n` : \"\";\n\tconst dequeueRow = dequeue ? `| \\`${dequeue}\\` | ${t(\"Restore queued messages\")} |\\n` : \"\";\n\tconst cycleThinkingRow = cycleThinkingLevel ? `| \\`${cycleThinkingLevel}\\` | ${t(\"Cycle thinking level\")} |\\n` : \"\";\n\tconst cycleModelsRow =\n\t\tcycleModelForward || cycleModelBackward\n\t\t\t? `| \\`${cycleModelForward}\\` / \\`${cycleModelBackward}\\` | ${t(\"Cycle models\")} |\\n`\n\t\t\t: \"\";\n\t// Suspend has no default key on native Windows; hide the row there.\n\tconst suspendRow = suspend ? `| \\`${suspend}\\` | ${t(\"Suspend to background\")} |\\n` : \"\";\n\n\tconst hotkeys = `\n**${t(\"Navigation\")}**\n| ${t(\"Key\")} | ${t(\"Action\")} |\n|-----|--------|\n| \\`${cursorUp}\\` / \\`${cursorDown}\\` / \\`${cursorLeft}\\` / \\`${cursorRight}\\` | ${t(\"Move cursor / browse history\")} |\n| \\`${cursorWordLeft}\\` / \\`${cursorWordRight}\\` | ${t(\"Move by word\")} |\n| \\`${cursorLineStart}\\` | ${t(\"Start of line\")} |\n| \\`${cursorLineEnd}\\` | ${t(\"End of line\")} |\n| \\`${jumpForward}\\` | ${t(\"Jump forward to character\")} |\n| \\`${jumpBackward}\\` | ${t(\"Jump backward to character\")} |\n| \\`${pageUp}\\` / \\`${pageDown}\\` | ${t(\"Scroll by page\")} |\n\n**${t(\"Editing\")}**\n| ${t(\"Key\")} | ${t(\"Action\")} |\n|-----|--------|\n| \\`${submit}\\` | ${t(\"Send message\")} |\n| \\`${newLine}\\` | ${process.platform === \"win32\" ? t(\"New line (Ctrl+Enter on Windows Terminal)\") : t(\"New line\")} |\n| \\`${deleteWordBackward}\\` | ${t(\"Delete word backwards\")} |\n| \\`${deleteWordForward}\\` | ${t(\"Delete word forwards\")} |\n| \\`${deleteToLineStart}\\` | ${t(\"Delete to start of line\")} |\n| \\`${deleteToLineEnd}\\` | ${t(\"Delete to end of line\")} |\n| \\`${yank}\\` | ${t(\"Paste the most-recently-deleted text\")} |\n| \\`${yankPop}\\` | ${t(\"Cycle through the deleted text after pasting\")} |\n| \\`${undo}\\` | ${t(\"Undo\")} |\n\n**${t(\"Other\")}**\n| ${t(\"Key\")} | ${t(\"Action\")} |\n|-----|--------|\n| \\`${tab}\\` | ${t(\"Path completion / accept autocomplete\")} |\n| \\`${interrupt}\\` | ${t(\"Cancel autocomplete / abort streaming\")} |\n| \\`${clear}\\` | ${t(\"Clear editor (first) / exit (second)\")} |\n| \\`${exit}\\` | ${t(\"Exit (when editor is empty)\")} |\n${suspendRow}${cycleThinkingRow}${cycleModelsRow}${selectModelRow}| \\`${expandTools}\\` | ${t(\"Toggle tool output expansion\")} |\n| \\`${toggleThinking}\\` | ${t(\"Toggle thinking block visibility\")} |\n${externalEditorRow}${copyMessageRow}${followUpRow}${dequeueRow}\n| \\`${pasteImage}\\` | ${t(\"Paste image or text from clipboard\")} |\n| \\`/\\` | ${t(\"Slash commands\")} |\n| \\`!\\` | ${t(\"Run bash command\")} |\n| \\`!!\\` | ${t(\"Run bash command (excluded from context)\")} |\n`;\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new DynamicBorder());\n\thost.chatContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", t(\"Keyboard Shortcuts\"))), 1, 0));\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new Markdown(hotkeys.trim(), 1, 1, host.getMarkdownThemeWithSettings()));\n\thost.chatContainer.addChild(new DynamicBorder());\n\thost.ui.requestRender();\n}\n\nexport async function handleClearCommand(host: CommandHandlersHost): Promise<void> {\n\thost.clearStatusIndicator();\n\ttry {\n\t\tconst result = await host.runtimeFacade.newSession();\n\t\tif (result.cancelled) {\n\t\t\treturn;\n\t\t}\n\t\thost.chatContainer.addChild(new Spacer(1));\n\t\thost.chatContainer.addChild(new Text(`${theme.fg(\"accent\", t(\"✓ New session started\"))}`, 1, 1));\n\t\thost.ui.requestRender();\n\t} catch (error: unknown) {\n\t\tawait host.handleFatalRuntimeError(\"Failed to create session\", error);\n\t}\n}\n\nexport function handleDebugCommand(host: CommandHandlersHost): void {\n\tconst width = host.ui.terminal.columns;\n\tconst height = host.ui.terminal.rows;\n\tconst allLines = host.ui.render(width);\n\n\tconst debugLogPath = getDebugLogPath();\n\tconst debugData = [\n\t\tt(\"Debug output at {date}\", { date: new Date().toISOString() }),\n\t\tt(\"Terminal: {columns}x{rows}\", { columns: width, rows: height }),\n\t\tt(\"Total lines: {count}\", { count: allLines.length }),\n\t\t\"\",\n\t\tt(\"=== All rendered lines with visible widths ===\"),\n\t\t...allLines.map((line, idx) => {\n\t\t\tconst vw = visibleWidth(line);\n\t\t\tconst escaped = JSON.stringify(line);\n\t\t\treturn `[${idx}] (w=${vw}) ${escaped}`;\n\t\t}),\n\t\t\"\",\n\t\tt(\"=== Agent messages (JSONL) ===\"),\n\t\t...host.facade\n\t\t\t.getSessionAPI()\n\t\t\t.getMessages()\n\t\t\t.map((msg) => JSON.stringify(msg)),\n\t\t\"\",\n\t].join(\"\\n\");\n\n\tfs.mkdirSync(path.dirname(debugLogPath), { recursive: true });\n\tfs.writeFileSync(debugLogPath, debugData);\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(\n\t\tnew Text(`${theme.fg(\"accent\", t(\"✓ Debug log written\"))}\\n${theme.fg(\"muted\", debugLogPath)}`, 1, 1),\n\t);\n\thost.ui.requestRender();\n}\n\nexport function handleArminSaysHi(host: CommandHandlersHost): void {\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new ArminComponent(host.ui));\n\thost.ui.requestRender();\n}\n\nexport function handleDaxnuts(host: CommandHandlersHost): void {\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new DaxnutsComponent(host.ui));\n\thost.ui.requestRender();\n}\n\n/** Host-visible for ./interactive-auth.ts (S5 slice 3). */\nexport function checkDaxnutsEasterEgg(host: CommandHandlersHost, model: { provider: string; id: string }): void {\n\tif (model.provider === \"opencode\" && model.id.toLowerCase().includes(\"kimi-k2.5\")) {\n\t\thost.handleDaxnuts();\n\t}\n}\n\nexport async function handleBashCommand(\n\thost: CommandHandlersHost,\n\tcommand: string,\n\texcludeFromContext = false,\n): Promise<void> {\n\t// Dispatch the user.bash lifecycle facts, then execute locally.\n\thost.facade.emitUserBash(excludeFromContext);\n\n\t// Normal execution path\n\tconst isDeferred = host.facade.isStreaming();\n\thost.bashComponent = new BashExecutionComponent(command, host.ui, excludeFromContext);\n\n\tif (isDeferred) {\n\t\t// Show in pending area when agent is streaming\n\t\thost.pendingMessagesContainer.addChild(host.bashComponent);\n\t\thost.pendingBashComponents.push(host.bashComponent);\n\t} else {\n\t\t// Show in chat immediately when agent is idle\n\t\thost.chatContainer.addChild(host.bashComponent);\n\t}\n\thost.ui.requestRender();\n\n\ttry {\n\t\tconst result = await host.facade.executeBash(\n\t\t\tcommand,\n\t\t\t(chunk) => {\n\t\t\t\tif (host.bashComponent) {\n\t\t\t\t\thost.bashComponent.appendOutput(chunk);\n\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ excludeFromContext },\n\t\t);\n\n\t\tif (host.bashComponent) {\n\t\t\thost.bashComponent.setComplete(\n\t\t\t\tresult.exitCode,\n\t\t\t\tresult.cancelled,\n\t\t\t\tresult.truncated ? ({ truncated: true, content: result.output } as TruncationResult) : undefined,\n\t\t\t\tresult.fullOutputPath,\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\tif (host.bashComponent) {\n\t\t\thost.bashComponent.setComplete(undefined, false);\n\t\t}\n\t\thost.showError(\n\t\t\tt(\"Bash command failed: {error}\", {\n\t\t\t\terror: error instanceof Error ? error.message : t(\"Unknown error\"),\n\t\t\t}),\n\t\t);\n\t}\n\n\thost.bashComponent = undefined;\n\thost.ui.requestRender();\n}\n\nexport async function handleCompactCommand(host: CommandHandlersHost, customInstructions?: string): Promise<void> {\n\thost.clearStatusIndicator();\n\n\ttry {\n\t\tawait host.facade.compact(customInstructions);\n\t} catch {\n\t\t// Ignore, will be emitted as an event\n\t}\n}\n\n/** /clean-sessions:会话清理两阶段流程;输出/确认走宿主既有机制(设计 §7)。 */\nexport async function handleCleanSessionsCommand(host: CommandHandlersHost, arg: string | undefined): Promise<void> {\n\tawait runCleanSessions(\n\t\t{\n\t\t\tshowMessage: (text) => host.showStatus(text),\n\t\t\tshowError: (text) => host.showError(text),\n\t\t\tconfirm: (title, message) => host.showDialogConfirm(title, message),\n\t\t},\n\t\targ,\n\t);\n}\n"]}
|
|
@@ -22,10 +22,11 @@ import { MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
|
22
22
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
23
23
|
import { getUsageCostBreakdown } from "../../core/usage-totals.js";
|
|
24
24
|
import { loadThemeFromPath, setRegisteredThemes, theme } from "../../theme/theme.js";
|
|
25
|
-
import { getChangelogPath,
|
|
25
|
+
import { getChangelogPath, parseChangelog, splitChangelogPagerBlocksV1 } from "../../utils/changelog.js";
|
|
26
26
|
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
27
27
|
import { ArminComponent } from "./components/armin.js";
|
|
28
28
|
import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
29
|
+
import { ChangelogPager } from "./components/changelog-pager.js";
|
|
29
30
|
import { DaxnutsComponent } from "./components/daxnuts.js";
|
|
30
31
|
import { DynamicBorder } from "./components/dynamic-border.js";
|
|
31
32
|
import { formatTokens } from "./components/footer.js";
|
|
@@ -619,18 +620,27 @@ export function handleMetricsCommand(host, turnsArg) {
|
|
|
619
620
|
export function handleChangelogCommand(host) {
|
|
620
621
|
const changelogPath = getChangelogPath();
|
|
621
622
|
const allEntries = parseChangelog(changelogPath);
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
623
|
+
const blocks = splitChangelogPagerBlocksV1(allEntries);
|
|
624
|
+
if (blocks.length === 0) {
|
|
625
|
+
host.chatContainer.addChild(new Spacer(1));
|
|
626
|
+
host.chatContainer.addChild(new Text(t("No changelog entries found."), 1, 0));
|
|
627
|
+
host.ui.requestRender();
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
// Pager overlay instead of a chat dump (设计 docs/design/版本新特性面板与
|
|
631
|
+
// Changelog分页.md): full history stays out of the scrollback, blocks render
|
|
632
|
+
// lazily, closing removes everything. rows - 7 keeps title + blank + status
|
|
633
|
+
// inside the overlay's 90% maxHeight on short terminals.
|
|
634
|
+
const viewportRows = Math.max(5, Math.min(40, host.ui.terminal.rows - 7));
|
|
635
|
+
let handle;
|
|
636
|
+
const pager = new ChangelogPager({
|
|
637
|
+
title: t("Changelog"),
|
|
638
|
+
blocks,
|
|
639
|
+
viewportRows,
|
|
640
|
+
markdownTheme: host.getMarkdownThemeWithSettings(),
|
|
641
|
+
onClose: () => handle?.hide(),
|
|
642
|
+
});
|
|
643
|
+
handle = host.ui.showOverlay(pager, { width: "80%", minWidth: 40, maxHeight: "90%" });
|
|
634
644
|
host.ui.requestRender();
|
|
635
645
|
}
|
|
636
646
|
/**
|