@ai-agent-forge/agent-forge 0.88.2 → 0.88.4
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 +33 -0
- package/README.md +1 -1
- package/bundled-plugins/agent-loop-policy/agent-forge.json +1 -1
- package/bundled-plugins/agent-loop-policy/plugin.json +1 -1
- package/bundled-plugins/coding-tools/agent-forge.json +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/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/artifacts/paged-read.d.ts +6 -1
- package/dist/artifacts/paged-read.d.ts.map +1 -1
- package/dist/artifacts/paged-read.js.map +1 -1
- package/dist/bundle/.build-manifest.json +244 -234
- package/dist/bundle/chunks/{anthropic-messages-PLAKXR73.js → anthropic-messages-3FJHRAJL.js} +2 -2
- package/dist/bundle/chunks/anthropic.js +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-OUWQ2AZD.js → azure-openai-responses-GCL47IDS.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/{chunk-6F5JWGKE.js → chunk-32YDZI4J.js} +1 -1
- package/dist/bundle/chunks/{chunk-LSP24WNH.js → chunk-755VNWIF.js} +12 -12
- package/dist/bundle/chunks/{chunk-DRQDKNXA.js → chunk-BVVSHK6W.js} +1 -1
- package/dist/bundle/chunks/{chunk-HTC5K5SM.js → chunk-HEL6TAWE.js} +95 -90
- package/dist/bundle/chunks/{chunk-6R7UI4IY.js → chunk-QDLOMKF6.js} +1 -1
- package/dist/bundle/chunks/{chunk-44I47ACN.js → chunk-V3ACW7VD.js} +1 -1
- package/dist/bundle/chunks/{google-generative-ai-DLII6DAT.js → google-generative-ai-6BMXWA44.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-G7BZL7GM.js → google-vertex-6H7NLRTK.js} +1 -1
- package/dist/bundle/chunks/image-resize-worker.js +1 -1
- package/dist/bundle/chunks/{mistral-conversations-JXUK24ID.js → mistral-conversations-IXUHSFSJ.js} +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-5GSGFIPG.js → openai-codex-responses-YHUAEVCM.js} +1 -1
- package/dist/bundle/chunks/openai-codex.js +1 -1
- package/dist/bundle/chunks/{openai-completions-EI2I6DTG.js → openai-completions-ULQCFC72.js} +1 -1
- package/dist/bundle/chunks/openai-responses-NLLAJDPS.js +2 -0
- package/dist/bundle/chunks/{openrouter-images-G4GQRUSG.js → openrouter-images-GZZQZXMS.js} +1 -1
- package/dist/bundle/chunks/openrouter.js +1 -1
- package/dist/bundle/chunks/{pi-messages-PWU2VJMR.js → pi-messages-EJTI2EJ3.js} +1 -1
- package/dist/bundle/chunks/virtual-modules-TQWEGNNZ.js +5 -0
- 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/cli/args.d.ts +0 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -4
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/experimental/cli.d.ts +3 -3
- package/dist/cli/experimental/cli.d.ts.map +1 -1
- package/dist/cli/experimental/cli.js +2 -2
- package/dist/cli/experimental/cli.js.map +1 -1
- package/dist/cli/experimental/commands/agent-forge.d.ts +15 -0
- package/dist/cli/experimental/commands/agent-forge.d.ts.map +1 -0
- package/dist/cli/experimental/commands/{pi.js → agent-forge.js} +4 -4
- package/dist/cli/experimental/commands/agent-forge.js.map +1 -0
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +1 -1
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/config.d.ts +4 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -7
- package/dist/config.js.map +1 -1
- package/dist/core/agent-forge-manifest.d.ts +7 -0
- package/dist/core/agent-forge-manifest.d.ts.map +1 -0
- package/dist/core/{pi-manifest.js → agent-forge-manifest.js} +4 -4
- package/dist/core/agent-forge-manifest.js.map +1 -0
- package/dist/core/agent-session-compaction.d.ts +1 -1
- package/dist/core/agent-session-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-compaction.js +3 -3
- package/dist/core/agent-session-compaction.js.map +1 -1
- package/dist/core/agent-session-lifecycle.d.ts.map +1 -1
- package/dist/core/agent-session-lifecycle.js +12 -1
- package/dist/core/agent-session-lifecycle.js.map +1 -1
- package/dist/core/agent-session-projection.d.ts.map +1 -1
- package/dist/core/agent-session-projection.js +3 -3
- package/dist/core/agent-session-projection.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -1
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +1 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/experimental.d.ts.map +1 -1
- package/dist/core/experimental.js +1 -3
- package/dist/core/experimental.js.map +1 -1
- package/dist/core/extensions/capability-plugin.d.ts.map +1 -1
- package/dist/core/extensions/capability-plugin.js +15 -8
- package/dist/core/extensions/capability-plugin.js.map +1 -1
- package/dist/core/extensions/capability-runtime-store.d.ts +20 -1
- package/dist/core/extensions/capability-runtime-store.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime-store.js +67 -0
- package/dist/core/extensions/capability-runtime-store.js.map +1 -1
- package/dist/core/extensions/capability-runtime-types.d.ts +14 -0
- package/dist/core/extensions/capability-runtime-types.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime-types.js +7 -1
- package/dist/core/extensions/capability-runtime-types.js.map +1 -1
- package/dist/core/extensions/capability-runtime.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime.js +144 -42
- package/dist/core/extensions/capability-runtime.js.map +1 -1
- package/dist/core/extensions/virtual-modules.d.ts.map +1 -1
- package/dist/core/extensions/virtual-modules.js +12 -4
- package/dist/core/extensions/virtual-modules.js.map +1 -1
- package/dist/core/facade/coding-agent-facade.d.ts +1 -1
- package/dist/core/facade/coding-agent-facade.d.ts.map +1 -1
- package/dist/core/facade/coding-agent-facade.js +1 -1
- package/dist/core/facade/coding-agent-facade.js.map +1 -1
- package/dist/core/i18n/locales/zh-cn/core-contract-final.d.ts +1 -1
- package/dist/core/i18n/locales/zh-cn/core-contract-final.d.ts.map +1 -1
- package/dist/core/i18n/locales/zh-cn/core-contract-final.js +3 -3
- package/dist/core/i18n/locales/zh-cn/core-contract-final.js.map +1 -1
- package/dist/core/i18n/locales/zh-cn/core-contract-plugins.d.ts.map +1 -1
- package/dist/core/i18n/locales/zh-cn/core-contract-plugins.js +1 -1
- package/dist/core/i18n/locales/zh-cn/core-contract-plugins.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 -1
- package/dist/core/i18n/locales/zh-cn/core-errors.js.map +1 -1
- package/dist/core/i18n/locales/zh-cn/tui-components-misc.d.ts.map +1 -1
- package/dist/core/i18n/locales/zh-cn/tui-components-misc.js +2 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-misc.js.map +1 -1
- package/dist/core/model-resolver.d.ts +2 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +4 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-resource-collection.d.ts.map +1 -1
- package/dist/core/package-resource-collection.js +5 -5
- package/dist/core/package-resource-collection.js.map +1 -1
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +3 -3
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +1 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +6 -7
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +4 -4
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +5 -10
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +1 -3
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +1 -1
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/tool-definition.d.ts +1 -1
- package/dist/core/tools/tool-definition.d.ts.map +1 -1
- package/dist/core/tools/tool-definition.js.map +1 -1
- package/dist/core/trace-retention.d.ts +16 -1
- package/dist/core/trace-retention.d.ts.map +1 -1
- package/dist/core/trace-retention.js +43 -20
- package/dist/core/trace-retention.js.map +1 -1
- package/dist/export-html/ansi-to-html.d.ts.map +1 -0
- package/dist/export-html/ansi-to-html.js.map +1 -0
- package/dist/{core/export-html → export-html}/index.d.ts +1 -1
- package/dist/export-html/index.d.ts.map +1 -0
- package/dist/{core/export-html → export-html}/index.js +5 -5
- package/dist/export-html/index.js.map +1 -0
- package/dist/{core/export-html → export-html}/tool-renderer.d.ts +2 -2
- package/dist/export-html/tool-renderer.d.ts.map +1 -0
- package/dist/export-html/tool-renderer.js.map +1 -0
- package/dist/{modes/interactive → host}/model-catalog-refresh.d.ts +1 -1
- package/dist/host/model-catalog-refresh.d.ts.map +1 -0
- package/dist/{modes/interactive → host}/model-catalog-refresh.js +1 -1
- package/dist/host/model-catalog-refresh.js.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +25 -14
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/agents-view.d.ts.map +1 -1
- package/dist/modes/interactive/components/agents-view.js +1 -1
- package/dist/modes/interactive/components/agents-view.js.map +1 -1
- package/dist/modes/interactive/components/armin.d.ts.map +1 -1
- package/dist/modes/interactive/components/armin.js +1 -1
- package/dist/modes/interactive/components/armin.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +3 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +83 -24
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +1 -1
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +1 -1
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-entry.js +1 -1
- package/dist/modes/interactive/components/custom-entry.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +1 -1
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
- package/dist/modes/interactive/components/daxnuts.js +1 -1
- package/dist/modes/interactive/components/daxnuts.js.map +1 -1
- package/dist/modes/interactive/components/dialog-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/dialog-editor.js +1 -1
- package/dist/modes/interactive/components/dialog-editor.js.map +1 -1
- package/dist/modes/interactive/components/dialog-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/dialog-input.js +1 -1
- package/dist/modes/interactive/components/dialog-input.js.map +1 -1
- package/dist/modes/interactive/components/dialog-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/dialog-selector.js +1 -1
- package/dist/modes/interactive/components/dialog-selector.js.map +1 -1
- package/dist/modes/interactive/components/diff.d.ts.map +1 -1
- package/dist/modes/interactive/components/diff.js +1 -1
- package/dist/modes/interactive/components/diff.js.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +1 -1
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +1 -1
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +1 -1
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/mermaid.d.ts +1 -1
- package/dist/modes/interactive/components/mermaid.d.ts.map +1 -1
- package/dist/modes/interactive/components/mermaid.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +2 -2
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +1 -1
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/plugin-manager-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/plugin-manager-entry.js +1 -1
- package/dist/modes/interactive/components/plugin-manager-entry.js.map +1 -1
- package/dist/modes/interactive/components/plugin-selection-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/plugin-selection-entry.js +1 -1
- package/dist/modes/interactive/components/plugin-selection-entry.js.map +1 -1
- package/dist/modes/interactive/components/queue-manager-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/queue-manager-selector.js +1 -1
- package/dist/modes/interactive/components/queue-manager-selector.js.map +1 -1
- package/dist/modes/interactive/components/render-types.d.ts +1 -1
- package/dist/modes/interactive/components/render-types.d.ts.map +1 -1
- package/dist/modes/interactive/components/render-types.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +2 -2
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +1 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-submenu.js +1 -1
- package/dist/modes/interactive/components/settings-submenu.js.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.js +1 -1
- package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-indicator.js +1 -1
- package/dist/modes/interactive/components/status-indicator.js.map +1 -1
- package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/theme-selector.js +1 -1
- package/dist/modes/interactive/components/theme-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +1 -1
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +2 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +96 -4
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-run-group.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-run-group.js +1 -1
- package/dist/modes/interactive/components/tool-run-group.js.map +1 -1
- package/dist/modes/interactive/components/tree-list.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-list.js +1 -1
- package/dist/modes/interactive/components/tree-list.js.map +1 -1
- package/dist/modes/interactive/components/tree-render.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-render.js +1 -1
- package/dist/modes/interactive/components/tree-render.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +1 -1
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +1 -1
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +1 -1
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/components/workflow-view.d.ts.map +1 -1
- package/dist/modes/interactive/components/workflow-view.js +1 -1
- package/dist/modes/interactive/components/workflow-view.js.map +1 -1
- package/dist/modes/interactive/interactive-auth.d.ts +1 -1
- package/dist/modes/interactive/interactive-auth.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-auth.js +1 -1
- package/dist/modes/interactive/interactive-auth.js.map +1 -1
- package/dist/modes/interactive/interactive-commands.d.ts +123 -6
- package/dist/modes/interactive/interactive-commands.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-commands.js +691 -4
- package/dist/modes/interactive/interactive-commands.js.map +1 -1
- package/dist/modes/interactive/interactive-events.d.ts +174 -0
- package/dist/modes/interactive/interactive-events.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-events.js +704 -0
- package/dist/modes/interactive/interactive-events.js.map +1 -0
- package/dist/modes/interactive/interactive-format.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-format.js +1 -1
- package/dist/modes/interactive/interactive-format.js.map +1 -1
- package/dist/modes/interactive/interactive-lifecycle.d.ts +94 -0
- package/dist/modes/interactive/interactive-lifecycle.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-lifecycle.js +177 -0
- package/dist/modes/interactive/interactive-lifecycle.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +240 -133
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +251 -2159
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/interactive-queues.d.ts +96 -0
- package/dist/modes/interactive/interactive-queues.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-queues.js +183 -0
- package/dist/modes/interactive/interactive-queues.js.map +1 -0
- package/dist/modes/interactive/interactive-selectors.d.ts +95 -5
- package/dist/modes/interactive/interactive-selectors.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-selectors.js +517 -6
- package/dist/modes/interactive/interactive-selectors.js.map +1 -1
- package/dist/modes/interactive/startup-services.d.ts +31 -0
- package/dist/modes/interactive/startup-services.d.ts.map +1 -0
- package/dist/modes/interactive/startup-services.js +123 -0
- package/dist/modes/interactive/startup-services.js.map +1 -0
- package/dist/modes/memory-preheat.d.ts +11 -0
- package/dist/modes/memory-preheat.d.ts.map +1 -0
- package/dist/modes/memory-preheat.js +20 -0
- package/dist/modes/memory-preheat.js.map +1 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +3 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.d.ts +9 -1
- package/dist/plugins/ecosystem/node-package-install-host.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.js +79 -0
- package/dist/plugins/ecosystem/node-package-install-host.js.map +1 -1
- package/dist/remote/server-assembly.d.ts.map +1 -1
- package/dist/remote/server-assembly.js +7 -2
- package/dist/remote/server-assembly.js.map +1 -1
- package/dist/suites/builtin-suites.d.ts.map +1 -0
- package/dist/suites/builtin-suites.js.map +1 -0
- package/dist/suites/resolved-agent-profile.d.ts.map +1 -0
- package/dist/suites/resolved-agent-profile.js.map +1 -0
- package/dist/suites/suite-lifecycle.d.ts.map +1 -0
- package/dist/suites/suite-lifecycle.js.map +1 -0
- package/dist/suites/suite-loader.d.ts.map +1 -0
- package/dist/suites/suite-loader.js.map +1 -0
- package/dist/{modes/interactive/theme → theme}/dark.json +1 -1
- package/dist/{modes/interactive/theme → theme}/light.json +1 -1
- package/dist/{modes/interactive/theme → theme}/theme-controller.d.ts +1 -1
- package/dist/theme/theme-controller.d.ts.map +1 -0
- package/dist/{modes/interactive/theme → theme}/theme-controller.js +1 -1
- package/dist/theme/theme-controller.js.map +1 -0
- package/dist/{modes/interactive/theme → theme}/theme.d.ts +1 -1
- package/dist/theme/theme.d.ts.map +1 -0
- package/dist/{modes/interactive/theme → theme}/theme.js +5 -5
- package/dist/theme/theme.js.map +1 -0
- package/docs/compaction.md +7 -7
- package/docs/environment-variables.md +10 -4
- package/docs/keybindings.md +3 -3
- package/docs/models.md +17 -17
- package/docs/packages.md +1 -1
- package/docs/prompt-templates.md +1 -1
- package/docs/rpc.md +2 -2
- package/docs/sdk.md +5 -5
- package/docs/session-format.md +3 -3
- package/docs/settings.md +15 -1
- package/docs/shell-aliases.md +1 -1
- package/docs/skills.md +1 -1
- package/docs/terminal-setup.md +17 -17
- package/docs/themes.md +5 -5
- package/docs/tmux.md +4 -4
- package/docs/tui.md +1 -1
- package/docs/usage.md +292 -292
- package/docs/windows.md +1 -1
- package/examples/extensions/bash-spawn-hook/index.ts +1 -1
- package/examples/extensions/dynamic-resources/dynamic.json +1 -1
- package/examples/extensions/mcp-stdio/index.d.ts +64 -0
- package/examples/extensions/mcp-stdio/index.d.ts.map +1 -0
- package/examples/extensions/mcp-stdio/index.js +292 -0
- package/examples/extensions/mcp-stdio/index.js.map +1 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -6
- package/examples/extensions/with-deps/plugin.json +1 -1
- package/package.json +2 -2
- package/dist/bundle/chunks/openai-responses-AUJQ3NWT.js +0 -2
- package/dist/bundle/chunks/virtual-modules-MRXD3RWG.js +0 -5
- package/dist/cli/experimental/commands/pi.d.ts +0 -15
- package/dist/cli/experimental/commands/pi.d.ts.map +0 -1
- package/dist/cli/experimental/commands/pi.js.map +0 -1
- package/dist/core/export-html/ansi-to-html.d.ts.map +0 -1
- package/dist/core/export-html/ansi-to-html.js.map +0 -1
- package/dist/core/export-html/index.d.ts.map +0 -1
- package/dist/core/export-html/index.js.map +0 -1
- package/dist/core/export-html/tool-renderer.d.ts.map +0 -1
- package/dist/core/export-html/tool-renderer.js.map +0 -1
- package/dist/core/pi-manifest.d.ts +0 -7
- package/dist/core/pi-manifest.d.ts.map +0 -1
- package/dist/core/pi-manifest.js.map +0 -1
- package/dist/modes/interactive/model-catalog-refresh.d.ts.map +0 -1
- package/dist/modes/interactive/model-catalog-refresh.js.map +0 -1
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +0 -1
- package/dist/modes/interactive/theme/theme-controller.js.map +0 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +0 -1
- package/dist/modes/interactive/theme/theme.js.map +0 -1
- package/dist/profiles/builtin-suites.d.ts.map +0 -1
- package/dist/profiles/builtin-suites.js.map +0 -1
- package/dist/profiles/resolved-agent-profile.d.ts.map +0 -1
- package/dist/profiles/resolved-agent-profile.js.map +0 -1
- package/dist/profiles/suite-lifecycle.d.ts.map +0 -1
- package/dist/profiles/suite-lifecycle.js.map +0 -1
- package/dist/profiles/suite-loader.d.ts.map +0 -1
- package/dist/profiles/suite-loader.js.map +0 -1
- package/dist/utils/deprecation.d.ts +0 -4
- package/dist/utils/deprecation.d.ts.map +0 -1
- package/dist/utils/deprecation.js +0 -13
- package/dist/utils/deprecation.js.map +0 -1
- /package/dist/{core/export-html → export-html}/ansi-to-html.d.ts +0 -0
- /package/dist/{core/export-html → export-html}/ansi-to-html.js +0 -0
- /package/dist/{core/export-html → export-html}/template.css +0 -0
- /package/dist/{core/export-html → export-html}/template.html +0 -0
- /package/dist/{core/export-html → export-html}/template.js +0 -0
- /package/dist/{core/export-html → export-html}/tool-renderer.js +0 -0
- /package/dist/{core/export-html → export-html}/vendor/highlight.min.js +0 -0
- /package/dist/{core/export-html → export-html}/vendor/marked.min.js +0 -0
- /package/dist/{profiles → suites}/builtin-suites.d.ts +0 -0
- /package/dist/{profiles → suites}/builtin-suites.js +0 -0
- /package/dist/{profiles → suites}/resolved-agent-profile.d.ts +0 -0
- /package/dist/{profiles → suites}/resolved-agent-profile.js +0 -0
- /package/dist/{profiles → suites}/suite-lifecycle.d.ts +0 -0
- /package/dist/{profiles → suites}/suite-lifecycle.js +0 -0
- /package/dist/{profiles → suites}/suite-loader.d.ts +0 -0
- /package/dist/{profiles → suites}/suite-loader.js +0 -0
- /package/dist/{modes/interactive/theme → theme}/theme-schema.json +0 -0
|
@@ -8,91 +8,53 @@ import * as os from "node:os";
|
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { contentText } from "@agent-forge/ai";
|
|
10
10
|
import * as TuiLayouts from "@agent-forge/tui";
|
|
11
|
-
import { CombinedAutocompleteProvider, Container, getCapabilities, hyperlink, Markdown, ProcessTerminal, Spacer, setCapabilityOverrides, setKeybindings, Text,
|
|
12
|
-
import
|
|
13
|
-
import { spawn } from "child_process";
|
|
14
|
-
import { APP_NAME, APP_TITLE, getAgentDir, getDebugLogPath, VERSION } from "../../config.js";
|
|
15
|
-
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
16
|
-
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
17
|
-
import { listAgentSessions, readAgentSession } from "../../core/agent-session-store.js";
|
|
18
|
-
import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
|
|
19
|
-
import { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from "../../core/defaults.js";
|
|
11
|
+
import { CombinedAutocompleteProvider, Container, getCapabilities, hyperlink, Markdown, ProcessTerminal, Spacer, setCapabilityOverrides, setKeybindings, Text, TuiAltScreen, TuiMainScreen, } from "@agent-forge/tui";
|
|
12
|
+
import { APP_NAME, APP_TITLE, getAgentDir, VERSION } from "../../config.js";
|
|
20
13
|
import { createCodingAgentFacade, createCodingAgentRuntimeFacade, } from "../../core/facade/index.js";
|
|
21
14
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
22
|
-
import { configureHttpDispatcher
|
|
15
|
+
import { configureHttpDispatcher } from "../../core/http-dispatcher.js";
|
|
23
16
|
import { setI18nLanguage, t } from "../../core/i18n/index.js";
|
|
24
17
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
25
|
-
import {
|
|
26
|
-
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
27
|
-
import { runCleanSessions } from "../../core/session-cleanup.js";
|
|
28
|
-
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
29
|
-
import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
|
|
18
|
+
import { formatMissingSessionCwdPrompt } from "../../core/session-cwd.js";
|
|
30
19
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
20
|
+
import { listSuiteIds, resolveSuiteId, SUITE_ENV_VAR, SUITE_SESSION_ENTRY_TYPE } from "../../suites/suite-loader.js";
|
|
21
|
+
import { getEditorTheme, getMarkdownTheme, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "../../theme/theme.js";
|
|
22
|
+
import { InteractiveThemeController } from "../../theme/theme-controller.js";
|
|
33
23
|
import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.js";
|
|
34
24
|
import { copyToClipboard, readClipboardText } from "../../utils/clipboard.js";
|
|
35
25
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
36
26
|
import { parseGitUrl } from "../../utils/git.js";
|
|
37
27
|
import { openBrowser } from "../../utils/open-browser.js";
|
|
38
|
-
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
39
28
|
import { loadAllHighlightLanguages } from "../../utils/syntax-highlight.js";
|
|
40
29
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
41
|
-
import {
|
|
42
|
-
import { AgentsViewComponent } from "./components/agents-view.js";
|
|
43
|
-
import { ArminComponent } from "./components/armin.js";
|
|
30
|
+
import { kickMemoryModelPreheat } from "../memory-preheat.js";
|
|
44
31
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
45
|
-
import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
46
|
-
import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
|
|
47
|
-
import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
|
|
48
32
|
import { CustomEditor } from "./components/custom-editor.js";
|
|
49
|
-
import { CustomMessageComponent } from "./components/custom-message.js";
|
|
50
|
-
import { DaxnutsComponent } from "./components/daxnuts.js";
|
|
51
33
|
import { DynamicBorder } from "./components/dynamic-border.js";
|
|
52
|
-
import { FooterComponent
|
|
53
|
-
import { formatKeyText,
|
|
34
|
+
import { FooterComponent } from "./components/footer.js";
|
|
35
|
+
import { formatKeyText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
54
36
|
import { createMermaidMarkdownTransformer } from "./components/mermaid.js";
|
|
55
|
-
import { readSessionSuiteIdFromFile
|
|
56
|
-
import {
|
|
57
|
-
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
58
|
-
import { BranchSummaryStatusIndicator, CompactionStatusIndicator, IdleStatus, RetryStatusIndicator, WorkingStatusIndicator, } from "./components/status-indicator.js";
|
|
59
|
-
import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
60
|
-
import { ToolRunGroupComponent } from "./components/tool-run-group.js";
|
|
61
|
-
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
62
|
-
import { UserMessageComponent } from "./components/user-message.js";
|
|
63
|
-
import { WorkflowViewComponent } from "./components/workflow-view.js";
|
|
37
|
+
import { readSessionSuiteIdFromFile } from "./components/session-selector.js";
|
|
38
|
+
import { IdleStatus, WorkingStatusIndicator, } from "./components/status-indicator.js";
|
|
64
39
|
import { editInExternalEditor } from "./external-editor.js";
|
|
65
40
|
import * as authFlow from "./interactive-auth.js";
|
|
66
|
-
import { COMMAND_DISPATCH_ENTRIES, filterCapabilityCommandsForCompletions, matchCommandDispatchEntry, } from "./interactive-commands.js";
|
|
41
|
+
import { buildRegisteredThemes, COMMAND_DISPATCH_ENTRIES, checkDaxnutsEasterEgg, filterCapabilityCommandsForCompletions, getAppKeyDisplay, getEditorKeyDisplay, getPathCommandArgument, handleArminSaysHi, handleBashCommand, handleChangelogCommand, handleCleanSessionsCommand, handleClearCommand, handleCompactCommand, handleCopyCommand, handleDaxnuts, handleDebugCommand, handleExportCommand, handleHelpCommand, handleHotkeysCommand, handleImportCommand, handleMetricsCommand, handleNameCommand, handleReloadCommand, handleSessionCommand, handleStopAgentsCommand, matchCommandDispatchEntry, } from "./interactive-commands.js";
|
|
67
42
|
import { createInteractionHostV1, hideDialogEditor, hideDialogInput, hideDialogSelector, isExpandable, showDialogConfirm, showDialogEditor, showDialogInput, showDialogNotify, showDialogSelector, } from "./interactive-dialog-ui.js";
|
|
68
|
-
import
|
|
69
|
-
import {
|
|
43
|
+
import * as eventsFlow from "./interactive-events.js";
|
|
44
|
+
import { buildScopeGroups, findSourceInfoForPath, formatContextPath, formatDiagnostics, formatDisplayPath, formatPathWithSource, formatScopeGroups, getCompactPackageSourceLabel, getCompactPathLabel, getDisplaySourceInfo, getScopeGroup, getShortPath, isPackageSource, } from "./interactive-format.js";
|
|
45
|
+
import { createFuzzyAutocompleteItems, formatLoginProviderCompletionDescription, getLoginProviderCompletionOptions, getLoginProviderSearchText, isAnthropicSubscriptionAuthKey, quoteIfNeeded, } from "./interactive-helpers.js";
|
|
46
|
+
import * as lifecycleFlow from "./interactive-lifecycle.js";
|
|
47
|
+
import * as queuesFlow from "./interactive-queues.js";
|
|
70
48
|
import * as selectorsFlow from "./interactive-selectors.js";
|
|
71
|
-
import { refreshModelCatalogs } from "./model-catalog-refresh.js";
|
|
72
49
|
import { getModelSearchText } from "./model-search.js";
|
|
73
|
-
import {
|
|
74
|
-
|
|
50
|
+
import { startStartupBackgroundChecks } from "./startup-services.js";
|
|
51
|
+
// Re-exported for existing consumers of the interactive mode module; the
|
|
52
|
+
// implementation moved to ./interactive-commands.ts with the command handlers
|
|
53
|
+
// (拆分 slice 1).
|
|
54
|
+
export { buildRegisteredThemes } from "./interactive-commands.js";
|
|
75
55
|
// Re-exported for existing consumers of the interactive mode module; the
|
|
76
56
|
// implementation moved to ./interactive-dialog-ui.ts with the extension UI block.
|
|
77
57
|
export { mapLegacyDialogResultToOutcome } from "./interactive-dialog-ui.js";
|
|
78
|
-
/**
|
|
79
|
-
* Rebuilds real Theme instances from the resource loader's ThemeMetaV1
|
|
80
|
-
* projection for the theme registry (B-S2b v1). Registration points
|
|
81
|
-
* (constructor, runtime session start, reload) need loadable instances —
|
|
82
|
-
* setRegisteredThemes stores what it is given and getThemeByName returns the
|
|
83
|
-
* registered instance first — while the loader surface now projects only
|
|
84
|
-
* metadata. Re-loading is synchronous and deterministic per sourcePath;
|
|
85
|
-
* theme names come from the same JSON the loader parsed, so meta.name ===
|
|
86
|
-
* Theme.name and the registry keeps its own name-filter/dedupe semantics.
|
|
87
|
-
*
|
|
88
|
-
* Contract: a meta without a sourcePath (or one whose file no longer parses)
|
|
89
|
-
* throws — themesOverride host inputs must always carry a loadable
|
|
90
|
-
* sourcePath. This adds one extra parse per theme at registration time
|
|
91
|
-
* (constructor / session start / reload, not per turn).
|
|
92
|
-
*/
|
|
93
|
-
export function buildRegisteredThemes(themes) {
|
|
94
|
-
return themes.map((meta) => loadThemeFromPath(meta.sourcePath));
|
|
95
|
-
}
|
|
96
58
|
class ExpandableText extends Text {
|
|
97
59
|
getCollapsedText;
|
|
98
60
|
getExpandedText;
|
|
@@ -105,12 +67,6 @@ class ExpandableText extends Text {
|
|
|
105
67
|
this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
|
|
106
68
|
}
|
|
107
69
|
}
|
|
108
|
-
function isCustomSessionEntry(item) {
|
|
109
|
-
return "type" in item && item.type === "custom";
|
|
110
|
-
}
|
|
111
|
-
function isCompactionCostNotice(item) {
|
|
112
|
-
return "type" in item && item.type === "compaction_cost";
|
|
113
|
-
}
|
|
114
70
|
// Empty-reply auto-continuation (#5, print-mode parity): sent at most once per
|
|
115
71
|
// submitted turn when the turn ends with an empty assistant reply and the
|
|
116
72
|
// model was neither aborted nor errored. The one-shot budget prevents loops.
|
|
@@ -232,6 +188,7 @@ export class ProviderFailoverTicker {
|
|
|
232
188
|
}
|
|
233
189
|
export class InteractiveMode {
|
|
234
190
|
runtimeHost;
|
|
191
|
+
/** Host-visible for ./interactive-selectors.ts (拆分 slice 2). */
|
|
235
192
|
renderer;
|
|
236
193
|
/** Host-visible for ./interactive-dialog-ui.ts (S5 slice 2). */
|
|
237
194
|
ui;
|
|
@@ -242,7 +199,9 @@ export class InteractiveMode {
|
|
|
242
199
|
documentContainer;
|
|
243
200
|
transcriptScrollView;
|
|
244
201
|
fullscreenLayoutRoot;
|
|
202
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
245
203
|
pendingMessagesContainer;
|
|
204
|
+
/** Host-visible for ./interactive-selectors.ts (拆分 slice 2). */
|
|
246
205
|
statusContainer;
|
|
247
206
|
/** Host-visible for ./interactive-dialog-ui.ts (S5 slice 2). */
|
|
248
207
|
defaultEditor;
|
|
@@ -263,6 +222,8 @@ export class InteractiveMode {
|
|
|
263
222
|
* Re-renders the footer once per second while the provider failover breaker is
|
|
264
223
|
* open (§4.7 Phase 3). Optional-typed to match the prototype-based test stubs
|
|
265
224
|
* (constructor always assigns it on real instances).
|
|
225
|
+
*
|
|
226
|
+
* Host-visible for ./interactive-events.ts (拆分 slice 5): per-event refresh.
|
|
266
227
|
*/
|
|
267
228
|
failoverTicker;
|
|
268
229
|
/** Host-visible for ./interactive-dialog-ui.ts (S5 slice 2). */
|
|
@@ -273,17 +234,22 @@ export class InteractiveMode {
|
|
|
273
234
|
/** Host-visible for ./interactive-dialog-ui.ts (S5 slice 2). */
|
|
274
235
|
keybindings;
|
|
275
236
|
version;
|
|
237
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5). */
|
|
276
238
|
isInitialized = false;
|
|
277
239
|
onInputCallback;
|
|
278
240
|
pendingUserInputs = [];
|
|
241
|
+
/** Host-visible for ./interactive-selectors.ts (拆分 slice 2). */
|
|
279
242
|
activeStatusIndicator = undefined;
|
|
280
243
|
idleStatus = new IdleStatus();
|
|
281
244
|
workingMessage = undefined;
|
|
245
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5). */
|
|
282
246
|
workingVisible = true;
|
|
283
247
|
workingIndicatorOptions = undefined;
|
|
284
248
|
defaultWorkingMessage = t("Working...");
|
|
285
249
|
defaultHiddenThinkingLabel = t("Thinking...");
|
|
250
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5). */
|
|
286
251
|
hiddenThinkingLabel = this.defaultHiddenThinkingLabel;
|
|
252
|
+
/** Host-visible for ./interactive-lifecycle.ts (拆分 slice 4): double-Ctrl+C exit window. */
|
|
287
253
|
lastSigintTime = 0;
|
|
288
254
|
lastEscapeTime = 0;
|
|
289
255
|
changelogMarkdown = undefined;
|
|
@@ -294,40 +260,57 @@ export class InteractiveMode {
|
|
|
294
260
|
lastStatusText = undefined;
|
|
295
261
|
managedToolStatusStarted = false;
|
|
296
262
|
// Streaming message tracking
|
|
263
|
+
/** Host-visible for ./interactive-selectors.ts (拆分 slice 2). */
|
|
297
264
|
streamingComponent = undefined;
|
|
265
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5). */
|
|
298
266
|
streamingMessage = undefined;
|
|
299
267
|
// Tool execution tracking: toolCallId -> component
|
|
268
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5). */
|
|
300
269
|
pendingTools = new Map();
|
|
301
270
|
// D-083 quiet transcript: the tool-run group for the current assistant
|
|
302
271
|
// turn (one group per message_start; null between turns).
|
|
272
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5). */
|
|
303
273
|
activeToolRunGroup = undefined;
|
|
304
274
|
// Tool output expansion state
|
|
305
275
|
/** Host-visible for ./interactive-dialog-ui.ts (S5 slice 2). */
|
|
306
276
|
toolOutputExpanded = false;
|
|
307
277
|
// Thinking block visibility state
|
|
278
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
308
279
|
hideThinkingBlock = false;
|
|
280
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
309
281
|
outputPad = 1;
|
|
282
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5). */
|
|
310
283
|
mermaidMarkdownTransformer = createMermaidMarkdownTransformer({
|
|
311
284
|
getMode: () => this.settingsManager.getMermaidRenderingMode(),
|
|
312
285
|
theme,
|
|
313
286
|
});
|
|
314
287
|
// Agent subscription unsubscribe function
|
|
288
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5). */
|
|
315
289
|
unsubscribe;
|
|
290
|
+
/** Host-visible for ./interactive-lifecycle.ts (拆分 slice 4). */
|
|
316
291
|
signalCleanupHandlers = [];
|
|
317
292
|
// Track if editor is in bash mode (text starts with !)
|
|
318
293
|
isBashMode = false;
|
|
319
294
|
// Track current bash execution component
|
|
295
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
320
296
|
bashComponent = undefined;
|
|
321
297
|
// Track pending bash components (shown in pending area, moved to chat on submit)
|
|
298
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
322
299
|
pendingBashComponents = [];
|
|
323
300
|
// Auto-compaction state
|
|
301
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5). */
|
|
324
302
|
autoCompactionEscapeHandler;
|
|
325
303
|
// Auto-retry state
|
|
304
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5). */
|
|
326
305
|
retryEscapeHandler;
|
|
327
306
|
// Messages queued while compaction is running
|
|
307
|
+
/** Host-visible for ./interactive-queues.ts (拆分 slice 3). */
|
|
328
308
|
compactionQueuedMessages = [];
|
|
329
309
|
// Shutdown state
|
|
310
|
+
/** Host-visible for ./interactive-lifecycle.ts (拆分 slice 4). */
|
|
330
311
|
shutdownRequested = false;
|
|
312
|
+
/** Host-visible for ./interactive-lifecycle.ts (拆分 slice 4): reentrancy guard. */
|
|
313
|
+
isShuttingDown = false;
|
|
331
314
|
// Extension UI state: host-visible for ./interactive-dialog-ui.ts (S5 slice 2).
|
|
332
315
|
dialogSelector = undefined;
|
|
333
316
|
dialogInput = undefined;
|
|
@@ -343,12 +326,15 @@ export class InteractiveMode {
|
|
|
343
326
|
headerContainer;
|
|
344
327
|
// Built-in header (logo + keybinding hints + changelog)
|
|
345
328
|
builtInHeader = undefined;
|
|
329
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
346
330
|
options;
|
|
347
331
|
onRightClickPaste = () => {
|
|
348
332
|
void this.handleRightClickPaste();
|
|
349
333
|
};
|
|
334
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
350
335
|
themeController;
|
|
351
336
|
// Convenience accessors
|
|
337
|
+
/** Host-visible for ./interactive-selectors.ts (拆分 slice 2): only the curated AgentSessionHostSurface (setTransport) is consumed there. */
|
|
352
338
|
get session() {
|
|
353
339
|
return this.runtimeHost.session;
|
|
354
340
|
}
|
|
@@ -620,6 +606,7 @@ export class InteractiveMode {
|
|
|
620
606
|
}
|
|
621
607
|
this.ui.stop({ preserveScreen: this.renderer.mode === "fullscreen" });
|
|
622
608
|
}
|
|
609
|
+
/** Host-visible for ./interactive-selectors.ts (拆分 slice 2). */
|
|
623
610
|
switchTuiMode(mode, restoreProgress = true, startRenderer = true) {
|
|
624
611
|
const previousUi = this.renderer;
|
|
625
612
|
if (mode === previousUi.mode)
|
|
@@ -837,46 +824,28 @@ export class InteractiveMode {
|
|
|
837
824
|
this.ui.terminal.setTitle(`${APP_TITLE} - ${cwdBasename}`);
|
|
838
825
|
}
|
|
839
826
|
}
|
|
827
|
+
/** Host-visible for ./startup-services.ts. */
|
|
828
|
+
getVersion() {
|
|
829
|
+
return this.version;
|
|
830
|
+
}
|
|
831
|
+
/** Narrow cwd projection for startup background checks (D-048). */
|
|
832
|
+
getCwd() {
|
|
833
|
+
return this.facade.getCwd();
|
|
834
|
+
}
|
|
835
|
+
/** Live initialization read for late-arriving background checks. */
|
|
836
|
+
hasInitialized() {
|
|
837
|
+
return this.isInitialized;
|
|
838
|
+
}
|
|
840
839
|
/**
|
|
841
840
|
* Run the interactive mode. This is the main entry point.
|
|
842
841
|
* Initializes the UI, shows warnings, processes initial messages, and starts the interactive loop.
|
|
843
842
|
*/
|
|
844
843
|
async run() {
|
|
845
844
|
await this.init();
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
.then(() => this.updateAvailableProviderCount())
|
|
851
|
-
.catch(() => { })
|
|
852
|
-
.finally(() => clearTimeout(timeout));
|
|
853
|
-
}
|
|
854
|
-
// Start version check asynchronously
|
|
855
|
-
checkForNewVersion(this.version).then((newRelease) => {
|
|
856
|
-
if (newRelease) {
|
|
857
|
-
this.showNewVersionNotification(newRelease);
|
|
858
|
-
}
|
|
859
|
-
});
|
|
860
|
-
// Start package update check asynchronously
|
|
861
|
-
this.checkForPackageUpdates()
|
|
862
|
-
.then((updates) => {
|
|
863
|
-
if (updates.length > 0) {
|
|
864
|
-
this.showPackageUpdateNotification(updates);
|
|
865
|
-
}
|
|
866
|
-
})
|
|
867
|
-
.finally(() => {
|
|
868
|
-
// On Windows, npm can overwrite the shared console title while checking
|
|
869
|
-
// extension package versions. Restore the product title after the startup check.
|
|
870
|
-
if (process.platform === "win32" && this.isInitialized) {
|
|
871
|
-
this.updateTerminalTitle();
|
|
872
|
-
}
|
|
873
|
-
});
|
|
874
|
-
// Check tmux keyboard setup asynchronously
|
|
875
|
-
this.checkTmuxKeyboardSetup().then((warning) => {
|
|
876
|
-
if (warning) {
|
|
877
|
-
this.showWarning(warning);
|
|
878
|
-
}
|
|
879
|
-
});
|
|
845
|
+
// Fire-and-forget startup background checks (catalog refresh, version
|
|
846
|
+
// check, package updates, tmux probe); scheduling lives in
|
|
847
|
+
// ./startup-services.ts.
|
|
848
|
+
startStartupBackgroundChecks(this);
|
|
880
849
|
// Show startup warnings
|
|
881
850
|
const { migratedProviders, startupDiagnostics, modelFallbackMessage, initialMessage, initialImages, initialMessages, } = this.options;
|
|
882
851
|
for (const diagnostic of startupDiagnostics ?? []) {
|
|
@@ -963,64 +932,6 @@ export class InteractiveMode {
|
|
|
963
932
|
this.showStatus(t("Previous reply was empty, retrying..."));
|
|
964
933
|
await this.facade.prompt(EMPTY_REPLY_RETRY_PROMPT);
|
|
965
934
|
}
|
|
966
|
-
async checkForPackageUpdates() {
|
|
967
|
-
if (process.env.AGENT_FORGE_OFFLINE) {
|
|
968
|
-
return [];
|
|
969
|
-
}
|
|
970
|
-
try {
|
|
971
|
-
const packageManager = new DefaultPackageManager({
|
|
972
|
-
cwd: this.facade.getCwd(),
|
|
973
|
-
agentDir: getAgentDir(),
|
|
974
|
-
settingsManager: this.settingsManager,
|
|
975
|
-
});
|
|
976
|
-
const updates = await packageManager.checkForAvailableUpdates();
|
|
977
|
-
return updates.map((update) => update.displayName);
|
|
978
|
-
}
|
|
979
|
-
catch {
|
|
980
|
-
return [];
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
async checkTmuxKeyboardSetup() {
|
|
984
|
-
if (!process.env.TMUX)
|
|
985
|
-
return undefined;
|
|
986
|
-
const runTmuxShow = (option) => {
|
|
987
|
-
return new Promise((resolve) => {
|
|
988
|
-
const proc = spawn("tmux", ["show", "-gv", option], {
|
|
989
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
990
|
-
});
|
|
991
|
-
let stdout = "";
|
|
992
|
-
const timer = setTimeout(() => {
|
|
993
|
-
proc.kill();
|
|
994
|
-
resolve(undefined);
|
|
995
|
-
}, 2000);
|
|
996
|
-
proc.stdout?.on("data", (data) => {
|
|
997
|
-
stdout += data.toString();
|
|
998
|
-
});
|
|
999
|
-
proc.on("error", () => {
|
|
1000
|
-
clearTimeout(timer);
|
|
1001
|
-
resolve(undefined);
|
|
1002
|
-
});
|
|
1003
|
-
proc.on("close", (code) => {
|
|
1004
|
-
clearTimeout(timer);
|
|
1005
|
-
resolve(code === 0 ? stdout.trim() : undefined);
|
|
1006
|
-
});
|
|
1007
|
-
});
|
|
1008
|
-
};
|
|
1009
|
-
const [extendedKeys, extendedKeysFormat] = await Promise.all([
|
|
1010
|
-
runTmuxShow("extended-keys"),
|
|
1011
|
-
runTmuxShow("extended-keys-format"),
|
|
1012
|
-
]);
|
|
1013
|
-
// If we couldn't query tmux (timeout, sandbox, etc.), don't warn
|
|
1014
|
-
if (extendedKeys === undefined)
|
|
1015
|
-
return undefined;
|
|
1016
|
-
if (extendedKeys !== "on" && extendedKeys !== "always") {
|
|
1017
|
-
return t("tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart tmux.");
|
|
1018
|
-
}
|
|
1019
|
-
if (extendedKeysFormat === "xterm") {
|
|
1020
|
-
return t("tmux extended-keys-format is xterm. Agent Forge works best with csi-u. Add `set -g extended-keys-format csi-u` to ~/.tmux.conf and restart tmux.");
|
|
1021
|
-
}
|
|
1022
|
-
return undefined;
|
|
1023
|
-
}
|
|
1024
935
|
/**
|
|
1025
936
|
* Get changelog entries to display on startup.
|
|
1026
937
|
* Only shows new entries since last seen version, skips for resumed sessions.
|
|
@@ -1045,6 +956,7 @@ export class InteractiveMode {
|
|
|
1045
956
|
}
|
|
1046
957
|
return undefined;
|
|
1047
958
|
}
|
|
959
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
1048
960
|
getMarkdownThemeWithSettings() {
|
|
1049
961
|
return {
|
|
1050
962
|
...getMarkdownTheme(),
|
|
@@ -1102,6 +1014,7 @@ export class InteractiveMode {
|
|
|
1102
1014
|
formatDiagnostics(diagnostics, sourceInfos) {
|
|
1103
1015
|
return formatDiagnostics(diagnostics, sourceInfos);
|
|
1104
1016
|
}
|
|
1017
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
1105
1018
|
showLoadedResources(options) {
|
|
1106
1019
|
// Resource rendering is idempotent; chat clears no longer clear this separate container.
|
|
1107
1020
|
this.loadedResourcesContainer.clear();
|
|
@@ -1231,14 +1144,17 @@ export class InteractiveMode {
|
|
|
1231
1144
|
*/
|
|
1232
1145
|
async bindCurrentSessionExtensions() {
|
|
1233
1146
|
await this.session.startSession();
|
|
1147
|
+
kickMemoryModelPreheat(this.session);
|
|
1234
1148
|
setRegisteredThemes(buildRegisteredThemes(this.facade.getThemes().themes));
|
|
1235
1149
|
this.setupAutocompleteProvider();
|
|
1236
1150
|
this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
|
|
1237
1151
|
this.showStartupNoticesIfNeeded();
|
|
1238
1152
|
}
|
|
1153
|
+
/** Host-visible for ./interactive-selectors.ts (拆分 slice 2). */
|
|
1239
1154
|
applyFullscreenScrollbarSetting() {
|
|
1240
1155
|
this.transcriptScrollView?.setScrollbar(this.settingsManager.getFullscreenScrollbar());
|
|
1241
1156
|
}
|
|
1157
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
1242
1158
|
applyRuntimeSettings() {
|
|
1243
1159
|
setI18nLanguage(this.settingsManager.getUiLanguage());
|
|
1244
1160
|
setCapabilityOverrides(this.settingsManager.getTerminalCapabilityOverrides());
|
|
@@ -1290,6 +1206,7 @@ export class InteractiveMode {
|
|
|
1290
1206
|
this.updateEditorBorderColor();
|
|
1291
1207
|
this.updateTerminalTitle();
|
|
1292
1208
|
}
|
|
1209
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
1293
1210
|
async handleFatalRuntimeError(prefix, error) {
|
|
1294
1211
|
const message = error instanceof Error ? error.message : String(error);
|
|
1295
1212
|
this.showError(`${prefix}: ${message}`);
|
|
@@ -1309,19 +1226,24 @@ export class InteractiveMode {
|
|
|
1309
1226
|
}
|
|
1310
1227
|
/**
|
|
1311
1228
|
* Get a registered tool definition by name (for custom rendering).
|
|
1229
|
+
*
|
|
1230
|
+
* Host-visible for ./interactive-events.ts (拆分 slice 5).
|
|
1312
1231
|
*/
|
|
1313
1232
|
getRegisteredToolDefinition(toolName) {
|
|
1314
|
-
return
|
|
1233
|
+
return eventsFlow.getRegisteredToolDefinition(this, toolName);
|
|
1315
1234
|
}
|
|
1235
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5). */
|
|
1316
1236
|
getMarkdownTransformers() {
|
|
1317
|
-
return
|
|
1237
|
+
return eventsFlow.getMarkdownTransformers(this);
|
|
1318
1238
|
}
|
|
1239
|
+
/** Host-visible for ./interactive-selectors.ts (拆分 slice 2). */
|
|
1319
1240
|
showStatusIndicator(indicator) {
|
|
1320
1241
|
this.activeStatusIndicator?.dispose();
|
|
1321
1242
|
this.activeStatusIndicator = indicator;
|
|
1322
1243
|
this.statusContainer.clear();
|
|
1323
1244
|
this.statusContainer.addChild(indicator);
|
|
1324
1245
|
}
|
|
1246
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
1325
1247
|
clearStatusIndicator(kind) {
|
|
1326
1248
|
if (kind && this.activeStatusIndicator?.kind !== kind) {
|
|
1327
1249
|
return;
|
|
@@ -1334,6 +1256,7 @@ export class InteractiveMode {
|
|
|
1334
1256
|
this.statusContainer.addChild(this.idleStatus);
|
|
1335
1257
|
}
|
|
1336
1258
|
}
|
|
1259
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5): turn_start working indicator. */
|
|
1337
1260
|
showWorkingStatusIndicator() {
|
|
1338
1261
|
this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.workingIndicatorOptions));
|
|
1339
1262
|
}
|
|
@@ -1378,6 +1301,7 @@ export class InteractiveMode {
|
|
|
1378
1301
|
this.activeStatusIndicator.setMessage(message ?? this.defaultWorkingMessage);
|
|
1379
1302
|
}
|
|
1380
1303
|
}
|
|
1304
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
1381
1305
|
resetDialogUI() {
|
|
1382
1306
|
if (this.dialogSelector) {
|
|
1383
1307
|
this.hideDialogSelector();
|
|
@@ -1443,6 +1367,7 @@ export class InteractiveMode {
|
|
|
1443
1367
|
async showDialogConfirm(title, message, opts) {
|
|
1444
1368
|
return showDialogConfirm(this, title, message, opts);
|
|
1445
1369
|
}
|
|
1370
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
1446
1371
|
async promptForMissingSessionCwd(error) {
|
|
1447
1372
|
const confirmed = await this.showDialogConfirm("Session cwd not found", formatMissingSessionCwdPrompt(error.issue));
|
|
1448
1373
|
return confirmed ? error.issue.fallbackCwd : undefined;
|
|
@@ -1724,360 +1649,24 @@ export class InteractiveMode {
|
|
|
1724
1649
|
setEditorText(text) {
|
|
1725
1650
|
this.editor.setText(text);
|
|
1726
1651
|
}
|
|
1652
|
+
// =========================================================================
|
|
1653
|
+
// Agent event bridge and transcript rendering — implementation in
|
|
1654
|
+
// ./interactive-events.ts (拆分 slice 5) behind the EventsHost interface;
|
|
1655
|
+
// same-name delegates keep the rebind/subscribe flow and prototype-level
|
|
1656
|
+
// tests bound to these names. showManagedToolStatus, showStatus, and
|
|
1657
|
+
// getUserInput stay on the mode (startup/footer/main-loop faces).
|
|
1658
|
+
// =========================================================================
|
|
1659
|
+
/** Session subscription; implementation in ./interactive-events.ts (拆分 slice 5). */
|
|
1727
1660
|
subscribeToAgent() {
|
|
1728
|
-
|
|
1729
|
-
await this.handleEvent(event);
|
|
1730
|
-
});
|
|
1661
|
+
eventsFlow.subscribeToAgent(this);
|
|
1731
1662
|
}
|
|
1663
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in ./interactive-events.ts. */
|
|
1732
1664
|
async handleEvent(event) {
|
|
1733
|
-
|
|
1734
|
-
await this.init();
|
|
1735
|
-
}
|
|
1736
|
-
this.footer.invalidate();
|
|
1737
|
-
// Failover snapshots land as entry_appended events; the ticker decides here
|
|
1738
|
-
// whether the per-second countdown re-render is needed (§4.7 Phase 3).
|
|
1739
|
-
this.failoverTicker?.refresh();
|
|
1740
|
-
switch (event.type) {
|
|
1741
|
-
case "agent_start":
|
|
1742
|
-
this.pendingTools.clear();
|
|
1743
|
-
// Restore main escape handler if retry handler is still active
|
|
1744
|
-
// (retry success event fires later, but we need main handler now)
|
|
1745
|
-
if (this.retryEscapeHandler) {
|
|
1746
|
-
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
1747
|
-
this.retryEscapeHandler = undefined;
|
|
1748
|
-
}
|
|
1749
|
-
break;
|
|
1750
|
-
case "turn_start":
|
|
1751
|
-
if (this.settingsManager.getShowTerminalProgress()) {
|
|
1752
|
-
this.ui.terminal.setProgress(true);
|
|
1753
|
-
}
|
|
1754
|
-
if (this.workingVisible) {
|
|
1755
|
-
if (this.activeStatusIndicator?.kind !== "working") {
|
|
1756
|
-
this.showWorkingStatusIndicator();
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
else {
|
|
1760
|
-
this.clearStatusIndicator();
|
|
1761
|
-
}
|
|
1762
|
-
this.ui.requestRender();
|
|
1763
|
-
break;
|
|
1764
|
-
case "queue_update":
|
|
1765
|
-
this.updatePendingMessagesDisplay();
|
|
1766
|
-
this.ui.requestRender();
|
|
1767
|
-
break;
|
|
1768
|
-
case "entry_appended":
|
|
1769
|
-
// Custom entries have no renderer source since the legacy extension
|
|
1770
|
-
// surface was removed; nothing to append to the chat.
|
|
1771
|
-
break;
|
|
1772
|
-
case "session_info_changed":
|
|
1773
|
-
this.updateTerminalTitle();
|
|
1774
|
-
this.footer.invalidate();
|
|
1775
|
-
this.ui.requestRender();
|
|
1776
|
-
break;
|
|
1777
|
-
case "thinking_level_changed":
|
|
1778
|
-
this.footer.invalidate();
|
|
1779
|
-
this.updateEditorBorderColor();
|
|
1780
|
-
break;
|
|
1781
|
-
case "message_start":
|
|
1782
|
-
if (event.message.role === "custom") {
|
|
1783
|
-
this.addMessageToChat(event.message);
|
|
1784
|
-
this.ui.requestRender();
|
|
1785
|
-
}
|
|
1786
|
-
else if (event.message.role === "user") {
|
|
1787
|
-
this.addMessageToChat(event.message);
|
|
1788
|
-
this.updatePendingMessagesDisplay();
|
|
1789
|
-
this.ui.requestRender();
|
|
1790
|
-
}
|
|
1791
|
-
else if (event.message.role === "assistant") {
|
|
1792
|
-
// D-083: a new assistant turn closes the previous turn's
|
|
1793
|
-
// tool-run group (tools executed between the turns) and
|
|
1794
|
-
// starts a fresh one on its first toolCall.
|
|
1795
|
-
this.activeToolRunGroup?.markRunComplete();
|
|
1796
|
-
this.activeToolRunGroup = undefined;
|
|
1797
|
-
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
|
|
1798
|
-
// D-083: components created after a ctrl+o toggle inherit the
|
|
1799
|
-
// current expansion so detail mode holds across turns.
|
|
1800
|
-
if (this.toolOutputExpanded) {
|
|
1801
|
-
this.streamingComponent.setExpanded(true);
|
|
1802
|
-
}
|
|
1803
|
-
this.streamingMessage = event.message;
|
|
1804
|
-
this.chatContainer.addChild(this.streamingComponent);
|
|
1805
|
-
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
1806
|
-
this.ui.requestRender();
|
|
1807
|
-
}
|
|
1808
|
-
break;
|
|
1809
|
-
case "message_update":
|
|
1810
|
-
if (this.streamingComponent && event.message.role === "assistant") {
|
|
1811
|
-
this.streamingMessage = event.message;
|
|
1812
|
-
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
1813
|
-
for (const content of this.streamingMessage.content) {
|
|
1814
|
-
if (content.type === "toolCall") {
|
|
1815
|
-
if (!this.pendingTools.has(content.id)) {
|
|
1816
|
-
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
|
|
1817
|
-
showImages: this.settingsManager.getShowImages(),
|
|
1818
|
-
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
1819
|
-
}, this.getRegisteredToolDefinition(content.name), this.ui, this.facade.getCwd());
|
|
1820
|
-
component.setExpanded(this.toolOutputExpanded);
|
|
1821
|
-
this.addToolToRunGroup(component);
|
|
1822
|
-
this.pendingTools.set(content.id, component);
|
|
1823
|
-
}
|
|
1824
|
-
else {
|
|
1825
|
-
const component = this.pendingTools.get(content.id);
|
|
1826
|
-
// v8 ignore next -- has() 已确认键存在,get() 恒返回组件
|
|
1827
|
-
if (component) {
|
|
1828
|
-
component.updateArgs(content.arguments);
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
this.ui.requestRender();
|
|
1834
|
-
}
|
|
1835
|
-
break;
|
|
1836
|
-
case "message_end":
|
|
1837
|
-
if (event.message.role === "user")
|
|
1838
|
-
break;
|
|
1839
|
-
if (this.streamingComponent && event.message.role === "assistant") {
|
|
1840
|
-
this.streamingMessage = event.message;
|
|
1841
|
-
let errorMessage;
|
|
1842
|
-
if (this.streamingMessage.stopReason === "aborted") {
|
|
1843
|
-
const retryAttempt = this.facade.getRetryAttempt();
|
|
1844
|
-
errorMessage =
|
|
1845
|
-
retryAttempt > 0
|
|
1846
|
-
? retryAttempt === 1
|
|
1847
|
-
? t("Aborted after {count} retry attempt", { count: retryAttempt })
|
|
1848
|
-
: t("Aborted after {count} retry attempts", { count: retryAttempt })
|
|
1849
|
-
: t("Operation aborted");
|
|
1850
|
-
this.streamingMessage.errorMessage = errorMessage;
|
|
1851
|
-
}
|
|
1852
|
-
this.streamingComponent.updateContent(this.streamingMessage, false);
|
|
1853
|
-
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
1854
|
-
if (!errorMessage) {
|
|
1855
|
-
errorMessage = this.streamingMessage.errorMessage || t("Error");
|
|
1856
|
-
}
|
|
1857
|
-
for (const [, component] of this.pendingTools.entries()) {
|
|
1858
|
-
component.updateResult({
|
|
1859
|
-
content: [{ type: "text", text: errorMessage }],
|
|
1860
|
-
isError: true,
|
|
1861
|
-
});
|
|
1862
|
-
this.activeToolRunGroup?.noteResult(true);
|
|
1863
|
-
}
|
|
1864
|
-
// No tool execution follows an aborted/errored message:
|
|
1865
|
-
// finalize the group marker here.
|
|
1866
|
-
this.activeToolRunGroup?.markRunComplete();
|
|
1867
|
-
this.pendingTools.clear();
|
|
1868
|
-
}
|
|
1869
|
-
else {
|
|
1870
|
-
// Args are now complete - trigger diff computation for edit tools
|
|
1871
|
-
for (const [, component] of this.pendingTools.entries()) {
|
|
1872
|
-
component.setArgsComplete();
|
|
1873
|
-
}
|
|
1874
|
-
this.maybeShowThinkingDropNotice(this.streamingMessage);
|
|
1875
|
-
this.maybeShowCacheMissNotice(this.streamingMessage);
|
|
1876
|
-
}
|
|
1877
|
-
this.streamingComponent = undefined;
|
|
1878
|
-
this.streamingMessage = undefined;
|
|
1879
|
-
// D-083: a toolUse stop reason is followed by tool execution
|
|
1880
|
-
// events (start/end arrive after message_end — the session
|
|
1881
|
-
// executes tools once the streamed message completes), so the
|
|
1882
|
-
// group stays open for setActiveTool/noteResult here and is
|
|
1883
|
-
// finalized at agent_end / the next message_start.
|
|
1884
|
-
this.footer.invalidate();
|
|
1885
|
-
}
|
|
1886
|
-
this.ui.requestRender();
|
|
1887
|
-
break;
|
|
1888
|
-
case "bash_execution_update":
|
|
1889
|
-
// The bash execution callback handles TUI output rendering.
|
|
1890
|
-
break;
|
|
1891
|
-
case "tool_execution_start": {
|
|
1892
|
-
let component = this.pendingTools.get(event.toolCallId);
|
|
1893
|
-
if (!component) {
|
|
1894
|
-
component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
|
|
1895
|
-
showImages: this.settingsManager.getShowImages(),
|
|
1896
|
-
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
1897
|
-
}, this.getRegisteredToolDefinition(event.toolName), this.ui, this.facade.getCwd());
|
|
1898
|
-
component.setExpanded(this.toolOutputExpanded);
|
|
1899
|
-
this.addToolToRunGroup(component);
|
|
1900
|
-
this.pendingTools.set(event.toolCallId, component);
|
|
1901
|
-
}
|
|
1902
|
-
component.markExecutionStarted();
|
|
1903
|
-
this.activeToolRunGroup?.setActiveTool(event.toolName);
|
|
1904
|
-
this.ui.requestRender();
|
|
1905
|
-
break;
|
|
1906
|
-
}
|
|
1907
|
-
case "tool_execution_update": {
|
|
1908
|
-
const component = this.pendingTools.get(event.toolCallId);
|
|
1909
|
-
if (component) {
|
|
1910
|
-
component.updateResult({ ...event.partialResult, isError: false }, true);
|
|
1911
|
-
this.ui.requestRender();
|
|
1912
|
-
}
|
|
1913
|
-
break;
|
|
1914
|
-
}
|
|
1915
|
-
case "tool_execution_end": {
|
|
1916
|
-
const component = this.pendingTools.get(event.toolCallId);
|
|
1917
|
-
if (component) {
|
|
1918
|
-
component.updateResult({ ...event.result, isError: event.isError });
|
|
1919
|
-
this.activeToolRunGroup?.noteResult(event.isError);
|
|
1920
|
-
this.pendingTools.delete(event.toolCallId);
|
|
1921
|
-
this.ui.requestRender();
|
|
1922
|
-
}
|
|
1923
|
-
break;
|
|
1924
|
-
}
|
|
1925
|
-
case "capability_command_result": {
|
|
1926
|
-
const links = event.result.links?.map((link) => `${link.label}: ${link.url}`) ?? [];
|
|
1927
|
-
const message = [event.result.message, ...links].filter(Boolean).join("\n");
|
|
1928
|
-
if (event.result.status === "error")
|
|
1929
|
-
this.showError(message);
|
|
1930
|
-
else
|
|
1931
|
-
this.showStatus(message);
|
|
1932
|
-
this.ui.requestRender();
|
|
1933
|
-
break;
|
|
1934
|
-
}
|
|
1935
|
-
case "agent_end":
|
|
1936
|
-
if (this.settingsManager.getShowTerminalProgress()) {
|
|
1937
|
-
this.ui.terminal.setProgress(false);
|
|
1938
|
-
}
|
|
1939
|
-
this.clearStatusIndicator("working");
|
|
1940
|
-
if (this.streamingComponent) {
|
|
1941
|
-
this.chatContainer.removeChild(this.streamingComponent);
|
|
1942
|
-
this.streamingComponent = undefined;
|
|
1943
|
-
this.streamingMessage = undefined;
|
|
1944
|
-
}
|
|
1945
|
-
this.pendingTools.clear();
|
|
1946
|
-
// D-083: the whole agent turn (messages + tool executions) is
|
|
1947
|
-
// over — finalize the last group's marker (count/sole-tool name
|
|
1948
|
-
// + failure count). Completed groups stay in the transcript.
|
|
1949
|
-
this.activeToolRunGroup?.markRunComplete();
|
|
1950
|
-
this.activeToolRunGroup = undefined;
|
|
1951
|
-
this.ui.requestRender();
|
|
1952
|
-
break;
|
|
1953
|
-
case "agent_settled":
|
|
1954
|
-
await this.checkShutdownRequested();
|
|
1955
|
-
break;
|
|
1956
|
-
case "compaction_start": {
|
|
1957
|
-
if (this.settingsManager.getShowTerminalProgress()) {
|
|
1958
|
-
this.ui.terminal.setProgress(true);
|
|
1959
|
-
}
|
|
1960
|
-
// Keep editor active; submissions are queued during compaction.
|
|
1961
|
-
this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
|
|
1962
|
-
this.defaultEditor.onEscape = () => {
|
|
1963
|
-
this.facade.abortCompaction();
|
|
1964
|
-
};
|
|
1965
|
-
this.showStatusIndicator(new CompactionStatusIndicator(this.ui, event.reason));
|
|
1966
|
-
this.ui.requestRender();
|
|
1967
|
-
break;
|
|
1968
|
-
}
|
|
1969
|
-
case "compaction_end": {
|
|
1970
|
-
if (this.settingsManager.getShowTerminalProgress()) {
|
|
1971
|
-
this.ui.terminal.setProgress(false);
|
|
1972
|
-
}
|
|
1973
|
-
if (this.autoCompactionEscapeHandler) {
|
|
1974
|
-
this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
|
|
1975
|
-
this.autoCompactionEscapeHandler = undefined;
|
|
1976
|
-
}
|
|
1977
|
-
this.clearStatusIndicator("compaction");
|
|
1978
|
-
if (event.aborted) {
|
|
1979
|
-
if (event.reason === "manual") {
|
|
1980
|
-
this.showError(t("Compaction cancelled"));
|
|
1981
|
-
}
|
|
1982
|
-
else {
|
|
1983
|
-
this.showStatus(t("Auto-compaction cancelled"));
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
else if (event.result) {
|
|
1987
|
-
const entries = this.facade.getContextEntries();
|
|
1988
|
-
if (entries[0]?.type !== "compaction") {
|
|
1989
|
-
throw new Error(t("Completed compaction is missing from the session context"));
|
|
1990
|
-
}
|
|
1991
|
-
this.chatContainer.clear();
|
|
1992
|
-
// The latest compaction is prepended for model context; append it below at its chronological position.
|
|
1993
|
-
this.renderSessionEntries(entries.slice(1));
|
|
1994
|
-
this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString()));
|
|
1995
|
-
if (event.result.usage) {
|
|
1996
|
-
this.addCompactionCostNotice({
|
|
1997
|
-
type: "compaction_cost",
|
|
1998
|
-
kind: "compaction",
|
|
1999
|
-
usage: event.result.usage,
|
|
2000
|
-
});
|
|
2001
|
-
}
|
|
2002
|
-
this.footer.invalidate();
|
|
2003
|
-
}
|
|
2004
|
-
else if (event.errorMessage) {
|
|
2005
|
-
if (event.reason === "manual") {
|
|
2006
|
-
this.showError(event.errorMessage);
|
|
2007
|
-
}
|
|
2008
|
-
else {
|
|
2009
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2010
|
-
this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
void this.flushCompactionQueue({ willRetry: event.willRetry });
|
|
2014
|
-
this.ui.requestRender();
|
|
2015
|
-
break;
|
|
2016
|
-
}
|
|
2017
|
-
case "compaction_skipped": {
|
|
2018
|
-
this.showStatus(t("Auto-compaction skipped: nothing to compact"));
|
|
2019
|
-
this.ui.requestRender();
|
|
2020
|
-
break;
|
|
2021
|
-
}
|
|
2022
|
-
case "auto_retry_start": {
|
|
2023
|
-
// Set up escape to abort retry
|
|
2024
|
-
this.retryEscapeHandler = this.defaultEditor.onEscape;
|
|
2025
|
-
this.defaultEditor.onEscape = () => {
|
|
2026
|
-
this.facade.abortRetry();
|
|
2027
|
-
};
|
|
2028
|
-
this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
|
|
2029
|
-
this.ui.requestRender();
|
|
2030
|
-
break;
|
|
2031
|
-
}
|
|
2032
|
-
case "auto_retry_end": {
|
|
2033
|
-
// Restore escape handler
|
|
2034
|
-
if (this.retryEscapeHandler) {
|
|
2035
|
-
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2036
|
-
this.retryEscapeHandler = undefined;
|
|
2037
|
-
}
|
|
2038
|
-
this.clearStatusIndicator("retry");
|
|
2039
|
-
// Show error only on final failure (success shows normal response)
|
|
2040
|
-
if (!event.success) {
|
|
2041
|
-
this.showError(t("Retry failed after {count} attempts: {error}", {
|
|
2042
|
-
count: event.attempt,
|
|
2043
|
-
error: event.finalError || t("Unknown error"),
|
|
2044
|
-
}));
|
|
2045
|
-
}
|
|
2046
|
-
this.ui.requestRender();
|
|
2047
|
-
break;
|
|
2048
|
-
}
|
|
2049
|
-
case "summarization_retry_scheduled": {
|
|
2050
|
-
this.showError(event.errorMessage);
|
|
2051
|
-
this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
|
|
2052
|
-
this.ui.requestRender();
|
|
2053
|
-
break;
|
|
2054
|
-
}
|
|
2055
|
-
case "summarization_retry_attempt_start": {
|
|
2056
|
-
this.clearStatusIndicator("retry");
|
|
2057
|
-
if (event.source === "branchSummary") {
|
|
2058
|
-
this.showStatusIndicator(new BranchSummaryStatusIndicator(this.ui));
|
|
2059
|
-
}
|
|
2060
|
-
else {
|
|
2061
|
-
this.showStatusIndicator(new CompactionStatusIndicator(this.ui, event.reason));
|
|
2062
|
-
}
|
|
2063
|
-
this.ui.requestRender();
|
|
2064
|
-
break;
|
|
2065
|
-
}
|
|
2066
|
-
case "summarization_retry_finished": {
|
|
2067
|
-
this.clearStatusIndicator("retry");
|
|
2068
|
-
this.ui.requestRender();
|
|
2069
|
-
break;
|
|
2070
|
-
}
|
|
2071
|
-
}
|
|
1665
|
+
return eventsFlow.handleEvent(this, event);
|
|
2072
1666
|
}
|
|
2073
|
-
/**
|
|
1667
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in ./interactive-events.ts. */
|
|
2074
1668
|
getUserMessageText(message) {
|
|
2075
|
-
|
|
2076
|
-
return "";
|
|
2077
|
-
const textBlocks = typeof message.content === "string"
|
|
2078
|
-
? [{ type: "text", text: message.content }]
|
|
2079
|
-
: message.content.filter((c) => c.type === "text");
|
|
2080
|
-
return textBlocks.map((c) => c.text).join("");
|
|
1669
|
+
return eventsFlow.getUserMessageText(message);
|
|
2081
1670
|
}
|
|
2082
1671
|
/** Show a managed-tool status update in the chat. */
|
|
2083
1672
|
showManagedToolStatus(status) {
|
|
@@ -2115,293 +1704,57 @@ export class InteractiveMode {
|
|
|
2115
1704
|
this.lastStatusText = text;
|
|
2116
1705
|
this.ui.requestRender();
|
|
2117
1706
|
}
|
|
1707
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in ./interactive-events.ts. */
|
|
2118
1708
|
addMessageToChat(message, options) {
|
|
2119
|
-
|
|
2120
|
-
case "bashExecution": {
|
|
2121
|
-
const component = new BashExecutionComponent(message.command, this.ui, message.excludeFromContext);
|
|
2122
|
-
if (message.output) {
|
|
2123
|
-
component.appendOutput(message.output);
|
|
2124
|
-
}
|
|
2125
|
-
component.setComplete(message.exitCode, message.cancelled, message.truncated ? { truncated: true } : undefined, message.fullOutputPath);
|
|
2126
|
-
this.chatContainer.addChild(component);
|
|
2127
|
-
break;
|
|
2128
|
-
}
|
|
2129
|
-
case "custom": {
|
|
2130
|
-
if (message.display) {
|
|
2131
|
-
const renderer = undefined;
|
|
2132
|
-
const component = new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2133
|
-
component.setExpanded(this.toolOutputExpanded);
|
|
2134
|
-
this.chatContainer.addChild(component);
|
|
2135
|
-
}
|
|
2136
|
-
break;
|
|
2137
|
-
}
|
|
2138
|
-
case "compactionSummary": {
|
|
2139
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2140
|
-
const component = new CompactionSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
|
|
2141
|
-
component.setExpanded(this.toolOutputExpanded);
|
|
2142
|
-
this.chatContainer.addChild(component);
|
|
2143
|
-
break;
|
|
2144
|
-
}
|
|
2145
|
-
case "branchSummary": {
|
|
2146
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2147
|
-
const component = new BranchSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
|
|
2148
|
-
component.setExpanded(this.toolOutputExpanded);
|
|
2149
|
-
this.chatContainer.addChild(component);
|
|
2150
|
-
break;
|
|
2151
|
-
}
|
|
2152
|
-
case "user": {
|
|
2153
|
-
const textContent = this.getUserMessageText(message);
|
|
2154
|
-
if (textContent) {
|
|
2155
|
-
if (this.chatContainer.children.length > 0) {
|
|
2156
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2157
|
-
}
|
|
2158
|
-
const skillBlock = parseSkillBlock(textContent);
|
|
2159
|
-
if (skillBlock) {
|
|
2160
|
-
// Render skill block (collapsible)
|
|
2161
|
-
const component = new SkillInvocationMessageComponent(skillBlock, this.getMarkdownThemeWithSettings());
|
|
2162
|
-
component.setExpanded(this.toolOutputExpanded);
|
|
2163
|
-
this.chatContainer.addChild(component);
|
|
2164
|
-
// Render user message separately if present
|
|
2165
|
-
if (skillBlock.userMessage) {
|
|
2166
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2167
|
-
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2168
|
-
this.chatContainer.addChild(userComponent);
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
else {
|
|
2172
|
-
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2173
|
-
this.chatContainer.addChild(userComponent);
|
|
2174
|
-
}
|
|
2175
|
-
if (options?.populateHistory) {
|
|
2176
|
-
this.editor.addToHistory?.(textContent);
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
break;
|
|
2180
|
-
}
|
|
2181
|
-
case "assistant": {
|
|
2182
|
-
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
|
|
2183
|
-
// D-083: restored/finished-turn thinking must follow detail mode
|
|
2184
|
-
// (ctrl+o) — the setToolsExpanded loop only reaches children on
|
|
2185
|
-
// toggle, not components created while already expanded.
|
|
2186
|
-
if (this.toolOutputExpanded) {
|
|
2187
|
-
assistantComponent.setExpanded(true);
|
|
2188
|
-
}
|
|
2189
|
-
this.chatContainer.addChild(assistantComponent);
|
|
2190
|
-
break;
|
|
2191
|
-
}
|
|
2192
|
-
case "toolResult": {
|
|
2193
|
-
// Tool results are rendered inline with tool calls, handled separately
|
|
2194
|
-
break;
|
|
2195
|
-
}
|
|
2196
|
-
default: {
|
|
2197
|
-
// v8 ignore next -- exhaustive over AgentMessage roles
|
|
2198
|
-
const _exhaustive = message;
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
1709
|
+
eventsFlow.addMessageToChat(this, message, options);
|
|
2201
1710
|
}
|
|
1711
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in ./interactive-events.ts. */
|
|
2202
1712
|
renderSessionItems(items, options = {}) {
|
|
2203
|
-
|
|
2204
|
-
const renderedPendingTools = new Map();
|
|
2205
|
-
// Cache-miss notices are not persisted; re-derive them from the full entry
|
|
2206
|
-
// list and re-inject them after the assistant messages that paid for them.
|
|
2207
|
-
const cacheMisses = this.settingsManager.getShowCacheMissNotices()
|
|
2208
|
-
? collectCacheMisses(this.facade.getEntries(), this.facade.getModelPriceSource())
|
|
2209
|
-
: new Map();
|
|
2210
|
-
if (options.updateFooter) {
|
|
2211
|
-
this.footer.invalidate();
|
|
2212
|
-
this.updateEditorBorderColor();
|
|
2213
|
-
}
|
|
2214
|
-
for (const item of items) {
|
|
2215
|
-
if (isCustomSessionEntry(item)) {
|
|
2216
|
-
// No entry renderer source since the legacy extension surface was
|
|
2217
|
-
// removed; previously renderer-less entries were skipped as well.
|
|
2218
|
-
continue;
|
|
2219
|
-
}
|
|
2220
|
-
if (isCompactionCostNotice(item)) {
|
|
2221
|
-
this.addCompactionCostNotice(item);
|
|
2222
|
-
continue;
|
|
2223
|
-
}
|
|
2224
|
-
const message = item;
|
|
2225
|
-
// Assistant messages need special handling for tool calls
|
|
2226
|
-
if (message.role === "assistant") {
|
|
2227
|
-
this.addMessageToChat(message);
|
|
2228
|
-
// No message_start on the restore path: reset the group pointer
|
|
2229
|
-
// so each assistant message forms its own tool-run group (D-083).
|
|
2230
|
-
this.activeToolRunGroup = undefined;
|
|
2231
|
-
// Render tool call components
|
|
2232
|
-
for (const content of message.content) {
|
|
2233
|
-
if (content.type === "toolCall") {
|
|
2234
|
-
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
|
|
2235
|
-
showImages: this.settingsManager.getShowImages(),
|
|
2236
|
-
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
2237
|
-
}, this.getRegisteredToolDefinition(content.name), this.ui, this.facade.getCwd());
|
|
2238
|
-
component.setExpanded(this.toolOutputExpanded);
|
|
2239
|
-
this.addToolToRunGroup(component);
|
|
2240
|
-
if (message.stopReason === "aborted" || message.stopReason === "error") {
|
|
2241
|
-
let errorMessage;
|
|
2242
|
-
if (message.stopReason === "aborted") {
|
|
2243
|
-
const retryAttempt = this.facade.getRetryAttempt();
|
|
2244
|
-
errorMessage =
|
|
2245
|
-
retryAttempt > 0
|
|
2246
|
-
? retryAttempt === 1
|
|
2247
|
-
? t("Aborted after {count} retry attempt", { count: retryAttempt })
|
|
2248
|
-
: t("Aborted after {count} retry attempts", { count: retryAttempt })
|
|
2249
|
-
: t("Operation aborted");
|
|
2250
|
-
}
|
|
2251
|
-
else {
|
|
2252
|
-
errorMessage = message.errorMessage || t("Error");
|
|
2253
|
-
}
|
|
2254
|
-
component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
|
|
2255
|
-
this.noteResultInActiveToolRunGroup(true);
|
|
2256
|
-
}
|
|
2257
|
-
else {
|
|
2258
|
-
renderedPendingTools.set(content.id, component);
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
this.markActiveToolRunGroupComplete();
|
|
2263
|
-
if (message.stopReason !== "aborted" && message.stopReason !== "error") {
|
|
2264
|
-
const miss = cacheMisses.get(message);
|
|
2265
|
-
if (miss)
|
|
2266
|
-
this.addCacheMissNotice(miss);
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
else if (message.role === "toolResult") {
|
|
2270
|
-
// Match tool results to pending tool components
|
|
2271
|
-
const component = renderedPendingTools.get(message.toolCallId);
|
|
2272
|
-
if (component) {
|
|
2273
|
-
component.updateResult(message);
|
|
2274
|
-
// Failure never goes silent (D-083): count it on the group
|
|
2275
|
-
// the restored tool call belongs to.
|
|
2276
|
-
this.noteResultInActiveToolRunGroup(message.isError);
|
|
2277
|
-
renderedPendingTools.delete(message.toolCallId);
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
|
-
else {
|
|
2281
|
-
// All other messages use standard rendering
|
|
2282
|
-
this.addMessageToChat(message, options);
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
for (const [toolCallId, component] of renderedPendingTools) {
|
|
2286
|
-
this.pendingTools.set(toolCallId, component);
|
|
2287
|
-
}
|
|
2288
|
-
// Groups stay in the transcript; reset the pointer so a live turn
|
|
2289
|
-
// starts a fresh group (D-083).
|
|
2290
|
-
this.activeToolRunGroup = undefined;
|
|
2291
|
-
this.ui.requestRender();
|
|
1713
|
+
eventsFlow.renderSessionItems(this, items, options);
|
|
2292
1714
|
}
|
|
2293
1715
|
/**
|
|
2294
1716
|
* Render session entries to chat. Used for initial load and rebuild after compaction.
|
|
2295
1717
|
* @param entries Compaction-aware session entries to render
|
|
2296
1718
|
* @param options.updateFooter Update footer state
|
|
2297
1719
|
* @param options.populateHistory Add user messages to editor history
|
|
1720
|
+
*
|
|
1721
|
+
* Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in
|
|
1722
|
+
* ./interactive-events.ts.
|
|
2298
1723
|
*/
|
|
2299
1724
|
renderSessionEntries(entries, options = {}) {
|
|
2300
|
-
|
|
2301
|
-
if (entry.type === "custom") {
|
|
2302
|
-
return [entry];
|
|
2303
|
-
}
|
|
2304
|
-
const messages = sessionEntryToContextMessages(entry);
|
|
2305
|
-
if ((entry.type === "compaction" || entry.type === "branch_summary") && entry.usage && messages.length > 0) {
|
|
2306
|
-
return [...messages, { type: "compaction_cost", kind: entry.type, usage: entry.usage }];
|
|
2307
|
-
}
|
|
2308
|
-
return messages;
|
|
2309
|
-
});
|
|
2310
|
-
this.renderSessionItems(items, options);
|
|
1725
|
+
eventsFlow.renderSessionEntries(this, entries, options);
|
|
2311
1726
|
}
|
|
2312
1727
|
/**
|
|
2313
1728
|
* Render billing usage for a compaction or branch summary. The notice is derived
|
|
2314
1729
|
* from persisted summary usage and is not stored as a separate session entry.
|
|
1730
|
+
*
|
|
1731
|
+
* Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in
|
|
1732
|
+
* ./interactive-events.ts.
|
|
2315
1733
|
*/
|
|
2316
1734
|
addCompactionCostNotice(notice) {
|
|
2317
|
-
|
|
2318
|
-
return;
|
|
2319
|
-
const { usage } = notice;
|
|
2320
|
-
const tokens = usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
2321
|
-
const cost = usage.cost.total >= 0.01 ? ` (~$${usage.cost.total.toFixed(2)})` : "";
|
|
2322
|
-
const label = notice.kind === "compaction" ? t("Compaction") : t("Branch summary");
|
|
2323
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2324
|
-
this.chatContainer.addChild(new Text(theme.fg("warning", `${label}: ${t("{tokens} tokens billed{cost}", { tokens: formatTokens(tokens), cost })}`), 1, 0));
|
|
2325
|
-
}
|
|
2326
|
-
static countDroppedThinkingBlocks(message) {
|
|
2327
|
-
let count = 0;
|
|
2328
|
-
for (const diagnostic of message.diagnostics ?? []) {
|
|
2329
|
-
if (diagnostic.type !== "anthropic_input_transformations")
|
|
2330
|
-
continue;
|
|
2331
|
-
const transformations = diagnostic.details?.transformations;
|
|
2332
|
-
if (!Array.isArray(transformations))
|
|
2333
|
-
continue;
|
|
2334
|
-
count += transformations.filter((transformation) => typeof transformation === "object" &&
|
|
2335
|
-
transformation !== null &&
|
|
2336
|
-
transformation.type === "thinking_dropped").length;
|
|
2337
|
-
}
|
|
2338
|
-
return count;
|
|
1735
|
+
eventsFlow.addCompactionCostNotice(this, notice);
|
|
2339
1736
|
}
|
|
1737
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in ./interactive-events.ts. */
|
|
2340
1738
|
maybeShowThinkingDropNotice(message) {
|
|
2341
|
-
|
|
2342
|
-
return;
|
|
2343
|
-
const droppedCount = InteractiveMode.countDroppedThinkingBlocks(message);
|
|
2344
|
-
if (droppedCount === 0)
|
|
2345
|
-
return;
|
|
2346
|
-
let previousDroppedCount = 0;
|
|
2347
|
-
// message_end reaches the UI before the current message is persisted,
|
|
2348
|
-
// so the branch's last assistant message is the previous response.
|
|
2349
|
-
const entries = this.facade.getEntries();
|
|
2350
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
2351
|
-
const entry = entries[i];
|
|
2352
|
-
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
2353
|
-
previousDroppedCount = InteractiveMode.countDroppedThinkingBlocks(entry.message);
|
|
2354
|
-
break;
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
if (droppedCount <= previousDroppedCount)
|
|
2358
|
-
return;
|
|
2359
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2360
|
-
this.chatContainer.addChild(new Text(theme.fg("warning", t("Anthropic dropped {count} thinking blocks (details in session)", { count: droppedCount })), 1, 0));
|
|
1739
|
+
eventsFlow.maybeShowThinkingDropNotice(this, message);
|
|
2361
1740
|
}
|
|
2362
1741
|
/**
|
|
2363
1742
|
* Show a transcript notice when a completed assistant message paid for a
|
|
2364
1743
|
* significant cache miss. Only states observable facts: the miss itself,
|
|
2365
1744
|
* a model switch, or an idle gap past the cache TTL.
|
|
1745
|
+
*
|
|
1746
|
+
* Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in
|
|
1747
|
+
* ./interactive-events.ts.
|
|
2366
1748
|
*/
|
|
2367
1749
|
maybeShowCacheMissNotice(message) {
|
|
2368
|
-
|
|
2369
|
-
return;
|
|
2370
|
-
// Entries don't contain `message` yet: message_end fires before persistence.
|
|
2371
|
-
const miss = detectCacheMiss(this.facade.getEntries(), message, this.facade.getModelPriceSource());
|
|
2372
|
-
if (miss)
|
|
2373
|
-
this.addCacheMissNotice(miss);
|
|
1750
|
+
eventsFlow.maybeShowCacheMissNotice(this, message);
|
|
2374
1751
|
}
|
|
1752
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in ./interactive-events.ts. */
|
|
2375
1753
|
addCacheMissNotice(miss) {
|
|
2376
|
-
|
|
2377
|
-
return;
|
|
2378
|
-
const cost = miss.missedCost >= 0.01 ? ` (~$${miss.missedCost.toFixed(2)})` : "";
|
|
2379
|
-
const reBilled = t("{tokens} tokens re-billed{cost}", { tokens: formatTokens(miss.missedTokens), cost });
|
|
2380
|
-
let label = t("Cache miss");
|
|
2381
|
-
if (miss.modelChanged) {
|
|
2382
|
-
label = t("Cache miss after model switch");
|
|
2383
|
-
}
|
|
2384
|
-
else if (miss.idleMs >= CACHE_TTL_MS) {
|
|
2385
|
-
label = t("Cache miss after {minutes}m idle", { minutes: Math.round(miss.idleMs / 60_000) });
|
|
2386
|
-
}
|
|
2387
|
-
const text = theme.fg("warning", `${label}: ${reBilled}`);
|
|
2388
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2389
|
-
this.chatContainer.addChild(new Text(text, 1, 0));
|
|
1754
|
+
eventsFlow.addCacheMissNotice(this, miss);
|
|
2390
1755
|
}
|
|
2391
1756
|
renderInitialMessages() {
|
|
2392
|
-
|
|
2393
|
-
this.renderSessionEntries(entries, {
|
|
2394
|
-
updateFooter: true,
|
|
2395
|
-
populateHistory: true,
|
|
2396
|
-
});
|
|
2397
|
-
// Show compaction info if session was compacted
|
|
2398
|
-
const allEntries = this.facade.getEntries();
|
|
2399
|
-
const compactionCount = allEntries.filter((e) => e.type === "compaction").length;
|
|
2400
|
-
if (compactionCount > 0) {
|
|
2401
|
-
this.showStatus(compactionCount === 1
|
|
2402
|
-
? t("Session compacted {count} time", { count: compactionCount })
|
|
2403
|
-
: t("Session compacted {count} times", { count: compactionCount }));
|
|
2404
|
-
}
|
|
1757
|
+
eventsFlow.renderInitialMessages(this);
|
|
2405
1758
|
}
|
|
2406
1759
|
async getUserInput() {
|
|
2407
1760
|
const queuedInput = this.pendingUserInputs.shift();
|
|
@@ -2415,251 +1768,84 @@ export class InteractiveMode {
|
|
|
2415
1768
|
};
|
|
2416
1769
|
});
|
|
2417
1770
|
}
|
|
1771
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1); implementation in ./interactive-events.ts (拆分 slice 5). */
|
|
2418
1772
|
rebuildChatFromMessages() {
|
|
2419
|
-
|
|
2420
|
-
this.renderSessionEntries(this.facade.getContextEntries());
|
|
1773
|
+
eventsFlow.rebuildChatFromMessages(this);
|
|
2421
1774
|
}
|
|
2422
1775
|
// =========================================================================
|
|
2423
|
-
//
|
|
1776
|
+
// Exit and signal lifecycle (implementation in ./interactive-lifecycle.ts,
|
|
1777
|
+
// 拆分 slice 4) — same-name delegates keep the editor key wiring, init/stop,
|
|
1778
|
+
// and prototype-level tests bound to these names. The main loop and the TUI
|
|
1779
|
+
// teardown (stop) stay on the mode, as does formatResumeCommand (D-041 §3.5).
|
|
2424
1780
|
// =========================================================================
|
|
1781
|
+
/** Ctrl+C interrupt/exit grading; implementation in ./interactive-lifecycle.ts (拆分 slice 4). */
|
|
2425
1782
|
handleCtrlC() {
|
|
2426
|
-
|
|
2427
|
-
if (now - this.lastSigintTime < 500) {
|
|
2428
|
-
void this.shutdown();
|
|
2429
|
-
}
|
|
2430
|
-
else {
|
|
2431
|
-
this.clearEditor();
|
|
2432
|
-
this.lastSigintTime = now;
|
|
2433
|
-
}
|
|
1783
|
+
lifecycleFlow.handleCtrlC(this);
|
|
2434
1784
|
}
|
|
1785
|
+
/** Ctrl+D exit on empty editor; implementation in ./interactive-lifecycle.ts (拆分 slice 4). */
|
|
2435
1786
|
handleCtrlD() {
|
|
2436
|
-
|
|
2437
|
-
void this.shutdown();
|
|
1787
|
+
lifecycleFlow.handleCtrlD(this);
|
|
2438
1788
|
}
|
|
2439
1789
|
/**
|
|
2440
1790
|
* Gracefully shutdown the agent.
|
|
2441
1791
|
* Stops the TUI before emitting shutdown events so extension UI cleanup cannot
|
|
2442
|
-
* repaint the final frame while the process is exiting
|
|
1792
|
+
* repaint the final frame while the process is exiting; implementation in
|
|
1793
|
+
* ./interactive-lifecycle.ts (拆分 slice 4).
|
|
2443
1794
|
*/
|
|
2444
|
-
isShuttingDown = false;
|
|
2445
1795
|
async shutdown(options) {
|
|
2446
|
-
|
|
2447
|
-
return;
|
|
2448
|
-
this.isShuttingDown = true;
|
|
2449
|
-
// Keep signal handlers registered until terminal cleanup has completed.
|
|
2450
|
-
// `signal-exit` checks the listener list during the same SIGTERM/SIGHUP
|
|
2451
|
-
// dispatch and re-sends the signal if only its own listeners remain.
|
|
2452
|
-
if (options?.fromSignal) {
|
|
2453
|
-
// Signal-triggered shutdown (SIGTERM/SIGHUP). Emit extension cleanup
|
|
2454
|
-
// (session_shutdown) BEFORE touching the terminal. Extension teardown
|
|
2455
|
-
// such as removing sockets does not write to the tty, so it must not be
|
|
2456
|
-
// skipped if a later terminal-restore write fails on a dead or stalled
|
|
2457
|
-
// terminal. If the terminal is gone, the restore writes below emit EIO,
|
|
2458
|
-
// which the stdout/stderr error handler turns into emergencyTerminalExit;
|
|
2459
|
-
// the render loop is already idle, so this cannot hot-spin (see #4144).
|
|
2460
|
-
await this.runtimeFacade.dispose();
|
|
2461
|
-
this.themeController.disableAutoSync();
|
|
2462
|
-
await this.ui.terminal.drainInput(1000);
|
|
2463
|
-
this.stop();
|
|
2464
|
-
process.exit(0);
|
|
2465
|
-
}
|
|
2466
|
-
// Interactive quit (Ctrl+D, Ctrl+C, /quit, extension shutdown()). Stop the
|
|
2467
|
-
// TUI before emitting shutdown events so extension UI cleanup cannot repaint
|
|
2468
|
-
// the final frame while the process is exiting.
|
|
2469
|
-
// Drain any in-flight Kitty key release events before stopping.
|
|
2470
|
-
// This prevents escape sequences from leaking to the parent shell over slow SSH.
|
|
2471
|
-
this.themeController.disableAutoSync();
|
|
2472
|
-
await this.ui.terminal.drainInput(1000);
|
|
2473
|
-
this.stop();
|
|
2474
|
-
await this.runtimeFacade.dispose();
|
|
2475
|
-
const resumeCommand = formatResumeCommand(this.runtimeFacade);
|
|
2476
|
-
if (resumeCommand) {
|
|
2477
|
-
process.stdout.write(`${chalk.dim(t("To resume this session:"))} ${resumeCommand}\n`);
|
|
2478
|
-
}
|
|
2479
|
-
process.exit(0);
|
|
1796
|
+
return lifecycleFlow.shutdown(this, options);
|
|
2480
1797
|
}
|
|
1798
|
+
/** Host-visible for ./interactive-lifecycle.ts (拆分 slice 4): dead-terminal emergency exit. */
|
|
2481
1799
|
emergencyTerminalExit() {
|
|
2482
|
-
this
|
|
2483
|
-
this.unregisterSignalHandlers();
|
|
2484
|
-
killTrackedDetachedChildren();
|
|
2485
|
-
// The terminal is gone. Do not run normal shutdown because TUI and
|
|
2486
|
-
// extension cleanup can write restore sequences and re-trigger EIO.
|
|
2487
|
-
process.exit(129);
|
|
1800
|
+
lifecycleFlow.emergencyTerminalExit(this);
|
|
2488
1801
|
}
|
|
2489
|
-
/**
|
|
2490
|
-
* Last-resort handler for uncaught exceptions. The TUI puts stdin into raw
|
|
2491
|
-
* mode and hides the cursor; without this handler, an uncaught throw from
|
|
2492
|
-
* anywhere (e.g. an extension's async `ChildProcess.on("exit")` callback)
|
|
2493
|
-
* tears down the process while leaving the terminal in raw mode with no
|
|
2494
|
-
* cursor, requiring `stty sane && reset` to recover.
|
|
2495
|
-
*
|
|
2496
|
-
* Unlike emergencyTerminalExit, the terminal is still alive here, so we
|
|
2497
|
-
* call ui.stop() to restore cooked mode, the cursor, and disable bracketed
|
|
2498
|
-
* paste / Kitty / modifyOtherKeys sequences.
|
|
2499
|
-
*/
|
|
1802
|
+
/** Host-visible for ./interactive-lifecycle.ts (拆分 slice 4): uncaught-exception crash handler. */
|
|
2500
1803
|
uncaughtCrash(error) {
|
|
2501
|
-
|
|
2502
|
-
process.exit(1);
|
|
2503
|
-
}
|
|
2504
|
-
this.isShuttingDown = true;
|
|
2505
|
-
try {
|
|
2506
|
-
this.unregisterSignalHandlers();
|
|
2507
|
-
}
|
|
2508
|
-
catch { }
|
|
2509
|
-
try {
|
|
2510
|
-
killTrackedDetachedChildren();
|
|
2511
|
-
}
|
|
2512
|
-
catch { }
|
|
2513
|
-
try {
|
|
2514
|
-
this.ui.stop();
|
|
2515
|
-
}
|
|
2516
|
-
catch { }
|
|
2517
|
-
console.error(t("{app} exiting due to uncaughtException:", { app: APP_NAME }));
|
|
2518
|
-
console.error(error);
|
|
2519
|
-
process.exit(1);
|
|
1804
|
+
lifecycleFlow.uncaughtCrash(this, error);
|
|
2520
1805
|
}
|
|
2521
|
-
/**
|
|
2522
|
-
* Check if shutdown was requested and perform shutdown if so.
|
|
2523
|
-
*/
|
|
1806
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in ./interactive-lifecycle.ts (拆分 slice 4). */
|
|
2524
1807
|
async checkShutdownRequested() {
|
|
2525
|
-
|
|
2526
|
-
return;
|
|
2527
|
-
await this.shutdown();
|
|
1808
|
+
return lifecycleFlow.checkShutdownRequested(this);
|
|
2528
1809
|
}
|
|
1810
|
+
/** Signal/stderr/uncaught registration; implementation in ./interactive-lifecycle.ts (拆分 slice 4). */
|
|
2529
1811
|
registerSignalHandlers() {
|
|
2530
|
-
|
|
2531
|
-
const signals = ["SIGTERM"];
|
|
2532
|
-
if (process.platform !== "win32") {
|
|
2533
|
-
signals.push("SIGHUP");
|
|
2534
|
-
}
|
|
2535
|
-
for (const signal of signals) {
|
|
2536
|
-
const handler = () => {
|
|
2537
|
-
// SIGHUP no longer hard-exits: graceful shutdown emits session_shutdown
|
|
2538
|
-
// first, then attempts terminal restore. A genuinely dead terminal
|
|
2539
|
-
// surfaces as an EIO on the restore writes, which the stdout/stderr
|
|
2540
|
-
// error handler converts into emergencyTerminalExit (see #4144, #5080).
|
|
2541
|
-
killTrackedDetachedChildren();
|
|
2542
|
-
void this.shutdown({ fromSignal: true });
|
|
2543
|
-
};
|
|
2544
|
-
process.prependListener(signal, handler);
|
|
2545
|
-
this.signalCleanupHandlers.push(() => process.off(signal, handler));
|
|
2546
|
-
}
|
|
2547
|
-
const terminalErrorHandler = (error) => {
|
|
2548
|
-
if (isDeadTerminalError(error)) {
|
|
2549
|
-
this.emergencyTerminalExit();
|
|
2550
|
-
}
|
|
2551
|
-
throw error;
|
|
2552
|
-
};
|
|
2553
|
-
process.stdout.on("error", terminalErrorHandler);
|
|
2554
|
-
process.stderr.on("error", terminalErrorHandler);
|
|
2555
|
-
this.signalCleanupHandlers.push(() => process.stdout.off("error", terminalErrorHandler));
|
|
2556
|
-
this.signalCleanupHandlers.push(() => process.stderr.off("error", terminalErrorHandler));
|
|
2557
|
-
// Restore the terminal before the process dies on any uncaught throw.
|
|
2558
|
-
// Without this, an unhandled exception from extension code (or anywhere
|
|
2559
|
-
// in the application) leaves the terminal in raw mode with no cursor.
|
|
2560
|
-
const uncaughtExceptionHandler = (error) => this.uncaughtCrash(error);
|
|
2561
|
-
process.prependListener("uncaughtException", uncaughtExceptionHandler);
|
|
2562
|
-
this.signalCleanupHandlers.push(() => process.off("uncaughtException", uncaughtExceptionHandler));
|
|
1812
|
+
lifecycleFlow.registerSignalHandlers(this);
|
|
2563
1813
|
}
|
|
1814
|
+
/** Host-visible for ./interactive-lifecycle.ts (拆分 slice 4): also called by the mode's stop(). */
|
|
2564
1815
|
unregisterSignalHandlers() {
|
|
2565
|
-
|
|
2566
|
-
cleanup();
|
|
2567
|
-
}
|
|
2568
|
-
this.signalCleanupHandlers = [];
|
|
1816
|
+
lifecycleFlow.unregisterSignalHandlers(this);
|
|
2569
1817
|
}
|
|
1818
|
+
/** Suspend to background (Ctrl+Z); implementation in ./interactive-lifecycle.ts (拆分 slice 4). */
|
|
2570
1819
|
handleCtrlZ() {
|
|
2571
|
-
|
|
2572
|
-
this.showStatus(t("Suspend to background is not supported on Windows"));
|
|
2573
|
-
return;
|
|
2574
|
-
}
|
|
2575
|
-
// Keep the event loop alive while suspended. Without this, stopping the TUI
|
|
2576
|
-
// can leave Node with no ref'ed handles, causing the process to exit on fg
|
|
2577
|
-
// before the SIGCONT handler gets a chance to restore the terminal.
|
|
2578
|
-
const suspendKeepAlive = setInterval(() => { }, 2 ** 30);
|
|
2579
|
-
// Ignore SIGINT while suspended so Ctrl+C in the terminal does not
|
|
2580
|
-
// kill the backgrounded process. The handler is removed on resume.
|
|
2581
|
-
const ignoreSigint = () => { };
|
|
2582
|
-
process.on("SIGINT", ignoreSigint);
|
|
2583
|
-
// Set up handler to restore TUI when resumed
|
|
2584
|
-
process.once("SIGCONT", () => {
|
|
2585
|
-
clearInterval(suspendKeepAlive);
|
|
2586
|
-
process.removeListener("SIGINT", ignoreSigint);
|
|
2587
|
-
this.ui.start();
|
|
2588
|
-
this.ui.requestRender(true);
|
|
2589
|
-
});
|
|
2590
|
-
try {
|
|
2591
|
-
// Stop the TUI (restore terminal to normal mode)
|
|
2592
|
-
this.ui.stop();
|
|
2593
|
-
// Send SIGTSTP to process group (pid=0 means all processes in group)
|
|
2594
|
-
process.kill(0, "SIGTSTP");
|
|
2595
|
-
}
|
|
2596
|
-
catch (error) {
|
|
2597
|
-
clearInterval(suspendKeepAlive);
|
|
2598
|
-
process.removeListener("SIGINT", ignoreSigint);
|
|
2599
|
-
throw error;
|
|
2600
|
-
}
|
|
1820
|
+
lifecycleFlow.handleCtrlZ(this);
|
|
2601
1821
|
}
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
if (this.facade.isStreaming()) {
|
|
2614
|
-
// An explicit /queue <text> argument must not touch the editor draft.
|
|
2615
|
-
if (explicitText === undefined) {
|
|
2616
|
-
this.editor.addToHistory?.(text);
|
|
2617
|
-
this.editor.setText("");
|
|
2618
|
-
}
|
|
2619
|
-
await this.facade.prompt(text, { streamingBehavior: "followUp" });
|
|
2620
|
-
this.updatePendingMessagesDisplay();
|
|
2621
|
-
this.ui.requestRender();
|
|
2622
|
-
}
|
|
2623
|
-
// If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
|
|
2624
|
-
else if (this.editor.onSubmit) {
|
|
2625
|
-
if (explicitText === undefined)
|
|
2626
|
-
this.editor.setText("");
|
|
2627
|
-
this.editor.onSubmit(text);
|
|
2628
|
-
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Host-visible for ./interactive-lifecycle.ts (拆分 slice 4): the function
|
|
1824
|
+
* stays resident in this module (D-041 §3.5 — resume hints are host TUI
|
|
1825
|
+
* behavior, not a facade contract).
|
|
1826
|
+
*/
|
|
1827
|
+
formatResumeCommand(session) {
|
|
1828
|
+
return formatResumeCommand(session);
|
|
1829
|
+
}
|
|
1830
|
+
/** Follow-up queue entry (Alt+Enter / /queue); implementation in ./interactive-queues.ts (拆分 slice 3). */
|
|
1831
|
+
handleFollowUp(explicitText) {
|
|
1832
|
+
return queuesFlow.handleFollowUp(this, explicitText);
|
|
2629
1833
|
}
|
|
1834
|
+
/** Dequeue restore (keybinding / /dequeue); implementation in ./interactive-queues.ts (拆分 slice 3). */
|
|
2630
1835
|
handleDequeue() {
|
|
2631
|
-
|
|
2632
|
-
if (restored === 0) {
|
|
2633
|
-
this.showStatus(t("No queued messages to restore"));
|
|
2634
|
-
}
|
|
2635
|
-
else if (restored === 1) {
|
|
2636
|
-
this.showStatus(t("Restored {count} queued message to editor", { count: restored }));
|
|
2637
|
-
}
|
|
2638
|
-
else {
|
|
2639
|
-
this.showStatus(t("Restored {count} queued messages to editor", { count: restored }));
|
|
2640
|
-
}
|
|
1836
|
+
queuesFlow.handleDequeue(this);
|
|
2641
1837
|
}
|
|
2642
|
-
/** /queue command host face (single-entry iron law)
|
|
1838
|
+
/** /queue command host face (single-entry iron law); implementation in ./interactive-queues.ts (拆分 slice 3). */
|
|
2643
1839
|
handleQueueCommand(text) {
|
|
2644
|
-
|
|
2645
|
-
this.showStatus(t("Usage: /queue <text>"));
|
|
2646
|
-
return Promise.resolve();
|
|
2647
|
-
}
|
|
2648
|
-
return this.handleFollowUp(text);
|
|
1840
|
+
return queuesFlow.handleQueueCommand(this, text);
|
|
2649
1841
|
}
|
|
2650
|
-
/** /dequeue command host face
|
|
1842
|
+
/** /dequeue command host face; implementation in ./interactive-queues.ts (拆分 slice 3). */
|
|
2651
1843
|
handleDequeueCommand() {
|
|
2652
|
-
|
|
1844
|
+
queuesFlow.handleDequeueCommand(this);
|
|
2653
1845
|
}
|
|
2654
|
-
/** /queue-list command host face
|
|
1846
|
+
/** /queue-list command host face; implementation in ./interactive-queues.ts (拆分 slice 3). */
|
|
2655
1847
|
handleQueueListCommand() {
|
|
2656
|
-
|
|
2657
|
-
const followUp = this.facade.getFollowUpMessages();
|
|
2658
|
-
if (steering.length === 0 && followUp.length === 0) {
|
|
2659
|
-
this.showStatus(t("No queued messages to manage"));
|
|
2660
|
-
return;
|
|
2661
|
-
}
|
|
2662
|
-
selectorsFlow.showQueueManagerSelector(this);
|
|
1848
|
+
queuesFlow.handleQueueListCommand(this);
|
|
2663
1849
|
}
|
|
2664
1850
|
/** /editor command host face: edit the current input in an external editor. */
|
|
2665
1851
|
handleEditorCommand() {
|
|
@@ -2719,26 +1905,28 @@ export class InteractiveMode {
|
|
|
2719
1905
|
* assistant turn's tool-run group (creating the group on first tool of
|
|
2720
1906
|
* the turn) instead of the chat container directly. Collapsed groups
|
|
2721
1907
|
* render one dim marker line for the whole turn.
|
|
1908
|
+
*
|
|
1909
|
+
* Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in
|
|
1910
|
+
* ./interactive-events.ts.
|
|
2722
1911
|
*/
|
|
2723
1912
|
addToolToRunGroup(component) {
|
|
2724
|
-
|
|
2725
|
-
this.activeToolRunGroup = new ToolRunGroupComponent();
|
|
2726
|
-
this.activeToolRunGroup.setExpanded(this.toolOutputExpanded);
|
|
2727
|
-
this.chatContainer.addChild(this.activeToolRunGroup);
|
|
2728
|
-
}
|
|
2729
|
-
this.activeToolRunGroup.addTool(component);
|
|
1913
|
+
eventsFlow.addToolToRunGroup(this, component);
|
|
2730
1914
|
}
|
|
2731
1915
|
/**
|
|
2732
1916
|
* Narrowing-safe faces for the active group inside renderSessionItems:
|
|
2733
1917
|
* addToolToRunGroup may (re)assign this.activeToolRunGroup behind the
|
|
2734
1918
|
* compiler's back, so group reads go through methods instead of the
|
|
2735
1919
|
* narrowed property reference.
|
|
1920
|
+
*
|
|
1921
|
+
* Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in
|
|
1922
|
+
* ./interactive-events.ts.
|
|
2736
1923
|
*/
|
|
2737
1924
|
noteResultInActiveToolRunGroup(isError) {
|
|
2738
|
-
|
|
1925
|
+
eventsFlow.noteResultInActiveToolRunGroup(this, isError);
|
|
2739
1926
|
}
|
|
1927
|
+
/** Host-visible for ./interactive-events.ts (拆分 slice 5); implementation in ./interactive-events.ts. */
|
|
2740
1928
|
markActiveToolRunGroupComplete() {
|
|
2741
|
-
|
|
1929
|
+
eventsFlow.markActiveToolRunGroupComplete(this);
|
|
2742
1930
|
}
|
|
2743
1931
|
setToolsExpanded(expanded) {
|
|
2744
1932
|
if (expanded === this.toolOutputExpanded)
|
|
@@ -2758,6 +1946,7 @@ export class InteractiveMode {
|
|
|
2758
1946
|
this.showStatus(expanded ? t("Tool output: expanded") : t("Tool output: collapsed"));
|
|
2759
1947
|
}
|
|
2760
1948
|
/** Update rendered assistant messages without rebuilding live tool components. */
|
|
1949
|
+
/** Host-visible for ./interactive-selectors.ts (拆分 slice 2). */
|
|
2761
1950
|
updateThinkingBlockVisibility() {
|
|
2762
1951
|
for (const child of this.chatContainer.children) {
|
|
2763
1952
|
if (child instanceof AssistantMessageComponent) {
|
|
@@ -2845,58 +2034,27 @@ export class InteractiveMode {
|
|
|
2845
2034
|
/**
|
|
2846
2035
|
* Get all queued messages (read-only).
|
|
2847
2036
|
* Combines session queue and compaction queue.
|
|
2037
|
+
*
|
|
2038
|
+
* Host-visible for ./interactive-queues.ts (拆分 slice 3): the merged read
|
|
2039
|
+
* behind the pending-messages display.
|
|
2848
2040
|
*/
|
|
2849
2041
|
getAllQueuedMessages() {
|
|
2850
|
-
return
|
|
2851
|
-
steering: [
|
|
2852
|
-
...this.facade.getSteeringMessages(),
|
|
2853
|
-
...this.compactionQueuedMessages.filter((msg) => msg.mode === "steer").map((msg) => msg.text),
|
|
2854
|
-
],
|
|
2855
|
-
followUp: [
|
|
2856
|
-
...this.facade.getFollowUpMessages(),
|
|
2857
|
-
...this.compactionQueuedMessages.filter((msg) => msg.mode === "followUp").map((msg) => msg.text),
|
|
2858
|
-
],
|
|
2859
|
-
};
|
|
2042
|
+
return queuesFlow.getAllQueuedMessages(this);
|
|
2860
2043
|
}
|
|
2861
2044
|
/**
|
|
2862
2045
|
* Clear all queued messages and return their contents.
|
|
2863
|
-
* Clears both session queue and compaction queue
|
|
2046
|
+
* Clears both session queue and compaction queue; implementation in
|
|
2047
|
+
* ./interactive-queues.ts (拆分 slice 3). Stays reachable from the mode's
|
|
2048
|
+
* restoreQueuedMessagesToEditor.
|
|
2864
2049
|
*/
|
|
2865
2050
|
clearAllQueues() {
|
|
2866
|
-
|
|
2867
|
-
const compactionSteering = this.compactionQueuedMessages
|
|
2868
|
-
.filter((msg) => msg.mode === "steer")
|
|
2869
|
-
.map((msg) => msg.text);
|
|
2870
|
-
const compactionFollowUp = this.compactionQueuedMessages
|
|
2871
|
-
.filter((msg) => msg.mode === "followUp")
|
|
2872
|
-
.map((msg) => msg.text);
|
|
2873
|
-
this.compactionQueuedMessages = [];
|
|
2874
|
-
return {
|
|
2875
|
-
steering: [...steering, ...compactionSteering],
|
|
2876
|
-
followUp: [...followUp, ...compactionFollowUp],
|
|
2877
|
-
};
|
|
2051
|
+
return queuesFlow.clearAllQueues(this);
|
|
2878
2052
|
}
|
|
2879
|
-
/** Host-visible for ./interactive-selectors.ts (queue manager close path). */
|
|
2053
|
+
/** Host-visible for ./interactive-selectors.ts (queue manager close path) and ./interactive-events.ts (拆分 slice 3); implementation in ./interactive-queues.ts. */
|
|
2880
2054
|
updatePendingMessagesDisplay() {
|
|
2881
|
-
|
|
2882
|
-
const { steering: steeringMessages, followUp: followUpMessages } = this.getAllQueuedMessages();
|
|
2883
|
-
if (steeringMessages.length > 0 || followUpMessages.length > 0) {
|
|
2884
|
-
this.pendingMessagesContainer.addChild(new Spacer(1));
|
|
2885
|
-
for (const message of steeringMessages) {
|
|
2886
|
-
const text = theme.fg("dim", t("Steering: {message}", { message }));
|
|
2887
|
-
this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
|
|
2888
|
-
}
|
|
2889
|
-
for (const message of followUpMessages) {
|
|
2890
|
-
const text = theme.fg("dim", t("Follow-up: {message}", { message }));
|
|
2891
|
-
this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
|
|
2892
|
-
}
|
|
2893
|
-
// The dequeue keybinding's shipped default was cleared (iron law):
|
|
2894
|
-
// /queue's sibling command is the discoverable entry.
|
|
2895
|
-
const dequeueHint = this.getAppKeyDisplay("app.message.dequeue") || "/dequeue";
|
|
2896
|
-
const hintText = theme.fg("dim", t("↳ {key} to edit all queued messages", { key: dequeueHint }));
|
|
2897
|
-
this.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
|
|
2898
|
-
}
|
|
2055
|
+
queuesFlow.updatePendingMessagesDisplay(this);
|
|
2899
2056
|
}
|
|
2057
|
+
/** Host-visible for ./interactive-selectors.ts (拆分 slice 2). */
|
|
2900
2058
|
restoreQueuedMessagesToEditor(options) {
|
|
2901
2059
|
const { steering, followUp } = this.clearAllQueues();
|
|
2902
2060
|
const allQueued = [...steering, ...followUp];
|
|
@@ -2920,68 +2078,13 @@ export class InteractiveMode {
|
|
|
2920
2078
|
}
|
|
2921
2079
|
return allQueued.length;
|
|
2922
2080
|
}
|
|
2081
|
+
/** Host-visible for ./interactive-queues.ts (拆分 slice 3); implementation in ./interactive-queues.ts. */
|
|
2923
2082
|
queueCompactionMessage(text, mode) {
|
|
2924
|
-
|
|
2925
|
-
this.editor.addToHistory?.(text);
|
|
2926
|
-
this.editor.setText("");
|
|
2927
|
-
this.updatePendingMessagesDisplay();
|
|
2928
|
-
this.showStatus(t("Queued message for after compaction"));
|
|
2083
|
+
queuesFlow.queueCompactionMessage(this, text, mode);
|
|
2929
2084
|
}
|
|
2085
|
+
/** Host-visible for ./interactive-selectors.ts (拆分 slice 2) and ./interactive-events.ts; implementation in ./interactive-queues.ts (拆分 slice 3). */
|
|
2930
2086
|
async flushCompactionQueue(options) {
|
|
2931
|
-
|
|
2932
|
-
return;
|
|
2933
|
-
}
|
|
2934
|
-
const queuedMessages = [...this.compactionQueuedMessages];
|
|
2935
|
-
this.compactionQueuedMessages = [];
|
|
2936
|
-
this.updatePendingMessagesDisplay();
|
|
2937
|
-
const restoreQueue = (error) => {
|
|
2938
|
-
this.facade.clearQueue();
|
|
2939
|
-
this.compactionQueuedMessages = queuedMessages;
|
|
2940
|
-
this.updatePendingMessagesDisplay();
|
|
2941
|
-
this.showError(queuedMessages.length > 1
|
|
2942
|
-
? t("Failed to send queued messages: {error}", {
|
|
2943
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2944
|
-
})
|
|
2945
|
-
: t("Failed to send queued message: {error}", {
|
|
2946
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2947
|
-
}));
|
|
2948
|
-
};
|
|
2949
|
-
try {
|
|
2950
|
-
if (options?.willRetry) {
|
|
2951
|
-
// When retry is pending, queue messages for the retry turn
|
|
2952
|
-
for (const message of queuedMessages) {
|
|
2953
|
-
if (message.mode === "followUp") {
|
|
2954
|
-
await this.facade.followUp(message.text);
|
|
2955
|
-
}
|
|
2956
|
-
else {
|
|
2957
|
-
await this.facade.steer(message.text);
|
|
2958
|
-
}
|
|
2959
|
-
}
|
|
2960
|
-
this.updatePendingMessagesDisplay();
|
|
2961
|
-
return;
|
|
2962
|
-
}
|
|
2963
|
-
const [firstPrompt, ...rest] = queuedMessages;
|
|
2964
|
-
// Start a prompt when idle, or queue it into a run still finishing compaction.
|
|
2965
|
-
const promptPromise = this.facade
|
|
2966
|
-
.prompt(firstPrompt.text, { streamingBehavior: firstPrompt.mode })
|
|
2967
|
-
.catch((error) => {
|
|
2968
|
-
restoreQueue(error);
|
|
2969
|
-
});
|
|
2970
|
-
// Queue remaining messages
|
|
2971
|
-
for (const message of rest) {
|
|
2972
|
-
if (message.mode === "followUp") {
|
|
2973
|
-
await this.facade.followUp(message.text);
|
|
2974
|
-
}
|
|
2975
|
-
else {
|
|
2976
|
-
await this.facade.steer(message.text);
|
|
2977
|
-
}
|
|
2978
|
-
}
|
|
2979
|
-
this.updatePendingMessagesDisplay();
|
|
2980
|
-
void promptPromise;
|
|
2981
|
-
}
|
|
2982
|
-
catch (error) {
|
|
2983
|
-
restoreQueue(error);
|
|
2984
|
-
}
|
|
2087
|
+
return queuesFlow.flushCompactionQueue(this, options);
|
|
2985
2088
|
}
|
|
2986
2089
|
/** Move pending bash components from pending area to chat */
|
|
2987
2090
|
flushPendingBashComponents() {
|
|
@@ -2993,15 +2096,14 @@ export class InteractiveMode {
|
|
|
2993
2096
|
}
|
|
2994
2097
|
// =========================================================================
|
|
2995
2098
|
// Selectors
|
|
2996
|
-
//
|
|
2997
|
-
//
|
|
2099
|
+
// All selector/view implementations — the S5 slice 5 suite/thinking/model/
|
|
2100
|
+
// scoped-models/user-message-fork/clone selectors and the 拆分 slice 2
|
|
2101
|
+
// settings/workflow/agents/tree/session selectors, resume, and the
|
|
2102
|
+
// startup-suite recovery — live in ./interactive-selectors.ts behind the
|
|
2998
2103
|
// SelectorsHost interface; same-name delegates below keep the command
|
|
2999
2104
|
// dispatch and prototype-level tests bound to these names. The selector
|
|
3000
|
-
// infrastructure (showSelector/disposeActiveSelector)
|
|
3001
|
-
//
|
|
3002
|
-
// selectors assemble from broad private UI state, and the session/resume
|
|
3003
|
-
// path is the documented session-boundary carve-out (SessionManager
|
|
3004
|
-
// manager-level writes plus per-entry display reads).
|
|
2105
|
+
// infrastructure (showSelector/disposeActiveSelector) and the shared
|
|
2106
|
+
// detectCrossSuiteResume helper stay on the mode.
|
|
3005
2107
|
// =========================================================================
|
|
3006
2108
|
disposeActiveSelector() {
|
|
3007
2109
|
const dispose = this.activeSelectorDispose;
|
|
@@ -3036,230 +2138,9 @@ export class InteractiveMode {
|
|
|
3036
2138
|
this.ui.setFocus(created.focus);
|
|
3037
2139
|
this.ui.requestRender();
|
|
3038
2140
|
}
|
|
2141
|
+
/** Settings selector; implementation in ./interactive-selectors.ts (拆分 slice 2). */
|
|
3039
2142
|
showSettingsSelector() {
|
|
3040
|
-
|
|
3041
|
-
let selector;
|
|
3042
|
-
const defaultProvider = this.settingsManager.getDefaultProvider();
|
|
3043
|
-
const defaultModelId = this.settingsManager.getDefaultModel();
|
|
3044
|
-
const defaultModel = defaultProvider && defaultModelId ? `${defaultProvider}/${defaultModelId}` : t("not set");
|
|
3045
|
-
selector = new SettingsSelectorComponent({
|
|
3046
|
-
defaultModel,
|
|
3047
|
-
currentModel: this.facade.getModel(),
|
|
3048
|
-
availableDefaultModels: this.facade.getAvailableModels(),
|
|
3049
|
-
showImages: this.settingsManager.getShowImages(),
|
|
3050
|
-
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
3051
|
-
autoResizeImages: this.settingsManager.getImageAutoResize(),
|
|
3052
|
-
blockImages: this.settingsManager.getBlockImages(),
|
|
3053
|
-
steeringMode: this.facade.getSteeringMode(),
|
|
3054
|
-
followUpMode: this.facade.getFollowUpMode(),
|
|
3055
|
-
transport: this.settingsManager.getTransport(),
|
|
3056
|
-
httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
|
|
3057
|
-
thinkingLevel: this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL,
|
|
3058
|
-
availableThinkingLevels: [...THINKING_LEVEL_OPTIONS],
|
|
3059
|
-
modelThinkingLevels: this.settingsManager.getAllModelThinkingLevels(),
|
|
3060
|
-
currentTheme: this.themeController.getThemeSelection() || "dark",
|
|
3061
|
-
terminalTheme: this.themeController.getTerminalTheme(),
|
|
3062
|
-
availableThemes: getAvailableThemes(),
|
|
3063
|
-
hideThinkingBlock: this.hideThinkingBlock,
|
|
3064
|
-
mermaidRenderingMode: this.settingsManager.getMermaidRenderingMode(),
|
|
3065
|
-
collapseChangelog: this.settingsManager.getCollapseChangelog(),
|
|
3066
|
-
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
3067
|
-
uiLanguage: this.settingsManager.getUiLanguage(),
|
|
3068
|
-
treeFilterMode: this.settingsManager.getTreeFilterMode(),
|
|
3069
|
-
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
3070
|
-
showCacheMissNotices: this.settingsManager.getShowCacheMissNotices(),
|
|
3071
|
-
editorPaddingX: this.settingsManager.getEditorPaddingX(),
|
|
3072
|
-
outputPad: this.settingsManager.getOutputPad(),
|
|
3073
|
-
autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
|
|
3074
|
-
quietStartup: this.settingsManager.getQuietStartup(),
|
|
3075
|
-
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
3076
|
-
showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
|
|
3077
|
-
tuiMode: this.ui.mode,
|
|
3078
|
-
fullscreenExitOutput: this.settingsManager.getFullscreenExitOutput(),
|
|
3079
|
-
fullscreenScrollbar: this.settingsManager.getFullscreenScrollbar(),
|
|
3080
|
-
fullscreenCopyOnSelect: this.settingsManager.getFullscreenCopyOnSelect(),
|
|
3081
|
-
fullscreenWheelScrollLines: this.settingsManager.getFullscreenWheelScrollLines(),
|
|
3082
|
-
warnings: this.settingsManager.getWarnings(),
|
|
3083
|
-
}, {
|
|
3084
|
-
onShowImagesChange: (enabled) => {
|
|
3085
|
-
this.settingsManager.setShowImages(enabled);
|
|
3086
|
-
for (const child of this.chatContainer.children) {
|
|
3087
|
-
if (child instanceof ToolExecutionComponent) {
|
|
3088
|
-
child.setShowImages(enabled);
|
|
3089
|
-
}
|
|
3090
|
-
}
|
|
3091
|
-
},
|
|
3092
|
-
onImageWidthCellsChange: (width) => {
|
|
3093
|
-
this.settingsManager.setImageWidthCells(width);
|
|
3094
|
-
for (const child of this.chatContainer.children) {
|
|
3095
|
-
if (child instanceof ToolExecutionComponent) {
|
|
3096
|
-
child.setImageWidthCells(width);
|
|
3097
|
-
}
|
|
3098
|
-
}
|
|
3099
|
-
},
|
|
3100
|
-
onAutoResizeImagesChange: (enabled) => {
|
|
3101
|
-
this.settingsManager.setImageAutoResize(enabled);
|
|
3102
|
-
},
|
|
3103
|
-
onBlockImagesChange: (blocked) => {
|
|
3104
|
-
this.settingsManager.setBlockImages(blocked);
|
|
3105
|
-
},
|
|
3106
|
-
onSteeringModeChange: (mode) => {
|
|
3107
|
-
this.facade.setSteeringMode(mode);
|
|
3108
|
-
},
|
|
3109
|
-
onFollowUpModeChange: (mode) => {
|
|
3110
|
-
this.facade.setFollowUpMode(mode);
|
|
3111
|
-
},
|
|
3112
|
-
onTransportChange: (transport) => {
|
|
3113
|
-
this.settingsManager.setTransport(transport);
|
|
3114
|
-
this.session.host.setTransport(transport);
|
|
3115
|
-
},
|
|
3116
|
-
onHttpIdleTimeoutMsChange: (timeoutMs) => {
|
|
3117
|
-
this.settingsManager.setHttpIdleTimeoutMs(timeoutMs);
|
|
3118
|
-
configureHttpDispatcher(timeoutMs);
|
|
3119
|
-
this.showStatus(t("HTTP idle timeout: {value}", { value: formatHttpIdleTimeoutMs(timeoutMs) }));
|
|
3120
|
-
},
|
|
3121
|
-
onModelThinkingLevelChange: (provider, modelId, level) => {
|
|
3122
|
-
this.settingsManager.setModelThinkingLevel(provider, modelId, level);
|
|
3123
|
-
// If the override is for the current model, apply it to the session too
|
|
3124
|
-
const current = this.facade.getModel();
|
|
3125
|
-
if (current && current.provider === provider && current.id === modelId) {
|
|
3126
|
-
this.facade.setThinkingLevel(level);
|
|
3127
|
-
this.footer.invalidate();
|
|
3128
|
-
this.updateEditorBorderColor();
|
|
3129
|
-
}
|
|
3130
|
-
},
|
|
3131
|
-
onModelThinkingLevelRemove: (provider, modelId) => {
|
|
3132
|
-
this.settingsManager.removeModelThinkingLevel(provider, modelId);
|
|
3133
|
-
// If the override was for the current model, revert to global default
|
|
3134
|
-
const current = this.facade.getModel();
|
|
3135
|
-
if (current && current.provider === provider && current.id === modelId) {
|
|
3136
|
-
const globalDefault = this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
|
|
3137
|
-
this.facade.setThinkingLevel(globalDefault);
|
|
3138
|
-
this.footer.invalidate();
|
|
3139
|
-
this.updateEditorBorderColor();
|
|
3140
|
-
}
|
|
3141
|
-
},
|
|
3142
|
-
onThemeChange: (themeSetting) => {
|
|
3143
|
-
this.settingsManager.setTheme(themeSetting);
|
|
3144
|
-
void this.themeController.setThemeSetting(themeSetting);
|
|
3145
|
-
},
|
|
3146
|
-
onThemePreview: (themeName) => this.themeController.preview(themeName),
|
|
3147
|
-
onHideThinkingBlockChange: (hidden) => {
|
|
3148
|
-
this.hideThinkingBlock = hidden;
|
|
3149
|
-
this.settingsManager.setHideThinkingBlock(hidden);
|
|
3150
|
-
this.updateThinkingBlockVisibility();
|
|
3151
|
-
},
|
|
3152
|
-
onMermaidRenderingModeChange: (mode) => {
|
|
3153
|
-
this.settingsManager.setMermaidRenderingMode(mode);
|
|
3154
|
-
this.chatContainer.invalidate();
|
|
3155
|
-
this.ui.requestRender();
|
|
3156
|
-
},
|
|
3157
|
-
onShowCacheMissNoticesChange: (shown) => {
|
|
3158
|
-
this.settingsManager.setShowCacheMissNotices(shown);
|
|
3159
|
-
this.rebuildChatFromMessages();
|
|
3160
|
-
},
|
|
3161
|
-
onCollapseChangelogChange: (collapsed) => {
|
|
3162
|
-
this.settingsManager.setCollapseChangelog(collapsed);
|
|
3163
|
-
},
|
|
3164
|
-
onQuietStartupChange: (enabled) => {
|
|
3165
|
-
this.settingsManager.setQuietStartup(enabled);
|
|
3166
|
-
},
|
|
3167
|
-
onDoubleEscapeActionChange: (action) => {
|
|
3168
|
-
this.settingsManager.setDoubleEscapeAction(action);
|
|
3169
|
-
},
|
|
3170
|
-
onUiLanguageChange: (language) => {
|
|
3171
|
-
this.settingsManager.setUiLanguage(language);
|
|
3172
|
-
setI18nLanguage(language);
|
|
3173
|
-
this.ui.requestRender();
|
|
3174
|
-
},
|
|
3175
|
-
onTreeFilterModeChange: (mode) => {
|
|
3176
|
-
this.settingsManager.setTreeFilterMode(mode);
|
|
3177
|
-
},
|
|
3178
|
-
onShowHardwareCursorChange: (enabled) => {
|
|
3179
|
-
this.settingsManager.setShowHardwareCursor(enabled);
|
|
3180
|
-
this.ui.setShowHardwareCursor(enabled);
|
|
3181
|
-
},
|
|
3182
|
-
onEditorPaddingXChange: (padding) => {
|
|
3183
|
-
this.settingsManager.setEditorPaddingX(padding);
|
|
3184
|
-
this.defaultEditor.setPaddingX(padding);
|
|
3185
|
-
if (this.editor !== this.defaultEditor && this.editor.setPaddingX !== undefined) {
|
|
3186
|
-
this.editor.setPaddingX(padding);
|
|
3187
|
-
}
|
|
3188
|
-
},
|
|
3189
|
-
onOutputPadChange: (padding) => {
|
|
3190
|
-
this.settingsManager.setOutputPad(padding);
|
|
3191
|
-
this.outputPad = padding;
|
|
3192
|
-
if (this.streamingComponent || this.facade.isStreaming()) {
|
|
3193
|
-
for (const child of this.chatContainer.children) {
|
|
3194
|
-
if (child instanceof AssistantMessageComponent ||
|
|
3195
|
-
child instanceof CustomMessageComponent ||
|
|
3196
|
-
child instanceof UserMessageComponent) {
|
|
3197
|
-
child.setOutputPad(padding);
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3200
|
-
if (this.streamingComponent) {
|
|
3201
|
-
this.streamingComponent.setOutputPad(padding);
|
|
3202
|
-
}
|
|
3203
|
-
this.ui.requestRender();
|
|
3204
|
-
return;
|
|
3205
|
-
}
|
|
3206
|
-
this.rebuildChatFromMessages();
|
|
3207
|
-
},
|
|
3208
|
-
onAutocompleteMaxVisibleChange: (maxVisible) => {
|
|
3209
|
-
this.settingsManager.setAutocompleteMaxVisible(maxVisible);
|
|
3210
|
-
this.defaultEditor.setAutocompleteMaxVisible(maxVisible);
|
|
3211
|
-
if (this.editor !== this.defaultEditor && this.editor.setAutocompleteMaxVisible !== undefined) {
|
|
3212
|
-
this.editor.setAutocompleteMaxVisible(maxVisible);
|
|
3213
|
-
}
|
|
3214
|
-
},
|
|
3215
|
-
onClearOnShrinkChange: (enabled) => {
|
|
3216
|
-
this.settingsManager.setClearOnShrink(enabled);
|
|
3217
|
-
this.ui.setClearOnShrink(enabled);
|
|
3218
|
-
if (!enabled && !this.activeStatusIndicator) {
|
|
3219
|
-
this.statusContainer.clear();
|
|
3220
|
-
}
|
|
3221
|
-
},
|
|
3222
|
-
onShowTerminalProgressChange: (enabled) => {
|
|
3223
|
-
this.settingsManager.setShowTerminalProgress(enabled);
|
|
3224
|
-
},
|
|
3225
|
-
onTuiModeChange: (mode) => {
|
|
3226
|
-
if (!this.switchTuiMode(mode)) {
|
|
3227
|
-
selector?.getSettingsList().updateValue("tui-mode", this.ui.mode);
|
|
3228
|
-
this.showStatus(t("Close active overlays before changing TUI mode"));
|
|
3229
|
-
return;
|
|
3230
|
-
}
|
|
3231
|
-
this.settingsManager.setTuiMode(mode);
|
|
3232
|
-
if (!this.activeStatusIndicator)
|
|
3233
|
-
this.statusContainer.clear();
|
|
3234
|
-
this.showStatus(t("TUI mode: {mode}", { mode }));
|
|
3235
|
-
},
|
|
3236
|
-
onFullscreenExitOutputChange: (output) => {
|
|
3237
|
-
this.settingsManager.setFullscreenExitOutput(output);
|
|
3238
|
-
},
|
|
3239
|
-
onFullscreenScrollbarChange: (mode) => {
|
|
3240
|
-
this.settingsManager.setFullscreenScrollbar(mode);
|
|
3241
|
-
this.applyFullscreenScrollbarSetting();
|
|
3242
|
-
},
|
|
3243
|
-
onFullscreenCopyOnSelectChange: (enabled) => {
|
|
3244
|
-
this.settingsManager.setFullscreenCopyOnSelect(enabled);
|
|
3245
|
-
if (this.renderer instanceof TuiAltScreen)
|
|
3246
|
-
this.renderer.setCopyOnSelect(enabled);
|
|
3247
|
-
},
|
|
3248
|
-
onFullscreenWheelScrollLinesChange: (lines) => {
|
|
3249
|
-
this.settingsManager.setFullscreenWheelScrollLines(lines);
|
|
3250
|
-
if (this.renderer instanceof TuiAltScreen)
|
|
3251
|
-
this.renderer.setWheelScrollLines(lines);
|
|
3252
|
-
},
|
|
3253
|
-
onWarningsChange: (warnings) => {
|
|
3254
|
-
this.settingsManager.setWarnings(warnings);
|
|
3255
|
-
},
|
|
3256
|
-
onCancel: () => {
|
|
3257
|
-
done();
|
|
3258
|
-
this.ui.requestRender();
|
|
3259
|
-
},
|
|
3260
|
-
});
|
|
3261
|
-
return { component: selector, focus: selector.getSettingsList() };
|
|
3262
|
-
});
|
|
2143
|
+
selectorsFlow.showSettingsSelector(this);
|
|
3263
2144
|
}
|
|
3264
2145
|
/** /thinking command entry; implementation in ./interactive-selectors.ts (S5 slice 5). */
|
|
3265
2146
|
handleThinkingCommand(searchTerm) {
|
|
@@ -3306,6 +2187,7 @@ export class InteractiveMode {
|
|
|
3306
2187
|
* file's binding entry and cached per path — the selector refilters the
|
|
3307
2188
|
* list on every keystroke.
|
|
3308
2189
|
*/
|
|
2190
|
+
/** Host-visible for ./interactive-selectors.ts (拆分 slice 2). */
|
|
3309
2191
|
readSessionSuiteId(sessionPath) {
|
|
3310
2192
|
this.sessionSuiteIdCache ??= new Map();
|
|
3311
2193
|
if (this.sessionSuiteIdCache.has(sessionPath))
|
|
@@ -3330,36 +2212,12 @@ export class InteractiveMode {
|
|
|
3330
2212
|
selectorsFlow.openSuiteListSelector(this, callbacks);
|
|
3331
2213
|
}
|
|
3332
2214
|
/**
|
|
3333
|
-
* Interactive recovery for an unresolved startup suite id (设计 §9)
|
|
3334
|
-
*
|
|
3335
|
-
*
|
|
3336
|
-
* fallback assembly the runtime started with.
|
|
2215
|
+
* Interactive recovery for an unresolved startup suite id (设计 §9); the
|
|
2216
|
+
* implementation is in ./interactive-selectors.ts (拆分 slice 2). The
|
|
2217
|
+
* startup path on the mode calls this delegate.
|
|
3337
2218
|
*/
|
|
3338
2219
|
async showStartupSuiteSelector(pending) {
|
|
3339
|
-
|
|
3340
|
-
this.openSuiteListSelector({
|
|
3341
|
-
availableSuites: pending.available,
|
|
3342
|
-
note: t('Suite "{id}" was not found. The selected suite applies to the new session.', {
|
|
3343
|
-
id: pending.requestedId,
|
|
3344
|
-
}),
|
|
3345
|
-
onSelect: (suiteId) => resolve(suiteId),
|
|
3346
|
-
onCancel: () => resolve(undefined),
|
|
3347
|
-
});
|
|
3348
|
-
});
|
|
3349
|
-
if (selected === undefined) {
|
|
3350
|
-
this.showWarning(t('Suite "{id}" was not found — continuing without a suite. Use /suite to choose one.', {
|
|
3351
|
-
id: pending.requestedId,
|
|
3352
|
-
}));
|
|
3353
|
-
return;
|
|
3354
|
-
}
|
|
3355
|
-
this.settingsManager.setProjectSuite(selected);
|
|
3356
|
-
try {
|
|
3357
|
-
await this.runtimeFacade.newSession();
|
|
3358
|
-
this.showStatus(t("Suite set to {id} — new session started", { id: selected }));
|
|
3359
|
-
}
|
|
3360
|
-
catch (error) {
|
|
3361
|
-
await this.handleFatalRuntimeError(t("Failed to create session"), error);
|
|
3362
|
-
}
|
|
2220
|
+
return selectorsFlow.showStartupSuiteSelector(this, pending);
|
|
3363
2221
|
}
|
|
3364
2222
|
/** /model command entry; implementation in ./interactive-selectors.ts (S5 slice 5). */
|
|
3365
2223
|
async handleModelCommand(searchTerm) {
|
|
@@ -3424,244 +2282,32 @@ export class InteractiveMode {
|
|
|
3424
2282
|
async handleCloneCommand() {
|
|
3425
2283
|
await selectorsFlow.handleCloneCommand(this);
|
|
3426
2284
|
}
|
|
3427
|
-
/**
|
|
3428
|
-
* Workflow overlay (§9 L2/L3): read-only panorama + transition log from
|
|
3429
|
-
* `workflow.state`/`workflow.transition` entries. Actions are forwarded to
|
|
3430
|
-
* the editor as reviewable text; the main agent executes them via
|
|
3431
|
-
* workflow_advance (rendering and execution stay separated).
|
|
3432
|
-
*/
|
|
2285
|
+
/** Workflow overlay; implementation in ./interactive-selectors.ts (拆分 slice 2). */
|
|
3433
2286
|
showWorkflowView() {
|
|
3434
|
-
|
|
3435
|
-
.getEntries()
|
|
3436
|
-
.some((entry) => entry.type === "custom" && entry.customType === "workflow.state");
|
|
3437
|
-
if (!hasSnapshot) {
|
|
3438
|
-
this.showStatus(t("No workflow in this session"));
|
|
3439
|
-
return;
|
|
3440
|
-
}
|
|
3441
|
-
this.showSelector((done) => {
|
|
3442
|
-
const view = new WorkflowViewComponent({
|
|
3443
|
-
getEntries: () => this.facade.getEntries(),
|
|
3444
|
-
maxRows: Math.max(6, Math.floor(this.ui.terminal.rows / 2)),
|
|
3445
|
-
requestRender: () => this.ui.requestRender(),
|
|
3446
|
-
});
|
|
3447
|
-
view.onClose = done;
|
|
3448
|
-
view.onForwardAction = (text) => {
|
|
3449
|
-
done();
|
|
3450
|
-
this.editor.setText(text);
|
|
3451
|
-
this.ui.requestRender();
|
|
3452
|
-
this.showStatus(t("Workflow action staged — review in the editor and submit"));
|
|
3453
|
-
};
|
|
3454
|
-
return { component: view, focus: view, dispose: () => view.dispose() };
|
|
3455
|
-
});
|
|
2287
|
+
selectorsFlow.showWorkflowView(this);
|
|
3456
2288
|
}
|
|
3457
|
-
/**
|
|
3458
|
-
* Subagent session browser (/agents): read-only list + detail of this
|
|
3459
|
-
* session's subagent transcripts from the D-060 §3 on-disk store — the same
|
|
3460
|
-
* parent-scoped summary source as the subagent_list model tool (only this
|
|
3461
|
-
* session's children, newest first). Detail entries load lazily per
|
|
3462
|
-
* session; a failed load renders inside the view instead of throwing.
|
|
3463
|
-
*/
|
|
2289
|
+
/** Subagent session browser; implementation in ./interactive-selectors.ts (拆分 slice 2). */
|
|
3464
2290
|
showAgentsView() {
|
|
3465
|
-
|
|
3466
|
-
const sessions = listAgentSessions({ agentDir, parentAgentSession: this.facade.getSessionId() });
|
|
3467
|
-
if (sessions.length === 0) {
|
|
3468
|
-
this.showStatus(t("No subagent sessions in this session"));
|
|
3469
|
-
return;
|
|
3470
|
-
}
|
|
3471
|
-
this.showSelector((done) => {
|
|
3472
|
-
const view = new AgentsViewComponent({
|
|
3473
|
-
sessions,
|
|
3474
|
-
loadEntries: (sessionId) => readAgentSession({ agentDir, sessionId }),
|
|
3475
|
-
maxRows: Math.max(6, Math.floor(this.ui.terminal.rows / 2)),
|
|
3476
|
-
requestRender: () => this.ui.requestRender(),
|
|
3477
|
-
});
|
|
3478
|
-
view.onClose = done;
|
|
3479
|
-
return { component: view, focus: view };
|
|
3480
|
-
});
|
|
2291
|
+
selectorsFlow.showAgentsView(this);
|
|
3481
2292
|
}
|
|
2293
|
+
/** Tree selector; implementation in ./interactive-selectors.ts (拆分 slice 2). */
|
|
3482
2294
|
showTreeSelector(initialSelectedId) {
|
|
3483
|
-
|
|
3484
|
-
const realLeafId = this.facade.getLeafId();
|
|
3485
|
-
const initialFilterMode = this.settingsManager.getTreeFilterMode();
|
|
3486
|
-
if (tree.length === 0) {
|
|
3487
|
-
this.showStatus(t("No entries in session"));
|
|
3488
|
-
return;
|
|
3489
|
-
}
|
|
3490
|
-
this.showSelector((done) => {
|
|
3491
|
-
const selector = new TreeSelectorComponent(tree, realLeafId, this.ui.terminal.rows, async (entryId) => {
|
|
3492
|
-
// Selecting the current leaf is a no-op (already there)
|
|
3493
|
-
if (entryId === this.facade.getLeafId()) {
|
|
3494
|
-
done();
|
|
3495
|
-
this.showStatus(t("Already at this point"));
|
|
3496
|
-
return;
|
|
3497
|
-
}
|
|
3498
|
-
// Ask about summarization
|
|
3499
|
-
done(); // Close selector first
|
|
3500
|
-
// Loop until user makes a complete choice or cancels to tree
|
|
3501
|
-
let wantsSummary = false;
|
|
3502
|
-
let customInstructions;
|
|
3503
|
-
// Check if we should skip the prompt (user preference to always default to no summary)
|
|
3504
|
-
if (!this.settingsManager.getBranchSummarySkipPrompt()) {
|
|
3505
|
-
while (true) {
|
|
3506
|
-
const summaryChoice = await this.showDialogSelector(t("Summarize branch?"), [
|
|
3507
|
-
t("No summary"),
|
|
3508
|
-
t("Summarize"),
|
|
3509
|
-
t("Summarize with custom prompt"),
|
|
3510
|
-
]);
|
|
3511
|
-
if (summaryChoice === undefined) {
|
|
3512
|
-
// User pressed escape - re-show tree selector with same selection
|
|
3513
|
-
this.showTreeSelector(entryId);
|
|
3514
|
-
return;
|
|
3515
|
-
}
|
|
3516
|
-
wantsSummary = summaryChoice !== t("No summary");
|
|
3517
|
-
if (summaryChoice === t("Summarize with custom prompt")) {
|
|
3518
|
-
customInstructions = await this.showDialogEditor(t("Custom summarization instructions"));
|
|
3519
|
-
if (customInstructions === undefined) {
|
|
3520
|
-
// User cancelled - loop back to summary selector
|
|
3521
|
-
continue;
|
|
3522
|
-
}
|
|
3523
|
-
}
|
|
3524
|
-
// User made a complete choice
|
|
3525
|
-
break;
|
|
3526
|
-
}
|
|
3527
|
-
}
|
|
3528
|
-
// The user committed to navigating: stop the active response first.
|
|
3529
|
-
// 中断分级:导航清场只停回合,不终止后台子代理(设计 §4.2)。
|
|
3530
|
-
if (this.facade.isStreaming()) {
|
|
3531
|
-
this.restoreQueuedMessagesToEditor();
|
|
3532
|
-
await this.facade.abort({ cancelAgentTasks: false });
|
|
3533
|
-
}
|
|
3534
|
-
// Set up escape handler and status indicator if summarizing
|
|
3535
|
-
let showingSummaryIndicator = false;
|
|
3536
|
-
const originalOnEscape = this.defaultEditor.onEscape;
|
|
3537
|
-
if (wantsSummary) {
|
|
3538
|
-
this.defaultEditor.onEscape = () => {
|
|
3539
|
-
this.facade.abortBranchSummary();
|
|
3540
|
-
};
|
|
3541
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
3542
|
-
this.showStatusIndicator(new BranchSummaryStatusIndicator(this.ui));
|
|
3543
|
-
showingSummaryIndicator = true;
|
|
3544
|
-
this.ui.requestRender();
|
|
3545
|
-
}
|
|
3546
|
-
try {
|
|
3547
|
-
const result = await this.facade.navigateTree(entryId, {
|
|
3548
|
-
summarize: wantsSummary,
|
|
3549
|
-
customInstructions,
|
|
3550
|
-
});
|
|
3551
|
-
if (result.aborted) {
|
|
3552
|
-
// Summarization aborted - re-show tree selector with same selection
|
|
3553
|
-
this.showStatus(t("Branch summarization cancelled"));
|
|
3554
|
-
this.showTreeSelector(entryId);
|
|
3555
|
-
return;
|
|
3556
|
-
}
|
|
3557
|
-
if (result.cancelled) {
|
|
3558
|
-
this.showStatus(t("Navigation cancelled"));
|
|
3559
|
-
return;
|
|
3560
|
-
}
|
|
3561
|
-
// Update UI
|
|
3562
|
-
this.chatContainer.clear();
|
|
3563
|
-
this.renderInitialMessages();
|
|
3564
|
-
if (result.editorText && !this.editor.getText().trim()) {
|
|
3565
|
-
this.editor.setText(result.editorText);
|
|
3566
|
-
}
|
|
3567
|
-
this.showStatus(t("Navigated to selected point"));
|
|
3568
|
-
void this.flushCompactionQueue({ willRetry: false });
|
|
3569
|
-
}
|
|
3570
|
-
catch (error) {
|
|
3571
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
3572
|
-
}
|
|
3573
|
-
finally {
|
|
3574
|
-
if (showingSummaryIndicator) {
|
|
3575
|
-
this.clearStatusIndicator("branchSummary");
|
|
3576
|
-
}
|
|
3577
|
-
this.defaultEditor.onEscape = originalOnEscape;
|
|
3578
|
-
}
|
|
3579
|
-
}, () => {
|
|
3580
|
-
done();
|
|
3581
|
-
this.ui.requestRender();
|
|
3582
|
-
}, (entryId, label) => {
|
|
3583
|
-
this.facade.appendLabelChange(entryId, label);
|
|
3584
|
-
this.ui.requestRender();
|
|
3585
|
-
}, initialSelectedId, initialFilterMode);
|
|
3586
|
-
selector.onCopy = async (text) => {
|
|
3587
|
-
if (!text) {
|
|
3588
|
-
this.showError(t("Selected entry has no text to copy"));
|
|
3589
|
-
return;
|
|
3590
|
-
}
|
|
3591
|
-
try {
|
|
3592
|
-
await copyToClipboard(text);
|
|
3593
|
-
this.showStatus(t("Copied selected message to clipboard"));
|
|
3594
|
-
}
|
|
3595
|
-
catch (error) {
|
|
3596
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
3597
|
-
}
|
|
3598
|
-
};
|
|
3599
|
-
return { component: selector, focus: selector };
|
|
3600
|
-
});
|
|
2295
|
+
selectorsFlow.showTreeSelector(this, initialSelectedId);
|
|
3601
2296
|
}
|
|
2297
|
+
/** Session selector; implementation in ./interactive-selectors.ts (拆分 slice 2). */
|
|
3602
2298
|
showSessionSelector() {
|
|
3603
|
-
|
|
3604
|
-
const selector = new SessionSelectorComponent((onProgress, signal) => this.runtimeFacade.listSessions(onProgress, signal), (onProgress, signal) => this.runtimeFacade.listAllSessions(onProgress, signal), async (sessionPath) => {
|
|
3605
|
-
done();
|
|
3606
|
-
await this.handleResumeSession(sessionPath);
|
|
3607
|
-
}, () => {
|
|
3608
|
-
done();
|
|
3609
|
-
this.ui.requestRender();
|
|
3610
|
-
}, () => {
|
|
3611
|
-
void this.shutdown();
|
|
3612
|
-
}, () => this.ui.requestRender(), {
|
|
3613
|
-
renameSession: async (sessionFilePath, nextName) => {
|
|
3614
|
-
const next = (nextName ?? "").trim();
|
|
3615
|
-
if (!next)
|
|
3616
|
-
return;
|
|
3617
|
-
const mgr = SessionManager.open(sessionFilePath);
|
|
3618
|
-
mgr.appendSessionInfo(next);
|
|
3619
|
-
},
|
|
3620
|
-
showRenameHint: true,
|
|
3621
|
-
keybindings: this.keybindings,
|
|
3622
|
-
// 会话选择器默认只列当前方案的会话;跨方案会话仅在显式
|
|
3623
|
-
// "show all" 后可见(设计 §10.1)。
|
|
3624
|
-
suiteFilter: {
|
|
3625
|
-
currentSuiteId: this.getCurrentSessionSuiteId(),
|
|
3626
|
-
getSessionSuiteId: (session) => this.readSessionSuiteId(session.path),
|
|
3627
|
-
},
|
|
3628
|
-
}, this.runtimeFacade.getSessionFile());
|
|
3629
|
-
return { component: selector, focus: selector };
|
|
3630
|
-
});
|
|
2299
|
+
selectorsFlow.showSessionSelector(this);
|
|
3631
2300
|
}
|
|
2301
|
+
/** Queue manager overlay; implementation in ./interactive-selectors.ts (S5 slice 5). Host-visible for ./interactive-queues.ts (拆分 slice 3). */
|
|
2302
|
+
showQueueManagerSelector() {
|
|
2303
|
+
selectorsFlow.showQueueManagerSelector(this);
|
|
2304
|
+
}
|
|
2305
|
+
/**
|
|
2306
|
+
* Session resume; implementation in ./interactive-selectors.ts (拆分 slice
|
|
2307
|
+
* 2). Kept as a delegate for the prototype-level test bindings.
|
|
2308
|
+
*/
|
|
3632
2309
|
async handleResumeSession(sessionPath, options) {
|
|
3633
|
-
|
|
3634
|
-
try {
|
|
3635
|
-
const result = await this.runtimeFacade.switchSession(sessionPath, {
|
|
3636
|
-
withSession: options?.withSession,
|
|
3637
|
-
});
|
|
3638
|
-
if (result.cancelled) {
|
|
3639
|
-
return result;
|
|
3640
|
-
}
|
|
3641
|
-
this.showStatus(t("Resumed session"));
|
|
3642
|
-
this.detectCrossSuiteResume();
|
|
3643
|
-
return result;
|
|
3644
|
-
}
|
|
3645
|
-
catch (error) {
|
|
3646
|
-
if (error instanceof MissingSessionCwdError) {
|
|
3647
|
-
const selectedCwd = await this.promptForMissingSessionCwd(error);
|
|
3648
|
-
if (!selectedCwd) {
|
|
3649
|
-
this.showStatus(t("Resume cancelled"));
|
|
3650
|
-
return { cancelled: true };
|
|
3651
|
-
}
|
|
3652
|
-
const result = await this.runtimeFacade.switchSession(sessionPath, {
|
|
3653
|
-
cwdOverride: selectedCwd,
|
|
3654
|
-
withSession: options?.withSession,
|
|
3655
|
-
});
|
|
3656
|
-
if (result.cancelled) {
|
|
3657
|
-
return result;
|
|
3658
|
-
}
|
|
3659
|
-
this.showStatus(t("Resumed session in current cwd"));
|
|
3660
|
-
this.detectCrossSuiteResume();
|
|
3661
|
-
return result;
|
|
3662
|
-
}
|
|
3663
|
-
return this.handleFatalRuntimeError(t("Failed to resume session"), error);
|
|
3664
|
-
}
|
|
2310
|
+
return selectorsFlow.handleResumeSession(this, sessionPath, options);
|
|
3665
2311
|
}
|
|
3666
2312
|
/**
|
|
3667
2313
|
* Cross-suite resume detection (设计 §10.1 最小可用版): compare the loaded
|
|
@@ -3671,6 +2317,9 @@ export class InteractiveMode {
|
|
|
3671
2317
|
* behavior). On a mismatch, record the fact for later debugging: an info
|
|
3672
2318
|
* status line plus a `suite_mismatch` custom entry on the session. No
|
|
3673
2319
|
* context serialization or conversion is attempted.
|
|
2320
|
+
*
|
|
2321
|
+
* Host-visible for ./interactive-selectors.ts (拆分 slice 2): also called
|
|
2322
|
+
* from the startup/resume path on the mode, so it stays here.
|
|
3674
2323
|
*/
|
|
3675
2324
|
detectCrossSuiteResume() {
|
|
3676
2325
|
const originalSuiteId = this.getCurrentSessionSuiteId();
|
|
@@ -3804,645 +2453,88 @@ export class InteractiveMode {
|
|
|
3804
2453
|
// Command handlers
|
|
3805
2454
|
// =========================================================================
|
|
3806
2455
|
async handleReloadCommand() {
|
|
3807
|
-
|
|
3808
|
-
this.showWarning(t("Wait for the current response to finish before reloading."));
|
|
3809
|
-
return;
|
|
3810
|
-
}
|
|
3811
|
-
if (this.facade.isCompacting()) {
|
|
3812
|
-
this.showWarning(t("Wait for compaction to finish before reloading."));
|
|
3813
|
-
return;
|
|
3814
|
-
}
|
|
3815
|
-
this.resetDialogUI();
|
|
3816
|
-
const reloadBox = new Container();
|
|
3817
|
-
const borderColor = (s) => theme.fg("border", s);
|
|
3818
|
-
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
3819
|
-
reloadBox.addChild(new Spacer(1));
|
|
3820
|
-
reloadBox.addChild(new Text(theme.fg("muted", t("Reloading keybindings, skills, prompts, themes, and context files...")), 1, 0));
|
|
3821
|
-
reloadBox.addChild(new Spacer(1));
|
|
3822
|
-
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
3823
|
-
const previousEditor = this.editor;
|
|
3824
|
-
this.editorContainer.clear();
|
|
3825
|
-
this.editorContainer.addChild(reloadBox);
|
|
3826
|
-
this.ui.setFocus(reloadBox);
|
|
3827
|
-
this.ui.requestRender(true);
|
|
3828
|
-
await new Promise((resolve) => process.nextTick(resolve));
|
|
3829
|
-
const dismissReloadBox = (editor) => {
|
|
3830
|
-
this.editorContainer.clear();
|
|
3831
|
-
this.editorContainer.addChild(editor);
|
|
3832
|
-
this.ui.setFocus(editor);
|
|
3833
|
-
this.ui.requestRender();
|
|
3834
|
-
};
|
|
3835
|
-
let chatRestoredBeforeSessionStart = false;
|
|
3836
|
-
let reloadBoxDismissed = false;
|
|
3837
|
-
const restoreChatBeforeSessionStart = () => {
|
|
3838
|
-
if (chatRestoredBeforeSessionStart) {
|
|
3839
|
-
return;
|
|
3840
|
-
}
|
|
3841
|
-
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
3842
|
-
this.outputPad = this.settingsManager.getOutputPad();
|
|
3843
|
-
this.rebuildChatFromMessages();
|
|
3844
|
-
chatRestoredBeforeSessionStart = true;
|
|
3845
|
-
};
|
|
3846
|
-
try {
|
|
3847
|
-
await this.facade.reload({ beforeSessionStart: restoreChatBeforeSessionStart });
|
|
3848
|
-
restoreChatBeforeSessionStart();
|
|
3849
|
-
this.keybindings.reload();
|
|
3850
|
-
const activeHeader = this.builtInHeader;
|
|
3851
|
-
if (isExpandable(activeHeader)) {
|
|
3852
|
-
activeHeader.setExpanded(this.toolOutputExpanded);
|
|
3853
|
-
}
|
|
3854
|
-
setRegisteredThemes(buildRegisteredThemes(this.facade.getThemes().themes));
|
|
3855
|
-
this.applyRuntimeSettings();
|
|
3856
|
-
await this.themeController.applyFromSettings();
|
|
3857
|
-
this.setupAutocompleteProvider();
|
|
3858
|
-
this.showLoadedResources({
|
|
3859
|
-
force: false,
|
|
3860
|
-
showDiagnosticsWhenQuiet: true,
|
|
3861
|
-
});
|
|
3862
|
-
const modelsJsonError = this.facade.getModelsError();
|
|
3863
|
-
if (modelsJsonError) {
|
|
3864
|
-
this.showError(t("models.json error: {error}", { error: modelsJsonError }));
|
|
3865
|
-
}
|
|
3866
|
-
this.showStatus(t("Reloaded keybindings, skills, prompts, themes, and context files"));
|
|
3867
|
-
dismissReloadBox(this.editor);
|
|
3868
|
-
reloadBoxDismissed = true;
|
|
3869
|
-
}
|
|
3870
|
-
catch (error) {
|
|
3871
|
-
// v8 ignore next -- reloadBoxDismissed 置位后 catch 前无可抛出点,恒走 dismiss 分支
|
|
3872
|
-
if (!reloadBoxDismissed) {
|
|
3873
|
-
dismissReloadBox(previousEditor);
|
|
3874
|
-
}
|
|
3875
|
-
this.showError(t("Reload failed: {error}", { error: error instanceof Error ? error.message : String(error) }));
|
|
3876
|
-
}
|
|
2456
|
+
return handleReloadCommand(this);
|
|
3877
2457
|
}
|
|
3878
2458
|
async handleExportCommand(text) {
|
|
3879
|
-
|
|
3880
|
-
try {
|
|
3881
|
-
if (outputPath?.endsWith(".jsonl")) {
|
|
3882
|
-
const filePath = this.facade.exportToJsonl(outputPath);
|
|
3883
|
-
this.showStatus(t("Session exported to: {path}", { path: filePath }));
|
|
3884
|
-
}
|
|
3885
|
-
else {
|
|
3886
|
-
const filePath = await this.facade.exportToHtml(outputPath, {
|
|
3887
|
-
themeName: theme.name,
|
|
3888
|
-
});
|
|
3889
|
-
this.showStatus(t("Session exported to: {path}", { path: filePath }));
|
|
3890
|
-
}
|
|
3891
|
-
}
|
|
3892
|
-
catch (error) {
|
|
3893
|
-
this.showError(t("Failed to export session: {error}", {
|
|
3894
|
-
error: error instanceof Error ? error.message : t("Unknown error"),
|
|
3895
|
-
}));
|
|
3896
|
-
}
|
|
2459
|
+
return handleExportCommand(this, text);
|
|
3897
2460
|
}
|
|
2461
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: prototype-level test bindings call this delegate; implementation in ./interactive-commands.ts
|
|
3898
2462
|
getPathCommandArgument(text, command) {
|
|
3899
|
-
|
|
3900
|
-
return undefined;
|
|
3901
|
-
}
|
|
3902
|
-
if (!text.startsWith(`${command} `)) {
|
|
3903
|
-
return undefined;
|
|
3904
|
-
}
|
|
3905
|
-
const argsString = text.slice(command.length + 1).trimStart();
|
|
3906
|
-
if (!argsString) {
|
|
3907
|
-
return undefined;
|
|
3908
|
-
}
|
|
3909
|
-
const firstChar = argsString[0];
|
|
3910
|
-
if (firstChar === '"' || firstChar === "'") {
|
|
3911
|
-
const closingQuoteIndex = argsString.indexOf(firstChar, 1);
|
|
3912
|
-
if (closingQuoteIndex < 0) {
|
|
3913
|
-
return undefined;
|
|
3914
|
-
}
|
|
3915
|
-
return argsString.slice(1, closingQuoteIndex);
|
|
3916
|
-
}
|
|
3917
|
-
const firstWhitespaceIndex = argsString.search(/\s/);
|
|
3918
|
-
if (firstWhitespaceIndex < 0) {
|
|
3919
|
-
return argsString;
|
|
3920
|
-
}
|
|
3921
|
-
return argsString.slice(0, firstWhitespaceIndex);
|
|
2463
|
+
return getPathCommandArgument(text, command);
|
|
3922
2464
|
}
|
|
3923
2465
|
async handleImportCommand(text) {
|
|
3924
|
-
|
|
3925
|
-
if (!inputPath) {
|
|
3926
|
-
this.showError(t("Usage: /import <path.jsonl>"));
|
|
3927
|
-
return;
|
|
3928
|
-
}
|
|
3929
|
-
const confirmed = await this.showDialogConfirm(t("Import session"), t("Replace current session with {path}?", { path: inputPath }));
|
|
3930
|
-
if (!confirmed) {
|
|
3931
|
-
this.showStatus(t("Import cancelled"));
|
|
3932
|
-
return;
|
|
3933
|
-
}
|
|
3934
|
-
try {
|
|
3935
|
-
this.clearStatusIndicator();
|
|
3936
|
-
const result = await this.runtimeFacade.importFromJsonl(inputPath);
|
|
3937
|
-
if (result.cancelled) {
|
|
3938
|
-
this.showStatus(t("Import cancelled"));
|
|
3939
|
-
return;
|
|
3940
|
-
}
|
|
3941
|
-
this.showStatus(t("Session imported from: {path}", { path: inputPath }));
|
|
3942
|
-
}
|
|
3943
|
-
catch (error) {
|
|
3944
|
-
if (error instanceof MissingSessionCwdError) {
|
|
3945
|
-
const selectedCwd = await this.promptForMissingSessionCwd(error);
|
|
3946
|
-
if (!selectedCwd) {
|
|
3947
|
-
this.showStatus(t("Import cancelled"));
|
|
3948
|
-
return;
|
|
3949
|
-
}
|
|
3950
|
-
const result = await this.runtimeFacade.importFromJsonl(inputPath, selectedCwd);
|
|
3951
|
-
if (result.cancelled) {
|
|
3952
|
-
this.showStatus(t("Import cancelled"));
|
|
3953
|
-
return;
|
|
3954
|
-
}
|
|
3955
|
-
this.showStatus(t("Session imported from: {path}", { path: inputPath }));
|
|
3956
|
-
return;
|
|
3957
|
-
}
|
|
3958
|
-
if (error instanceof SessionImportFileNotFoundError) {
|
|
3959
|
-
this.showError(t("Failed to import session: {error}", { error: error.message }));
|
|
3960
|
-
return;
|
|
3961
|
-
}
|
|
3962
|
-
await this.handleFatalRuntimeError(t("Failed to import session"), error);
|
|
3963
|
-
}
|
|
2466
|
+
return handleImportCommand(this, text);
|
|
3964
2467
|
}
|
|
3965
2468
|
async handleCopyCommand(options = {}) {
|
|
3966
|
-
|
|
3967
|
-
this.ui instanceof TuiAltScreen &&
|
|
3968
|
-
!this.ui.getCopyOnSelect() &&
|
|
3969
|
-
this.ui.hasActiveSelection()) {
|
|
3970
|
-
await this.ui.copyActiveSelectionToClipboard();
|
|
3971
|
-
return;
|
|
3972
|
-
}
|
|
3973
|
-
const text = this.facade.getLastAssistantText();
|
|
3974
|
-
if (!text) {
|
|
3975
|
-
this.showError(t("No agent messages to copy yet."));
|
|
3976
|
-
return;
|
|
3977
|
-
}
|
|
3978
|
-
try {
|
|
3979
|
-
await copyToClipboard(text);
|
|
3980
|
-
if (options.flashConfirmation && this.ui instanceof TuiAltScreen) {
|
|
3981
|
-
this.ui.flash(t("Copied!"));
|
|
3982
|
-
}
|
|
3983
|
-
else {
|
|
3984
|
-
this.showStatus(t("Copied last agent message to clipboard"));
|
|
3985
|
-
}
|
|
3986
|
-
}
|
|
3987
|
-
catch (error) {
|
|
3988
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
3989
|
-
}
|
|
2469
|
+
return handleCopyCommand(this, options);
|
|
3990
2470
|
}
|
|
3991
2471
|
handleNameCommand(text) {
|
|
3992
|
-
|
|
3993
|
-
if (!name) {
|
|
3994
|
-
const currentName = this.facade.getSessionName();
|
|
3995
|
-
if (currentName) {
|
|
3996
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
3997
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", t("Session name: {name}", { name: currentName })), 1, 0));
|
|
3998
|
-
}
|
|
3999
|
-
else {
|
|
4000
|
-
this.showWarning(t("Usage: /name <name>"));
|
|
4001
|
-
}
|
|
4002
|
-
this.ui.requestRender();
|
|
4003
|
-
return;
|
|
4004
|
-
}
|
|
4005
|
-
this.facade.setSessionName(name);
|
|
4006
|
-
const sessionName = this.facade.getSessionName();
|
|
4007
|
-
if (sessionName !== name) {
|
|
4008
|
-
this.showWarning(t("Session name was normalized from {from} to {to}", {
|
|
4009
|
-
from: JSON.stringify(name),
|
|
4010
|
-
to: JSON.stringify(sessionName),
|
|
4011
|
-
}));
|
|
4012
|
-
}
|
|
4013
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4014
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", t("Session name set: {name}", { name: sessionName ?? name })), 1, 0));
|
|
4015
|
-
this.ui.requestRender();
|
|
2472
|
+
handleNameCommand(this, text);
|
|
4016
2473
|
}
|
|
4017
2474
|
/**
|
|
4018
2475
|
* 中断分级(设计 子代理运行时 §4.2,2026-10-02):/stop-agents 的唯一执行
|
|
4019
2476
|
* 面——终止全部后台子代理(不触当前回合)。Esc 从不做这件事。
|
|
4020
2477
|
*/
|
|
4021
2478
|
handleStopAgentsCommand() {
|
|
4022
|
-
|
|
4023
|
-
if (active === 0) {
|
|
4024
|
-
this.showStatus(t("No background subagents running"));
|
|
4025
|
-
return;
|
|
4026
|
-
}
|
|
4027
|
-
this.facade.abortAgentTasks();
|
|
4028
|
-
this.showStatus(active === 1
|
|
4029
|
-
? t("Terminated {count} background subagent", { count: active })
|
|
4030
|
-
: t("Terminated {count} background subagents", { count: active }));
|
|
2479
|
+
handleStopAgentsCommand(this);
|
|
4031
2480
|
}
|
|
4032
2481
|
handleSessionCommand() {
|
|
4033
|
-
|
|
4034
|
-
const sessionName = this.facade.getSessionName();
|
|
4035
|
-
const entries = this.facade.getEntries();
|
|
4036
|
-
const cacheWaste = computeCacheWaste(entries, this.facade.getModelPriceSource());
|
|
4037
|
-
// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
|
|
4038
|
-
// resolves to a concrete responseModel). Usage without model attribution is
|
|
4039
|
-
// grouped separately so the breakdown reconciles with the session total.
|
|
4040
|
-
const usageBreakdown = getUsageCostBreakdown(entries);
|
|
4041
|
-
let info = `${theme.bold(t("Session Info"))}\n\n`;
|
|
4042
|
-
if (sessionName) {
|
|
4043
|
-
info += `${theme.fg("dim", t("Name:"))} ${sessionName}\n`;
|
|
4044
|
-
}
|
|
4045
|
-
info += `${theme.fg("dim", t("File:"))} ${stats.sessionFile ?? t("In-memory")}\n`;
|
|
4046
|
-
info += `${theme.fg("dim", t("ID:"))} ${stats.sessionId}\n\n`;
|
|
4047
|
-
info += `${theme.bold(t("Messages"))}\n`;
|
|
4048
|
-
info += `${theme.fg("dim", t("Total:"))} ${stats.totalMessages}\n`;
|
|
4049
|
-
info += `${theme.fg("dim", t("User:"))} ${stats.userMessages}\n`;
|
|
4050
|
-
info += `${theme.fg("dim", t("Assistant:"))} ${stats.assistantMessages}\n`;
|
|
4051
|
-
info += `${theme.fg("dim", t("Tools:"))} ${t("{calls} calls, {results} results", {
|
|
4052
|
-
calls: stats.toolCalls,
|
|
4053
|
-
results: stats.toolResults,
|
|
4054
|
-
})}\n\n`;
|
|
4055
|
-
info += `${theme.bold(t("Tokens"))}\n`;
|
|
4056
|
-
// "Input" is the full prompt volume. With cache activity, split it into
|
|
4057
|
-
// cached (served from cache) vs uncached (everything else) - the only
|
|
4058
|
-
// provider-independent split. Cache writes, where reported, are a detail
|
|
4059
|
-
// of the uncached portion.
|
|
4060
|
-
const { input, cacheRead, cacheWrite } = stats.tokens;
|
|
4061
|
-
const promptTokens = input + cacheRead + cacheWrite;
|
|
4062
|
-
info += `${theme.fg("dim", t("Input:"))} ${promptTokens.toLocaleString()}\n`;
|
|
4063
|
-
if (promptTokens > 0 && (cacheRead > 0 || cacheWrite > 0)) {
|
|
4064
|
-
const hitRate = theme.fg("dim", `(${((cacheRead / promptTokens) * 100).toFixed(1)}%)`);
|
|
4065
|
-
info += ` ${theme.fg("dim", t("Cached:"))} ${cacheRead.toLocaleString()} ${hitRate}\n`;
|
|
4066
|
-
const written = cacheWrite > 0
|
|
4067
|
-
? ` ${theme.fg("dim", t("({count} written to cache)", { count: cacheWrite.toLocaleString() }))}`
|
|
4068
|
-
: "";
|
|
4069
|
-
info += ` ${theme.fg("dim", t("Uncached:"))} ${(input + cacheWrite).toLocaleString()}${written}\n`;
|
|
4070
|
-
}
|
|
4071
|
-
info += `${theme.fg("dim", t("Output:"))} ${stats.tokens.output.toLocaleString()}\n`;
|
|
4072
|
-
info += `${theme.fg("dim", t("Total:"))} ${stats.tokens.total.toLocaleString()}\n`;
|
|
4073
|
-
// 双维度统计(用户裁决 2026-09-25):总计之外按供应商/模型分桶——缓存读/写、
|
|
4074
|
-
// 命中率、费用逐桶可见。此前该拆分门控在 cost>0 且多桶才渲染,单价为 0 的
|
|
4075
|
-
// 自定义供应商(如自建代理)永远看不到。
|
|
4076
|
-
if (usageBreakdown.length > 0) {
|
|
4077
|
-
info += `\n${theme.bold(t("By Provider/Model"))}\n`;
|
|
4078
|
-
for (const entry of usageBreakdown) {
|
|
4079
|
-
const bucket = entry.totals;
|
|
4080
|
-
const promptBucket = bucket.input + bucket.cacheRead + bucket.cacheWrite;
|
|
4081
|
-
let line = ` ${theme.bold(entry.key)}`;
|
|
4082
|
-
if (promptBucket > 0) {
|
|
4083
|
-
line += `\n ${theme.fg("dim", t("Input:"))} ${promptBucket.toLocaleString()}`;
|
|
4084
|
-
if (bucket.cacheRead > 0 || bucket.cacheWrite > 0) {
|
|
4085
|
-
const rate = promptBucket > 0 ? ((bucket.cacheRead / promptBucket) * 100).toFixed(1) : "0.0";
|
|
4086
|
-
line += ` ${theme.fg("dim", t("(cache {rate}%, W {written})", { rate, written: bucket.cacheWrite.toLocaleString() }))}`;
|
|
4087
|
-
}
|
|
4088
|
-
}
|
|
4089
|
-
if (bucket.output > 0)
|
|
4090
|
-
line += `\n ${theme.fg("dim", t("Output:"))} ${bucket.output.toLocaleString()}`;
|
|
4091
|
-
if (entry.cost > 0)
|
|
4092
|
-
line += ` ${theme.fg("dim", `$${entry.cost.toFixed(3)}`)}`;
|
|
4093
|
-
info += `${line}\n`;
|
|
4094
|
-
}
|
|
4095
|
-
}
|
|
4096
|
-
if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
|
|
4097
|
-
info += `\n${theme.bold(t("Cost"))}\n`;
|
|
4098
|
-
info += `${theme.fg("dim", t("Total:"))} $${stats.cost.toFixed(3)}`;
|
|
4099
|
-
if (usageBreakdown.length > 1) {
|
|
4100
|
-
for (const entry of usageBreakdown) {
|
|
4101
|
-
info += `\n ${theme.fg("dim", `${entry.key}:`)} $${entry.cost.toFixed(3)} ${theme.fg("dim", t("({tokens} tokens)", { tokens: formatTokens(entry.tokens) }))}`;
|
|
4102
|
-
}
|
|
4103
|
-
}
|
|
4104
|
-
if (cacheWaste.missedTokens > 0) {
|
|
4105
|
-
const missLabel = cacheWaste.missCount === 1
|
|
4106
|
-
? t("{count} miss", { count: cacheWaste.missCount })
|
|
4107
|
-
: t("{count} misses", { count: cacheWaste.missCount });
|
|
4108
|
-
const detail = t("{tokens} tokens, {misses}", {
|
|
4109
|
-
tokens: cacheWaste.missedTokens.toLocaleString(),
|
|
4110
|
-
misses: missLabel,
|
|
4111
|
-
});
|
|
4112
|
-
info +=
|
|
4113
|
-
cacheWaste.missedCost >= 0.0001
|
|
4114
|
-
? `\n${theme.fg("dim", t("Cache Re-billed:"))} $${cacheWaste.missedCost.toFixed(3)} ${theme.fg("dim", `(${detail})`)}`
|
|
4115
|
-
: `\n${theme.fg("dim", t("Cache Re-billed:"))} ${detail}`;
|
|
4116
|
-
}
|
|
4117
|
-
}
|
|
4118
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4119
|
-
this.chatContainer.addChild(new Text(info, 1, 0));
|
|
4120
|
-
this.ui.requestRender();
|
|
2482
|
+
handleSessionCommand(this);
|
|
4121
2483
|
}
|
|
4122
2484
|
handleMetricsCommand(turnsArg) {
|
|
4123
|
-
|
|
4124
|
-
if (!provider) {
|
|
4125
|
-
this.showWarning(t("Metrics are not available: the observability plugin is not mounted in this session"));
|
|
4126
|
-
return;
|
|
4127
|
-
}
|
|
4128
|
-
let windowTurns;
|
|
4129
|
-
if (turnsArg !== undefined) {
|
|
4130
|
-
const parsed = Number.parseInt(turnsArg, 10);
|
|
4131
|
-
if (!Number.isSafeInteger(parsed) || parsed < 1) {
|
|
4132
|
-
this.showWarning(t("Invalid turns: {turns}. Usage: /metrics [turns]", { turns: turnsArg }));
|
|
4133
|
-
return;
|
|
4134
|
-
}
|
|
4135
|
-
windowTurns = parsed;
|
|
4136
|
-
}
|
|
4137
|
-
let snapshot;
|
|
4138
|
-
try {
|
|
4139
|
-
const result = provider({ windowTurns });
|
|
4140
|
-
if (result === undefined) {
|
|
4141
|
-
this.showWarning(t("Metrics are not available: the observability plugin is not mounted in this session"));
|
|
4142
|
-
return;
|
|
4143
|
-
}
|
|
4144
|
-
snapshot = result;
|
|
4145
|
-
}
|
|
4146
|
-
catch (error) {
|
|
4147
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
4148
|
-
return;
|
|
4149
|
-
}
|
|
4150
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4151
|
-
this.chatContainer.addChild(new Text(formatMetricsSnapshot(snapshot), 1, 0));
|
|
4152
|
-
this.ui.requestRender();
|
|
2485
|
+
handleMetricsCommand(this, turnsArg);
|
|
4153
2486
|
}
|
|
4154
2487
|
handleChangelogCommand() {
|
|
4155
|
-
|
|
4156
|
-
const allEntries = parseChangelog(changelogPath);
|
|
4157
|
-
const changelogMarkdown = allEntries.length > 0
|
|
4158
|
-
? allEntries
|
|
4159
|
-
.reverse()
|
|
4160
|
-
.map((e) => normalizeChangelogLinks(e.content, e))
|
|
4161
|
-
.join("\n\n")
|
|
4162
|
-
: t("No changelog entries found.");
|
|
4163
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4164
|
-
this.chatContainer.addChild(new DynamicBorder());
|
|
4165
|
-
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", t("What's New"))), 1, 0));
|
|
4166
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4167
|
-
this.chatContainer.addChild(new Markdown(changelogMarkdown, 1, 1, this.getMarkdownThemeWithSettings()));
|
|
4168
|
-
this.chatContainer.addChild(new DynamicBorder());
|
|
4169
|
-
this.ui.requestRender();
|
|
2488
|
+
handleChangelogCommand(this);
|
|
4170
2489
|
}
|
|
4171
2490
|
/**
|
|
4172
2491
|
* Get capitalized display string for an app keybinding action.
|
|
2492
|
+
*
|
|
2493
|
+
* Host-visible for ./interactive-queues.ts (拆分 slice 3): the dequeue hint
|
|
2494
|
+
* in the pending-messages display.
|
|
4173
2495
|
*/
|
|
4174
2496
|
getAppKeyDisplay(action) {
|
|
4175
|
-
return
|
|
2497
|
+
return getAppKeyDisplay(action);
|
|
4176
2498
|
}
|
|
4177
2499
|
/**
|
|
4178
2500
|
* Get capitalized display string for an editor keybinding action.
|
|
4179
2501
|
*/
|
|
2502
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: thin delegate kept per 拆分计划 rule 2; implementation in ./interactive-commands.ts
|
|
4180
2503
|
getEditorKeyDisplay(action) {
|
|
4181
|
-
return
|
|
2504
|
+
return getEditorKeyDisplay(action);
|
|
4182
2505
|
}
|
|
4183
2506
|
handleHelpCommand() {
|
|
4184
|
-
|
|
4185
|
-
if (rows.length === 0)
|
|
4186
|
-
return "";
|
|
4187
|
-
let table = `\n**${title}**\n| ${t("Command")} | ${t("Description")} |\n|---------|-------------|\n`;
|
|
4188
|
-
for (const row of rows) {
|
|
4189
|
-
const command = row.hint ? `\`/${row.name} ${row.hint}\`` : `\`/${row.name}\``;
|
|
4190
|
-
table += `| ${command} | ${row.description ?? ""} |\n`;
|
|
4191
|
-
}
|
|
4192
|
-
return table;
|
|
4193
|
-
};
|
|
4194
|
-
const builtinRows = BUILTIN_SLASH_COMMANDS.map((command) => ({
|
|
4195
|
-
name: command.name,
|
|
4196
|
-
hint: command.argumentHint,
|
|
4197
|
-
description: command.description,
|
|
4198
|
-
}));
|
|
4199
|
-
const promptRows = this.facade.getPromptTemplates().map((template) => ({
|
|
4200
|
-
name: template.name,
|
|
4201
|
-
hint: template.argumentHint,
|
|
4202
|
-
description: template.description,
|
|
4203
|
-
}));
|
|
4204
|
-
const skillRows = this.facade.getSkills().skills.map((skill) => ({
|
|
4205
|
-
name: `skill:${skill.name}`,
|
|
4206
|
-
description: skill.description,
|
|
4207
|
-
}));
|
|
4208
|
-
const help = section(t("Built-in commands"), builtinRows) +
|
|
4209
|
-
section(t("Prompt templates"), promptRows) +
|
|
4210
|
-
section(t("Skills"), skillRows) +
|
|
4211
|
-
`\n${t("Type `/` for autocomplete. Use `/hotkeys` for keyboard shortcuts.")}`;
|
|
4212
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4213
|
-
this.chatContainer.addChild(new DynamicBorder());
|
|
4214
|
-
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", t("Available Commands"))), 1, 0));
|
|
4215
|
-
this.chatContainer.addChild(new Markdown(help.trim(), 1, 1, this.getMarkdownThemeWithSettings()));
|
|
4216
|
-
this.chatContainer.addChild(new DynamicBorder());
|
|
4217
|
-
this.ui.requestRender();
|
|
2507
|
+
handleHelpCommand(this);
|
|
4218
2508
|
}
|
|
4219
2509
|
handleHotkeysCommand() {
|
|
4220
|
-
|
|
4221
|
-
const cursorUp = this.getEditorKeyDisplay("tui.editor.cursorUp");
|
|
4222
|
-
const cursorDown = this.getEditorKeyDisplay("tui.editor.cursorDown");
|
|
4223
|
-
const cursorLeft = this.getEditorKeyDisplay("tui.editor.cursorLeft");
|
|
4224
|
-
const cursorRight = this.getEditorKeyDisplay("tui.editor.cursorRight");
|
|
4225
|
-
const cursorWordLeft = this.getEditorKeyDisplay("tui.editor.cursorWordLeft");
|
|
4226
|
-
const cursorWordRight = this.getEditorKeyDisplay("tui.editor.cursorWordRight");
|
|
4227
|
-
const cursorLineStart = this.getEditorKeyDisplay("tui.editor.cursorLineStart");
|
|
4228
|
-
const cursorLineEnd = this.getEditorKeyDisplay("tui.editor.cursorLineEnd");
|
|
4229
|
-
const jumpForward = this.getEditorKeyDisplay("tui.editor.jumpForward");
|
|
4230
|
-
const jumpBackward = this.getEditorKeyDisplay("tui.editor.jumpBackward");
|
|
4231
|
-
const pageUp = this.getEditorKeyDisplay("tui.editor.pageUp");
|
|
4232
|
-
const pageDown = this.getEditorKeyDisplay("tui.editor.pageDown");
|
|
4233
|
-
// Editing keybindings
|
|
4234
|
-
const submit = this.getEditorKeyDisplay("tui.input.submit");
|
|
4235
|
-
const newLine = this.getEditorKeyDisplay("tui.input.newLine");
|
|
4236
|
-
const deleteWordBackward = this.getEditorKeyDisplay("tui.editor.deleteWordBackward");
|
|
4237
|
-
const deleteWordForward = this.getEditorKeyDisplay("tui.editor.deleteWordForward");
|
|
4238
|
-
const deleteToLineStart = this.getEditorKeyDisplay("tui.editor.deleteToLineStart");
|
|
4239
|
-
const deleteToLineEnd = this.getEditorKeyDisplay("tui.editor.deleteToLineEnd");
|
|
4240
|
-
const yank = this.getEditorKeyDisplay("tui.editor.yank");
|
|
4241
|
-
const yankPop = this.getEditorKeyDisplay("tui.editor.yankPop");
|
|
4242
|
-
const undo = this.getEditorKeyDisplay("tui.editor.undo");
|
|
4243
|
-
const tab = this.getEditorKeyDisplay("tui.input.tab");
|
|
4244
|
-
// App keybindings
|
|
4245
|
-
const interrupt = this.getAppKeyDisplay("app.interrupt");
|
|
4246
|
-
const clear = this.getAppKeyDisplay("app.clear");
|
|
4247
|
-
const exit = this.getAppKeyDisplay("app.exit");
|
|
4248
|
-
const suspend = this.getAppKeyDisplay("app.suspend");
|
|
4249
|
-
const cycleThinkingLevel = this.getAppKeyDisplay("app.thinking.cycle");
|
|
4250
|
-
const cycleModelForward = this.getAppKeyDisplay("app.model.cycleForward");
|
|
4251
|
-
const selectModel = this.getAppKeyDisplay("app.model.select");
|
|
4252
|
-
const expandTools = this.getAppKeyDisplay("app.tools.expand");
|
|
4253
|
-
const toggleThinking = this.getAppKeyDisplay("app.thinking.toggle");
|
|
4254
|
-
const externalEditor = this.getAppKeyDisplay("app.editor.external");
|
|
4255
|
-
const cycleModelBackward = this.getAppKeyDisplay("app.model.cycleBackward");
|
|
4256
|
-
const copyMessage = this.getAppKeyDisplay("app.message.copy");
|
|
4257
|
-
const followUp = this.getAppKeyDisplay("app.message.followUp");
|
|
4258
|
-
const dequeue = this.getAppKeyDisplay("app.message.dequeue");
|
|
4259
|
-
const pasteImage = this.getAppKeyDisplay("app.clipboard.pasteImage");
|
|
4260
|
-
// Iron law (2026-10-01): these shipped defaults were cleared in favor of
|
|
4261
|
-
// the /model command; the row only appears when a user re-configures a key.
|
|
4262
|
-
const selectModelRow = selectModel ? `| \`${selectModel}\` | ${t("Open model selector")} |\n` : "";
|
|
4263
|
-
// Iron law (2026-10-02 复审): ctrl+g/ctrl+x stay bound only for the
|
|
4264
|
-
// components without a command channel — the main editor ignores them
|
|
4265
|
-
// (/editor and /copy are its single entries), so the rows say where
|
|
4266
|
-
// each key actually acts.
|
|
4267
|
-
const copyMessageRow = copyMessage ? `| \`${copyMessage}\` | ${t("Copy selection in /tree")} |\n` : "";
|
|
4268
|
-
const externalEditorRow = externalEditor
|
|
4269
|
-
? `| \`${externalEditor}\` | ${t("External editor (inside dialog inputs)")} |\n`
|
|
4270
|
-
: "";
|
|
4271
|
-
// Iron law (2026-10-01): the message queue is the /queue and /dequeue
|
|
4272
|
-
// commands' feature; rows only appear when a user re-configures a key.
|
|
4273
|
-
const followUpRow = followUp ? `| \`${followUp}\` | ${t("Queue follow-up message")} |\n` : "";
|
|
4274
|
-
const dequeueRow = dequeue ? `| \`${dequeue}\` | ${t("Restore queued messages")} |\n` : "";
|
|
4275
|
-
const cycleThinkingRow = cycleThinkingLevel
|
|
4276
|
-
? `| \`${cycleThinkingLevel}\` | ${t("Cycle thinking level")} |\n`
|
|
4277
|
-
: "";
|
|
4278
|
-
const cycleModelsRow = cycleModelForward || cycleModelBackward
|
|
4279
|
-
? `| \`${cycleModelForward}\` / \`${cycleModelBackward}\` | ${t("Cycle models")} |\n`
|
|
4280
|
-
: "";
|
|
4281
|
-
// Suspend has no default key on native Windows; hide the row there.
|
|
4282
|
-
const suspendRow = suspend ? `| \`${suspend}\` | ${t("Suspend to background")} |\n` : "";
|
|
4283
|
-
const hotkeys = `
|
|
4284
|
-
**${t("Navigation")}**
|
|
4285
|
-
| ${t("Key")} | ${t("Action")} |
|
|
4286
|
-
|-----|--------|
|
|
4287
|
-
| \`${cursorUp}\` / \`${cursorDown}\` / \`${cursorLeft}\` / \`${cursorRight}\` | ${t("Move cursor / browse history")} |
|
|
4288
|
-
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | ${t("Move by word")} |
|
|
4289
|
-
| \`${cursorLineStart}\` | ${t("Start of line")} |
|
|
4290
|
-
| \`${cursorLineEnd}\` | ${t("End of line")} |
|
|
4291
|
-
| \`${jumpForward}\` | ${t("Jump forward to character")} |
|
|
4292
|
-
| \`${jumpBackward}\` | ${t("Jump backward to character")} |
|
|
4293
|
-
| \`${pageUp}\` / \`${pageDown}\` | ${t("Scroll by page")} |
|
|
4294
|
-
|
|
4295
|
-
**${t("Editing")}**
|
|
4296
|
-
| ${t("Key")} | ${t("Action")} |
|
|
4297
|
-
|-----|--------|
|
|
4298
|
-
| \`${submit}\` | ${t("Send message")} |
|
|
4299
|
-
| \`${newLine}\` | ${process.platform === "win32" ? t("New line (Ctrl+Enter on Windows Terminal)") : t("New line")} |
|
|
4300
|
-
| \`${deleteWordBackward}\` | ${t("Delete word backwards")} |
|
|
4301
|
-
| \`${deleteWordForward}\` | ${t("Delete word forwards")} |
|
|
4302
|
-
| \`${deleteToLineStart}\` | ${t("Delete to start of line")} |
|
|
4303
|
-
| \`${deleteToLineEnd}\` | ${t("Delete to end of line")} |
|
|
4304
|
-
| \`${yank}\` | ${t("Paste the most-recently-deleted text")} |
|
|
4305
|
-
| \`${yankPop}\` | ${t("Cycle through the deleted text after pasting")} |
|
|
4306
|
-
| \`${undo}\` | ${t("Undo")} |
|
|
4307
|
-
|
|
4308
|
-
**${t("Other")}**
|
|
4309
|
-
| ${t("Key")} | ${t("Action")} |
|
|
4310
|
-
|-----|--------|
|
|
4311
|
-
| \`${tab}\` | ${t("Path completion / accept autocomplete")} |
|
|
4312
|
-
| \`${interrupt}\` | ${t("Cancel autocomplete / abort streaming")} |
|
|
4313
|
-
| \`${clear}\` | ${t("Clear editor (first) / exit (second)")} |
|
|
4314
|
-
| \`${exit}\` | ${t("Exit (when editor is empty)")} |
|
|
4315
|
-
${suspendRow}${cycleThinkingRow}${cycleModelsRow}${selectModelRow}| \`${expandTools}\` | ${t("Toggle tool output expansion")} |
|
|
4316
|
-
| \`${toggleThinking}\` | ${t("Toggle thinking block visibility")} |
|
|
4317
|
-
${externalEditorRow}${copyMessageRow}${followUpRow}${dequeueRow}
|
|
4318
|
-
| \`${pasteImage}\` | ${t("Paste image or text from clipboard")} |
|
|
4319
|
-
| \`/\` | ${t("Slash commands")} |
|
|
4320
|
-
| \`!\` | ${t("Run bash command")} |
|
|
4321
|
-
| \`!!\` | ${t("Run bash command (excluded from context)")} |
|
|
4322
|
-
`;
|
|
4323
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4324
|
-
this.chatContainer.addChild(new DynamicBorder());
|
|
4325
|
-
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", t("Keyboard Shortcuts"))), 1, 0));
|
|
4326
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4327
|
-
this.chatContainer.addChild(new Markdown(hotkeys.trim(), 1, 1, this.getMarkdownThemeWithSettings()));
|
|
4328
|
-
this.chatContainer.addChild(new DynamicBorder());
|
|
4329
|
-
this.ui.requestRender();
|
|
2510
|
+
handleHotkeysCommand(this);
|
|
4330
2511
|
}
|
|
4331
2512
|
async handleClearCommand() {
|
|
4332
|
-
this
|
|
4333
|
-
try {
|
|
4334
|
-
const result = await this.runtimeFacade.newSession();
|
|
4335
|
-
if (result.cancelled) {
|
|
4336
|
-
return;
|
|
4337
|
-
}
|
|
4338
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4339
|
-
this.chatContainer.addChild(new Text(`${theme.fg("accent", t("✓ New session started"))}`, 1, 1));
|
|
4340
|
-
this.ui.requestRender();
|
|
4341
|
-
}
|
|
4342
|
-
catch (error) {
|
|
4343
|
-
await this.handleFatalRuntimeError("Failed to create session", error);
|
|
4344
|
-
}
|
|
2513
|
+
return handleClearCommand(this);
|
|
4345
2514
|
}
|
|
4346
2515
|
handleDebugCommand() {
|
|
4347
|
-
|
|
4348
|
-
const height = this.ui.terminal.rows;
|
|
4349
|
-
const allLines = this.ui.render(width);
|
|
4350
|
-
const debugLogPath = getDebugLogPath();
|
|
4351
|
-
const debugData = [
|
|
4352
|
-
t("Debug output at {date}", { date: new Date().toISOString() }),
|
|
4353
|
-
t("Terminal: {columns}x{rows}", { columns: width, rows: height }),
|
|
4354
|
-
t("Total lines: {count}", { count: allLines.length }),
|
|
4355
|
-
"",
|
|
4356
|
-
t("=== All rendered lines with visible widths ==="),
|
|
4357
|
-
...allLines.map((line, idx) => {
|
|
4358
|
-
const vw = visibleWidth(line);
|
|
4359
|
-
const escaped = JSON.stringify(line);
|
|
4360
|
-
return `[${idx}] (w=${vw}) ${escaped}`;
|
|
4361
|
-
}),
|
|
4362
|
-
"",
|
|
4363
|
-
t("=== Agent messages (JSONL) ==="),
|
|
4364
|
-
...this.facade
|
|
4365
|
-
.getSessionAPI()
|
|
4366
|
-
.getMessages()
|
|
4367
|
-
.map((msg) => JSON.stringify(msg)),
|
|
4368
|
-
"",
|
|
4369
|
-
].join("\n");
|
|
4370
|
-
fs.mkdirSync(path.dirname(debugLogPath), { recursive: true });
|
|
4371
|
-
fs.writeFileSync(debugLogPath, debugData);
|
|
4372
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4373
|
-
this.chatContainer.addChild(new Text(`${theme.fg("accent", t("✓ Debug log written"))}\n${theme.fg("muted", debugLogPath)}`, 1, 1));
|
|
4374
|
-
this.ui.requestRender();
|
|
2516
|
+
handleDebugCommand(this);
|
|
4375
2517
|
}
|
|
4376
2518
|
handleArminSaysHi() {
|
|
4377
|
-
this
|
|
4378
|
-
this.chatContainer.addChild(new ArminComponent(this.ui));
|
|
4379
|
-
this.ui.requestRender();
|
|
2519
|
+
handleArminSaysHi(this);
|
|
4380
2520
|
}
|
|
2521
|
+
/** Host-visible for ./interactive-commands.ts (拆分 slice 1). */
|
|
4381
2522
|
handleDaxnuts() {
|
|
4382
|
-
this
|
|
4383
|
-
this.chatContainer.addChild(new DaxnutsComponent(this.ui));
|
|
4384
|
-
this.ui.requestRender();
|
|
2523
|
+
handleDaxnuts(this);
|
|
4385
2524
|
}
|
|
4386
2525
|
/** Host-visible for ./interactive-auth.ts (S5 slice 3). */
|
|
4387
2526
|
checkDaxnutsEasterEgg(model) {
|
|
4388
|
-
|
|
4389
|
-
this.handleDaxnuts();
|
|
4390
|
-
}
|
|
2527
|
+
checkDaxnutsEasterEgg(this, model);
|
|
4391
2528
|
}
|
|
4392
2529
|
async handleBashCommand(command, excludeFromContext = false) {
|
|
4393
|
-
|
|
4394
|
-
this.facade.emitUserBash(excludeFromContext);
|
|
4395
|
-
// Normal execution path
|
|
4396
|
-
const isDeferred = this.facade.isStreaming();
|
|
4397
|
-
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext);
|
|
4398
|
-
if (isDeferred) {
|
|
4399
|
-
// Show in pending area when agent is streaming
|
|
4400
|
-
this.pendingMessagesContainer.addChild(this.bashComponent);
|
|
4401
|
-
this.pendingBashComponents.push(this.bashComponent);
|
|
4402
|
-
}
|
|
4403
|
-
else {
|
|
4404
|
-
// Show in chat immediately when agent is idle
|
|
4405
|
-
this.chatContainer.addChild(this.bashComponent);
|
|
4406
|
-
}
|
|
4407
|
-
this.ui.requestRender();
|
|
4408
|
-
try {
|
|
4409
|
-
const result = await this.facade.executeBash(command, (chunk) => {
|
|
4410
|
-
if (this.bashComponent) {
|
|
4411
|
-
this.bashComponent.appendOutput(chunk);
|
|
4412
|
-
this.ui.requestRender();
|
|
4413
|
-
}
|
|
4414
|
-
}, { excludeFromContext });
|
|
4415
|
-
if (this.bashComponent) {
|
|
4416
|
-
this.bashComponent.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath);
|
|
4417
|
-
}
|
|
4418
|
-
}
|
|
4419
|
-
catch (error) {
|
|
4420
|
-
if (this.bashComponent) {
|
|
4421
|
-
this.bashComponent.setComplete(undefined, false);
|
|
4422
|
-
}
|
|
4423
|
-
this.showError(t("Bash command failed: {error}", {
|
|
4424
|
-
error: error instanceof Error ? error.message : t("Unknown error"),
|
|
4425
|
-
}));
|
|
4426
|
-
}
|
|
4427
|
-
this.bashComponent = undefined;
|
|
4428
|
-
this.ui.requestRender();
|
|
2530
|
+
await handleBashCommand(this, command, excludeFromContext);
|
|
4429
2531
|
}
|
|
4430
2532
|
async handleCompactCommand(customInstructions) {
|
|
4431
|
-
this
|
|
4432
|
-
try {
|
|
4433
|
-
await this.facade.compact(customInstructions);
|
|
4434
|
-
}
|
|
4435
|
-
catch {
|
|
4436
|
-
// Ignore, will be emitted as an event
|
|
4437
|
-
}
|
|
2533
|
+
return handleCompactCommand(this, customInstructions);
|
|
4438
2534
|
}
|
|
4439
2535
|
/** /clean-sessions:会话清理两阶段流程;输出/确认走宿主既有机制(设计 §7)。 */
|
|
4440
2536
|
async handleCleanSessionsCommand(arg) {
|
|
4441
|
-
|
|
4442
|
-
showMessage: (text) => this.showStatus(text),
|
|
4443
|
-
showError: (text) => this.showError(text),
|
|
4444
|
-
confirm: (title, message) => this.showDialogConfirm(title, message),
|
|
4445
|
-
}, arg);
|
|
2537
|
+
return handleCleanSessionsCommand(this, arg);
|
|
4446
2538
|
}
|
|
4447
2539
|
stop(fullscreenExitOutput = this.settingsManager.getFullscreenExitOutput()) {
|
|
4448
2540
|
this.disposeActiveSelector();
|