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