@sammorrowdrums/mcpi 0.70.9 → 0.85.0-bootstrap.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1621 -0
- package/LICENSE +21 -0
- package/README.md +196 -141
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +4 -0
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/register-bedrock.d.ts.map +1 -1
- package/dist/bun/register-bedrock.js +1 -1
- package/dist/bun/register-bedrock.js.map +1 -1
- package/dist/bun/restore-sandbox-env.d.ts +17 -0
- package/dist/bun/restore-sandbox-env.d.ts.map +1 -0
- package/dist/bun/restore-sandbox-env.js +36 -0
- package/dist/bun/restore-sandbox-env.js.map +1 -0
- package/dist/cli/args.d.ts +9 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +116 -16
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/auth-check.d.ts +19 -0
- package/dist/cli/auth-check.d.ts.map +1 -0
- package/dist/cli/auth-check.js +50 -0
- package/dist/cli/auth-check.js.map +1 -0
- package/dist/cli/auth-command.d.ts +24 -0
- package/dist/cli/auth-command.d.ts.map +1 -0
- package/dist/cli/auth-command.js +103 -0
- package/dist/cli/auth-command.js.map +1 -0
- package/dist/cli/config-selector.d.ts +6 -4
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +4 -4
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/credential-print.d.ts +13 -0
- package/dist/cli/credential-print.d.ts.map +1 -0
- package/dist/cli/credential-print.js +76 -0
- package/dist/cli/credential-print.js.map +1 -0
- package/dist/cli/experimental/auth.d.ts +16 -0
- package/dist/cli/experimental/auth.d.ts.map +1 -0
- package/dist/cli/experimental/auth.js +13 -0
- package/dist/cli/experimental/auth.js.map +1 -0
- package/dist/cli/experimental/cli.d.ts +6 -0
- package/dist/cli/experimental/cli.d.ts.map +1 -0
- package/dist/cli/experimental/cli.js +5 -0
- package/dist/cli/experimental/cli.js.map +1 -0
- package/dist/cli/experimental/command-options.d.ts +17 -0
- package/dist/cli/experimental/command-options.d.ts.map +1 -0
- package/dist/cli/experimental/command-options.js +35 -0
- package/dist/cli/experimental/command-options.js.map +1 -0
- package/dist/cli/experimental/command.d.ts +63 -0
- package/dist/cli/experimental/command.d.ts.map +1 -0
- package/dist/cli/experimental/command.js +130 -0
- package/dist/cli/experimental/command.js.map +1 -0
- package/dist/cli/experimental/commands/client.d.ts +13 -0
- package/dist/cli/experimental/commands/client.d.ts.map +1 -0
- package/dist/cli/experimental/commands/client.js +25 -0
- package/dist/cli/experimental/commands/client.js.map +1 -0
- package/dist/cli/experimental/commands/mcpi.d.ts +15 -0
- package/dist/cli/experimental/commands/mcpi.d.ts.map +1 -0
- package/dist/cli/experimental/commands/mcpi.js +28 -0
- package/dist/cli/experimental/commands/mcpi.js.map +1 -0
- package/dist/cli/experimental/commands/server.d.ts +13 -0
- package/dist/cli/experimental/commands/server.d.ts.map +1 -0
- package/dist/cli/experimental/commands/server.js +25 -0
- package/dist/cli/experimental/commands/server.js.map +1 -0
- package/dist/cli/experimental/transport-address.d.ts +10 -0
- package/dist/cli/experimental/transport-address.d.ts.map +1 -0
- package/dist/cli/experimental/transport-address.js +38 -0
- package/dist/cli/experimental/transport-address.js.map +1 -0
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +13 -26
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/cli/initial-message.d.ts +1 -1
- package/dist/cli/initial-message.d.ts.map +1 -1
- package/dist/cli/initial-message.js.map +1 -1
- package/dist/cli/list-models.d.ts +2 -2
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +4 -4
- package/dist/cli/list-models.js.map +1 -1
- package/dist/cli/project-trust.d.ts +10 -0
- package/dist/cli/project-trust.d.ts.map +1 -0
- package/dist/cli/project-trust.js +48 -0
- package/dist/cli/project-trust.js.map +1 -0
- package/dist/cli/session-picker.d.ts +3 -2
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js +5 -4
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/cli/startup-ui.d.ts +20 -0
- package/dist/cli/startup-ui.d.ts.map +1 -0
- package/dist/cli/startup-ui.js +184 -0
- package/dist/cli/startup-ui.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -3
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/remote-session.d.ts +53 -0
- package/dist/client/remote-session.d.ts.map +1 -0
- package/dist/client/remote-session.js +340 -0
- package/dist/client/remote-session.js.map +1 -0
- package/dist/client/transcript.d.ts +12 -0
- package/dist/client/transcript.d.ts.map +1 -0
- package/dist/client/transcript.js +98 -0
- package/dist/client/transcript.js.map +1 -0
- package/dist/config.d.ts +34 -12
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +312 -61
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +11 -9
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +16 -7
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +13 -12
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +34 -14
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +110 -53
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +658 -488
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-guidance.d.ts.map +1 -1
- package/dist/core/auth-guidance.js.map +1 -1
- package/dist/core/auth-storage.d.ts +35 -104
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +272 -286
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +11 -8
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/cache-stats.d.ts +49 -0
- package/dist/core/cache-stats.d.ts.map +1 -0
- package/dist/core/cache-stats.js +101 -0
- package/dist/core/cache-stats.js.map +1 -0
- package/dist/core/compaction/branch-summarization.d.ts +16 -6
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +13 -10
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +25 -9
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +163 -133
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/index.d.ts +3 -3
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/utils.d.ts +3 -3
- package/dist/core/compaction/utils.d.ts.map +1 -1
- package/dist/core/compaction/utils.js +7 -18
- package/dist/core/compaction/utils.js.map +1 -1
- package/dist/core/defaults.d.ts +1 -1
- package/dist/core/defaults.d.ts.map +1 -1
- package/dist/core/defaults.js.map +1 -1
- package/dist/core/env-overlay.d.ts +21 -0
- package/dist/core/env-overlay.d.ts.map +1 -0
- package/dist/core/env-overlay.js +25 -0
- package/dist/core/env-overlay.js.map +1 -0
- package/dist/core/exec.d.ts +8 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +7 -1
- package/dist/core/exec.js.map +1 -1
- package/dist/core/experimental.d.ts +6 -0
- package/dist/core/experimental.d.ts.map +1 -0
- package/dist/core/experimental.js +8 -0
- package/dist/core/experimental.js.map +1 -0
- package/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
- package/dist/core/export-html/ansi-to-html.js +1 -1
- package/dist/core/export-html/ansi-to-html.js.map +1 -1
- package/dist/core/export-html/index.d.ts +2 -2
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +8 -6
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/export-html/template.css +53 -4
- package/dist/core/export-html/template.js +129 -35
- package/dist/core/export-html/tool-renderer.d.ts +2 -8
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +15 -2
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/export-html/vendor/marked.min.js +75 -3
- package/dist/core/extensions/index.d.ts +8 -8
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +5 -7
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +160 -87
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +32 -13
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +192 -4
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +217 -69
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/wrapper.d.ts +3 -3
- package/dist/core/extensions/wrapper.d.ts.map +1 -1
- package/dist/core/extensions/wrapper.js +22 -3
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +12 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +30 -2
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts +22 -0
- package/dist/core/http-dispatcher.d.ts.map +1 -0
- package/dist/core/http-dispatcher.js +96 -0
- package/dist/core/http-dispatcher.js.map +1 -0
- package/dist/core/index.d.ts +9 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts +79 -10
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +11 -10
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/messages.d.ts +2 -2
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-config.d.ts +571 -0
- package/dist/core/model-config.d.ts.map +1 -0
- package/dist/core/model-config.js +254 -0
- package/dist/core/model-config.js.map +1 -0
- package/dist/core/model-registry.d.ts +23 -110
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +65 -616
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +20 -7
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +139 -43
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +100 -0
- package/dist/core/model-runtime.d.ts.map +1 -0
- package/dist/core/model-runtime.js +602 -0
- package/dist/core/model-runtime.js.map +1 -0
- package/dist/core/models-store.d.ts +22 -0
- package/dist/core/models-store.d.ts.map +1 -0
- package/dist/core/models-store.js +113 -0
- package/dist/core/models-store.js.map +1 -0
- package/dist/core/output-guard.d.ts +1 -0
- package/dist/core/output-guard.d.ts.map +1 -1
- package/dist/core/output-guard.js +52 -22
- package/dist/core/output-guard.js.map +1 -1
- package/dist/core/package-manager.d.ts +21 -4
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +428 -191
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/pi-manifest.d.ts +8 -0
- package/dist/core/pi-manifest.d.ts.map +1 -0
- package/dist/core/pi-manifest.js +25 -0
- package/dist/core/pi-manifest.js.map +1 -0
- package/dist/core/project-trust.d.ts +15 -0
- package/dist/core/project-trust.d.ts.map +1 -0
- package/dist/core/project-trust.js +59 -0
- package/dist/core/project-trust.js.map +1 -0
- package/dist/core/prompt-templates.d.ts +4 -2
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +36 -50
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/provider-attribution.d.ts +4 -0
- package/dist/core/provider-attribution.d.ts.map +1 -0
- package/dist/core/provider-attribution.js +72 -0
- package/dist/core/provider-attribution.js.map +1 -0
- package/dist/core/provider-composer.d.ts +58 -0
- package/dist/core/provider-composer.d.ts.map +1 -0
- package/dist/core/provider-composer.js +396 -0
- package/dist/core/provider-composer.js.map +1 -0
- package/dist/core/radius.d.ts +2 -0
- package/dist/core/radius.d.ts.map +1 -0
- package/dist/core/radius.js +2 -0
- package/dist/core/radius.js.map +1 -0
- package/dist/core/remote-catalog-provider.d.ts +17 -0
- package/dist/core/remote-catalog-provider.d.ts.map +1 -0
- package/dist/core/remote-catalog-provider.js +137 -0
- package/dist/core/remote-catalog-provider.js.map +1 -0
- package/dist/core/resolve-config-value.d.ts +13 -6
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +143 -22
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/resource-loader.d.ts +40 -12
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +236 -87
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-credentials.d.ts +15 -0
- package/dist/core/runtime-credentials.d.ts.map +1 -0
- package/dist/core/runtime-credentials.js +39 -0
- package/dist/core/runtime-credentials.js.map +1 -0
- package/dist/core/sdk.d.ts +26 -25
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +56 -49
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +42 -19
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +454 -223
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +77 -8
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +255 -67
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +2 -2
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +12 -34
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts +2 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +4 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/source-info.d.ts +1 -1
- package/dist/core/source-info.d.ts.map +1 -1
- package/dist/core/source-info.js.map +1 -1
- package/dist/core/system-prompt.d.ts +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +15 -25
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/telemetry.d.ts +7 -1
- package/dist/core/telemetry.d.ts.map +1 -1
- package/dist/core/telemetry.js +7 -1
- package/dist/core/telemetry.js.map +1 -1
- package/dist/core/timings.d.ts +5 -3
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +26 -16
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/bash.d.ts +11 -7
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +220 -188
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +26 -5
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +110 -23
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +11 -5
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +67 -100
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +27 -12
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/find.d.ts +10 -3
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +41 -37
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +7 -3
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +12 -8
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +18 -18
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +7 -3
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +17 -15
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +52 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -0
- package/dist/core/tools/output-accumulator.js +184 -0
- package/dist/core/tools/output-accumulator.js.map +1 -0
- package/dist/core/tools/path-utils.d.ts +2 -0
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +39 -21
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/core/tools/read.d.ts +7 -3
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +101 -51
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +5 -2
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +18 -2
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts +2 -2
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +3 -3
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/tools/truncate.d.ts.map +1 -1
- package/dist/core/tools/truncate.js +12 -2
- package/dist/core/tools/truncate.js.map +1 -1
- package/dist/core/tools/write.d.ts +6 -2
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +35 -45
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/trust-manager.d.ts +36 -0
- package/dist/core/trust-manager.d.ts.map +1 -0
- package/dist/core/trust-manager.js +202 -0
- package/dist/core/trust-manager.js.map +1 -0
- package/dist/core/usage-totals.d.ts +19 -0
- package/dist/core/usage-totals.d.ts.map +1 -0
- package/dist/core/usage-totals.js +52 -0
- package/dist/core/usage-totals.js.map +1 -0
- package/dist/extensions/index.d.ts +3 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +3 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/llama/client.d.ts +61 -0
- package/dist/extensions/llama/client.d.ts.map +1 -0
- package/dist/extensions/llama/client.js +302 -0
- package/dist/extensions/llama/client.js.map +1 -0
- package/dist/extensions/llama/huggingface.d.ts +23 -0
- package/dist/extensions/llama/huggingface.d.ts.map +1 -0
- package/dist/extensions/llama/huggingface.js +141 -0
- package/dist/extensions/llama/huggingface.js.map +1 -0
- package/dist/extensions/llama/index.d.ts +3 -0
- package/dist/extensions/llama/index.d.ts.map +1 -0
- package/dist/extensions/llama/index.js +217 -0
- package/dist/extensions/llama/index.js.map +1 -0
- package/dist/extensions/llama/provider.d.ts +10 -0
- package/dist/extensions/llama/provider.d.ts.map +1 -0
- package/dist/extensions/llama/provider.js +121 -0
- package/dist/extensions/llama/provider.js.map +1 -0
- package/dist/extensions/llama/ui.d.ts +42 -0
- package/dist/extensions/llama/ui.d.ts.map +1 -0
- package/dist/extensions/llama/ui.js +416 -0
- package/dist/extensions/llama/ui.js.map +1 -0
- package/dist/index.d.ts +34 -27
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -5
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +2 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +311 -101
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts +5 -5
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +10 -10
- package/dist/migrations.js.map +1 -1
- package/dist/modes/index.d.ts +6 -5
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/armin.d.ts +1 -1
- package/dist/modes/interactive/components/armin.d.ts.map +1 -1
- package/dist/modes/interactive/components/armin.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +8 -3
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +55 -26
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +2 -2
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +4 -4
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +2 -2
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js +1 -1
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +2 -2
- 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 +2 -2
- 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 +37 -6
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +318 -34
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.d.ts +3 -3
- package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.js +2 -2
- package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +2 -2
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +9 -2
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/custom-entry.d.ts +19 -0
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -0
- package/dist/modes/interactive/components/custom-entry.js +52 -0
- package/dist/modes/interactive/components/custom-entry.js.map +1 -0
- package/dist/modes/interactive/components/custom-message.d.ts +6 -4
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +11 -3
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/daxnuts.d.ts +1 -1
- package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
- package/dist/modes/interactive/components/daxnuts.js.map +1 -1
- package/dist/modes/interactive/components/diff.d.ts.map +1 -1
- package/dist/modes/interactive/components/diff.js.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts +1 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts +5 -4
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +19 -32
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-input.d.ts +1 -1
- package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-input.js +1 -1
- package/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +3 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +7 -2
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -0
- package/dist/modes/interactive/components/first-time-setup.js +103 -0
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts +9 -4
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +52 -31
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +33 -31
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +2 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts +6 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +20 -6
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +12 -6
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +46 -17
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/markdown-transform.d.ts +3 -0
- package/dist/modes/interactive/components/markdown-transform.d.ts.map +1 -0
- package/dist/modes/interactive/components/markdown-transform.js +19 -0
- package/dist/modes/interactive/components/markdown-transform.js.map +1 -0
- package/dist/modes/interactive/components/mermaid.d.ts +11 -0
- package/dist/modes/interactive/components/mermaid.d.ts.map +1 -0
- package/dist/modes/interactive/components/mermaid.js +75 -0
- package/dist/modes/interactive/components/mermaid.js.map +1 -0
- package/dist/modes/interactive/components/model-selector.d.ts +13 -6
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +92 -44
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +7 -4
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +32 -28
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +5 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +52 -14
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.js +1 -1
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +4 -5
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +18 -16
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +23 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +322 -22
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.d.ts +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 +2 -2
- 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 +28 -0
- package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -0
- package/dist/modes/interactive/components/status-indicator.js +60 -0
- package/dist/modes/interactive/components/status-indicator.js.map +1 -0
- package/dist/modes/interactive/components/theme-selector.d.ts +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 +2 -2
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +3 -2
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +2 -2
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +33 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +7 -2
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +179 -26
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/trust-selector.d.ts +23 -0
- package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/trust-selector.js +91 -0
- package/dist/modes/interactive/components/trust-selector.js.map +1 -0
- package/dist/modes/interactive/components/user-message-selector.d.ts +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 +9 -3
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +26 -6
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -1
- package/dist/modes/interactive/components/visual-truncate.js +1 -1
- package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts +12 -0
- package/dist/modes/interactive/external-editor.d.ts.map +1 -0
- package/dist/modes/interactive/external-editor.js +37 -0
- package/dist/modes/interactive/external-editor.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +104 -26
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1565 -692
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
- package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
- package/dist/modes/interactive/model-catalog-refresh.js +34 -0
- package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
- package/dist/modes/interactive/model-search.d.ts +12 -0
- package/dist/modes/interactive/model-search.d.ts.map +1 -0
- package/dist/modes/interactive/model-search.js +15 -0
- package/dist/modes/interactive/model-search.js.map +1 -0
- package/dist/modes/interactive/theme/dark.json +10 -5
- package/dist/modes/interactive/theme/light.json +10 -5
- package/dist/modes/interactive/theme/theme-controller.d.ts +40 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme-controller.js +125 -0
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -0
- package/dist/modes/interactive/theme/theme-schema.json +22 -5
- package/dist/modes/interactive/theme/theme.d.ts +48 -7
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +194 -76
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +31 -0
- package/dist/modes/json-event.d.ts.map +1 -0
- package/dist/modes/json-event.js +15 -0
- package/dist/modes/json-event.js.map +1 -0
- package/dist/modes/print-mode.d.ts +3 -3
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +16 -5
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +33 -10
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +89 -11
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +2 -2
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +69 -9
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +43 -5
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/package-manager-cli.d.ts +6 -2
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +527 -25
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/path-migration.d.ts +13 -0
- package/dist/path-migration.d.ts.map +1 -0
- package/dist/path-migration.js +52 -0
- package/dist/path-migration.js.map +1 -0
- package/dist/rpc-entry.d.ts +3 -0
- package/dist/rpc-entry.d.ts.map +1 -0
- package/dist/rpc-entry.js +11 -0
- package/dist/rpc-entry.js.map +1 -0
- package/dist/server/create-harness.d.ts +26 -0
- package/dist/server/create-harness.d.ts.map +1 -0
- package/dist/server/create-harness.js +106 -0
- package/dist/server/create-harness.js.map +1 -0
- package/dist/utils/abort.d.ts +5 -0
- package/dist/utils/abort.d.ts.map +1 -0
- package/dist/utils/abort.js +48 -0
- package/dist/utils/abort.js.map +1 -0
- package/dist/utils/ansi.d.ts +2 -0
- package/dist/utils/ansi.d.ts.map +1 -0
- package/dist/utils/ansi.js +52 -0
- package/dist/utils/ansi.js.map +1 -0
- package/dist/utils/changelog.d.ts +2 -1
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js +85 -0
- package/dist/utils/changelog.js.map +1 -1
- package/dist/utils/child-process.d.ts +12 -5
- package/dist/utils/child-process.d.ts.map +1 -1
- package/dist/utils/child-process.js +33 -5
- package/dist/utils/child-process.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +4 -4
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +4 -1
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js +14 -8
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/clipboard.d.ts +2 -0
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +129 -46
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/deprecation.d.ts +4 -0
- package/dist/utils/deprecation.d.ts.map +1 -0
- package/dist/utils/deprecation.js +13 -0
- package/dist/utils/deprecation.js.map +1 -0
- package/dist/utils/exif-orientation.d.ts +1 -1
- package/dist/utils/exif-orientation.d.ts.map +1 -1
- package/dist/utils/exif-orientation.js.map +1 -1
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +54 -22
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/html.d.ts +7 -0
- package/dist/utils/html.d.ts.map +1 -0
- package/dist/utils/html.js +40 -0
- package/dist/utils/html.js.map +1 -0
- package/dist/utils/image-convert.d.ts +1 -0
- package/dist/utils/image-convert.d.ts.map +1 -1
- package/dist/utils/image-convert.js +21 -15
- package/dist/utils/image-convert.js.map +1 -1
- package/dist/utils/image-process.d.ts +18 -0
- package/dist/utils/image-process.d.ts.map +1 -0
- package/dist/utils/image-process.js +83 -0
- package/dist/utils/image-process.js.map +1 -0
- package/dist/utils/image-resize-core.d.ts +30 -0
- package/dist/utils/image-resize-core.d.ts.map +1 -0
- package/dist/utils/image-resize-core.js +124 -0
- package/dist/utils/image-resize-core.js.map +1 -0
- package/dist/utils/image-resize-worker.d.ts +2 -0
- package/dist/utils/image-resize-worker.d.ts.map +1 -0
- package/dist/utils/image-resize-worker.js +31 -0
- package/dist/utils/image-resize-worker.js.map +1 -0
- package/dist/utils/image-resize.d.ts +7 -27
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +75 -115
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/json.d.ts +3 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +7 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/management-http.d.ts +23 -0
- package/dist/utils/management-http.d.ts.map +1 -0
- package/dist/utils/management-http.js +50 -0
- package/dist/utils/management-http.js.map +1 -0
- package/dist/utils/mime.d.ts +1 -0
- package/dist/utils/mime.d.ts.map +1 -1
- package/dist/utils/mime.js +100 -16
- package/dist/utils/mime.js.map +1 -1
- package/dist/utils/open-browser.d.ts +9 -0
- package/dist/utils/open-browser.d.ts.map +1 -0
- package/dist/utils/open-browser.js +22 -0
- package/dist/utils/open-browser.js.map +1 -0
- package/dist/utils/paths.d.ts +28 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +97 -2
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/pi-user-agent.d.ts +2 -0
- package/dist/utils/pi-user-agent.d.ts.map +1 -0
- package/dist/utils/pi-user-agent.js +5 -0
- package/dist/utils/pi-user-agent.js.map +1 -0
- package/dist/utils/shell.d.ts +1 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +18 -6
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts +12 -0
- package/dist/utils/syntax-highlight.d.ts.map +1 -0
- package/dist/utils/syntax-highlight.js +118 -0
- package/dist/utils/syntax-highlight.js.map +1 -0
- package/dist/utils/tool-result-images.d.ts +19 -0
- package/dist/utils/tool-result-images.d.ts.map +1 -0
- package/dist/utils/tool-result-images.js +45 -0
- package/dist/utils/tool-result-images.js.map +1 -0
- package/dist/utils/tools-manager.d.ts +10 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +105 -36
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +19 -0
- package/dist/utils/version-check.d.ts.map +1 -0
- package/dist/utils/version-check.js +85 -0
- package/dist/utils/version-check.js.map +1 -0
- package/dist/utils/windows-self-update.d.ts +3 -0
- package/dist/utils/windows-self-update.d.ts.map +1 -0
- package/dist/utils/windows-self-update.js +77 -0
- package/dist/utils/windows-self-update.js.map +1 -0
- package/docs/compaction.md +39 -32
- package/docs/containerization.md +113 -0
- package/docs/custom-provider.md +212 -75
- package/docs/development.md +9 -9
- package/docs/docs.json +156 -0
- package/docs/environment-variables.md +186 -0
- package/docs/extensions.md +652 -140
- package/docs/index.md +81 -0
- package/docs/json.md +26 -15
- package/docs/keybindings.md +60 -21
- package/docs/llama-cpp.md +99 -0
- package/docs/models.md +168 -34
- package/docs/packages.md +46 -36
- package/docs/prompt-templates.md +13 -5
- package/docs/providers.md +147 -26
- package/docs/quickstart.md +167 -0
- package/docs/rpc.md +218 -36
- package/docs/sdk.md +190 -125
- package/docs/security.md +59 -0
- package/docs/{session.md → session-format.md} +48 -22
- package/docs/sessions.md +145 -0
- package/docs/settings.md +130 -25
- package/docs/shell-aliases.md +2 -2
- package/docs/skills.md +13 -14
- package/docs/terminal-setup.md +73 -16
- package/docs/termux.md +10 -10
- package/docs/themes.md +41 -16
- package/docs/tmux.md +6 -4
- package/docs/tui.md +85 -61
- package/docs/usage.md +302 -0
- package/docs/windows.md +2 -2
- package/examples/README.md +1 -1
- package/examples/extensions/README.md +12 -9
- package/examples/extensions/bash-spawn-hook.ts +2 -2
- package/examples/extensions/border-status-editor.ts +145 -0
- package/examples/extensions/built-in-tool-renderer.ts +2 -2
- package/examples/extensions/claude-rules.ts +1 -1
- package/examples/extensions/commands.ts +1 -1
- package/examples/extensions/custom-compaction.ts +6 -16
- package/examples/extensions/custom-footer.ts +1 -1
- package/examples/extensions/custom-header.ts +7 -7
- package/examples/extensions/custom-provider-anthropic/index.ts +15 -8
- package/examples/extensions/custom-provider-anthropic/package-lock.json +4 -4
- package/examples/extensions/custom-provider-anthropic/package.json +3 -3
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +65 -9
- package/examples/extensions/custom-provider-gitlab-duo/package.json +2 -2
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +2 -2
- package/examples/extensions/doom-overlay/README.md +1 -1
- package/examples/extensions/doom-overlay/doom-component.ts +4 -4
- package/examples/extensions/doom-overlay/doom-keys.ts +1 -1
- package/examples/extensions/doom-overlay/index.ts +5 -5
- package/examples/extensions/doom-overlay/wad-finder.ts +7 -3
- package/examples/extensions/dynamic-resources/dynamic.json +1 -1
- package/examples/extensions/entry-renderer.ts +41 -0
- package/examples/extensions/git-merge-and-resolve.ts +115 -0
- package/examples/extensions/github-issue-autocomplete.ts +2 -2
- package/examples/extensions/gondolin/index.ts +531 -0
- package/examples/extensions/gondolin/package-lock.json +185 -0
- package/examples/extensions/gondolin/package.json +19 -0
- package/examples/extensions/handoff.ts +50 -14
- package/examples/extensions/hidden-thinking-label.ts +1 -1
- package/examples/extensions/inline-bash.ts +2 -2
- package/examples/extensions/input-transform-streaming.ts +39 -0
- package/examples/extensions/input-transform.ts +2 -2
- package/examples/extensions/interactive-shell.ts +2 -2
- package/examples/extensions/kimi-deferred-tools.ts +61 -0
- package/examples/extensions/mac-system-theme.ts +2 -2
- package/examples/extensions/message-renderer.ts +3 -3
- package/examples/extensions/minimal-mode.ts +2 -2
- package/examples/extensions/modal-editor.ts +2 -2
- package/examples/extensions/model-status.ts +1 -1
- package/examples/extensions/notify.ts +3 -3
- package/examples/extensions/overlay-qa-tests.ts +242 -140
- package/examples/extensions/overlay-test.ts +9 -6
- package/examples/extensions/pirate.ts +1 -1
- package/examples/extensions/plan-mode/README.md +3 -2
- package/examples/extensions/plan-mode/index.ts +90 -40
- package/examples/extensions/preset.ts +16 -10
- package/examples/extensions/project-trust.ts +64 -0
- package/examples/extensions/prompt-customizer.ts +1 -1
- package/examples/extensions/provider-payload.ts +5 -5
- package/examples/extensions/qna.ts +4 -8
- package/examples/extensions/question.ts +41 -19
- package/examples/extensions/questionnaire.ts +50 -29
- package/examples/extensions/rainbow-editor.ts +1 -1
- package/examples/extensions/reload-runtime.ts +2 -2
- package/examples/extensions/rpc-demo.ts +1 -1
- package/examples/extensions/sandbox/index.ts +9 -9
- package/examples/extensions/sandbox/package-lock.json +7 -7
- package/examples/extensions/sandbox/package.json +3 -3
- package/examples/extensions/shutdown-command.ts +5 -5
- package/examples/extensions/snake.ts +2 -2
- package/examples/extensions/space-invaders.ts +2 -2
- package/examples/extensions/ssh.ts +2 -2
- package/examples/extensions/structured-output.ts +1 -1
- package/examples/extensions/subagent/README.md +18 -13
- package/examples/extensions/subagent/agents.ts +42 -11
- package/examples/extensions/subagent/index.ts +76 -30
- package/examples/extensions/summarize.ts +12 -19
- package/examples/extensions/tic-tac-toe.ts +2 -2
- package/examples/extensions/titlebar-spinner.ts +3 -3
- package/examples/extensions/todo.ts +2 -2
- package/examples/extensions/tool-override.ts +1 -1
- package/examples/extensions/tools.ts +7 -2
- package/examples/extensions/truncated-tool.ts +2 -2
- package/examples/extensions/with-deps/package-lock.json +4 -4
- package/examples/extensions/with-deps/package.json +4 -4
- package/examples/extensions/working-indicator.ts +4 -4
- package/examples/extensions/working-message-test.ts +1 -1
- package/examples/rpc-extension-ui.ts +11 -2
- package/examples/sdk/01-minimal.ts +15 -11
- package/examples/sdk/02-custom-model.ts +18 -18
- package/examples/sdk/03-custom-prompt.ts +25 -17
- package/examples/sdk/04-skills.ts +3 -3
- package/examples/sdk/05-tools.ts +8 -4
- package/examples/sdk/06-extensions.ts +13 -9
- package/examples/sdk/07-context-files.ts +2 -2
- package/examples/sdk/08-prompt-templates.ts +3 -3
- package/examples/sdk/09-api-keys-and-oauth.ts +19 -33
- package/examples/sdk/10-settings.ts +4 -4
- package/examples/sdk/11-sessions.ts +4 -0
- package/examples/sdk/12-full-control.ts +22 -21
- package/examples/sdk/README.md +23 -30
- package/npm-shrinkwrap.json +1818 -0
- package/package.json +117 -99
- package/dist/modes/interactive/assets/clankolas.png +0 -0
- package/dist/modes/interactive/components/auth-selector-status.d.ts +0 -20
- package/dist/modes/interactive/components/auth-selector-status.d.ts.map +0 -1
- package/dist/modes/interactive/components/auth-selector-status.js +0 -23
- package/dist/modes/interactive/components/auth-selector-status.js.map +0 -1
- package/dist/modes/interactive/components/earendil-announcement.d.ts +0 -5
- package/dist/modes/interactive/components/earendil-announcement.d.ts.map +0 -1
- package/dist/modes/interactive/components/earendil-announcement.js +0 -40
- package/dist/modes/interactive/components/earendil-announcement.js.map +0 -1
- package/docs/images/exy.png +0 -0
- package/docs/tree.md +0 -233
- package/examples/extensions/antigravity-image-gen.ts +0 -418
- package/examples/extensions/custom-provider-qwen-cli/index.ts +0 -345
- package/examples/extensions/custom-provider-qwen-cli/package.json +0 -16
|
@@ -6,56 +6,70 @@ import * as crypto from "node:crypto";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
9
|
+
import * as TuiLayouts from "@sammorrowdrums/mcpi-tui";
|
|
10
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@sammorrowdrums/mcpi-tui";
|
|
11
|
+
import chalk from "chalk";
|
|
11
12
|
import { spawn, spawnSync } from "child_process";
|
|
12
|
-
import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl,
|
|
13
|
+
import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, ENV_SHARE_VIEWER_URL, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, getStateDir, VERSION, } from "../../config.js";
|
|
13
14
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
14
15
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
16
|
+
import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
|
|
15
17
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
18
|
+
import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
|
|
16
19
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
17
20
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
18
|
-
import { defaultModelPerProvider, findExactModelReferenceMatch,
|
|
21
|
+
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScopeFromModels, } from "../../core/model-resolver.js";
|
|
22
|
+
import { CredentialSynchronizationError } from "../../core/model-runtime.js";
|
|
19
23
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
20
24
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
21
|
-
import { SessionManager } from "../../core/session-manager.js";
|
|
25
|
+
import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
|
|
22
26
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
27
|
+
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
|
|
28
|
+
import { getUsageCostBreakdown } from "../../core/usage-totals.js";
|
|
29
|
+
import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.js";
|
|
30
|
+
import { copyToClipboard, readClipboardText } from "../../utils/clipboard.js";
|
|
26
31
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
27
32
|
import { parseGitUrl } from "../../utils/git.js";
|
|
33
|
+
import { openBrowser } from "../../utils/open-browser.js";
|
|
34
|
+
import { getCwdRelativePath } from "../../utils/paths.js";
|
|
28
35
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
29
36
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
37
|
+
import { checkForNewPiVersion } from "../../utils/version-check.js";
|
|
30
38
|
import { ArminComponent } from "./components/armin.js";
|
|
31
39
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
32
40
|
import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
33
41
|
import { BorderedLoader } from "./components/bordered-loader.js";
|
|
34
42
|
import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
|
|
35
43
|
import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
|
|
36
|
-
import { CountdownTimer } from "./components/countdown-timer.js";
|
|
37
44
|
import { CustomEditor } from "./components/custom-editor.js";
|
|
45
|
+
import { CustomEntryComponent } from "./components/custom-entry.js";
|
|
38
46
|
import { CustomMessageComponent } from "./components/custom-message.js";
|
|
39
47
|
import { DaxnutsComponent } from "./components/daxnuts.js";
|
|
40
48
|
import { DynamicBorder } from "./components/dynamic-border.js";
|
|
41
|
-
import { EarendilAnnouncementComponent } from "./components/earendil-announcement.js";
|
|
42
49
|
import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
43
50
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
44
51
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
45
|
-
import { FooterComponent } from "./components/footer.js";
|
|
46
|
-
import { keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
52
|
+
import { FooterComponent, formatTokens } from "./components/footer.js";
|
|
53
|
+
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
47
54
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
55
|
+
import { createMermaidMarkdownTransformer } from "./components/mermaid.js";
|
|
48
56
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
49
|
-
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
57
|
+
import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
|
|
50
58
|
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
51
59
|
import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
52
60
|
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
53
61
|
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
62
|
+
import { BranchSummaryStatusIndicator, CompactionStatusIndicator, IdleStatus, RetryStatusIndicator, WorkingStatusIndicator, } from "./components/status-indicator.js";
|
|
54
63
|
import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
55
64
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
65
|
+
import { TrustSelectorComponent } from "./components/trust-selector.js";
|
|
56
66
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
57
67
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
58
|
-
import {
|
|
68
|
+
import { editInExternalEditor } from "./external-editor.js";
|
|
69
|
+
import { refreshModelCatalogs } from "./model-catalog-refresh.js";
|
|
70
|
+
import { getModelSearchText } from "./model-search.js";
|
|
71
|
+
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
72
|
+
import { InteractiveThemeController } from "./theme/theme-controller.js";
|
|
59
73
|
function isExpandable(obj) {
|
|
60
74
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
61
75
|
}
|
|
@@ -71,75 +85,172 @@ class ExpandableText extends Text {
|
|
|
71
85
|
this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
|
-
|
|
88
|
+
function isCustomSessionEntry(item) {
|
|
89
|
+
return "type" in item && item.type === "custom";
|
|
90
|
+
}
|
|
91
|
+
const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
|
|
92
|
+
function isDeadTerminalError(error) {
|
|
93
|
+
if (!error || typeof error !== "object" || !("code" in error)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const code = error.code;
|
|
97
|
+
return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
|
|
98
|
+
}
|
|
99
|
+
const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage. Disable this warning in /settings.";
|
|
75
100
|
function isAnthropicSubscriptionAuthKey(apiKey) {
|
|
76
101
|
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
|
77
102
|
}
|
|
78
103
|
function isUnknownModel(model) {
|
|
79
104
|
return !!model && model.provider === "unknown" && model.id === "unknown" && model.api === "unknown";
|
|
80
105
|
}
|
|
106
|
+
function quoteIfNeeded(value) {
|
|
107
|
+
if (value.length > 0 && !/[^a-zA-Z0-9_\-./~:@]/.test(value)) {
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
111
|
+
}
|
|
112
|
+
export function formatResumeCommand(sessionManager) {
|
|
113
|
+
if (!process.stdout.isTTY)
|
|
114
|
+
return undefined;
|
|
115
|
+
if (!sessionManager.isPersisted())
|
|
116
|
+
return undefined;
|
|
117
|
+
const sessionFile = sessionManager.getSessionFile();
|
|
118
|
+
if (!sessionFile || !fs.existsSync(sessionFile))
|
|
119
|
+
return undefined;
|
|
120
|
+
const args = [APP_NAME];
|
|
121
|
+
if (!sessionManager.usesDefaultSessionDir()) {
|
|
122
|
+
args.push("--session-dir", quoteIfNeeded(sessionManager.getSessionDir()));
|
|
123
|
+
}
|
|
124
|
+
args.push("--session", sessionManager.getSessionId());
|
|
125
|
+
return args.join(" ");
|
|
126
|
+
}
|
|
81
127
|
function hasDefaultModelProvider(providerId) {
|
|
82
128
|
return providerId in defaultModelPerProvider;
|
|
83
129
|
}
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
};
|
|
107
|
-
const BUILT_IN_API_KEY_LOGIN_PROVIDERS = new Set(Object.keys(API_KEY_LOGIN_PROVIDERS));
|
|
108
|
-
const BUILT_IN_MODEL_PROVIDERS = new Set(getProviders());
|
|
109
|
-
export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProviderIds = BUILT_IN_MODEL_PROVIDERS) {
|
|
110
|
-
if (BUILT_IN_API_KEY_LOGIN_PROVIDERS.has(providerId)) {
|
|
111
|
-
return true;
|
|
130
|
+
const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
|
|
131
|
+
function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
|
|
132
|
+
const filtered = fuzzyFilter(items, prefix, getSearchText);
|
|
133
|
+
if (filtered.length === 0)
|
|
134
|
+
return null;
|
|
135
|
+
return filtered.map(toAutocompleteItem);
|
|
136
|
+
}
|
|
137
|
+
function getLoginProviderCompletionOptions(providerOptions) {
|
|
138
|
+
const byId = new Map();
|
|
139
|
+
for (const provider of providerOptions) {
|
|
140
|
+
const existing = byId.get(provider.id);
|
|
141
|
+
if (existing) {
|
|
142
|
+
if (!existing.authTypes.includes(provider.authType)) {
|
|
143
|
+
existing.authTypes.push(provider.authType);
|
|
144
|
+
existing.authTypes.sort((a, b) => AUTH_TYPE_ORDER[a] - AUTH_TYPE_ORDER[b]);
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
byId.set(provider.id, {
|
|
149
|
+
id: provider.id,
|
|
150
|
+
name: provider.name,
|
|
151
|
+
authTypes: [provider.authType],
|
|
152
|
+
});
|
|
112
153
|
}
|
|
113
|
-
|
|
114
|
-
|
|
154
|
+
return Array.from(byId.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
155
|
+
}
|
|
156
|
+
function getLoginProviderSearchText(provider) {
|
|
157
|
+
const authTypes = provider.authTypes
|
|
158
|
+
.map((authType) => `${authType} ${formatAuthSelectorProviderType(authType)}`)
|
|
159
|
+
.join(" ");
|
|
160
|
+
return `${provider.id} ${provider.name} ${authTypes}`;
|
|
161
|
+
}
|
|
162
|
+
function formatLoginProviderCompletionDescription(provider) {
|
|
163
|
+
const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
|
|
164
|
+
return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
|
|
165
|
+
}
|
|
166
|
+
/** Composition root for selecting the interactive terminal renderer. */
|
|
167
|
+
export function createInteractiveTui(options) {
|
|
168
|
+
const terminal = options.terminal ?? new ProcessTerminal();
|
|
169
|
+
if (options.tuiMode === "fullscreen") {
|
|
170
|
+
const styleSearchMatch = (text) => theme.bg("searchMatchBg", theme.fg("searchMatchText", text));
|
|
171
|
+
return new TuiAltScreen(terminal, options.showHardwareCursor, options.logDirectory, {
|
|
172
|
+
searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
|
|
173
|
+
searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
|
|
174
|
+
openUrl: openBrowser,
|
|
175
|
+
onRightClickPaste: options.onRightClickPaste,
|
|
176
|
+
copySelection: async (text) => {
|
|
177
|
+
try {
|
|
178
|
+
await copyToClipboard(text);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
});
|
|
115
186
|
}
|
|
116
|
-
return
|
|
187
|
+
return new TuiMainScreen(terminal, options.showHardwareCursor, options.logDirectory);
|
|
117
188
|
}
|
|
118
|
-
|
|
119
|
-
|
|
189
|
+
/** Stable reference for components while InteractiveMode replaces the active renderer. */
|
|
190
|
+
export function createInteractiveTuiReference(getTui) {
|
|
191
|
+
return new Proxy({}, {
|
|
192
|
+
get: (_target, property) => {
|
|
193
|
+
const tui = getTui();
|
|
194
|
+
const value = Reflect.get(tui, property, tui);
|
|
195
|
+
if (typeof value !== "function")
|
|
196
|
+
return value;
|
|
197
|
+
let methodTui = tui;
|
|
198
|
+
let method = value;
|
|
199
|
+
return (...args) => {
|
|
200
|
+
const currentTui = getTui();
|
|
201
|
+
if (currentTui !== methodTui) {
|
|
202
|
+
const currentMethod = Reflect.get(currentTui, property, currentTui);
|
|
203
|
+
if (typeof currentMethod !== "function") {
|
|
204
|
+
throw new TypeError(`TUI property ${String(property)} is not callable`);
|
|
205
|
+
}
|
|
206
|
+
methodTui = currentTui;
|
|
207
|
+
method = currentMethod;
|
|
208
|
+
}
|
|
209
|
+
return Reflect.apply(method, methodTui, args);
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
set: (_target, property, value) => {
|
|
213
|
+
const tui = getTui();
|
|
214
|
+
return Reflect.set(tui, property, value, tui);
|
|
215
|
+
},
|
|
216
|
+
has: (_target, property) => Reflect.has(getTui(), property),
|
|
217
|
+
getPrototypeOf: () => Reflect.getPrototypeOf(getTui()),
|
|
218
|
+
});
|
|
120
219
|
}
|
|
121
220
|
export class InteractiveMode {
|
|
122
|
-
options;
|
|
123
221
|
runtimeHost;
|
|
222
|
+
renderer;
|
|
124
223
|
ui;
|
|
224
|
+
mainScreenRenderState;
|
|
225
|
+
loadedResourcesContainer;
|
|
125
226
|
chatContainer;
|
|
227
|
+
documentContainer;
|
|
228
|
+
transcriptScrollView;
|
|
229
|
+
fullscreenLayoutRoot;
|
|
126
230
|
pendingMessagesContainer;
|
|
127
231
|
statusContainer;
|
|
128
232
|
defaultEditor;
|
|
129
233
|
editor;
|
|
234
|
+
editorComponentFactory;
|
|
130
235
|
autocompleteProvider;
|
|
131
236
|
autocompleteProviderWrappers = [];
|
|
132
237
|
fdPath;
|
|
133
238
|
editorContainer;
|
|
239
|
+
activeSelectorToken;
|
|
240
|
+
activeSelectorDispose;
|
|
134
241
|
footer;
|
|
242
|
+
footerContainer;
|
|
135
243
|
footerDataProvider;
|
|
136
244
|
// Stored so the same manager can be injected into custom editors, selectors, and extension UI.
|
|
137
245
|
keybindings;
|
|
138
246
|
version;
|
|
139
247
|
isInitialized = false;
|
|
140
248
|
onInputCallback;
|
|
141
|
-
|
|
249
|
+
pendingUserInputs = [];
|
|
250
|
+
activeStatusIndicator = undefined;
|
|
251
|
+
idleStatus = new IdleStatus();
|
|
142
252
|
workingMessage = undefined;
|
|
253
|
+
workingVisible = true;
|
|
143
254
|
workingIndicatorOptions = undefined;
|
|
144
255
|
defaultWorkingMessage = "Working...";
|
|
145
256
|
defaultHiddenThinkingLabel = "Thinking...";
|
|
@@ -152,6 +263,7 @@ export class InteractiveMode {
|
|
|
152
263
|
// Status line tracking (for mutating immediately-sequential status updates)
|
|
153
264
|
lastStatusSpacer = undefined;
|
|
154
265
|
lastStatusText = undefined;
|
|
266
|
+
managedToolStatusStarted = false;
|
|
155
267
|
// Streaming message tracking
|
|
156
268
|
streamingComponent = undefined;
|
|
157
269
|
streamingMessage = undefined;
|
|
@@ -161,6 +273,11 @@ export class InteractiveMode {
|
|
|
161
273
|
toolOutputExpanded = false;
|
|
162
274
|
// Thinking block visibility state
|
|
163
275
|
hideThinkingBlock = false;
|
|
276
|
+
outputPad = 1;
|
|
277
|
+
mermaidMarkdownTransformer = createMermaidMarkdownTransformer({
|
|
278
|
+
getMode: () => this.settingsManager.getMermaidRenderingMode(),
|
|
279
|
+
theme,
|
|
280
|
+
});
|
|
164
281
|
// Skill commands: command name -> skill file path
|
|
165
282
|
skillCommands = new Map();
|
|
166
283
|
// Agent subscription unsubscribe function
|
|
@@ -173,11 +290,8 @@ export class InteractiveMode {
|
|
|
173
290
|
// Track pending bash components (shown in pending area, moved to chat on submit)
|
|
174
291
|
pendingBashComponents = [];
|
|
175
292
|
// Auto-compaction state
|
|
176
|
-
autoCompactionLoader = undefined;
|
|
177
293
|
autoCompactionEscapeHandler;
|
|
178
294
|
// Auto-retry state
|
|
179
|
-
retryLoader = undefined;
|
|
180
|
-
retryCountdown = undefined;
|
|
181
295
|
retryEscapeHandler;
|
|
182
296
|
// Messages queued while compaction is running
|
|
183
297
|
compactionQueuedMessages = [];
|
|
@@ -187,7 +301,7 @@ export class InteractiveMode {
|
|
|
187
301
|
extensionSelector = undefined;
|
|
188
302
|
extensionInput = undefined;
|
|
189
303
|
extensionEditor = undefined;
|
|
190
|
-
|
|
304
|
+
extensionTerminalInputSubscriptions = new Set();
|
|
191
305
|
// Extension widgets (components rendered above/below the editor)
|
|
192
306
|
extensionWidgetsAbove = new Map();
|
|
193
307
|
extensionWidgetsBelow = new Map();
|
|
@@ -201,6 +315,12 @@ export class InteractiveMode {
|
|
|
201
315
|
builtInHeader = undefined;
|
|
202
316
|
// Custom header from extension (undefined = use built-in header)
|
|
203
317
|
customHeader = undefined;
|
|
318
|
+
options;
|
|
319
|
+
onRightClickPaste = () => {
|
|
320
|
+
void this.handleRightClickPaste();
|
|
321
|
+
};
|
|
322
|
+
autoTrustOnReloadCwd;
|
|
323
|
+
themeController;
|
|
204
324
|
// Convenience accessors
|
|
205
325
|
get session() {
|
|
206
326
|
return this.runtimeHost.session;
|
|
@@ -215,19 +335,33 @@ export class InteractiveMode {
|
|
|
215
335
|
return this.session.settingsManager;
|
|
216
336
|
}
|
|
217
337
|
constructor(runtimeHost, options = {}) {
|
|
218
|
-
this.options = options;
|
|
219
338
|
this.runtimeHost = runtimeHost;
|
|
339
|
+
const tuiMode = options.tuiMode ?? this.settingsManager.getTuiMode();
|
|
340
|
+
this.options = { ...options, tuiMode };
|
|
341
|
+
this.autoTrustOnReloadCwd = options.autoTrustOnReloadCwd;
|
|
220
342
|
this.runtimeHost.setBeforeSessionInvalidate(() => {
|
|
221
343
|
this.resetExtensionUI();
|
|
222
344
|
});
|
|
223
345
|
this.runtimeHost.setRebindSession(async () => {
|
|
224
|
-
await this.rebindCurrentSession();
|
|
346
|
+
await this.rebindCurrentSession({ renderBeforeBind: true });
|
|
347
|
+
await this.themeController.applyFromSettings();
|
|
225
348
|
});
|
|
226
349
|
this.version = VERSION;
|
|
227
|
-
this.
|
|
350
|
+
this.renderer = createInteractiveTui({
|
|
351
|
+
tuiMode,
|
|
352
|
+
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
353
|
+
logDirectory: getStateDir(),
|
|
354
|
+
onRightClickPaste: this.onRightClickPaste,
|
|
355
|
+
});
|
|
356
|
+
this.ui = createInteractiveTuiReference(() => this.renderer);
|
|
228
357
|
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
|
229
358
|
this.headerContainer = new Container();
|
|
359
|
+
this.loadedResourcesContainer = new Container();
|
|
230
360
|
this.chatContainer = new Container();
|
|
361
|
+
this.documentContainer = new Container();
|
|
362
|
+
this.documentContainer.addChild(this.headerContainer);
|
|
363
|
+
this.documentContainer.addChild(this.loadedResourcesContainer);
|
|
364
|
+
this.documentContainer.addChild(this.chatContainer);
|
|
231
365
|
this.pendingMessagesContainer = new Container();
|
|
232
366
|
this.statusContainer = new Container();
|
|
233
367
|
this.widgetContainerAbove = new Container();
|
|
@@ -246,11 +380,19 @@ export class InteractiveMode {
|
|
|
246
380
|
this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
|
|
247
381
|
this.footer = new FooterComponent(this.session, this.footerDataProvider);
|
|
248
382
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
383
|
+
this.footerContainer = new Container();
|
|
384
|
+
this.footerContainer.addChild(this.footer);
|
|
249
385
|
// Load hide thinking block setting
|
|
250
386
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
387
|
+
this.outputPad = this.settingsManager.getOutputPad();
|
|
251
388
|
// Register themes from resource loader and initialize
|
|
252
389
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
253
|
-
|
|
390
|
+
this.themeController = new InteractiveThemeController(this.ui, {
|
|
391
|
+
getSettingsManager: () => this.settingsManager,
|
|
392
|
+
showError: (message) => this.showError(message),
|
|
393
|
+
onChanged: () => this.updateEditorBorderColor(),
|
|
394
|
+
initialThemeSetting: options.initialThemeSetting,
|
|
395
|
+
});
|
|
254
396
|
}
|
|
255
397
|
getAutocompleteSourceTag(sourceInfo) {
|
|
256
398
|
if (!sourceInfo) {
|
|
@@ -296,33 +438,41 @@ export class InteractiveMode {
|
|
|
296
438
|
const slashCommands = BUILTIN_SLASH_COMMANDS.map((command) => ({
|
|
297
439
|
name: command.name,
|
|
298
440
|
description: command.description,
|
|
441
|
+
...(command.argumentHint && { argumentHint: command.argumentHint }),
|
|
299
442
|
}));
|
|
300
443
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
301
444
|
if (modelCommand) {
|
|
302
445
|
modelCommand.getArgumentCompletions = (prefix) => {
|
|
303
|
-
// Get available models (scoped or from registry)
|
|
304
446
|
const models = this.session.scopedModels.length > 0
|
|
305
447
|
? this.session.scopedModels.map((s) => s.model)
|
|
306
|
-
: this.session.
|
|
448
|
+
: this.session.modelRuntime.getAvailableSnapshot();
|
|
307
449
|
if (models.length === 0)
|
|
308
450
|
return null;
|
|
309
451
|
// Create items with provider/id format
|
|
310
452
|
const items = models.map((m) => ({
|
|
311
453
|
id: m.id,
|
|
312
454
|
provider: m.provider,
|
|
455
|
+
name: m.name,
|
|
313
456
|
label: `${m.provider}/${m.id}`,
|
|
314
457
|
}));
|
|
315
|
-
|
|
316
|
-
const filtered = fuzzyFilter(items, prefix, (item) => `${item.id} ${item.provider}`);
|
|
317
|
-
if (filtered.length === 0)
|
|
318
|
-
return null;
|
|
319
|
-
return filtered.map((item) => ({
|
|
458
|
+
return createFuzzyAutocompleteItems(items, prefix, getModelSearchText, (item) => ({
|
|
320
459
|
value: item.label,
|
|
321
460
|
label: item.id,
|
|
322
461
|
description: item.provider,
|
|
323
462
|
}));
|
|
324
463
|
};
|
|
325
464
|
}
|
|
465
|
+
const loginCommand = slashCommands.find((command) => command.name === "login");
|
|
466
|
+
if (loginCommand) {
|
|
467
|
+
loginCommand.getArgumentCompletions = (prefix) => {
|
|
468
|
+
const providers = getLoginProviderCompletionOptions(this.getLoginProviderOptions());
|
|
469
|
+
return createFuzzyAutocompleteItems(providers, prefix, getLoginProviderSearchText, (provider) => ({
|
|
470
|
+
value: provider.id,
|
|
471
|
+
label: provider.id,
|
|
472
|
+
description: formatLoginProviderCompletionDescription(provider),
|
|
473
|
+
}));
|
|
474
|
+
};
|
|
475
|
+
}
|
|
326
476
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
327
477
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
328
478
|
name: cmd.name,
|
|
@@ -356,8 +506,13 @@ export class InteractiveMode {
|
|
|
356
506
|
}
|
|
357
507
|
setupAutocompleteProvider() {
|
|
358
508
|
let provider = this.createBaseAutocompleteProvider();
|
|
509
|
+
const triggerCharacters = [];
|
|
359
510
|
for (const wrapProvider of this.autocompleteProviderWrappers) {
|
|
360
511
|
provider = wrapProvider(provider);
|
|
512
|
+
triggerCharacters.push(...(provider.triggerCharacters ?? []));
|
|
513
|
+
}
|
|
514
|
+
if (triggerCharacters.length > 0) {
|
|
515
|
+
provider.triggerCharacters = [...new Set(triggerCharacters)];
|
|
361
516
|
}
|
|
362
517
|
this.autocompleteProvider = provider;
|
|
363
518
|
this.defaultEditor.setAutocompleteProvider(provider);
|
|
@@ -391,18 +546,131 @@ export class InteractiveMode {
|
|
|
391
546
|
}
|
|
392
547
|
this.chatContainer.addChild(new DynamicBorder());
|
|
393
548
|
}
|
|
549
|
+
mountInteractiveTui(tui, components) {
|
|
550
|
+
for (const component of components)
|
|
551
|
+
tui.addChild(component);
|
|
552
|
+
if (TuiLayouts.isViewportTUI(tui)) {
|
|
553
|
+
if (!this.fullscreenLayoutRoot)
|
|
554
|
+
throw new Error("Fullscreen layout is not initialized");
|
|
555
|
+
tui.setLayoutRoot(this.fullscreenLayoutRoot);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
stopInteractiveTui(fullscreenExitOutput) {
|
|
559
|
+
if (this.renderer.mode === "fullscreen" && fullscreenExitOutput === "transcript") {
|
|
560
|
+
while (this.renderer.hasOverlayEntries)
|
|
561
|
+
this.renderer.hideOverlay();
|
|
562
|
+
this.switchTuiMode("regular", false, false);
|
|
563
|
+
this.renderer.renderNow();
|
|
564
|
+
}
|
|
565
|
+
this.ui.stop({ preserveScreen: this.renderer.mode === "fullscreen" });
|
|
566
|
+
}
|
|
567
|
+
switchTuiMode(mode, restoreProgress = true, startRenderer = true) {
|
|
568
|
+
const previousUi = this.renderer;
|
|
569
|
+
if (mode === previousUi.mode)
|
|
570
|
+
return true;
|
|
571
|
+
if (previousUi.hasOverlayEntries)
|
|
572
|
+
return false;
|
|
573
|
+
const components = [...previousUi.children];
|
|
574
|
+
const focus = previousUi.getFocusedComponent();
|
|
575
|
+
const terminal = previousUi.terminal;
|
|
576
|
+
const showHardwareCursor = previousUi.getShowHardwareCursor();
|
|
577
|
+
const clearOnShrink = previousUi.getClearOnShrink();
|
|
578
|
+
const onDebug = previousUi.onDebug;
|
|
579
|
+
if (previousUi instanceof TuiMainScreen) {
|
|
580
|
+
this.mainScreenRenderState = previousUi.captureRenderState();
|
|
581
|
+
}
|
|
582
|
+
previousUi.stop({ preserveScreen: true });
|
|
583
|
+
previousUi.setFocus(null);
|
|
584
|
+
previousUi.clear();
|
|
585
|
+
if (TuiLayouts.isViewportTUI(previousUi))
|
|
586
|
+
previousUi.setLayoutRoot(undefined);
|
|
587
|
+
const nextUi = createInteractiveTui({
|
|
588
|
+
tuiMode: mode,
|
|
589
|
+
showHardwareCursor,
|
|
590
|
+
logDirectory: getStateDir(),
|
|
591
|
+
terminal,
|
|
592
|
+
onRightClickPaste: this.onRightClickPaste,
|
|
593
|
+
});
|
|
594
|
+
nextUi.setClearOnShrink(clearOnShrink);
|
|
595
|
+
nextUi.onDebug = onDebug;
|
|
596
|
+
if (nextUi instanceof TuiMainScreen && this.mainScreenRenderState) {
|
|
597
|
+
nextUi.restoreRenderState(this.mainScreenRenderState);
|
|
598
|
+
}
|
|
599
|
+
this.renderer = nextUi;
|
|
600
|
+
this.options.tuiMode = mode;
|
|
601
|
+
this.mountInteractiveTui(nextUi, components);
|
|
602
|
+
nextUi.invalidate();
|
|
603
|
+
nextUi.setFocus(focus);
|
|
604
|
+
if (!startRenderer)
|
|
605
|
+
return true;
|
|
606
|
+
nextUi.start();
|
|
607
|
+
this.themeController.rebindTui();
|
|
608
|
+
this.rebindExtensionTerminalInputListeners();
|
|
609
|
+
if (restoreProgress &&
|
|
610
|
+
this.settingsManager.getShowTerminalProgress() &&
|
|
611
|
+
(this.session.isStreaming || this.session.isCompacting)) {
|
|
612
|
+
terminal.setProgress(true);
|
|
613
|
+
}
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
394
616
|
async init() {
|
|
395
617
|
if (this.isInitialized)
|
|
396
618
|
return;
|
|
397
619
|
this.registerSignalHandlers();
|
|
398
620
|
// Load changelog (only show new entries, skip for resumed sessions)
|
|
399
621
|
this.changelogMarkdown = this.getChangelogForDisplay();
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
622
|
+
if (this.session.scopedModels.length > 0 && (this.options.verbose || !this.settingsManager.getQuietStartup())) {
|
|
623
|
+
const modelList = this.session.scopedModels
|
|
624
|
+
.map((sm) => {
|
|
625
|
+
const thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : "";
|
|
626
|
+
return `${sm.model.id}${thinkingStr}`;
|
|
627
|
+
})
|
|
628
|
+
.join(", ");
|
|
629
|
+
const cycleKeys = this.keybindings.getKeys("app.model.cycleForward");
|
|
630
|
+
const cycleHint = cycleKeys.length > 0
|
|
631
|
+
? theme.fg("muted", ` (${formatKeyText(cycleKeys.join("/"), { capitalize: true })} to cycle)`)
|
|
632
|
+
: "";
|
|
633
|
+
console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
|
|
634
|
+
}
|
|
635
|
+
// Keep one component tree and remount it when changing renderers.
|
|
636
|
+
this.renderWidgets(); // Initialize with default spacer
|
|
637
|
+
this.transcriptScrollView = new TuiLayouts.ScrollView(this.documentContainer, {
|
|
638
|
+
follow: "end",
|
|
639
|
+
primary: true,
|
|
640
|
+
overscroll: "chain",
|
|
641
|
+
scrollbar: this.settingsManager.getFullscreenScrollbar(),
|
|
642
|
+
scrollbarStyle: (text) => theme.bg("scrollbarThumb", text),
|
|
643
|
+
});
|
|
644
|
+
const dock = new TuiLayouts.VStack([
|
|
645
|
+
{ component: this.pendingMessagesContainer, shrink: 1, minSize: 0 },
|
|
646
|
+
{ component: this.statusContainer, shrink: 1, minSize: 0 },
|
|
647
|
+
{ component: this.widgetContainerAbove, shrink: 1, minSize: 0 },
|
|
648
|
+
{ component: this.editorContainer, shrink: 1, minSize: 3 },
|
|
649
|
+
{ component: this.widgetContainerBelow, shrink: 1, minSize: 0 },
|
|
650
|
+
{ component: this.footerContainer, shrink: 1, minSize: 1 },
|
|
651
|
+
]);
|
|
652
|
+
this.fullscreenLayoutRoot = new TuiLayouts.VStack([
|
|
653
|
+
{ component: this.transcriptScrollView, basis: 0, grow: 1, shrink: 1, minSize: 1 },
|
|
654
|
+
{ component: dock, basis: "auto", grow: 0, shrink: 1, minSize: 1 },
|
|
655
|
+
]);
|
|
656
|
+
this.mountInteractiveTui(this.renderer, [
|
|
657
|
+
this.documentContainer,
|
|
658
|
+
this.pendingMessagesContainer,
|
|
659
|
+
this.statusContainer,
|
|
660
|
+
this.widgetContainerAbove,
|
|
661
|
+
this.editorContainer,
|
|
662
|
+
this.widgetContainerBelow,
|
|
663
|
+
this.footerContainer,
|
|
664
|
+
]);
|
|
665
|
+
// Accept text while startup completes, but only enable interrupt, exit, and submission feedback.
|
|
666
|
+
this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
|
|
667
|
+
this.defaultEditor.onCtrlD = () => this.handleCtrlD();
|
|
668
|
+
this.defaultEditor.onSubmit = (text) => this.handleStartupSubmit(text);
|
|
669
|
+
this.ui.setFocus(this.editor);
|
|
670
|
+
// Start the UI before initializing extensions so session_start handlers can use interactive dialogs
|
|
671
|
+
this.ui.start();
|
|
672
|
+
this.isInitialized = true;
|
|
673
|
+
await this.themeController.applyFromSettings();
|
|
406
674
|
// Add header with keybindings from config (unless silenced)
|
|
407
675
|
if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
|
|
408
676
|
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
|
|
@@ -426,7 +694,7 @@ export class InteractiveMode {
|
|
|
426
694
|
rawKeyHint("!!", "to run bash (no context)"),
|
|
427
695
|
hint("app.message.followUp", "to queue follow-up"),
|
|
428
696
|
hint("app.message.dequeue", "to edit all queued messages"),
|
|
429
|
-
hint("app.clipboard.pasteImage", "to paste image"),
|
|
697
|
+
hint("app.clipboard.pasteImage", "to paste image (with text fallback)"),
|
|
430
698
|
rawKeyHint("drop files", "to attach"),
|
|
431
699
|
].join("\n");
|
|
432
700
|
const compactInstructions = [
|
|
@@ -437,7 +705,7 @@ export class InteractiveMode {
|
|
|
437
705
|
hint("app.tools.expand", "more"),
|
|
438
706
|
].join(theme.fg("muted", " · "));
|
|
439
707
|
const compactOnboarding = theme.fg("dim", `Press ${keyText("app.tools.expand")} to show full startup help and loaded resources.`);
|
|
440
|
-
const onboarding = theme.fg("dim",
|
|
708
|
+
const onboarding = theme.fg("dim", `${APP_NAME} can explain its own features and look up its docs. Ask it how to use or extend ${APP_NAME}.`);
|
|
441
709
|
this.builtInHeader = new ExpandableText(() => `${logo}\n${compactInstructions}\n${compactOnboarding}\n\n${onboarding}`, () => `${logo}\n${expandedInstructions}\n\n${onboarding}`, this.getStartupExpansionState(), 1, 0);
|
|
442
710
|
// Setup UI layout
|
|
443
711
|
this.headerContainer.addChild(new Spacer(1));
|
|
@@ -449,20 +717,19 @@ export class InteractiveMode {
|
|
|
449
717
|
this.builtInHeader = new Text("", 0, 0);
|
|
450
718
|
this.headerContainer.addChild(this.builtInHeader);
|
|
451
719
|
}
|
|
452
|
-
this.ui.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
this.
|
|
720
|
+
this.ui.requestRender();
|
|
721
|
+
// Ensure fd and rg are available after mounting the TUI (downloads if missing, adds to PATH via getBinDir)
|
|
722
|
+
// so slow downloads do not make startup appear frozen.
|
|
723
|
+
// Both are needed: fd for autocomplete, rg for grep tool and bash commands.
|
|
724
|
+
const [fdPath] = await Promise.all([
|
|
725
|
+
ensureTool("fd", (status) => this.showManagedToolStatus(status)),
|
|
726
|
+
ensureTool("rg", (status) => this.showManagedToolStatus(status)),
|
|
727
|
+
]);
|
|
728
|
+
this.fdPath = fdPath;
|
|
729
|
+
// Enable the remaining input handlers only after managed-tool setup completes.
|
|
461
730
|
this.setupKeyHandlers();
|
|
462
731
|
this.setupEditorSubmitHandler();
|
|
463
|
-
|
|
464
|
-
this.ui.start();
|
|
465
|
-
this.isInitialized = true;
|
|
732
|
+
this.ui.requestRender();
|
|
466
733
|
// Initialize extensions first so resources are shown before messages
|
|
467
734
|
await this.rebindCurrentSession();
|
|
468
735
|
// Render initial messages AFTER showing loaded resources
|
|
@@ -499,17 +766,33 @@ export class InteractiveMode {
|
|
|
499
766
|
*/
|
|
500
767
|
async run() {
|
|
501
768
|
await this.init();
|
|
769
|
+
if (!process.env.MCPI_OFFLINE) {
|
|
770
|
+
const controller = new AbortController();
|
|
771
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
772
|
+
void refreshModelCatalogs(this.session.modelRuntime, controller.signal)
|
|
773
|
+
.then(() => this.updateAvailableProviderCount())
|
|
774
|
+
.catch(() => { })
|
|
775
|
+
.finally(() => clearTimeout(timeout));
|
|
776
|
+
}
|
|
502
777
|
// Start version check asynchronously
|
|
503
|
-
this.
|
|
504
|
-
if (
|
|
505
|
-
this.showNewVersionNotification(
|
|
778
|
+
checkForNewPiVersion(this.version).then((newRelease) => {
|
|
779
|
+
if (newRelease) {
|
|
780
|
+
this.showNewVersionNotification(newRelease);
|
|
506
781
|
}
|
|
507
782
|
});
|
|
508
783
|
// Start package update check asynchronously
|
|
509
|
-
this.checkForPackageUpdates()
|
|
784
|
+
this.checkForPackageUpdates()
|
|
785
|
+
.then((updates) => {
|
|
510
786
|
if (updates.length > 0) {
|
|
511
787
|
this.showPackageUpdateNotification(updates);
|
|
512
788
|
}
|
|
789
|
+
})
|
|
790
|
+
.finally(() => {
|
|
791
|
+
// On Windows, npm can overwrite the shared console title while checking
|
|
792
|
+
// extension package versions. Restore mcpi's title after the startup check.
|
|
793
|
+
if (process.platform === "win32" && this.isInitialized) {
|
|
794
|
+
this.updateTerminalTitle();
|
|
795
|
+
}
|
|
513
796
|
});
|
|
514
797
|
// Check tmux keyboard setup asynchronously
|
|
515
798
|
this.checkTmuxKeyboardSetup().then((warning) => {
|
|
@@ -522,7 +805,7 @@ export class InteractiveMode {
|
|
|
522
805
|
if (migratedProviders && migratedProviders.length > 0) {
|
|
523
806
|
this.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(", ")}`);
|
|
524
807
|
}
|
|
525
|
-
const modelsJsonError = this.session.
|
|
808
|
+
const modelsJsonError = this.session.modelRuntime.getError();
|
|
526
809
|
if (modelsJsonError) {
|
|
527
810
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
528
811
|
}
|
|
@@ -563,31 +846,8 @@ export class InteractiveMode {
|
|
|
563
846
|
}
|
|
564
847
|
}
|
|
565
848
|
}
|
|
566
|
-
/**
|
|
567
|
-
* Check npm registry for a newer version.
|
|
568
|
-
*/
|
|
569
|
-
async checkForNewVersion() {
|
|
570
|
-
if (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE)
|
|
571
|
-
return undefined;
|
|
572
|
-
try {
|
|
573
|
-
const response = await fetch("https://registry.npmjs.org/mcpi/latest", {
|
|
574
|
-
signal: AbortSignal.timeout(10000),
|
|
575
|
-
});
|
|
576
|
-
if (!response.ok)
|
|
577
|
-
return undefined;
|
|
578
|
-
const data = (await response.json());
|
|
579
|
-
const latestVersion = data.version;
|
|
580
|
-
if (latestVersion && latestVersion !== this.version) {
|
|
581
|
-
return latestVersion;
|
|
582
|
-
}
|
|
583
|
-
return undefined;
|
|
584
|
-
}
|
|
585
|
-
catch {
|
|
586
|
-
return undefined;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
849
|
async checkForPackageUpdates() {
|
|
590
|
-
if (process.env.
|
|
850
|
+
if (process.env.MCPI_OFFLINE) {
|
|
591
851
|
return [];
|
|
592
852
|
}
|
|
593
853
|
try {
|
|
@@ -640,7 +900,7 @@ export class InteractiveMode {
|
|
|
640
900
|
return "tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart tmux.";
|
|
641
901
|
}
|
|
642
902
|
if (extendedKeysFormat === "xterm") {
|
|
643
|
-
return
|
|
903
|
+
return `${APP_NAME} works best with csi-u, but tmux extended-keys-format is xterm. Add \`set -g extended-keys-format csi-u\` to ~/.tmux.conf and restart tmux.`;
|
|
644
904
|
}
|
|
645
905
|
return undefined;
|
|
646
906
|
}
|
|
@@ -657,32 +917,17 @@ export class InteractiveMode {
|
|
|
657
917
|
const changelogPath = getChangelogPath();
|
|
658
918
|
const entries = parseChangelog(changelogPath);
|
|
659
919
|
if (!lastVersion) {
|
|
660
|
-
// Fresh install - record the version,
|
|
920
|
+
// Fresh install - record the version, don't show changelog
|
|
661
921
|
this.settingsManager.setLastChangelogVersion(VERSION);
|
|
662
|
-
this.reportInstallTelemetry(VERSION);
|
|
663
922
|
return undefined;
|
|
664
923
|
}
|
|
665
924
|
const newEntries = getNewEntries(entries, lastVersion);
|
|
666
925
|
if (newEntries.length > 0) {
|
|
667
926
|
this.settingsManager.setLastChangelogVersion(VERSION);
|
|
668
|
-
|
|
669
|
-
return newEntries.map((e) => e.content).join("\n\n");
|
|
927
|
+
return newEntries.map((e) => normalizeChangelogLinks(e.content, e)).join("\n\n");
|
|
670
928
|
}
|
|
671
929
|
return undefined;
|
|
672
930
|
}
|
|
673
|
-
reportInstallTelemetry(version) {
|
|
674
|
-
if (process.env.PI_OFFLINE) {
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
if (!isInstallTelemetryEnabled(this.settingsManager)) {
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
void fetch(`https://pi.dev/install?version=${encodeURIComponent(version)}`, {
|
|
681
|
-
signal: AbortSignal.timeout(5000),
|
|
682
|
-
})
|
|
683
|
-
.then(() => undefined)
|
|
684
|
-
.catch(() => undefined);
|
|
685
|
-
}
|
|
686
931
|
getMarkdownThemeWithSettings() {
|
|
687
932
|
return {
|
|
688
933
|
...getMarkdownTheme(),
|
|
@@ -709,13 +954,9 @@ export class InteractiveMode {
|
|
|
709
954
|
formatContextPath(p) {
|
|
710
955
|
const cwd = path.resolve(this.sessionManager.getCwd());
|
|
711
956
|
const absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(cwd, p);
|
|
712
|
-
const relativePath =
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
!relativePath.startsWith(`..${path.sep}`) &&
|
|
716
|
-
!path.isAbsolute(relativePath));
|
|
717
|
-
if (isInsideCwd) {
|
|
718
|
-
return relativePath || ".";
|
|
957
|
+
const relativePath = getCwdRelativePath(absolutePath, cwd);
|
|
958
|
+
if (relativePath !== undefined) {
|
|
959
|
+
return relativePath;
|
|
719
960
|
}
|
|
720
961
|
return this.formatDisplayPath(absolutePath);
|
|
721
962
|
}
|
|
@@ -726,8 +967,15 @@ export class InteractiveMode {
|
|
|
726
967
|
* Get a short path relative to the package root for display.
|
|
727
968
|
*/
|
|
728
969
|
getShortPath(fullPath, sourceInfo) {
|
|
970
|
+
const normalizedFullPath = fullPath.replace(/\\/g, "/");
|
|
729
971
|
const baseDir = sourceInfo?.baseDir;
|
|
730
972
|
if (baseDir && this.isPackageSource(sourceInfo)) {
|
|
973
|
+
const normalizedBaseDir = baseDir.replace(/\\/g, "/");
|
|
974
|
+
const npmRootMatch = normalizedBaseDir.match(/^(.*\/node_modules)\/(@?[^/]+(?:\/[^/]+)?)$/);
|
|
975
|
+
// If fullPath is under the same node_modules root as baseDir, preserve that relative topology.
|
|
976
|
+
if (npmRootMatch?.[1] && normalizedFullPath.startsWith(`${npmRootMatch[1]}/`)) {
|
|
977
|
+
return path.posix.relative(normalizedBaseDir, normalizedFullPath);
|
|
978
|
+
}
|
|
731
979
|
const relativePath = path.relative(path.resolve(baseDir), path.resolve(fullPath));
|
|
732
980
|
if (relativePath &&
|
|
733
981
|
relativePath !== "." &&
|
|
@@ -738,11 +986,11 @@ export class InteractiveMode {
|
|
|
738
986
|
}
|
|
739
987
|
}
|
|
740
988
|
const source = sourceInfo?.source ?? "";
|
|
741
|
-
const npmMatch =
|
|
989
|
+
const npmMatch = normalizedFullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/);
|
|
742
990
|
if (npmMatch && source.startsWith("npm:")) {
|
|
743
991
|
return npmMatch[2];
|
|
744
992
|
}
|
|
745
|
-
const gitMatch =
|
|
993
|
+
const gitMatch = normalizedFullPath.match(/git\/[^/]+\/[^/]+\/(.*)/);
|
|
746
994
|
if (gitMatch && source.startsWith("git:")) {
|
|
747
995
|
return gitMatch[1];
|
|
748
996
|
}
|
|
@@ -974,6 +1222,8 @@ export class InteractiveMode {
|
|
|
974
1222
|
return lines.join("\n");
|
|
975
1223
|
}
|
|
976
1224
|
showLoadedResources(options) {
|
|
1225
|
+
// Resource rendering is idempotent; chat clears no longer clear this separate container.
|
|
1226
|
+
this.loadedResourcesContainer.clear();
|
|
977
1227
|
const showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
|
|
978
1228
|
const showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;
|
|
979
1229
|
if (!showListing && !showDiagnostics) {
|
|
@@ -989,14 +1239,17 @@ export class InteractiveMode {
|
|
|
989
1239
|
};
|
|
990
1240
|
const addLoadedSection = (name, collapsedBody, expandedBody = collapsedBody, color = "mdHeading") => {
|
|
991
1241
|
const section = new ExpandableText(() => `${sectionHeader(name, color)}\n${collapsedBody}`, () => `${sectionHeader(name, color)}\n${expandedBody}`, this.getStartupExpansionState(), 0, 0);
|
|
992
|
-
this.
|
|
993
|
-
this.
|
|
1242
|
+
this.loadedResourcesContainer.addChild(section);
|
|
1243
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
994
1244
|
};
|
|
995
1245
|
const skillsResult = this.session.resourceLoader.getSkills();
|
|
996
1246
|
const promptsResult = this.session.resourceLoader.getPrompts();
|
|
997
1247
|
const themesResult = this.session.resourceLoader.getThemes();
|
|
998
1248
|
const extensions = options?.extensions ??
|
|
999
|
-
this.session.resourceLoader
|
|
1249
|
+
this.session.resourceLoader
|
|
1250
|
+
.getExtensions()
|
|
1251
|
+
.extensions.filter((extension) => !extension.hidden)
|
|
1252
|
+
.map((extension) => ({
|
|
1000
1253
|
path: extension.path,
|
|
1001
1254
|
sourceInfo: extension.sourceInfo,
|
|
1002
1255
|
}));
|
|
@@ -1022,9 +1275,14 @@ export class InteractiveMode {
|
|
|
1022
1275
|
}
|
|
1023
1276
|
}
|
|
1024
1277
|
if (showListing) {
|
|
1025
|
-
const
|
|
1278
|
+
const systemPromptSource = this.session.resourceLoader.getSystemPromptSource();
|
|
1279
|
+
const contextFiles = [
|
|
1280
|
+
...(systemPromptSource ? [systemPromptSource] : []),
|
|
1281
|
+
...this.session.resourceLoader.getAppendSystemPromptSources(),
|
|
1282
|
+
...this.session.resourceLoader.getAgentsFiles().agentsFiles,
|
|
1283
|
+
];
|
|
1026
1284
|
if (contextFiles.length > 0) {
|
|
1027
|
-
this.
|
|
1285
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1028
1286
|
const contextList = contextFiles
|
|
1029
1287
|
.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`))
|
|
1030
1288
|
.join("\n");
|
|
@@ -1087,14 +1345,14 @@ export class InteractiveMode {
|
|
|
1087
1345
|
const skillDiagnostics = skillsResult.diagnostics;
|
|
1088
1346
|
if (skillDiagnostics.length > 0) {
|
|
1089
1347
|
const warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);
|
|
1090
|
-
this.
|
|
1091
|
-
this.
|
|
1348
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
|
|
1349
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1092
1350
|
}
|
|
1093
1351
|
const promptDiagnostics = promptsResult.diagnostics;
|
|
1094
1352
|
if (promptDiagnostics.length > 0) {
|
|
1095
1353
|
const warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);
|
|
1096
|
-
this.
|
|
1097
|
-
this.
|
|
1354
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
|
|
1355
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1098
1356
|
}
|
|
1099
1357
|
const extensionDiagnostics = [];
|
|
1100
1358
|
const extensionErrors = this.session.resourceLoader.getExtensions().errors;
|
|
@@ -1110,14 +1368,14 @@ export class InteractiveMode {
|
|
|
1110
1368
|
extensionDiagnostics.push(...shortcutDiagnostics);
|
|
1111
1369
|
if (extensionDiagnostics.length > 0) {
|
|
1112
1370
|
const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
|
|
1113
|
-
this.
|
|
1114
|
-
this.
|
|
1371
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
|
|
1372
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1115
1373
|
}
|
|
1116
1374
|
const themeDiagnostics = themesResult.diagnostics;
|
|
1117
1375
|
if (themeDiagnostics.length > 0) {
|
|
1118
1376
|
const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
|
|
1119
|
-
this.
|
|
1120
|
-
this.
|
|
1377
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
|
1378
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1121
1379
|
}
|
|
1122
1380
|
}
|
|
1123
1381
|
}
|
|
@@ -1128,21 +1386,16 @@ export class InteractiveMode {
|
|
|
1128
1386
|
const uiContext = this.createExtensionUIContext();
|
|
1129
1387
|
await this.session.bindExtensions({
|
|
1130
1388
|
uiContext,
|
|
1389
|
+
mode: "tui",
|
|
1390
|
+
abortHandler: () => {
|
|
1391
|
+
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1392
|
+
},
|
|
1131
1393
|
commandContextActions: {
|
|
1132
|
-
waitForIdle: () => this.session.
|
|
1394
|
+
waitForIdle: () => this.session.waitForIdle(),
|
|
1133
1395
|
newSession: async (options) => {
|
|
1134
|
-
|
|
1135
|
-
this.loadingAnimation.stop();
|
|
1136
|
-
this.loadingAnimation = undefined;
|
|
1137
|
-
}
|
|
1138
|
-
this.statusContainer.clear();
|
|
1396
|
+
this.clearStatusIndicator();
|
|
1139
1397
|
try {
|
|
1140
|
-
|
|
1141
|
-
if (!result.cancelled) {
|
|
1142
|
-
this.renderCurrentSessionState();
|
|
1143
|
-
this.ui.requestRender();
|
|
1144
|
-
}
|
|
1145
|
-
return result;
|
|
1398
|
+
return await this.runtimeHost.newSession(options);
|
|
1146
1399
|
}
|
|
1147
1400
|
catch (error) {
|
|
1148
1401
|
return this.handleFatalRuntimeError("Failed to create session", error);
|
|
@@ -1152,7 +1405,6 @@ export class InteractiveMode {
|
|
|
1152
1405
|
try {
|
|
1153
1406
|
const result = await this.runtimeHost.fork(entryId, options);
|
|
1154
1407
|
if (!result.cancelled) {
|
|
1155
|
-
this.renderCurrentSessionState();
|
|
1156
1408
|
this.editor.setText(result.selectedText ?? "");
|
|
1157
1409
|
this.showStatus("Forked to new session");
|
|
1158
1410
|
}
|
|
@@ -1190,7 +1442,7 @@ export class InteractiveMode {
|
|
|
1190
1442
|
},
|
|
1191
1443
|
shutdownHandler: () => {
|
|
1192
1444
|
this.shutdownRequested = true;
|
|
1193
|
-
if (
|
|
1445
|
+
if (this.session.isIdle) {
|
|
1194
1446
|
void this.shutdown();
|
|
1195
1447
|
}
|
|
1196
1448
|
},
|
|
@@ -1205,13 +1457,23 @@ export class InteractiveMode {
|
|
|
1205
1457
|
this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
|
|
1206
1458
|
this.showStartupNoticesIfNeeded();
|
|
1207
1459
|
}
|
|
1460
|
+
applyFullscreenScrollbarSetting() {
|
|
1461
|
+
this.transcriptScrollView?.setScrollbar(this.settingsManager.getFullscreenScrollbar());
|
|
1462
|
+
}
|
|
1208
1463
|
applyRuntimeSettings() {
|
|
1464
|
+
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
1465
|
+
this.applyFullscreenScrollbarSetting();
|
|
1209
1466
|
this.footer.setSession(this.session);
|
|
1210
1467
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
1211
1468
|
this.footerDataProvider.setCwd(this.sessionManager.getCwd());
|
|
1212
1469
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
1470
|
+
this.outputPad = this.settingsManager.getOutputPad();
|
|
1213
1471
|
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
1214
|
-
this.
|
|
1472
|
+
const clearOnShrink = this.settingsManager.getClearOnShrink();
|
|
1473
|
+
this.ui.setClearOnShrink(clearOnShrink);
|
|
1474
|
+
if (!clearOnShrink && !this.activeStatusIndicator) {
|
|
1475
|
+
this.statusContainer.clear();
|
|
1476
|
+
}
|
|
1215
1477
|
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
|
1216
1478
|
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
1217
1479
|
this.defaultEditor.setPaddingX(editorPaddingX);
|
|
@@ -1221,12 +1483,22 @@ export class InteractiveMode {
|
|
|
1221
1483
|
this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
|
|
1222
1484
|
}
|
|
1223
1485
|
}
|
|
1224
|
-
async rebindCurrentSession() {
|
|
1486
|
+
async rebindCurrentSession(options = {}) {
|
|
1487
|
+
const session = this.session;
|
|
1225
1488
|
this.unsubscribe?.();
|
|
1226
1489
|
this.unsubscribe = undefined;
|
|
1227
1490
|
this.applyRuntimeSettings();
|
|
1491
|
+
if (options.renderBeforeBind) {
|
|
1492
|
+
this.renderCurrentSessionState();
|
|
1493
|
+
this.subscribeToAgent();
|
|
1494
|
+
}
|
|
1228
1495
|
await this.bindCurrentSessionExtensions();
|
|
1229
|
-
this.
|
|
1496
|
+
if (this.session !== session) {
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
if (!options.renderBeforeBind) {
|
|
1500
|
+
this.subscribeToAgent();
|
|
1501
|
+
}
|
|
1230
1502
|
await this.updateAvailableProviderCount();
|
|
1231
1503
|
this.updateEditorBorderColor();
|
|
1232
1504
|
this.updateTerminalTitle();
|
|
@@ -1235,10 +1507,11 @@ export class InteractiveMode {
|
|
|
1235
1507
|
const message = error instanceof Error ? error.message : String(error);
|
|
1236
1508
|
this.showError(`${prefix}: ${message}`);
|
|
1237
1509
|
stopThemeWatcher();
|
|
1238
|
-
this.stop();
|
|
1510
|
+
this.stop("transcript");
|
|
1239
1511
|
process.exit(1);
|
|
1240
1512
|
}
|
|
1241
1513
|
renderCurrentSessionState() {
|
|
1514
|
+
this.loadedResourcesContainer.clear();
|
|
1242
1515
|
this.chatContainer.clear();
|
|
1243
1516
|
this.pendingMessagesContainer.clear();
|
|
1244
1517
|
this.compactionQueuedMessages = [];
|
|
@@ -1253,6 +1526,9 @@ export class InteractiveMode {
|
|
|
1253
1526
|
getRegisteredToolDefinition(toolName) {
|
|
1254
1527
|
return this.session.getToolDefinition(toolName);
|
|
1255
1528
|
}
|
|
1529
|
+
getMarkdownTransformers() {
|
|
1530
|
+
return [this.mermaidMarkdownTransformer, ...this.session.extensionRunner.getMarkdownTransformers()];
|
|
1531
|
+
}
|
|
1256
1532
|
/**
|
|
1257
1533
|
* Set up keyboard shortcuts registered by extensions.
|
|
1258
1534
|
*/
|
|
@@ -1263,14 +1539,20 @@ export class InteractiveMode {
|
|
|
1263
1539
|
// Create a context for shortcut handlers
|
|
1264
1540
|
const createContext = () => ({
|
|
1265
1541
|
ui: this.createExtensionUIContext(),
|
|
1542
|
+
mode: "tui",
|
|
1266
1543
|
hasUI: true,
|
|
1267
1544
|
cwd: this.sessionManager.getCwd(),
|
|
1268
1545
|
sessionManager: this.sessionManager,
|
|
1269
|
-
modelRegistry:
|
|
1546
|
+
modelRegistry: extensionRunner.getModelRegistry(),
|
|
1270
1547
|
model: this.session.model,
|
|
1271
|
-
|
|
1548
|
+
scopedModels: this.session.scopedModels,
|
|
1549
|
+
thinkingLevel: this.session.thinkingLevel,
|
|
1550
|
+
isIdle: () => this.session.isIdle,
|
|
1551
|
+
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
1272
1552
|
signal: this.session.agent.signal,
|
|
1273
|
-
abort: () =>
|
|
1553
|
+
abort: () => {
|
|
1554
|
+
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1555
|
+
},
|
|
1274
1556
|
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
|
1275
1557
|
shutdown: () => {
|
|
1276
1558
|
this.shutdownRequested = true;
|
|
@@ -1312,9 +1594,41 @@ export class InteractiveMode {
|
|
|
1312
1594
|
this.footerDataProvider.setExtensionStatus(key, text);
|
|
1313
1595
|
this.ui.requestRender();
|
|
1314
1596
|
}
|
|
1597
|
+
showStatusIndicator(indicator) {
|
|
1598
|
+
this.activeStatusIndicator?.dispose();
|
|
1599
|
+
this.activeStatusIndicator = indicator;
|
|
1600
|
+
this.statusContainer.clear();
|
|
1601
|
+
this.statusContainer.addChild(indicator);
|
|
1602
|
+
}
|
|
1603
|
+
clearStatusIndicator(kind) {
|
|
1604
|
+
if (kind && this.activeStatusIndicator?.kind !== kind) {
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
const hadActiveStatusIndicator = this.activeStatusIndicator !== undefined;
|
|
1608
|
+
this.activeStatusIndicator?.dispose();
|
|
1609
|
+
this.activeStatusIndicator = undefined;
|
|
1610
|
+
this.statusContainer.clear();
|
|
1611
|
+
if (hadActiveStatusIndicator && this.options.tuiMode === "regular" && this.ui.getClearOnShrink()) {
|
|
1612
|
+
this.statusContainer.addChild(this.idleStatus);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
setWorkingVisible(visible) {
|
|
1616
|
+
this.workingVisible = visible;
|
|
1617
|
+
if (!visible) {
|
|
1618
|
+
this.clearStatusIndicator("working");
|
|
1619
|
+
this.ui.requestRender();
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
if (this.session.isStreaming && this.activeStatusIndicator?.kind !== "working") {
|
|
1623
|
+
this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.workingIndicatorOptions));
|
|
1624
|
+
}
|
|
1625
|
+
this.ui.requestRender();
|
|
1626
|
+
}
|
|
1315
1627
|
setWorkingIndicator(options) {
|
|
1316
1628
|
this.workingIndicatorOptions = options;
|
|
1317
|
-
this.
|
|
1629
|
+
if (this.activeStatusIndicator?.kind === "working") {
|
|
1630
|
+
this.activeStatusIndicator.setIndicator(options);
|
|
1631
|
+
}
|
|
1318
1632
|
this.ui.requestRender();
|
|
1319
1633
|
}
|
|
1320
1634
|
setHiddenThinkingLabel(label) {
|
|
@@ -1400,9 +1714,10 @@ export class InteractiveMode {
|
|
|
1400
1714
|
this.defaultEditor.onExtensionShortcut = undefined;
|
|
1401
1715
|
this.updateTerminalTitle();
|
|
1402
1716
|
this.workingMessage = undefined;
|
|
1717
|
+
this.workingVisible = true;
|
|
1403
1718
|
this.setWorkingIndicator();
|
|
1404
|
-
if (this.
|
|
1405
|
-
this.
|
|
1719
|
+
if (this.activeStatusIndicator?.kind === "working") {
|
|
1720
|
+
this.activeStatusIndicator.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
|
|
1406
1721
|
}
|
|
1407
1722
|
this.setHiddenThinkingLabel();
|
|
1408
1723
|
}
|
|
@@ -1441,22 +1756,16 @@ export class InteractiveMode {
|
|
|
1441
1756
|
if (this.customFooter?.dispose) {
|
|
1442
1757
|
this.customFooter.dispose();
|
|
1443
1758
|
}
|
|
1444
|
-
|
|
1445
|
-
if (this.customFooter) {
|
|
1446
|
-
this.ui.removeChild(this.customFooter);
|
|
1447
|
-
}
|
|
1448
|
-
else {
|
|
1449
|
-
this.ui.removeChild(this.footer);
|
|
1450
|
-
}
|
|
1759
|
+
this.footerContainer.clear();
|
|
1451
1760
|
if (factory) {
|
|
1452
1761
|
// Create and add custom footer, passing the data provider
|
|
1453
1762
|
this.customFooter = factory(this.ui, theme, this.footerDataProvider);
|
|
1454
|
-
this.
|
|
1763
|
+
this.footerContainer.addChild(this.customFooter);
|
|
1455
1764
|
}
|
|
1456
1765
|
else {
|
|
1457
1766
|
// Restore built-in footer
|
|
1458
1767
|
this.customFooter = undefined;
|
|
1459
|
-
this.
|
|
1768
|
+
this.footerContainer.addChild(this.footer);
|
|
1460
1769
|
}
|
|
1461
1770
|
this.ui.requestRender();
|
|
1462
1771
|
}
|
|
@@ -1502,22 +1811,41 @@ export class InteractiveMode {
|
|
|
1502
1811
|
this.ui.requestRender();
|
|
1503
1812
|
}
|
|
1504
1813
|
addExtensionTerminalInputListener(handler) {
|
|
1505
|
-
const
|
|
1506
|
-
this.
|
|
1814
|
+
const subscription = { handler, unsubscribe: this.ui.addInputListener(handler) };
|
|
1815
|
+
this.extensionTerminalInputSubscriptions.add(subscription);
|
|
1507
1816
|
return () => {
|
|
1508
|
-
unsubscribe();
|
|
1509
|
-
this.
|
|
1817
|
+
subscription.unsubscribe();
|
|
1818
|
+
this.extensionTerminalInputSubscriptions.delete(subscription);
|
|
1510
1819
|
};
|
|
1511
1820
|
}
|
|
1512
|
-
|
|
1513
|
-
for (const
|
|
1514
|
-
unsubscribe();
|
|
1821
|
+
rebindExtensionTerminalInputListeners() {
|
|
1822
|
+
for (const subscription of this.extensionTerminalInputSubscriptions) {
|
|
1823
|
+
subscription.unsubscribe();
|
|
1824
|
+
subscription.unsubscribe = this.ui.addInputListener(subscription.handler);
|
|
1515
1825
|
}
|
|
1516
|
-
|
|
1826
|
+
}
|
|
1827
|
+
clearExtensionTerminalInputListeners() {
|
|
1828
|
+
for (const subscription of this.extensionTerminalInputSubscriptions)
|
|
1829
|
+
subscription.unsubscribe();
|
|
1830
|
+
this.extensionTerminalInputSubscriptions.clear();
|
|
1517
1831
|
}
|
|
1518
1832
|
/**
|
|
1519
1833
|
* Create the ExtensionUIContext for extensions.
|
|
1520
1834
|
*/
|
|
1835
|
+
createProjectTrustContext(cwd) {
|
|
1836
|
+
const ui = this.createExtensionUIContext();
|
|
1837
|
+
return {
|
|
1838
|
+
cwd,
|
|
1839
|
+
mode: "tui",
|
|
1840
|
+
hasUI: true,
|
|
1841
|
+
ui: {
|
|
1842
|
+
select: ui.select,
|
|
1843
|
+
confirm: ui.confirm,
|
|
1844
|
+
input: ui.input,
|
|
1845
|
+
notify: ui.notify,
|
|
1846
|
+
},
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1521
1849
|
createExtensionUIContext() {
|
|
1522
1850
|
return {
|
|
1523
1851
|
select: (title, options, opts) => this.showExtensionSelector(title, options, opts),
|
|
@@ -1528,15 +1856,11 @@ export class InteractiveMode {
|
|
|
1528
1856
|
setStatus: (key, text) => this.setExtensionStatus(key, text),
|
|
1529
1857
|
setWorkingMessage: (message) => {
|
|
1530
1858
|
this.workingMessage = message;
|
|
1531
|
-
if (this.
|
|
1532
|
-
|
|
1533
|
-
this.loadingAnimation.setMessage(message);
|
|
1534
|
-
}
|
|
1535
|
-
else {
|
|
1536
|
-
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
|
|
1537
|
-
}
|
|
1859
|
+
if (this.activeStatusIndicator?.kind === "working") {
|
|
1860
|
+
this.activeStatusIndicator.setMessage(message ?? this.defaultWorkingMessage);
|
|
1538
1861
|
}
|
|
1539
1862
|
},
|
|
1863
|
+
setWorkingVisible: (visible) => this.setWorkingVisible(visible),
|
|
1540
1864
|
setWorkingIndicator: (options) => this.setWorkingIndicator(options),
|
|
1541
1865
|
setHiddenThinkingLabel: (label) => this.setHiddenThinkingLabel(label),
|
|
1542
1866
|
setWidget: (key, content, options) => this.setExtensionWidget(key, content, options),
|
|
@@ -1553,6 +1877,7 @@ export class InteractiveMode {
|
|
|
1553
1877
|
this.setupAutocompleteProvider();
|
|
1554
1878
|
},
|
|
1555
1879
|
setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
|
|
1880
|
+
getEditorComponent: () => this.editorComponentFactory,
|
|
1556
1881
|
get theme() {
|
|
1557
1882
|
return theme;
|
|
1558
1883
|
},
|
|
@@ -1560,16 +1885,13 @@ export class InteractiveMode {
|
|
|
1560
1885
|
getTheme: (name) => getThemeByName(name),
|
|
1561
1886
|
setTheme: (themeOrName) => {
|
|
1562
1887
|
if (themeOrName instanceof Theme) {
|
|
1563
|
-
setThemeInstance(themeOrName);
|
|
1564
|
-
this.ui.requestRender();
|
|
1565
|
-
return { success: true };
|
|
1888
|
+
return this.themeController.setThemeInstance(themeOrName);
|
|
1566
1889
|
}
|
|
1567
|
-
const result =
|
|
1890
|
+
const result = this.themeController.setThemeName(themeOrName);
|
|
1568
1891
|
if (result.success) {
|
|
1569
1892
|
if (this.settingsManager.getTheme() !== themeOrName) {
|
|
1570
1893
|
this.settingsManager.setTheme(themeOrName);
|
|
1571
1894
|
}
|
|
1572
|
-
this.ui.requestRender();
|
|
1573
1895
|
}
|
|
1574
1896
|
return result;
|
|
1575
1897
|
},
|
|
@@ -1599,7 +1921,8 @@ export class InteractiveMode {
|
|
|
1599
1921
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
1600
1922
|
this.hideExtensionSelector();
|
|
1601
1923
|
resolve(undefined);
|
|
1602
|
-
}, { tui: this.ui, timeout: opts?.timeout });
|
|
1924
|
+
}, { tui: this.ui, timeout: opts?.timeout, onToggleToolsExpanded: () => this.toggleToolOutputExpansion() });
|
|
1925
|
+
this.disposeActiveSelector();
|
|
1603
1926
|
this.editorContainer.clear();
|
|
1604
1927
|
this.editorContainer.addChild(this.extensionSelector);
|
|
1605
1928
|
this.ui.setFocus(this.extensionSelector);
|
|
@@ -1651,6 +1974,7 @@ export class InteractiveMode {
|
|
|
1651
1974
|
this.hideExtensionInput();
|
|
1652
1975
|
resolve(undefined);
|
|
1653
1976
|
}, { tui: this.ui, timeout: opts?.timeout });
|
|
1977
|
+
this.disposeActiveSelector();
|
|
1654
1978
|
this.editorContainer.clear();
|
|
1655
1979
|
this.editorContainer.addChild(this.extensionInput);
|
|
1656
1980
|
this.ui.setFocus(this.extensionInput);
|
|
@@ -1679,7 +2003,8 @@ export class InteractiveMode {
|
|
|
1679
2003
|
}, () => {
|
|
1680
2004
|
this.hideExtensionEditor();
|
|
1681
2005
|
resolve(undefined);
|
|
1682
|
-
});
|
|
2006
|
+
}, undefined, this.settingsManager.getExternalEditorCommand());
|
|
2007
|
+
this.disposeActiveSelector();
|
|
1683
2008
|
this.editorContainer.clear();
|
|
1684
2009
|
this.editorContainer.addChild(this.extensionEditor);
|
|
1685
2010
|
this.ui.setFocus(this.extensionEditor);
|
|
@@ -1701,8 +2026,10 @@ export class InteractiveMode {
|
|
|
1701
2026
|
* Pass undefined to restore the default editor.
|
|
1702
2027
|
*/
|
|
1703
2028
|
setCustomEditorComponent(factory) {
|
|
2029
|
+
this.editorComponentFactory = factory;
|
|
1704
2030
|
// Save text from current editor before switching
|
|
1705
2031
|
const currentText = this.editor.getText();
|
|
2032
|
+
this.disposeActiveSelector();
|
|
1706
2033
|
this.editorContainer.clear();
|
|
1707
2034
|
if (factory) {
|
|
1708
2035
|
// Create the custom editor with tui, theme, and keybindings
|
|
@@ -1719,6 +2046,9 @@ export class InteractiveMode {
|
|
|
1719
2046
|
if (newEditor.setPaddingX !== undefined) {
|
|
1720
2047
|
newEditor.setPaddingX(this.defaultEditor.getPaddingX());
|
|
1721
2048
|
}
|
|
2049
|
+
if (newEditor.setAutocompleteMaxVisible !== undefined) {
|
|
2050
|
+
newEditor.setAutocompleteMaxVisible(this.defaultEditor.getAutocompleteMaxVisible());
|
|
2051
|
+
}
|
|
1722
2052
|
// Set autocomplete if supported
|
|
1723
2053
|
if (newEditor.setAutocompleteProvider && this.autocompleteProvider) {
|
|
1724
2054
|
newEditor.setAutocompleteProvider(this.autocompleteProvider);
|
|
@@ -1823,6 +2153,7 @@ export class InteractiveMode {
|
|
|
1823
2153
|
options?.onHandle?.(handle);
|
|
1824
2154
|
}
|
|
1825
2155
|
else {
|
|
2156
|
+
this.disposeActiveSelector();
|
|
1826
2157
|
this.editorContainer.clear();
|
|
1827
2158
|
this.editorContainer.addChild(component);
|
|
1828
2159
|
this.ui.setFocus(component);
|
|
@@ -1865,7 +2196,7 @@ export class InteractiveMode {
|
|
|
1865
2196
|
// Set up handlers on defaultEditor - they use this.editor for text access
|
|
1866
2197
|
// so they work correctly regardless of which editor is active
|
|
1867
2198
|
this.defaultEditor.onEscape = () => {
|
|
1868
|
-
if (this.
|
|
2199
|
+
if (this.session.isStreaming) {
|
|
1869
2200
|
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1870
2201
|
}
|
|
1871
2202
|
else if (this.session.isBashRunning) {
|
|
@@ -1908,7 +2239,8 @@ export class InteractiveMode {
|
|
|
1908
2239
|
this.defaultEditor.onAction("app.model.select", () => this.showModelSelector());
|
|
1909
2240
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
1910
2241
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
1911
|
-
this.defaultEditor.onAction("app.editor.external", () => this.
|
|
2242
|
+
this.defaultEditor.onAction("app.editor.external", () => void this.handleOpenExternalEditor());
|
|
2243
|
+
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand({ flashConfirmation: true }));
|
|
1912
2244
|
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
1913
2245
|
this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
|
|
1914
2246
|
this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
|
|
@@ -1922,31 +2254,55 @@ export class InteractiveMode {
|
|
|
1922
2254
|
this.updateEditorBorderColor();
|
|
1923
2255
|
}
|
|
1924
2256
|
};
|
|
1925
|
-
// Handle clipboard
|
|
2257
|
+
// Handle clipboard paste (triggered on Ctrl+V). Images are attached by path;
|
|
2258
|
+
// otherwise, paste plain text from the system clipboard.
|
|
1926
2259
|
this.defaultEditor.onPasteImage = () => {
|
|
1927
|
-
this.
|
|
2260
|
+
void this.handleClipboardPaste();
|
|
1928
2261
|
};
|
|
1929
2262
|
}
|
|
1930
|
-
async
|
|
2263
|
+
async handleRightClickPaste() {
|
|
2264
|
+
const target = this.renderer.getFocusedComponent();
|
|
2265
|
+
const handleInput = target?.handleInput;
|
|
2266
|
+
if (!target || !handleInput)
|
|
2267
|
+
return;
|
|
2268
|
+
try {
|
|
2269
|
+
const text = await readClipboardText();
|
|
2270
|
+
if (!text || this.renderer.getFocusedComponent() !== target)
|
|
2271
|
+
return;
|
|
2272
|
+
handleInput.call(target, `\x1b[200~${text}\x1b[201~`);
|
|
2273
|
+
this.ui.requestRender();
|
|
2274
|
+
}
|
|
2275
|
+
catch {
|
|
2276
|
+
// Silently ignore clipboard errors (may not have permission, etc.)
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
async handleClipboardPaste() {
|
|
1931
2280
|
try {
|
|
1932
2281
|
const image = await readClipboardImage();
|
|
1933
|
-
if (
|
|
2282
|
+
if (image) {
|
|
2283
|
+
const tmpDir = os.tmpdir();
|
|
2284
|
+
const ext = extensionForImageMimeType(image.mimeType) ?? "png";
|
|
2285
|
+
const fileName = `mcpi-clipboard-${crypto.randomUUID()}.${ext}`;
|
|
2286
|
+
const filePath = path.join(tmpDir, fileName);
|
|
2287
|
+
fs.writeFileSync(filePath, Buffer.from(image.bytes));
|
|
2288
|
+
this.editor.insertTextAtCursor?.(filePath);
|
|
2289
|
+
this.ui.requestRender();
|
|
1934
2290
|
return;
|
|
1935
2291
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
fs.writeFileSync(filePath, Buffer.from(image.bytes));
|
|
1942
|
-
// Insert file path directly
|
|
1943
|
-
this.editor.insertTextAtCursor?.(filePath);
|
|
1944
|
-
this.ui.requestRender();
|
|
2292
|
+
const text = await readClipboardText();
|
|
2293
|
+
if (text) {
|
|
2294
|
+
this.editor.insertTextAtCursor?.(text);
|
|
2295
|
+
this.ui.requestRender();
|
|
2296
|
+
}
|
|
1945
2297
|
}
|
|
1946
2298
|
catch {
|
|
1947
2299
|
// Silently ignore clipboard errors (may not have permission, etc.)
|
|
1948
2300
|
}
|
|
1949
2301
|
}
|
|
2302
|
+
handleStartupSubmit(text) {
|
|
2303
|
+
this.editor.setText(text);
|
|
2304
|
+
this.showStatus("Startup is still in progress");
|
|
2305
|
+
}
|
|
1950
2306
|
setupEditorSubmitHandler() {
|
|
1951
2307
|
this.defaultEditor.onSubmit = async (text) => {
|
|
1952
2308
|
text = text.trim();
|
|
@@ -2024,9 +2380,15 @@ export class InteractiveMode {
|
|
|
2024
2380
|
this.editor.setText("");
|
|
2025
2381
|
return;
|
|
2026
2382
|
}
|
|
2027
|
-
if (text === "/
|
|
2028
|
-
this.
|
|
2383
|
+
if (text === "/trust") {
|
|
2384
|
+
this.showTrustSelector();
|
|
2385
|
+
this.editor.setText("");
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
if (text === "/login" || text.startsWith("/login ")) {
|
|
2389
|
+
const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
|
|
2029
2390
|
this.editor.setText("");
|
|
2391
|
+
await this.handleLoginCommand(providerRef);
|
|
2030
2392
|
return;
|
|
2031
2393
|
}
|
|
2032
2394
|
if (text === "/logout") {
|
|
@@ -2060,11 +2422,6 @@ export class InteractiveMode {
|
|
|
2060
2422
|
this.editor.setText("");
|
|
2061
2423
|
return;
|
|
2062
2424
|
}
|
|
2063
|
-
if (text === "/dementedelves") {
|
|
2064
|
-
this.handleDementedDelves();
|
|
2065
|
-
this.editor.setText("");
|
|
2066
|
-
return;
|
|
2067
|
-
}
|
|
2068
2425
|
if (text === "/resume") {
|
|
2069
2426
|
this.showSessionSelector();
|
|
2070
2427
|
this.editor.setText("");
|
|
@@ -2120,6 +2477,9 @@ export class InteractiveMode {
|
|
|
2120
2477
|
if (this.onInputCallback) {
|
|
2121
2478
|
this.onInputCallback(text);
|
|
2122
2479
|
}
|
|
2480
|
+
else {
|
|
2481
|
+
this.pendingUserInputs.push(text);
|
|
2482
|
+
}
|
|
2123
2483
|
this.editor.addToHistory?.(text);
|
|
2124
2484
|
};
|
|
2125
2485
|
}
|
|
@@ -2135,6 +2495,7 @@ export class InteractiveMode {
|
|
|
2135
2495
|
this.footer.invalidate();
|
|
2136
2496
|
switch (event.type) {
|
|
2137
2497
|
case "agent_start":
|
|
2498
|
+
this.pendingTools.clear();
|
|
2138
2499
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2139
2500
|
this.ui.terminal.setProgress(true);
|
|
2140
2501
|
}
|
|
@@ -2144,26 +2505,33 @@ export class InteractiveMode {
|
|
|
2144
2505
|
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2145
2506
|
this.retryEscapeHandler = undefined;
|
|
2146
2507
|
}
|
|
2147
|
-
if (this.
|
|
2148
|
-
this.
|
|
2149
|
-
this.retryCountdown = undefined;
|
|
2150
|
-
}
|
|
2151
|
-
if (this.retryLoader) {
|
|
2152
|
-
this.retryLoader.stop();
|
|
2153
|
-
this.retryLoader = undefined;
|
|
2508
|
+
if (this.workingVisible) {
|
|
2509
|
+
this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.workingIndicatorOptions));
|
|
2154
2510
|
}
|
|
2155
|
-
|
|
2156
|
-
this.
|
|
2511
|
+
else {
|
|
2512
|
+
this.clearStatusIndicator();
|
|
2157
2513
|
}
|
|
2158
|
-
this.statusContainer.clear();
|
|
2159
|
-
this.loadingAnimation = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.workingMessage || this.defaultWorkingMessage, this.workingIndicatorOptions);
|
|
2160
|
-
this.statusContainer.addChild(this.loadingAnimation);
|
|
2161
2514
|
this.ui.requestRender();
|
|
2162
2515
|
break;
|
|
2163
2516
|
case "queue_update":
|
|
2164
2517
|
this.updatePendingMessagesDisplay();
|
|
2165
2518
|
this.ui.requestRender();
|
|
2166
2519
|
break;
|
|
2520
|
+
case "entry_appended":
|
|
2521
|
+
if (event.entry.type === "custom") {
|
|
2522
|
+
this.addCustomEntryToChat(event.entry);
|
|
2523
|
+
this.ui.requestRender();
|
|
2524
|
+
}
|
|
2525
|
+
break;
|
|
2526
|
+
case "session_info_changed":
|
|
2527
|
+
this.updateTerminalTitle();
|
|
2528
|
+
this.footer.invalidate();
|
|
2529
|
+
this.ui.requestRender();
|
|
2530
|
+
break;
|
|
2531
|
+
case "thinking_level_changed":
|
|
2532
|
+
this.footer.invalidate();
|
|
2533
|
+
this.updateEditorBorderColor();
|
|
2534
|
+
break;
|
|
2167
2535
|
case "message_start":
|
|
2168
2536
|
if (event.message.role === "custom") {
|
|
2169
2537
|
this.addMessageToChat(event.message);
|
|
@@ -2175,17 +2543,17 @@ export class InteractiveMode {
|
|
|
2175
2543
|
this.ui.requestRender();
|
|
2176
2544
|
}
|
|
2177
2545
|
else if (event.message.role === "assistant") {
|
|
2178
|
-
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
2546
|
+
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
|
|
2179
2547
|
this.streamingMessage = event.message;
|
|
2180
2548
|
this.chatContainer.addChild(this.streamingComponent);
|
|
2181
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2549
|
+
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2182
2550
|
this.ui.requestRender();
|
|
2183
2551
|
}
|
|
2184
2552
|
break;
|
|
2185
2553
|
case "message_update":
|
|
2186
2554
|
if (this.streamingComponent && event.message.role === "assistant") {
|
|
2187
2555
|
this.streamingMessage = event.message;
|
|
2188
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2556
|
+
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2189
2557
|
for (const content of this.streamingMessage.content) {
|
|
2190
2558
|
if (content.type === "toolCall") {
|
|
2191
2559
|
if (!this.pendingTools.has(content.id)) {
|
|
@@ -2222,7 +2590,7 @@ export class InteractiveMode {
|
|
|
2222
2590
|
: "Operation aborted";
|
|
2223
2591
|
this.streamingMessage.errorMessage = errorMessage;
|
|
2224
2592
|
}
|
|
2225
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2593
|
+
this.streamingComponent.updateContent(this.streamingMessage, false);
|
|
2226
2594
|
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
2227
2595
|
if (!errorMessage) {
|
|
2228
2596
|
errorMessage = this.streamingMessage.errorMessage || "Error";
|
|
@@ -2240,6 +2608,7 @@ export class InteractiveMode {
|
|
|
2240
2608
|
for (const [, component] of this.pendingTools.entries()) {
|
|
2241
2609
|
component.setArgsComplete();
|
|
2242
2610
|
}
|
|
2611
|
+
this.maybeShowCacheMissNotice(this.streamingMessage);
|
|
2243
2612
|
}
|
|
2244
2613
|
this.streamingComponent = undefined;
|
|
2245
2614
|
this.streamingMessage = undefined;
|
|
@@ -2247,6 +2616,9 @@ export class InteractiveMode {
|
|
|
2247
2616
|
}
|
|
2248
2617
|
this.ui.requestRender();
|
|
2249
2618
|
break;
|
|
2619
|
+
case "bash_execution_update":
|
|
2620
|
+
// The bash execution callback handles TUI output rendering.
|
|
2621
|
+
break;
|
|
2250
2622
|
case "tool_execution_start": {
|
|
2251
2623
|
let component = this.pendingTools.get(event.toolCallId);
|
|
2252
2624
|
if (!component) {
|
|
@@ -2283,20 +2655,18 @@ export class InteractiveMode {
|
|
|
2283
2655
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2284
2656
|
this.ui.terminal.setProgress(false);
|
|
2285
2657
|
}
|
|
2286
|
-
|
|
2287
|
-
this.loadingAnimation.stop();
|
|
2288
|
-
this.loadingAnimation = undefined;
|
|
2289
|
-
this.statusContainer.clear();
|
|
2290
|
-
}
|
|
2658
|
+
this.clearStatusIndicator("working");
|
|
2291
2659
|
if (this.streamingComponent) {
|
|
2292
2660
|
this.chatContainer.removeChild(this.streamingComponent);
|
|
2293
2661
|
this.streamingComponent = undefined;
|
|
2294
2662
|
this.streamingMessage = undefined;
|
|
2295
2663
|
}
|
|
2296
2664
|
this.pendingTools.clear();
|
|
2297
|
-
await this.checkShutdownRequested();
|
|
2298
2665
|
this.ui.requestRender();
|
|
2299
2666
|
break;
|
|
2667
|
+
case "agent_settled":
|
|
2668
|
+
await this.checkShutdownRequested();
|
|
2669
|
+
break;
|
|
2300
2670
|
case "compaction_start": {
|
|
2301
2671
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2302
2672
|
this.ui.terminal.setProgress(true);
|
|
@@ -2306,13 +2676,7 @@ export class InteractiveMode {
|
|
|
2306
2676
|
this.defaultEditor.onEscape = () => {
|
|
2307
2677
|
this.session.abortCompaction();
|
|
2308
2678
|
};
|
|
2309
|
-
this.
|
|
2310
|
-
const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
|
|
2311
|
-
const label = event.reason === "manual"
|
|
2312
|
-
? `Compacting context... ${cancelHint}`
|
|
2313
|
-
: `${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... ${cancelHint}`;
|
|
2314
|
-
this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
|
|
2315
|
-
this.statusContainer.addChild(this.autoCompactionLoader);
|
|
2679
|
+
this.showStatusIndicator(new CompactionStatusIndicator(this.ui, event.reason));
|
|
2316
2680
|
this.ui.requestRender();
|
|
2317
2681
|
break;
|
|
2318
2682
|
}
|
|
@@ -2324,11 +2688,7 @@ export class InteractiveMode {
|
|
|
2324
2688
|
this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
|
|
2325
2689
|
this.autoCompactionEscapeHandler = undefined;
|
|
2326
2690
|
}
|
|
2327
|
-
|
|
2328
|
-
this.autoCompactionLoader.stop();
|
|
2329
|
-
this.autoCompactionLoader = undefined;
|
|
2330
|
-
this.statusContainer.clear();
|
|
2331
|
-
}
|
|
2691
|
+
this.clearStatusIndicator("compaction");
|
|
2332
2692
|
if (event.aborted) {
|
|
2333
2693
|
if (event.reason === "manual") {
|
|
2334
2694
|
this.showError("Compaction cancelled");
|
|
@@ -2362,17 +2722,7 @@ export class InteractiveMode {
|
|
|
2362
2722
|
this.defaultEditor.onEscape = () => {
|
|
2363
2723
|
this.session.abortRetry();
|
|
2364
2724
|
};
|
|
2365
|
-
|
|
2366
|
-
this.statusContainer.clear();
|
|
2367
|
-
this.retryCountdown?.dispose();
|
|
2368
|
-
const retryMessage = (seconds) => `Retrying (${event.attempt}/${event.maxAttempts}) in ${seconds}s... (${keyText("app.interrupt")} to cancel)`;
|
|
2369
|
-
this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), retryMessage(Math.ceil(event.delayMs / 1000)));
|
|
2370
|
-
this.retryCountdown = new CountdownTimer(event.delayMs, this.ui, (seconds) => {
|
|
2371
|
-
this.retryLoader?.setMessage(retryMessage(seconds));
|
|
2372
|
-
}, () => {
|
|
2373
|
-
this.retryCountdown = undefined;
|
|
2374
|
-
});
|
|
2375
|
-
this.statusContainer.addChild(this.retryLoader);
|
|
2725
|
+
this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
|
|
2376
2726
|
this.ui.requestRender();
|
|
2377
2727
|
break;
|
|
2378
2728
|
}
|
|
@@ -2382,16 +2732,7 @@ export class InteractiveMode {
|
|
|
2382
2732
|
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2383
2733
|
this.retryEscapeHandler = undefined;
|
|
2384
2734
|
}
|
|
2385
|
-
|
|
2386
|
-
this.retryCountdown.dispose();
|
|
2387
|
-
this.retryCountdown = undefined;
|
|
2388
|
-
}
|
|
2389
|
-
// Stop loader
|
|
2390
|
-
if (this.retryLoader) {
|
|
2391
|
-
this.retryLoader.stop();
|
|
2392
|
-
this.retryLoader = undefined;
|
|
2393
|
-
this.statusContainer.clear();
|
|
2394
|
-
}
|
|
2735
|
+
this.clearStatusIndicator("retry");
|
|
2395
2736
|
// Show error only on final failure (success shows normal response)
|
|
2396
2737
|
if (!event.success) {
|
|
2397
2738
|
this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
|
|
@@ -2399,6 +2740,28 @@ export class InteractiveMode {
|
|
|
2399
2740
|
this.ui.requestRender();
|
|
2400
2741
|
break;
|
|
2401
2742
|
}
|
|
2743
|
+
case "summarization_retry_scheduled": {
|
|
2744
|
+
this.showError(event.errorMessage);
|
|
2745
|
+
this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
|
|
2746
|
+
this.ui.requestRender();
|
|
2747
|
+
break;
|
|
2748
|
+
}
|
|
2749
|
+
case "summarization_retry_attempt_start": {
|
|
2750
|
+
this.clearStatusIndicator("retry");
|
|
2751
|
+
if (event.source === "branchSummary") {
|
|
2752
|
+
this.showStatusIndicator(new BranchSummaryStatusIndicator(this.ui));
|
|
2753
|
+
}
|
|
2754
|
+
else {
|
|
2755
|
+
this.showStatusIndicator(new CompactionStatusIndicator(this.ui, event.reason));
|
|
2756
|
+
}
|
|
2757
|
+
this.ui.requestRender();
|
|
2758
|
+
break;
|
|
2759
|
+
}
|
|
2760
|
+
case "summarization_retry_finished": {
|
|
2761
|
+
this.clearStatusIndicator("retry");
|
|
2762
|
+
this.ui.requestRender();
|
|
2763
|
+
break;
|
|
2764
|
+
}
|
|
2402
2765
|
}
|
|
2403
2766
|
}
|
|
2404
2767
|
/** Extract text content from a user message */
|
|
@@ -2410,6 +2773,19 @@ export class InteractiveMode {
|
|
|
2410
2773
|
: message.content.filter((c) => c.type === "text");
|
|
2411
2774
|
return textBlocks.map((c) => c.text).join("");
|
|
2412
2775
|
}
|
|
2776
|
+
/** Show a managed-tool status update in the chat. */
|
|
2777
|
+
showManagedToolStatus(status) {
|
|
2778
|
+
if (!this.managedToolStatusStarted) {
|
|
2779
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2780
|
+
this.managedToolStatusStarted = true;
|
|
2781
|
+
}
|
|
2782
|
+
const message = status.type === "warning" ? `Warning: ${status.message}` : status.message;
|
|
2783
|
+
const color = status.type === "warning" ? "warning" : "dim";
|
|
2784
|
+
this.chatContainer.addChild(new Text(theme.fg(color, message), 1, 0));
|
|
2785
|
+
this.lastStatusSpacer = undefined;
|
|
2786
|
+
this.lastStatusText = undefined;
|
|
2787
|
+
this.ui.requestRender();
|
|
2788
|
+
}
|
|
2413
2789
|
/**
|
|
2414
2790
|
* Show a status message in the chat.
|
|
2415
2791
|
*
|
|
@@ -2433,6 +2809,25 @@ export class InteractiveMode {
|
|
|
2433
2809
|
this.lastStatusText = text;
|
|
2434
2810
|
this.ui.requestRender();
|
|
2435
2811
|
}
|
|
2812
|
+
addCustomEntryToChat(entry) {
|
|
2813
|
+
const renderer = this.session.extensionRunner.getEntryRenderer(entry.customType);
|
|
2814
|
+
if (!renderer) {
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
const component = new CustomEntryComponent(entry, renderer);
|
|
2818
|
+
component.setExpanded(this.toolOutputExpanded);
|
|
2819
|
+
if (!component.hasContent()) {
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
if (this.streamingComponent) {
|
|
2823
|
+
const streamingIndex = this.chatContainer.children.indexOf(this.streamingComponent);
|
|
2824
|
+
if (streamingIndex >= 0) {
|
|
2825
|
+
this.chatContainer.children.splice(streamingIndex, 0, component);
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
this.chatContainer.addChild(component);
|
|
2830
|
+
}
|
|
2436
2831
|
addMessageToChat(message, options) {
|
|
2437
2832
|
switch (message.role) {
|
|
2438
2833
|
case "bashExecution": {
|
|
@@ -2447,7 +2842,7 @@ export class InteractiveMode {
|
|
|
2447
2842
|
case "custom": {
|
|
2448
2843
|
if (message.display) {
|
|
2449
2844
|
const renderer = this.session.extensionRunner.getMessageRenderer(message.customType);
|
|
2450
|
-
const component = new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings());
|
|
2845
|
+
const component = new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2451
2846
|
component.setExpanded(this.toolOutputExpanded);
|
|
2452
2847
|
this.chatContainer.addChild(component);
|
|
2453
2848
|
}
|
|
@@ -2481,12 +2876,13 @@ export class InteractiveMode {
|
|
|
2481
2876
|
this.chatContainer.addChild(component);
|
|
2482
2877
|
// Render user message separately if present
|
|
2483
2878
|
if (skillBlock.userMessage) {
|
|
2484
|
-
|
|
2879
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2880
|
+
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2485
2881
|
this.chatContainer.addChild(userComponent);
|
|
2486
2882
|
}
|
|
2487
2883
|
}
|
|
2488
2884
|
else {
|
|
2489
|
-
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());
|
|
2885
|
+
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2490
2886
|
this.chatContainer.addChild(userComponent);
|
|
2491
2887
|
}
|
|
2492
2888
|
if (options?.populateHistory) {
|
|
@@ -2496,7 +2892,7 @@ export class InteractiveMode {
|
|
|
2496
2892
|
break;
|
|
2497
2893
|
}
|
|
2498
2894
|
case "assistant": {
|
|
2499
|
-
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
2895
|
+
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
|
|
2500
2896
|
this.chatContainer.addChild(assistantComponent);
|
|
2501
2897
|
break;
|
|
2502
2898
|
}
|
|
@@ -2509,19 +2905,24 @@ export class InteractiveMode {
|
|
|
2509
2905
|
}
|
|
2510
2906
|
}
|
|
2511
2907
|
}
|
|
2512
|
-
|
|
2513
|
-
* Render session context to chat. Used for initial load and rebuild after compaction.
|
|
2514
|
-
* @param sessionContext Session context to render
|
|
2515
|
-
* @param options.updateFooter Update footer state
|
|
2516
|
-
* @param options.populateHistory Add user messages to editor history
|
|
2517
|
-
*/
|
|
2518
|
-
renderSessionContext(sessionContext, options = {}) {
|
|
2908
|
+
renderSessionItems(items, options = {}) {
|
|
2519
2909
|
this.pendingTools.clear();
|
|
2910
|
+
const renderedPendingTools = new Map();
|
|
2911
|
+
// Cache-miss notices are not persisted; re-derive them from the full entry
|
|
2912
|
+
// list and re-inject them after the assistant messages that paid for them.
|
|
2913
|
+
const cacheMisses = this.settingsManager.getShowCacheMissNotices()
|
|
2914
|
+
? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRuntime)
|
|
2915
|
+
: new Map();
|
|
2520
2916
|
if (options.updateFooter) {
|
|
2521
2917
|
this.footer.invalidate();
|
|
2522
2918
|
this.updateEditorBorderColor();
|
|
2523
2919
|
}
|
|
2524
|
-
for (const
|
|
2920
|
+
for (const item of items) {
|
|
2921
|
+
if (isCustomSessionEntry(item)) {
|
|
2922
|
+
this.addCustomEntryToChat(item);
|
|
2923
|
+
continue;
|
|
2924
|
+
}
|
|
2925
|
+
const message = item;
|
|
2525
2926
|
// Assistant messages need special handling for tool calls
|
|
2526
2927
|
if (message.role === "assistant") {
|
|
2527
2928
|
this.addMessageToChat(message);
|
|
@@ -2549,17 +2950,22 @@ export class InteractiveMode {
|
|
|
2549
2950
|
component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
|
|
2550
2951
|
}
|
|
2551
2952
|
else {
|
|
2552
|
-
|
|
2953
|
+
renderedPendingTools.set(content.id, component);
|
|
2553
2954
|
}
|
|
2554
2955
|
}
|
|
2555
2956
|
}
|
|
2957
|
+
if (message.stopReason !== "aborted" && message.stopReason !== "error") {
|
|
2958
|
+
const miss = cacheMisses.get(message);
|
|
2959
|
+
if (miss)
|
|
2960
|
+
this.addCacheMissNotice(miss);
|
|
2961
|
+
}
|
|
2556
2962
|
}
|
|
2557
2963
|
else if (message.role === "toolResult") {
|
|
2558
2964
|
// Match tool results to pending tool components
|
|
2559
|
-
const component =
|
|
2965
|
+
const component = renderedPendingTools.get(message.toolCallId);
|
|
2560
2966
|
if (component) {
|
|
2561
2967
|
component.updateResult(message);
|
|
2562
|
-
|
|
2968
|
+
renderedPendingTools.delete(message.toolCallId);
|
|
2563
2969
|
}
|
|
2564
2970
|
}
|
|
2565
2971
|
else {
|
|
@@ -2567,16 +2973,62 @@ export class InteractiveMode {
|
|
|
2567
2973
|
this.addMessageToChat(message, options);
|
|
2568
2974
|
}
|
|
2569
2975
|
}
|
|
2570
|
-
|
|
2976
|
+
for (const [toolCallId, component] of renderedPendingTools) {
|
|
2977
|
+
this.pendingTools.set(toolCallId, component);
|
|
2978
|
+
}
|
|
2571
2979
|
this.ui.requestRender();
|
|
2572
2980
|
}
|
|
2981
|
+
/**
|
|
2982
|
+
* Render session entries to chat. Used for initial load and rebuild after compaction.
|
|
2983
|
+
* @param entries Compaction-aware session entries to render
|
|
2984
|
+
* @param options.updateFooter Update footer state
|
|
2985
|
+
* @param options.populateHistory Add user messages to editor history
|
|
2986
|
+
*/
|
|
2987
|
+
renderSessionEntries(entries, options = {}) {
|
|
2988
|
+
const items = entries.flatMap((entry) => {
|
|
2989
|
+
if (entry.type === "custom") {
|
|
2990
|
+
return [entry];
|
|
2991
|
+
}
|
|
2992
|
+
return sessionEntryToContextMessages(entry);
|
|
2993
|
+
});
|
|
2994
|
+
this.renderSessionItems(items, options);
|
|
2995
|
+
}
|
|
2996
|
+
/**
|
|
2997
|
+
* Show a transcript notice when a completed assistant message paid for a
|
|
2998
|
+
* significant cache miss. Only states observable facts: the miss itself,
|
|
2999
|
+
* a model switch, or an idle gap past the cache TTL.
|
|
3000
|
+
*/
|
|
3001
|
+
maybeShowCacheMissNotice(message) {
|
|
3002
|
+
if (!this.settingsManager.getShowCacheMissNotices())
|
|
3003
|
+
return;
|
|
3004
|
+
// Entries don't contain `message` yet: message_end fires before persistence.
|
|
3005
|
+
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.modelRuntime);
|
|
3006
|
+
if (miss)
|
|
3007
|
+
this.addCacheMissNotice(miss);
|
|
3008
|
+
}
|
|
3009
|
+
addCacheMissNotice(miss) {
|
|
3010
|
+
if (miss.missedTokens < 20_000 && miss.missedCost < 0.1)
|
|
3011
|
+
return;
|
|
3012
|
+
const cost = miss.missedCost >= 0.01 ? ` (~$${miss.missedCost.toFixed(2)})` : "";
|
|
3013
|
+
const reBilled = `${formatTokens(miss.missedTokens)} tokens re-billed${cost}`;
|
|
3014
|
+
let label = "Cache miss";
|
|
3015
|
+
if (miss.modelChanged) {
|
|
3016
|
+
label = "Cache miss after model switch";
|
|
3017
|
+
}
|
|
3018
|
+
else if (miss.idleMs >= CACHE_TTL_MS) {
|
|
3019
|
+
label = `Cache miss after ${Math.round(miss.idleMs / 60_000)}m idle`;
|
|
3020
|
+
}
|
|
3021
|
+
const text = theme.fg("warning", `${label}: ${reBilled}`);
|
|
3022
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3023
|
+
this.chatContainer.addChild(new Text(text, 1, 0));
|
|
3024
|
+
}
|
|
2573
3025
|
renderInitialMessages() {
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
this.renderSessionContext(context, {
|
|
3026
|
+
const entries = this.sessionManager.buildContextEntries();
|
|
3027
|
+
this.renderSessionEntries(entries, {
|
|
2577
3028
|
updateFooter: true,
|
|
2578
3029
|
populateHistory: true,
|
|
2579
3030
|
});
|
|
3031
|
+
this.renderProjectTrustWarningIfNeeded();
|
|
2580
3032
|
// Show compaction info if session was compacted
|
|
2581
3033
|
const allEntries = this.sessionManager.getEntries();
|
|
2582
3034
|
const compactionCount = allEntries.filter((e) => e.type === "compaction").length;
|
|
@@ -2585,7 +3037,20 @@ export class InteractiveMode {
|
|
|
2585
3037
|
this.showStatus(`Session compacted ${times}`);
|
|
2586
3038
|
}
|
|
2587
3039
|
}
|
|
3040
|
+
renderProjectTrustWarningIfNeeded() {
|
|
3041
|
+
if (this.settingsManager.isProjectTrusted() || !hasTrustRequiringProjectResources(this.sessionManager.getCwd())) {
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
if (this.chatContainer.children.length > 0) {
|
|
3045
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3046
|
+
}
|
|
3047
|
+
this.chatContainer.addChild(new Text(theme.fg("warning", `This project is not trusted. Project ${CONFIG_DIR_NAME} resources and packages are ignored. Use /trust to save a trust decision, then restart mcpi.`), 1, 0));
|
|
3048
|
+
}
|
|
2588
3049
|
async getUserInput() {
|
|
3050
|
+
const queuedInput = this.pendingUserInputs.shift();
|
|
3051
|
+
if (queuedInput !== undefined) {
|
|
3052
|
+
return queuedInput;
|
|
3053
|
+
}
|
|
2589
3054
|
return new Promise((resolve) => {
|
|
2590
3055
|
this.onInputCallback = (text) => {
|
|
2591
3056
|
this.onInputCallback = undefined;
|
|
@@ -2595,8 +3060,7 @@ export class InteractiveMode {
|
|
|
2595
3060
|
}
|
|
2596
3061
|
rebuildChatFromMessages() {
|
|
2597
3062
|
this.chatContainer.clear();
|
|
2598
|
-
|
|
2599
|
-
this.renderSessionContext(context);
|
|
3063
|
+
this.renderSessionEntries(this.sessionManager.buildContextEntries());
|
|
2600
3064
|
}
|
|
2601
3065
|
// =========================================================================
|
|
2602
3066
|
// Key handlers
|
|
@@ -2621,20 +3085,84 @@ export class InteractiveMode {
|
|
|
2621
3085
|
* repaint the final frame while the process is exiting.
|
|
2622
3086
|
*/
|
|
2623
3087
|
isShuttingDown = false;
|
|
2624
|
-
async shutdown() {
|
|
3088
|
+
async shutdown(options) {
|
|
2625
3089
|
if (this.isShuttingDown)
|
|
2626
3090
|
return;
|
|
2627
3091
|
this.isShuttingDown = true;
|
|
2628
|
-
|
|
3092
|
+
// Keep signal handlers registered until terminal cleanup has completed.
|
|
3093
|
+
// `signal-exit` checks the listener list during the same SIGTERM/SIGHUP
|
|
3094
|
+
// dispatch and re-sends the signal if only its own listeners remain.
|
|
3095
|
+
if (options?.fromSignal) {
|
|
3096
|
+
// Signal-triggered shutdown (SIGTERM/SIGHUP). Emit extension cleanup
|
|
3097
|
+
// (session_shutdown) BEFORE touching the terminal. Extension teardown
|
|
3098
|
+
// such as removing sockets does not write to the tty, so it must not be
|
|
3099
|
+
// skipped if a later terminal-restore write fails on a dead or stalled
|
|
3100
|
+
// terminal. If the terminal is gone, the restore writes below emit EIO,
|
|
3101
|
+
// which the stdout/stderr error handler turns into emergencyTerminalExit;
|
|
3102
|
+
// the render loop is already idle, so this cannot hot-spin (see #4144).
|
|
3103
|
+
await this.runtimeHost.dispose();
|
|
3104
|
+
this.themeController.disableAutoSync();
|
|
3105
|
+
await this.ui.terminal.drainInput(1000);
|
|
3106
|
+
this.stop();
|
|
3107
|
+
process.exit(0);
|
|
3108
|
+
}
|
|
3109
|
+
// Interactive quit (Ctrl+D, Ctrl+C, /quit, extension shutdown()). Stop the
|
|
3110
|
+
// TUI before emitting shutdown events so extension UI cleanup cannot repaint
|
|
3111
|
+
// the final frame while the process is exiting.
|
|
2629
3112
|
// Drain any in-flight Kitty key release events before stopping.
|
|
2630
3113
|
// This prevents escape sequences from leaking to the parent shell over slow SSH.
|
|
3114
|
+
this.themeController.disableAutoSync();
|
|
2631
3115
|
await this.ui.terminal.drainInput(1000);
|
|
2632
3116
|
this.stop();
|
|
2633
3117
|
await this.runtimeHost.dispose();
|
|
3118
|
+
const resumeCommand = formatResumeCommand(this.sessionManager);
|
|
3119
|
+
if (resumeCommand) {
|
|
3120
|
+
process.stdout.write(`${chalk.dim("To resume this session:")} ${resumeCommand}\n`);
|
|
3121
|
+
}
|
|
2634
3122
|
process.exit(0);
|
|
2635
3123
|
}
|
|
3124
|
+
emergencyTerminalExit() {
|
|
3125
|
+
this.isShuttingDown = true;
|
|
3126
|
+
this.unregisterSignalHandlers();
|
|
3127
|
+
killTrackedDetachedChildren();
|
|
3128
|
+
// The terminal is gone. Do not run normal shutdown because TUI and
|
|
3129
|
+
// extension cleanup can write restore sequences and re-trigger EIO.
|
|
3130
|
+
process.exit(129);
|
|
3131
|
+
}
|
|
2636
3132
|
/**
|
|
2637
|
-
*
|
|
3133
|
+
* Last-resort handler for uncaught exceptions. The TUI puts stdin into raw
|
|
3134
|
+
* mode and hides the cursor; without this handler, an uncaught throw from
|
|
3135
|
+
* anywhere (e.g. an extension's async `ChildProcess.on("exit")` callback)
|
|
3136
|
+
* tears down the process while leaving the terminal in raw mode with no
|
|
3137
|
+
* cursor, requiring `stty sane && reset` to recover.
|
|
3138
|
+
*
|
|
3139
|
+
* Unlike emergencyTerminalExit, the terminal is still alive here, so we
|
|
3140
|
+
* call ui.stop() to restore cooked mode, the cursor, and disable bracketed
|
|
3141
|
+
* paste / Kitty / modifyOtherKeys sequences.
|
|
3142
|
+
*/
|
|
3143
|
+
uncaughtCrash(error) {
|
|
3144
|
+
if (this.isShuttingDown) {
|
|
3145
|
+
process.exit(1);
|
|
3146
|
+
}
|
|
3147
|
+
this.isShuttingDown = true;
|
|
3148
|
+
try {
|
|
3149
|
+
this.unregisterSignalHandlers();
|
|
3150
|
+
}
|
|
3151
|
+
catch { }
|
|
3152
|
+
try {
|
|
3153
|
+
killTrackedDetachedChildren();
|
|
3154
|
+
}
|
|
3155
|
+
catch { }
|
|
3156
|
+
try {
|
|
3157
|
+
this.ui.stop();
|
|
3158
|
+
}
|
|
3159
|
+
catch { }
|
|
3160
|
+
console.error(`${APP_NAME} exiting due to uncaughtException:`);
|
|
3161
|
+
console.error(error);
|
|
3162
|
+
process.exit(1);
|
|
3163
|
+
}
|
|
3164
|
+
/**
|
|
3165
|
+
* Check if shutdown was requested and perform shutdown if so.
|
|
2638
3166
|
*/
|
|
2639
3167
|
async checkShutdownRequested() {
|
|
2640
3168
|
if (!this.shutdownRequested)
|
|
@@ -2649,12 +3177,32 @@ export class InteractiveMode {
|
|
|
2649
3177
|
}
|
|
2650
3178
|
for (const signal of signals) {
|
|
2651
3179
|
const handler = () => {
|
|
3180
|
+
// SIGHUP no longer hard-exits: graceful shutdown emits session_shutdown
|
|
3181
|
+
// first, then attempts terminal restore. A genuinely dead terminal
|
|
3182
|
+
// surfaces as an EIO on the restore writes, which the stdout/stderr
|
|
3183
|
+
// error handler converts into emergencyTerminalExit (see #4144, #5080).
|
|
2652
3184
|
killTrackedDetachedChildren();
|
|
2653
|
-
void this.shutdown();
|
|
3185
|
+
void this.shutdown({ fromSignal: true });
|
|
2654
3186
|
};
|
|
2655
|
-
process.
|
|
3187
|
+
process.prependListener(signal, handler);
|
|
2656
3188
|
this.signalCleanupHandlers.push(() => process.off(signal, handler));
|
|
2657
3189
|
}
|
|
3190
|
+
const terminalErrorHandler = (error) => {
|
|
3191
|
+
if (isDeadTerminalError(error)) {
|
|
3192
|
+
this.emergencyTerminalExit();
|
|
3193
|
+
}
|
|
3194
|
+
throw error;
|
|
3195
|
+
};
|
|
3196
|
+
process.stdout.on("error", terminalErrorHandler);
|
|
3197
|
+
process.stderr.on("error", terminalErrorHandler);
|
|
3198
|
+
this.signalCleanupHandlers.push(() => process.stdout.off("error", terminalErrorHandler));
|
|
3199
|
+
this.signalCleanupHandlers.push(() => process.stderr.off("error", terminalErrorHandler));
|
|
3200
|
+
// Restore the terminal before the process dies on any uncaught throw.
|
|
3201
|
+
// Without this, an unhandled exception from extension code (or anywhere
|
|
3202
|
+
// in mcpi) leaves the terminal in raw mode with no cursor.
|
|
3203
|
+
const uncaughtExceptionHandler = (error) => this.uncaughtCrash(error);
|
|
3204
|
+
process.prependListener("uncaughtException", uncaughtExceptionHandler);
|
|
3205
|
+
this.signalCleanupHandlers.push(() => process.off("uncaughtException", uncaughtExceptionHandler));
|
|
2658
3206
|
}
|
|
2659
3207
|
unregisterSignalHandlers() {
|
|
2660
3208
|
for (const cleanup of this.signalCleanupHandlers) {
|
|
@@ -2721,6 +3269,7 @@ export class InteractiveMode {
|
|
|
2721
3269
|
}
|
|
2722
3270
|
// If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
|
|
2723
3271
|
else if (this.editor.onSubmit) {
|
|
3272
|
+
this.editor.setText("");
|
|
2724
3273
|
this.editor.onSubmit(text);
|
|
2725
3274
|
}
|
|
2726
3275
|
}
|
|
@@ -2777,17 +3326,21 @@ export class InteractiveMode {
|
|
|
2777
3326
|
this.setToolsExpanded(!this.toolOutputExpanded);
|
|
2778
3327
|
}
|
|
2779
3328
|
setToolsExpanded(expanded) {
|
|
3329
|
+
if (expanded === this.toolOutputExpanded)
|
|
3330
|
+
return;
|
|
2780
3331
|
this.toolOutputExpanded = expanded;
|
|
2781
3332
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
2782
3333
|
if (isExpandable(activeHeader)) {
|
|
2783
3334
|
activeHeader.setExpanded(expanded);
|
|
2784
3335
|
}
|
|
2785
|
-
for (const
|
|
2786
|
-
|
|
2787
|
-
child
|
|
3336
|
+
for (const container of [this.loadedResourcesContainer, this.chatContainer]) {
|
|
3337
|
+
for (const child of container.children) {
|
|
3338
|
+
if (isExpandable(child)) {
|
|
3339
|
+
child.setExpanded(expanded);
|
|
3340
|
+
}
|
|
2788
3341
|
}
|
|
2789
3342
|
}
|
|
2790
|
-
this.
|
|
3343
|
+
this.showStatus(`Tool output: ${expanded ? "expanded" : "collapsed"}`);
|
|
2791
3344
|
}
|
|
2792
3345
|
toggleThinkingBlockVisibility() {
|
|
2793
3346
|
this.hideThinkingBlock = !this.hideThinkingBlock;
|
|
@@ -2803,45 +3356,21 @@ export class InteractiveMode {
|
|
|
2803
3356
|
}
|
|
2804
3357
|
this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
|
|
2805
3358
|
}
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
const
|
|
2809
|
-
|
|
2810
|
-
this.showWarning("No editor configured. Set $VISUAL or $EDITOR environment variable.");
|
|
2811
|
-
return;
|
|
2812
|
-
}
|
|
2813
|
-
const currentText = this.editor.getExpandedText?.() ?? this.editor.getText();
|
|
2814
|
-
const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);
|
|
3359
|
+
async handleOpenExternalEditor() {
|
|
3360
|
+
const editorCmd = this.settingsManager.getExternalEditorCommand();
|
|
3361
|
+
const content = this.editor.getExpandedText?.() ?? this.editor.getText();
|
|
3362
|
+
this.ui.stop();
|
|
2815
3363
|
try {
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
this.ui.stop();
|
|
2820
|
-
// Split by space to support editor arguments (e.g., "code --wait")
|
|
2821
|
-
const [editor, ...editorArgs] = editorCmd.split(" ");
|
|
2822
|
-
// Spawn editor synchronously with inherited stdio for interactive editing
|
|
2823
|
-
const result = spawnSync(editor, [...editorArgs, tmpFile], {
|
|
2824
|
-
stdio: "inherit",
|
|
2825
|
-
shell: process.platform === "win32",
|
|
3364
|
+
const result = await editInExternalEditor({
|
|
3365
|
+
command: editorCmd,
|
|
3366
|
+
content,
|
|
2826
3367
|
});
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
const newContent = fs.readFileSync(tmpFile, "utf-8").replace(/\n$/, "");
|
|
2830
|
-
this.editor.setText(newContent);
|
|
3368
|
+
if (result.status === "complete") {
|
|
3369
|
+
this.editor.setText(result.content);
|
|
2831
3370
|
}
|
|
2832
|
-
// On non-zero exit, keep original text (no action needed)
|
|
2833
3371
|
}
|
|
2834
3372
|
finally {
|
|
2835
|
-
// Clean up temp file
|
|
2836
|
-
try {
|
|
2837
|
-
fs.unlinkSync(tmpFile);
|
|
2838
|
-
}
|
|
2839
|
-
catch {
|
|
2840
|
-
// Ignore cleanup errors
|
|
2841
|
-
}
|
|
2842
|
-
// Restart TUI
|
|
2843
3373
|
this.ui.start();
|
|
2844
|
-
// Force full re-render since external editor uses alternate screen
|
|
2845
3374
|
this.ui.requestRender(true);
|
|
2846
3375
|
}
|
|
2847
3376
|
}
|
|
@@ -2854,7 +3383,7 @@ export class InteractiveMode {
|
|
|
2854
3383
|
}
|
|
2855
3384
|
showError(errorMessage) {
|
|
2856
3385
|
this.chatContainer.addChild(new Spacer(1));
|
|
2857
|
-
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`),
|
|
3386
|
+
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), this.outputPad, 0));
|
|
2858
3387
|
this.ui.requestRender();
|
|
2859
3388
|
}
|
|
2860
3389
|
showWarning(warningMessage) {
|
|
@@ -2862,19 +3391,23 @@ export class InteractiveMode {
|
|
|
2862
3391
|
this.chatContainer.addChild(new Text(theme.fg("warning", `Warning: ${warningMessage}`), 1, 0));
|
|
2863
3392
|
this.ui.requestRender();
|
|
2864
3393
|
}
|
|
2865
|
-
showNewVersionNotification(
|
|
2866
|
-
const action = theme.fg("accent",
|
|
2867
|
-
const updateInstruction = theme.fg("muted", `New version ${
|
|
2868
|
-
const changelogUrl =
|
|
2869
|
-
const
|
|
3394
|
+
showNewVersionNotification(release) {
|
|
3395
|
+
const action = theme.fg("accent", `${APP_NAME} update`);
|
|
3396
|
+
const updateInstruction = theme.fg("muted", `New version ${release.version} is available. Run `) + action;
|
|
3397
|
+
const changelogUrl = "https://github.com/SamMorrowDrums/mcpi/releases";
|
|
3398
|
+
const changelogLink = getCapabilities().hyperlinks
|
|
3399
|
+
? hyperlink(theme.fg("accent", changelogUrl), changelogUrl)
|
|
3400
|
+
: theme.fg("accent", changelogUrl);
|
|
3401
|
+
const changelogLine = theme.fg("muted", "Changelog: ") + changelogLink;
|
|
2870
3402
|
this.chatContainer.addChild(new Spacer(1));
|
|
2871
3403
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2872
|
-
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}
|
|
3404
|
+
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}`, 1, 0));
|
|
3405
|
+
this.chatContainer.addChild(new Text(changelogLine, 1, 0));
|
|
2873
3406
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2874
3407
|
this.ui.requestRender();
|
|
2875
3408
|
}
|
|
2876
3409
|
showPackageUpdateNotification(packages) {
|
|
2877
|
-
const action = theme.fg("accent", `${APP_NAME} update`);
|
|
3410
|
+
const action = theme.fg("accent", `${APP_NAME} update --extensions`);
|
|
2878
3411
|
const updateInstruction = theme.fg("muted", "Package updates are available. Run ") + action;
|
|
2879
3412
|
const packageLines = packages.map((pkg) => `- ${pkg}`).join("\n");
|
|
2880
3413
|
this.chatContainer.addChild(new Spacer(1));
|
|
@@ -3016,8 +3549,10 @@ export class InteractiveMode {
|
|
|
3016
3549
|
for (const message of preCommands) {
|
|
3017
3550
|
await this.session.prompt(message.text);
|
|
3018
3551
|
}
|
|
3019
|
-
//
|
|
3020
|
-
const promptPromise = this.session
|
|
3552
|
+
// Start a prompt when idle, or queue it into a run still finishing compaction.
|
|
3553
|
+
const promptPromise = this.session
|
|
3554
|
+
.prompt(firstPrompt.text, { streamingBehavior: firstPrompt.mode })
|
|
3555
|
+
.catch((error) => {
|
|
3021
3556
|
restoreQueue(error);
|
|
3022
3557
|
});
|
|
3023
3558
|
// Queue remaining messages
|
|
@@ -3050,25 +3585,43 @@ export class InteractiveMode {
|
|
|
3050
3585
|
// =========================================================================
|
|
3051
3586
|
// Selectors
|
|
3052
3587
|
// =========================================================================
|
|
3588
|
+
disposeActiveSelector() {
|
|
3589
|
+
const dispose = this.activeSelectorDispose;
|
|
3590
|
+
this.activeSelectorToken = undefined;
|
|
3591
|
+
this.activeSelectorDispose = undefined;
|
|
3592
|
+
dispose?.();
|
|
3593
|
+
}
|
|
3053
3594
|
/**
|
|
3054
3595
|
* Shows a selector component in place of the editor.
|
|
3055
3596
|
* @param create Factory that receives a `done` callback and returns the component and focus target
|
|
3056
3597
|
*/
|
|
3057
3598
|
showSelector(create) {
|
|
3599
|
+
const token = {};
|
|
3600
|
+
let dispose;
|
|
3058
3601
|
const done = () => {
|
|
3602
|
+
dispose?.();
|
|
3603
|
+
if (this.activeSelectorToken !== token)
|
|
3604
|
+
return;
|
|
3605
|
+
this.activeSelectorToken = undefined;
|
|
3606
|
+
this.activeSelectorDispose = undefined;
|
|
3059
3607
|
this.editorContainer.clear();
|
|
3060
3608
|
this.editorContainer.addChild(this.editor);
|
|
3061
3609
|
this.ui.setFocus(this.editor);
|
|
3062
3610
|
};
|
|
3063
|
-
const
|
|
3611
|
+
const created = create(done);
|
|
3612
|
+
dispose = created.dispose;
|
|
3613
|
+
this.disposeActiveSelector();
|
|
3614
|
+
this.activeSelectorToken = token;
|
|
3615
|
+
this.activeSelectorDispose = dispose;
|
|
3064
3616
|
this.editorContainer.clear();
|
|
3065
|
-
this.editorContainer.addChild(component);
|
|
3066
|
-
this.ui.setFocus(focus);
|
|
3617
|
+
this.editorContainer.addChild(created.component);
|
|
3618
|
+
this.ui.setFocus(created.focus);
|
|
3067
3619
|
this.ui.requestRender();
|
|
3068
3620
|
}
|
|
3069
3621
|
showSettingsSelector() {
|
|
3070
3622
|
this.showSelector((done) => {
|
|
3071
|
-
|
|
3623
|
+
let selector;
|
|
3624
|
+
selector = new SettingsSelectorComponent({
|
|
3072
3625
|
autoCompact: this.session.autoCompactionEnabled,
|
|
3073
3626
|
showImages: this.settingsManager.getShowImages(),
|
|
3074
3627
|
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
@@ -3078,21 +3631,31 @@ export class InteractiveMode {
|
|
|
3078
3631
|
steeringMode: this.session.steeringMode,
|
|
3079
3632
|
followUpMode: this.session.followUpMode,
|
|
3080
3633
|
transport: this.settingsManager.getTransport(),
|
|
3634
|
+
httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
|
|
3081
3635
|
thinkingLevel: this.session.thinkingLevel,
|
|
3082
3636
|
availableThinkingLevels: this.session.getAvailableThinkingLevels(),
|
|
3083
|
-
currentTheme: this.
|
|
3637
|
+
currentTheme: this.themeController.getThemeSelection() || "dark",
|
|
3638
|
+
terminalTheme: this.themeController.getTerminalTheme(),
|
|
3084
3639
|
availableThemes: getAvailableThemes(),
|
|
3085
3640
|
hideThinkingBlock: this.hideThinkingBlock,
|
|
3641
|
+
mermaidRenderingMode: this.settingsManager.getMermaidRenderingMode(),
|
|
3086
3642
|
collapseChangelog: this.settingsManager.getCollapseChangelog(),
|
|
3087
3643
|
enableInstallTelemetry: this.settingsManager.getEnableInstallTelemetry(),
|
|
3088
3644
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
3089
3645
|
treeFilterMode: this.settingsManager.getTreeFilterMode(),
|
|
3090
3646
|
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
3647
|
+
showCacheMissNotices: this.settingsManager.getShowCacheMissNotices(),
|
|
3648
|
+
defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
|
|
3091
3649
|
editorPaddingX: this.settingsManager.getEditorPaddingX(),
|
|
3650
|
+
outputPad: this.settingsManager.getOutputPad(),
|
|
3092
3651
|
autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
|
|
3093
3652
|
quietStartup: this.settingsManager.getQuietStartup(),
|
|
3094
3653
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
3095
3654
|
showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
|
|
3655
|
+
tuiMode: this.ui.mode,
|
|
3656
|
+
fullscreenExitOutput: this.settingsManager.getFullscreenExitOutput(),
|
|
3657
|
+
fullscreenScrollbar: this.settingsManager.getFullscreenScrollbar(),
|
|
3658
|
+
warnings: this.settingsManager.getWarnings(),
|
|
3096
3659
|
}, {
|
|
3097
3660
|
onAutoCompactChange: (enabled) => {
|
|
3098
3661
|
this.session.setAutoCompactionEnabled(enabled);
|
|
@@ -3134,26 +3697,21 @@ export class InteractiveMode {
|
|
|
3134
3697
|
this.settingsManager.setTransport(transport);
|
|
3135
3698
|
this.session.agent.transport = transport;
|
|
3136
3699
|
},
|
|
3700
|
+
onHttpIdleTimeoutMsChange: (timeoutMs) => {
|
|
3701
|
+
this.settingsManager.setHttpIdleTimeoutMs(timeoutMs);
|
|
3702
|
+
configureHttpDispatcher(timeoutMs);
|
|
3703
|
+
this.showStatus(`HTTP idle timeout: ${formatHttpIdleTimeoutMs(timeoutMs)}`);
|
|
3704
|
+
},
|
|
3137
3705
|
onThinkingLevelChange: (level) => {
|
|
3138
3706
|
this.session.setThinkingLevel(level);
|
|
3139
3707
|
this.footer.invalidate();
|
|
3140
3708
|
this.updateEditorBorderColor();
|
|
3141
3709
|
},
|
|
3142
|
-
onThemeChange: (
|
|
3143
|
-
|
|
3144
|
-
this.
|
|
3145
|
-
this.ui.invalidate();
|
|
3146
|
-
if (!result.success) {
|
|
3147
|
-
this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`);
|
|
3148
|
-
}
|
|
3149
|
-
},
|
|
3150
|
-
onThemePreview: (themeName) => {
|
|
3151
|
-
const result = setTheme(themeName, true);
|
|
3152
|
-
if (result.success) {
|
|
3153
|
-
this.ui.invalidate();
|
|
3154
|
-
this.ui.requestRender();
|
|
3155
|
-
}
|
|
3710
|
+
onThemeChange: (themeSetting) => {
|
|
3711
|
+
this.settingsManager.setTheme(themeSetting);
|
|
3712
|
+
void this.themeController.setThemeSetting(themeSetting);
|
|
3156
3713
|
},
|
|
3714
|
+
onThemePreview: (themeName) => this.themeController.preview(themeName),
|
|
3157
3715
|
onHideThinkingBlockChange: (hidden) => {
|
|
3158
3716
|
this.hideThinkingBlock = hidden;
|
|
3159
3717
|
this.settingsManager.setHideThinkingBlock(hidden);
|
|
@@ -3165,6 +3723,15 @@ export class InteractiveMode {
|
|
|
3165
3723
|
this.chatContainer.clear();
|
|
3166
3724
|
this.rebuildChatFromMessages();
|
|
3167
3725
|
},
|
|
3726
|
+
onMermaidRenderingModeChange: (mode) => {
|
|
3727
|
+
this.settingsManager.setMermaidRenderingMode(mode);
|
|
3728
|
+
this.chatContainer.invalidate();
|
|
3729
|
+
this.ui.requestRender();
|
|
3730
|
+
},
|
|
3731
|
+
onShowCacheMissNoticesChange: (shown) => {
|
|
3732
|
+
this.settingsManager.setShowCacheMissNotices(shown);
|
|
3733
|
+
this.rebuildChatFromMessages();
|
|
3734
|
+
},
|
|
3168
3735
|
onCollapseChangelogChange: (collapsed) => {
|
|
3169
3736
|
this.settingsManager.setCollapseChangelog(collapsed);
|
|
3170
3737
|
},
|
|
@@ -3174,6 +3741,9 @@ export class InteractiveMode {
|
|
|
3174
3741
|
onQuietStartupChange: (enabled) => {
|
|
3175
3742
|
this.settingsManager.setQuietStartup(enabled);
|
|
3176
3743
|
},
|
|
3744
|
+
onDefaultProjectTrustChange: (defaultProjectTrust) => {
|
|
3745
|
+
this.settingsManager.setDefaultProjectTrust(defaultProjectTrust);
|
|
3746
|
+
},
|
|
3177
3747
|
onDoubleEscapeActionChange: (action) => {
|
|
3178
3748
|
this.settingsManager.setDoubleEscapeAction(action);
|
|
3179
3749
|
},
|
|
@@ -3191,6 +3761,25 @@ export class InteractiveMode {
|
|
|
3191
3761
|
this.editor.setPaddingX(padding);
|
|
3192
3762
|
}
|
|
3193
3763
|
},
|
|
3764
|
+
onOutputPadChange: (padding) => {
|
|
3765
|
+
this.settingsManager.setOutputPad(padding);
|
|
3766
|
+
this.outputPad = padding;
|
|
3767
|
+
if (this.streamingComponent || this.session.isStreaming) {
|
|
3768
|
+
for (const child of this.chatContainer.children) {
|
|
3769
|
+
if (child instanceof AssistantMessageComponent ||
|
|
3770
|
+
child instanceof CustomMessageComponent ||
|
|
3771
|
+
child instanceof UserMessageComponent) {
|
|
3772
|
+
child.setOutputPad(padding);
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
if (this.streamingComponent) {
|
|
3776
|
+
this.streamingComponent.setOutputPad(padding);
|
|
3777
|
+
}
|
|
3778
|
+
this.ui.requestRender();
|
|
3779
|
+
return;
|
|
3780
|
+
}
|
|
3781
|
+
this.rebuildChatFromMessages();
|
|
3782
|
+
},
|
|
3194
3783
|
onAutocompleteMaxVisibleChange: (maxVisible) => {
|
|
3195
3784
|
this.settingsManager.setAutocompleteMaxVisible(maxVisible);
|
|
3196
3785
|
this.defaultEditor.setAutocompleteMaxVisible(maxVisible);
|
|
@@ -3201,10 +3790,34 @@ export class InteractiveMode {
|
|
|
3201
3790
|
onClearOnShrinkChange: (enabled) => {
|
|
3202
3791
|
this.settingsManager.setClearOnShrink(enabled);
|
|
3203
3792
|
this.ui.setClearOnShrink(enabled);
|
|
3793
|
+
if (!enabled && !this.activeStatusIndicator) {
|
|
3794
|
+
this.statusContainer.clear();
|
|
3795
|
+
}
|
|
3204
3796
|
},
|
|
3205
3797
|
onShowTerminalProgressChange: (enabled) => {
|
|
3206
3798
|
this.settingsManager.setShowTerminalProgress(enabled);
|
|
3207
3799
|
},
|
|
3800
|
+
onTuiModeChange: (mode) => {
|
|
3801
|
+
if (!this.switchTuiMode(mode)) {
|
|
3802
|
+
selector?.getSettingsList().updateValue("tui-mode", this.ui.mode);
|
|
3803
|
+
this.showStatus("Close active overlays before changing TUI mode");
|
|
3804
|
+
return;
|
|
3805
|
+
}
|
|
3806
|
+
this.settingsManager.setTuiMode(mode);
|
|
3807
|
+
if (!this.activeStatusIndicator)
|
|
3808
|
+
this.statusContainer.clear();
|
|
3809
|
+
this.showStatus(`TUI mode: ${mode}`);
|
|
3810
|
+
},
|
|
3811
|
+
onFullscreenExitOutputChange: (output) => {
|
|
3812
|
+
this.settingsManager.setFullscreenExitOutput(output);
|
|
3813
|
+
},
|
|
3814
|
+
onFullscreenScrollbarChange: (mode) => {
|
|
3815
|
+
this.settingsManager.setFullscreenScrollbar(mode);
|
|
3816
|
+
this.applyFullscreenScrollbarSetting();
|
|
3817
|
+
},
|
|
3818
|
+
onWarningsChange: (warnings) => {
|
|
3819
|
+
this.settingsManager.setWarnings(warnings);
|
|
3820
|
+
},
|
|
3208
3821
|
onCancel: () => {
|
|
3209
3822
|
done();
|
|
3210
3823
|
this.ui.requestRender();
|
|
@@ -3236,42 +3849,63 @@ export class InteractiveMode {
|
|
|
3236
3849
|
this.showModelSelector(searchTerm);
|
|
3237
3850
|
}
|
|
3238
3851
|
async findExactModelMatch(searchTerm) {
|
|
3239
|
-
const
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
if (this.session.scopedModels.length > 0)
|
|
3244
|
-
return
|
|
3245
|
-
|
|
3246
|
-
|
|
3852
|
+
const cachedModels = this.session.scopedModels.length > 0
|
|
3853
|
+
? this.session.scopedModels.map((scoped) => scoped.model)
|
|
3854
|
+
: [...this.session.modelRuntime.getAvailableSnapshot()];
|
|
3855
|
+
const cachedMatch = findExactModelReferenceMatch(searchTerm, cachedModels);
|
|
3856
|
+
if (cachedMatch || this.session.scopedModels.length > 0)
|
|
3857
|
+
return cachedMatch;
|
|
3858
|
+
this.showStatus("Refreshing model catalogs…");
|
|
3859
|
+
const controller = new AbortController();
|
|
3860
|
+
let timedOut = false;
|
|
3861
|
+
const timeout = setTimeout(() => {
|
|
3862
|
+
timedOut = true;
|
|
3863
|
+
controller.abort();
|
|
3864
|
+
}, 15_000);
|
|
3247
3865
|
try {
|
|
3248
|
-
|
|
3866
|
+
const result = await refreshModelCatalogs(this.session.modelRuntime, controller.signal);
|
|
3867
|
+
if (result.aborted && timedOut) {
|
|
3868
|
+
this.showWarning("Model refresh timed out; searching cached models.");
|
|
3869
|
+
}
|
|
3870
|
+
else if (result.errors.size > 0) {
|
|
3871
|
+
this.showWarning(`Could not refresh ${[...result.errors.keys()].join(", ")}; searching cached models.`);
|
|
3872
|
+
}
|
|
3249
3873
|
}
|
|
3250
|
-
catch {
|
|
3251
|
-
|
|
3874
|
+
catch (error) {
|
|
3875
|
+
this.showWarning(timedOut
|
|
3876
|
+
? "Model refresh timed out; searching cached models."
|
|
3877
|
+
: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`);
|
|
3252
3878
|
}
|
|
3879
|
+
finally {
|
|
3880
|
+
clearTimeout(timeout);
|
|
3881
|
+
}
|
|
3882
|
+
return findExactModelReferenceMatch(searchTerm, [...this.session.modelRuntime.getAvailableSnapshot()]);
|
|
3253
3883
|
}
|
|
3254
|
-
/** Update the footer's available provider count from current
|
|
3255
|
-
|
|
3256
|
-
const models =
|
|
3257
|
-
|
|
3884
|
+
/** Update the footer's available provider count from the current snapshot without refreshing catalogs. */
|
|
3885
|
+
updateAvailableProviderCount() {
|
|
3886
|
+
const models = this.session.scopedModels.length > 0
|
|
3887
|
+
? this.session.scopedModels.map((scoped) => scoped.model)
|
|
3888
|
+
: this.session.modelRuntime.getAvailableSnapshot();
|
|
3889
|
+
const uniqueProviders = new Set(models.map((model) => model.provider));
|
|
3258
3890
|
this.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);
|
|
3259
3891
|
}
|
|
3260
3892
|
async maybeWarnAboutAnthropicSubscriptionAuth(model = this.session.model) {
|
|
3261
|
-
if (this.
|
|
3893
|
+
if (this.settingsManager.getWarnings().anthropicExtraUsage === false) {
|
|
3262
3894
|
return;
|
|
3263
3895
|
}
|
|
3264
|
-
if (
|
|
3896
|
+
if (this.anthropicSubscriptionWarningShown) {
|
|
3265
3897
|
return;
|
|
3266
3898
|
}
|
|
3267
|
-
|
|
3268
|
-
if (storedCredential?.type === "oauth") {
|
|
3269
|
-
this.anthropicSubscriptionWarningShown = true;
|
|
3270
|
-
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
3899
|
+
if (!model || model.provider !== "anthropic") {
|
|
3271
3900
|
return;
|
|
3272
3901
|
}
|
|
3273
3902
|
try {
|
|
3274
|
-
|
|
3903
|
+
if ((await this.session.modelRuntime.checkAuth("anthropic"))?.type === "oauth") {
|
|
3904
|
+
this.anthropicSubscriptionWarningShown = true;
|
|
3905
|
+
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
const apiKey = (await this.session.modelRuntime.getAuth(model.provider))?.auth.apiKey;
|
|
3275
3909
|
if (!isAnthropicSubscriptionAuthKey(apiKey)) {
|
|
3276
3910
|
return;
|
|
3277
3911
|
}
|
|
@@ -3282,9 +3916,54 @@ export class InteractiveMode {
|
|
|
3282
3916
|
// Ignore auth lookup failures for warning-only checks.
|
|
3283
3917
|
}
|
|
3284
3918
|
}
|
|
3919
|
+
maybeSaveImplicitProjectTrustAfterReload() {
|
|
3920
|
+
const cwd = this.sessionManager.getCwd();
|
|
3921
|
+
if (this.autoTrustOnReloadCwd !== cwd) {
|
|
3922
|
+
return false;
|
|
3923
|
+
}
|
|
3924
|
+
if (!this.settingsManager.isProjectTrusted() || !hasTrustRequiringProjectResources(cwd)) {
|
|
3925
|
+
return false;
|
|
3926
|
+
}
|
|
3927
|
+
const trustStore = new ProjectTrustStore(this.runtimeHost.services.agentDir);
|
|
3928
|
+
try {
|
|
3929
|
+
if (trustStore.get(cwd) !== null) {
|
|
3930
|
+
this.autoTrustOnReloadCwd = undefined;
|
|
3931
|
+
return false;
|
|
3932
|
+
}
|
|
3933
|
+
trustStore.set(cwd, true);
|
|
3934
|
+
this.autoTrustOnReloadCwd = undefined;
|
|
3935
|
+
return true;
|
|
3936
|
+
}
|
|
3937
|
+
catch (error) {
|
|
3938
|
+
this.showWarning(`Could not save project trust after reload: ${error instanceof Error ? error.message : String(error)}`);
|
|
3939
|
+
return false;
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
showTrustSelector() {
|
|
3943
|
+
const cwd = this.sessionManager.getCwd();
|
|
3944
|
+
const trustStore = new ProjectTrustStore(this.runtimeHost.services.agentDir);
|
|
3945
|
+
const savedDecision = trustStore.getEntry(cwd);
|
|
3946
|
+
this.showSelector((done) => {
|
|
3947
|
+
const selector = new TrustSelectorComponent({
|
|
3948
|
+
cwd,
|
|
3949
|
+
savedDecision,
|
|
3950
|
+
projectTrusted: this.settingsManager.isProjectTrusted(),
|
|
3951
|
+
onSelect: (selection) => {
|
|
3952
|
+
trustStore.setMany(selection.updates);
|
|
3953
|
+
done();
|
|
3954
|
+
this.showStatus(`Saved trust decision: ${selection.trusted ? "trusted" : "untrusted"}. Restart ${APP_NAME} for this to take effect.`);
|
|
3955
|
+
},
|
|
3956
|
+
onCancel: () => {
|
|
3957
|
+
done();
|
|
3958
|
+
this.ui.requestRender();
|
|
3959
|
+
},
|
|
3960
|
+
});
|
|
3961
|
+
return { component: selector, focus: selector };
|
|
3962
|
+
});
|
|
3963
|
+
}
|
|
3285
3964
|
showModelSelector(initialSearchInput) {
|
|
3286
3965
|
this.showSelector((done) => {
|
|
3287
|
-
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.
|
|
3966
|
+
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRuntime, this.session.scopedModels, async (model) => {
|
|
3288
3967
|
try {
|
|
3289
3968
|
await this.session.setModel(model);
|
|
3290
3969
|
this.footer.invalidate();
|
|
@@ -3302,64 +3981,68 @@ export class InteractiveMode {
|
|
|
3302
3981
|
done();
|
|
3303
3982
|
this.ui.requestRender();
|
|
3304
3983
|
}, initialSearchInput);
|
|
3305
|
-
return { component: selector, focus: selector };
|
|
3984
|
+
return { component: selector, focus: selector, dispose: () => selector.dispose() };
|
|
3306
3985
|
});
|
|
3307
3986
|
}
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
const
|
|
3312
|
-
if (allModels.length === 0) {
|
|
3313
|
-
this.showStatus("No models available");
|
|
3314
|
-
return;
|
|
3315
|
-
}
|
|
3316
|
-
// Check if session has scoped models (from previous session-only changes or CLI --models)
|
|
3987
|
+
showModelsSelector() {
|
|
3988
|
+
let availableModels = [...this.session.modelRuntime.getAvailableSnapshot()];
|
|
3989
|
+
let availableModelIds = new Set(availableModels.map((model) => `${model.provider}/${model.id}`));
|
|
3990
|
+
const configuredPatterns = this.settingsManager.getEnabledModels();
|
|
3317
3991
|
const sessionScopedModels = this.session.scopedModels;
|
|
3318
|
-
const
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
const updateSessionModels = async (enabledIds) => {
|
|
3992
|
+
const configuredEnabledIds = (models) => {
|
|
3993
|
+
if (!configuredPatterns?.length)
|
|
3994
|
+
return null;
|
|
3995
|
+
const resolved = resolveModelScopeFromModels(configuredPatterns, models);
|
|
3996
|
+
const ids = resolved.scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3997
|
+
for (const diagnostic of resolved.diagnostics) {
|
|
3998
|
+
if (diagnostic.code === "no-match" && !ids.includes(diagnostic.pattern))
|
|
3999
|
+
ids.push(diagnostic.pattern);
|
|
4000
|
+
}
|
|
4001
|
+
return ids;
|
|
4002
|
+
};
|
|
4003
|
+
let currentEnabledIds = sessionScopedModels.length > 0
|
|
4004
|
+
? sessionScopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`)
|
|
4005
|
+
: configuredEnabledIds(availableModels);
|
|
4006
|
+
let selectionChanged = false;
|
|
4007
|
+
const updateSessionModels = (enabledIds) => {
|
|
3335
4008
|
currentEnabledIds = enabledIds === null ? null : [...enabledIds];
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
4009
|
+
const hasEnabledAvailableModel = enabledIds?.some((id) => availableModelIds.has(id)) ?? false;
|
|
4010
|
+
const allAvailableModelsEnabled = enabledIds !== null && [...availableModelIds].every((id) => enabledIds.includes(id));
|
|
4011
|
+
if (enabledIds && hasEnabledAvailableModel && !allAvailableModelsEnabled) {
|
|
4012
|
+
const newScopedModels = resolveModelScopeFromModels(enabledIds, availableModels).scopedModels;
|
|
4013
|
+
this.session.setScopedModels(newScopedModels.map((scoped) => ({
|
|
4014
|
+
model: scoped.model,
|
|
4015
|
+
thinkingLevel: scoped.thinkingLevel,
|
|
3341
4016
|
})));
|
|
3342
4017
|
}
|
|
3343
4018
|
else {
|
|
3344
|
-
// All enabled or none enabled = no filter
|
|
3345
4019
|
this.session.setScopedModels([]);
|
|
3346
4020
|
}
|
|
3347
|
-
|
|
4021
|
+
this.updateAvailableProviderCount();
|
|
3348
4022
|
this.ui.requestRender();
|
|
3349
4023
|
};
|
|
3350
4024
|
this.showSelector((done) => {
|
|
4025
|
+
let disposed = false;
|
|
4026
|
+
let timedOut = false;
|
|
4027
|
+
const controller = new AbortController();
|
|
4028
|
+
const timeout = setTimeout(() => {
|
|
4029
|
+
timedOut = true;
|
|
4030
|
+
controller.abort();
|
|
4031
|
+
}, 15_000);
|
|
3351
4032
|
const selector = new ScopedModelsSelectorComponent({
|
|
3352
|
-
allModels,
|
|
4033
|
+
allModels: availableModels,
|
|
3353
4034
|
enabledModelIds: currentEnabledIds,
|
|
4035
|
+
refreshStatus: "Refreshing model catalogs…",
|
|
3354
4036
|
}, {
|
|
3355
|
-
onChange:
|
|
3356
|
-
|
|
4037
|
+
onChange: (enabledIds) => {
|
|
4038
|
+
selectionChanged = true;
|
|
4039
|
+
updateSessionModels(enabledIds);
|
|
3357
4040
|
},
|
|
3358
4041
|
onPersist: (enabledIds) => {
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
4042
|
+
const allEnabled = enabledIds !== null &&
|
|
4043
|
+
enabledIds.length === availableModels.length &&
|
|
4044
|
+
enabledIds.every((id) => availableModelIds.has(id));
|
|
4045
|
+
const newPatterns = enabledIds === null || allEnabled ? undefined : enabledIds;
|
|
3363
4046
|
this.settingsManager.setEnabledModels(newPatterns ? [...newPatterns] : undefined);
|
|
3364
4047
|
this.showStatus("Model selection saved to settings");
|
|
3365
4048
|
},
|
|
@@ -3368,7 +4051,50 @@ export class InteractiveMode {
|
|
|
3368
4051
|
this.ui.requestRender();
|
|
3369
4052
|
},
|
|
3370
4053
|
});
|
|
3371
|
-
|
|
4054
|
+
void refreshModelCatalogs(this.session.modelRuntime, controller.signal)
|
|
4055
|
+
.then((result) => {
|
|
4056
|
+
if (disposed)
|
|
4057
|
+
return;
|
|
4058
|
+
availableModels = [...this.session.modelRuntime.getAvailableSnapshot()];
|
|
4059
|
+
availableModelIds = new Set(availableModels.map((model) => `${model.provider}/${model.id}`));
|
|
4060
|
+
if (!selectionChanged && sessionScopedModels.length === 0) {
|
|
4061
|
+
currentEnabledIds = configuredEnabledIds(availableModels);
|
|
4062
|
+
selector.updateModels(availableModels, currentEnabledIds);
|
|
4063
|
+
}
|
|
4064
|
+
else {
|
|
4065
|
+
selector.updateModels(availableModels);
|
|
4066
|
+
}
|
|
4067
|
+
if (currentEnabledIds !== null)
|
|
4068
|
+
updateSessionModels(currentEnabledIds);
|
|
4069
|
+
if (result.aborted && timedOut) {
|
|
4070
|
+
selector.setRefreshStatus("Model refresh timed out; showing cached models.", "warning");
|
|
4071
|
+
}
|
|
4072
|
+
else if (result.errors.size > 0) {
|
|
4073
|
+
selector.setRefreshStatus(`Could not refresh ${[...result.errors.keys()].join(", ")}; showing cached models.`, "warning");
|
|
4074
|
+
}
|
|
4075
|
+
else {
|
|
4076
|
+
selector.setRefreshStatus("Model catalogs refreshed.", "success");
|
|
4077
|
+
}
|
|
4078
|
+
this.ui.requestRender();
|
|
4079
|
+
})
|
|
4080
|
+
.catch((error) => {
|
|
4081
|
+
if (disposed)
|
|
4082
|
+
return;
|
|
4083
|
+
selector.setRefreshStatus(timedOut
|
|
4084
|
+
? "Model refresh timed out; showing cached models."
|
|
4085
|
+
: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
4086
|
+
this.ui.requestRender();
|
|
4087
|
+
})
|
|
4088
|
+
.finally(() => clearTimeout(timeout));
|
|
4089
|
+
return {
|
|
4090
|
+
component: selector,
|
|
4091
|
+
focus: selector,
|
|
4092
|
+
dispose: () => {
|
|
4093
|
+
disposed = true;
|
|
4094
|
+
clearTimeout(timeout);
|
|
4095
|
+
controller.abort();
|
|
4096
|
+
},
|
|
4097
|
+
};
|
|
3372
4098
|
});
|
|
3373
4099
|
}
|
|
3374
4100
|
showUserMessageSelector() {
|
|
@@ -3380,20 +4106,17 @@ export class InteractiveMode {
|
|
|
3380
4106
|
const initialSelectedId = userMessages[userMessages.length - 1]?.entryId;
|
|
3381
4107
|
this.showSelector((done) => {
|
|
3382
4108
|
const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ id: m.entryId, text: m.text })), async (entryId) => {
|
|
4109
|
+
done();
|
|
3383
4110
|
try {
|
|
3384
4111
|
const result = await this.runtimeHost.fork(entryId);
|
|
3385
4112
|
if (result.cancelled) {
|
|
3386
|
-
done();
|
|
3387
4113
|
this.ui.requestRender();
|
|
3388
4114
|
return;
|
|
3389
4115
|
}
|
|
3390
|
-
this.renderCurrentSessionState();
|
|
3391
4116
|
this.editor.setText(result.selectedText ?? "");
|
|
3392
|
-
done();
|
|
3393
4117
|
this.showStatus("Forked to new session");
|
|
3394
4118
|
}
|
|
3395
4119
|
catch (error) {
|
|
3396
|
-
done();
|
|
3397
4120
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
3398
4121
|
}
|
|
3399
4122
|
}, () => {
|
|
@@ -3415,7 +4138,6 @@ export class InteractiveMode {
|
|
|
3415
4138
|
this.ui.requestRender();
|
|
3416
4139
|
return;
|
|
3417
4140
|
}
|
|
3418
|
-
this.renderCurrentSessionState();
|
|
3419
4141
|
this.editor.setText("");
|
|
3420
4142
|
this.showStatus("Cloned to new session");
|
|
3421
4143
|
}
|
|
@@ -3434,7 +4156,7 @@ export class InteractiveMode {
|
|
|
3434
4156
|
this.showSelector((done) => {
|
|
3435
4157
|
const selector = new TreeSelectorComponent(tree, realLeafId, this.ui.terminal.rows, async (entryId) => {
|
|
3436
4158
|
// Selecting the current leaf is a no-op (already there)
|
|
3437
|
-
if (entryId ===
|
|
4159
|
+
if (entryId === this.sessionManager.getLeafId()) {
|
|
3438
4160
|
done();
|
|
3439
4161
|
this.showStatus("Already at this point");
|
|
3440
4162
|
return;
|
|
@@ -3469,16 +4191,21 @@ export class InteractiveMode {
|
|
|
3469
4191
|
break;
|
|
3470
4192
|
}
|
|
3471
4193
|
}
|
|
3472
|
-
//
|
|
3473
|
-
|
|
4194
|
+
// The user committed to navigating: stop the active response first.
|
|
4195
|
+
if (this.session.isStreaming) {
|
|
4196
|
+
this.restoreQueuedMessagesToEditor();
|
|
4197
|
+
await this.session.abort();
|
|
4198
|
+
}
|
|
4199
|
+
// Set up escape handler and status indicator if summarizing
|
|
4200
|
+
let showingSummaryIndicator = false;
|
|
3474
4201
|
const originalOnEscape = this.defaultEditor.onEscape;
|
|
3475
4202
|
if (wantsSummary) {
|
|
3476
4203
|
this.defaultEditor.onEscape = () => {
|
|
3477
4204
|
this.session.abortBranchSummary();
|
|
3478
4205
|
};
|
|
3479
4206
|
this.chatContainer.addChild(new Spacer(1));
|
|
3480
|
-
|
|
3481
|
-
|
|
4207
|
+
this.showStatusIndicator(new BranchSummaryStatusIndicator(this.ui));
|
|
4208
|
+
showingSummaryIndicator = true;
|
|
3482
4209
|
this.ui.requestRender();
|
|
3483
4210
|
}
|
|
3484
4211
|
try {
|
|
@@ -3509,9 +4236,8 @@ export class InteractiveMode {
|
|
|
3509
4236
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
3510
4237
|
}
|
|
3511
4238
|
finally {
|
|
3512
|
-
if (
|
|
3513
|
-
|
|
3514
|
-
this.statusContainer.clear();
|
|
4239
|
+
if (showingSummaryIndicator) {
|
|
4240
|
+
this.clearStatusIndicator("branchSummary");
|
|
3515
4241
|
}
|
|
3516
4242
|
this.defaultEditor.onEscape = originalOnEscape;
|
|
3517
4243
|
}
|
|
@@ -3522,12 +4248,27 @@ export class InteractiveMode {
|
|
|
3522
4248
|
this.sessionManager.appendLabelChange(entryId, label);
|
|
3523
4249
|
this.ui.requestRender();
|
|
3524
4250
|
}, initialSelectedId, initialFilterMode);
|
|
4251
|
+
selector.onCopy = async (text) => {
|
|
4252
|
+
if (!text) {
|
|
4253
|
+
this.showError("Selected entry has no text to copy");
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
try {
|
|
4257
|
+
await copyToClipboard(text);
|
|
4258
|
+
this.showStatus("Copied selected message to clipboard");
|
|
4259
|
+
}
|
|
4260
|
+
catch (error) {
|
|
4261
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
4262
|
+
}
|
|
4263
|
+
};
|
|
3525
4264
|
return { component: selector, focus: selector };
|
|
3526
4265
|
});
|
|
3527
4266
|
}
|
|
3528
4267
|
showSessionSelector() {
|
|
3529
4268
|
this.showSelector((done) => {
|
|
3530
|
-
const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress),
|
|
4269
|
+
const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress), (onProgress) => this.sessionManager.usesDefaultSessionDir()
|
|
4270
|
+
? SessionManager.listAll(onProgress)
|
|
4271
|
+
: SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress), async (sessionPath) => {
|
|
3531
4272
|
done();
|
|
3532
4273
|
await this.handleResumeSession(sessionPath);
|
|
3533
4274
|
}, () => {
|
|
@@ -3550,19 +4291,15 @@ export class InteractiveMode {
|
|
|
3550
4291
|
});
|
|
3551
4292
|
}
|
|
3552
4293
|
async handleResumeSession(sessionPath, options) {
|
|
3553
|
-
|
|
3554
|
-
this.loadingAnimation.stop();
|
|
3555
|
-
this.loadingAnimation = undefined;
|
|
3556
|
-
}
|
|
3557
|
-
this.statusContainer.clear();
|
|
4294
|
+
this.clearStatusIndicator();
|
|
3558
4295
|
try {
|
|
3559
4296
|
const result = await this.runtimeHost.switchSession(sessionPath, {
|
|
3560
4297
|
withSession: options?.withSession,
|
|
4298
|
+
projectTrustContextFactory: (cwd) => this.createProjectTrustContext(cwd),
|
|
3561
4299
|
});
|
|
3562
4300
|
if (result.cancelled) {
|
|
3563
4301
|
return result;
|
|
3564
4302
|
}
|
|
3565
|
-
this.renderCurrentSessionState();
|
|
3566
4303
|
this.showStatus("Resumed session");
|
|
3567
4304
|
return result;
|
|
3568
4305
|
}
|
|
@@ -3576,11 +4313,11 @@ export class InteractiveMode {
|
|
|
3576
4313
|
const result = await this.runtimeHost.switchSession(sessionPath, {
|
|
3577
4314
|
cwdOverride: selectedCwd,
|
|
3578
4315
|
withSession: options?.withSession,
|
|
4316
|
+
projectTrustContextFactory: (cwd) => this.createProjectTrustContext(cwd),
|
|
3579
4317
|
});
|
|
3580
4318
|
if (result.cancelled) {
|
|
3581
4319
|
return result;
|
|
3582
4320
|
}
|
|
3583
|
-
this.renderCurrentSessionState();
|
|
3584
4321
|
this.showStatus("Resumed session in current cwd");
|
|
3585
4322
|
return result;
|
|
3586
4323
|
}
|
|
@@ -3588,54 +4325,124 @@ export class InteractiveMode {
|
|
|
3588
4325
|
}
|
|
3589
4326
|
}
|
|
3590
4327
|
getLoginProviderOptions(authType) {
|
|
3591
|
-
const
|
|
3592
|
-
const
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
4328
|
+
const options = [];
|
|
4329
|
+
for (const provider of this.session.modelRuntime.getProviders()) {
|
|
4330
|
+
const authStatus = this.session.modelRuntime.getProviderAuthStatus(provider.id);
|
|
4331
|
+
const status = authStatus.configured
|
|
4332
|
+
? {
|
|
4333
|
+
type: this.session.modelRuntime.isUsingOAuth(provider.id) ? "oauth" : "api_key",
|
|
4334
|
+
source: authStatus.label ?? authStatus.source,
|
|
4335
|
+
}
|
|
4336
|
+
: undefined;
|
|
4337
|
+
if ((!authType || authType === "oauth") && provider.auth.oauth) {
|
|
4338
|
+
options.push({
|
|
4339
|
+
id: provider.id,
|
|
4340
|
+
name: provider.name,
|
|
4341
|
+
authType: "oauth",
|
|
4342
|
+
method: provider.auth.oauth,
|
|
4343
|
+
status,
|
|
4344
|
+
});
|
|
3603
4345
|
}
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
4346
|
+
if ((!authType || authType === "api_key") && provider.auth.apiKey) {
|
|
4347
|
+
options.push({
|
|
4348
|
+
id: provider.id,
|
|
4349
|
+
name: provider.name,
|
|
4350
|
+
authType: "api_key",
|
|
4351
|
+
method: provider.auth.apiKey,
|
|
4352
|
+
status,
|
|
4353
|
+
});
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
4357
|
+
}
|
|
4358
|
+
async getLogoutProviderOptions() {
|
|
4359
|
+
return (await this.session.modelRuntime.listCredentials({ signal: AbortSignal.timeout(15_000) }))
|
|
4360
|
+
.map(({ providerId, type }) => ({
|
|
4361
|
+
id: providerId,
|
|
4362
|
+
name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
|
|
4363
|
+
authType: type,
|
|
4364
|
+
status: { type, source: "stored credential" },
|
|
4365
|
+
}))
|
|
4366
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
4367
|
+
}
|
|
4368
|
+
findLoginProviderOptions(providerRef) {
|
|
4369
|
+
const normalizedProviderRef = providerRef.trim().toLowerCase();
|
|
4370
|
+
if (!normalizedProviderRef) {
|
|
4371
|
+
return [];
|
|
3609
4372
|
}
|
|
3610
|
-
|
|
3611
|
-
|
|
4373
|
+
return this.getLoginProviderOptions().filter((provider) => provider.id.toLowerCase() === normalizedProviderRef ||
|
|
4374
|
+
provider.name.toLowerCase() === normalizedProviderRef);
|
|
3612
4375
|
}
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
4376
|
+
async handleLoginCommand(providerRef) {
|
|
4377
|
+
if (!providerRef) {
|
|
4378
|
+
this.showLoginAuthTypeSelector();
|
|
4379
|
+
return;
|
|
4380
|
+
}
|
|
4381
|
+
const providerOptions = this.findLoginProviderOptions(providerRef);
|
|
4382
|
+
if (providerOptions.length === 1) {
|
|
4383
|
+
await this.startProviderLogin(providerOptions[0]);
|
|
4384
|
+
return;
|
|
4385
|
+
}
|
|
4386
|
+
if (providerOptions.length > 1) {
|
|
4387
|
+
const providerIds = new Set(providerOptions.map((provider) => provider.id));
|
|
4388
|
+
if (providerIds.size === 1) {
|
|
4389
|
+
this.showLoginAuthTypeSelector(providerOptions);
|
|
4390
|
+
return;
|
|
3621
4391
|
}
|
|
3622
|
-
options.push({
|
|
3623
|
-
id: providerId,
|
|
3624
|
-
name: credential.type === "oauth"
|
|
3625
|
-
? (oauthNameById.get(providerId) ?? providerId)
|
|
3626
|
-
: getApiKeyProviderDisplayName(providerId),
|
|
3627
|
-
authType: credential.type,
|
|
3628
|
-
});
|
|
3629
4392
|
}
|
|
3630
|
-
|
|
4393
|
+
this.showLoginProviderSelector(undefined, providerRef);
|
|
4394
|
+
}
|
|
4395
|
+
async startProviderLogin(providerOption) {
|
|
4396
|
+
if (providerOption.authType === "oauth") {
|
|
4397
|
+
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
4398
|
+
}
|
|
4399
|
+
else if (providerOption.method?.login) {
|
|
4400
|
+
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
4401
|
+
}
|
|
4402
|
+
else {
|
|
4403
|
+
this.showAmbientAuthDialog(providerOption);
|
|
4404
|
+
}
|
|
3631
4405
|
}
|
|
3632
|
-
showLoginAuthTypeSelector() {
|
|
3633
|
-
const
|
|
3634
|
-
const
|
|
4406
|
+
showLoginAuthTypeSelector(providerOptions) {
|
|
4407
|
+
const oauthProvider = providerOptions?.find((provider) => provider.authType === "oauth");
|
|
4408
|
+
const oauthLoginLabel = oauthProvider?.method && "loginLabel" in oauthProvider.method ? oauthProvider.method.loginLabel : undefined;
|
|
4409
|
+
const subscriptionLabel = oauthLoginLabel ?? "Sign in with an account";
|
|
4410
|
+
const apiKeyLabel = "Sign in with an API key";
|
|
4411
|
+
const availableAuthTypes = providerOptions
|
|
4412
|
+
? new Set(providerOptions.map((provider) => provider.authType))
|
|
4413
|
+
: new Set(["oauth", "api_key"]);
|
|
4414
|
+
const options = [];
|
|
4415
|
+
if (availableAuthTypes.has("oauth")) {
|
|
4416
|
+
options.push(subscriptionLabel);
|
|
4417
|
+
}
|
|
4418
|
+
if (availableAuthTypes.has("api_key")) {
|
|
4419
|
+
options.push(apiKeyLabel);
|
|
4420
|
+
}
|
|
4421
|
+
if (options.length === 0) {
|
|
4422
|
+
this.showStatus("No login methods available.");
|
|
4423
|
+
return;
|
|
4424
|
+
}
|
|
4425
|
+
if (providerOptions && options.length === 1) {
|
|
4426
|
+
const providerOption = providerOptions[0];
|
|
4427
|
+
if (providerOption) {
|
|
4428
|
+
void this.startProviderLogin(providerOption);
|
|
4429
|
+
}
|
|
4430
|
+
return;
|
|
4431
|
+
}
|
|
4432
|
+
const title = providerOptions?.[0]
|
|
4433
|
+
? `Select authentication method for ${providerOptions[0].name}:`
|
|
4434
|
+
: "Select authentication method:";
|
|
3635
4435
|
this.showSelector((done) => {
|
|
3636
|
-
const selector = new ExtensionSelectorComponent(
|
|
4436
|
+
const selector = new ExtensionSelectorComponent(title, options, (option) => {
|
|
3637
4437
|
done();
|
|
3638
4438
|
const authType = option === subscriptionLabel ? "oauth" : "api_key";
|
|
4439
|
+
if (providerOptions) {
|
|
4440
|
+
const providerOption = providerOptions.find((provider) => provider.authType === authType);
|
|
4441
|
+
if (providerOption) {
|
|
4442
|
+
void this.startProviderLogin(providerOption);
|
|
4443
|
+
}
|
|
4444
|
+
return;
|
|
4445
|
+
}
|
|
3639
4446
|
this.showLoginProviderSelector(authType);
|
|
3640
4447
|
}, () => {
|
|
3641
4448
|
done();
|
|
@@ -3644,32 +4451,34 @@ export class InteractiveMode {
|
|
|
3644
4451
|
return { component: selector, focus: selector };
|
|
3645
4452
|
});
|
|
3646
4453
|
}
|
|
3647
|
-
showLoginProviderSelector(authType) {
|
|
4454
|
+
showLoginProviderSelector(authType, initialSearchInput) {
|
|
3648
4455
|
const providerOptions = this.getLoginProviderOptions(authType);
|
|
3649
4456
|
if (providerOptions.length === 0) {
|
|
3650
|
-
|
|
4457
|
+
const message = authType === "oauth"
|
|
4458
|
+
? "No subscription providers available."
|
|
4459
|
+
: authType === "api_key"
|
|
4460
|
+
? "No API key providers available."
|
|
4461
|
+
: "No login providers available.";
|
|
4462
|
+
this.showStatus(message);
|
|
3651
4463
|
return;
|
|
3652
4464
|
}
|
|
3653
4465
|
this.showSelector((done) => {
|
|
3654
|
-
const selector = new OAuthSelectorComponent("login",
|
|
4466
|
+
const selector = new OAuthSelectorComponent("login", providerOptions, async (providerId, selectedAuthType) => {
|
|
3655
4467
|
done();
|
|
3656
|
-
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
4468
|
+
const providerOption = providerOptions.find((provider) => provider.id === providerId && provider.authType === selectedAuthType);
|
|
3657
4469
|
if (!providerOption) {
|
|
3658
4470
|
return;
|
|
3659
4471
|
}
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
this.
|
|
4472
|
+
await this.startProviderLogin(providerOption);
|
|
4473
|
+
}, () => {
|
|
4474
|
+
done();
|
|
4475
|
+
if (authType) {
|
|
4476
|
+
this.showLoginAuthTypeSelector();
|
|
3665
4477
|
}
|
|
3666
4478
|
else {
|
|
3667
|
-
|
|
4479
|
+
this.ui.requestRender();
|
|
3668
4480
|
}
|
|
3669
|
-
},
|
|
3670
|
-
done();
|
|
3671
|
-
this.showLoginAuthTypeSelector();
|
|
3672
|
-
});
|
|
4481
|
+
}, initialSearchInput);
|
|
3673
4482
|
return { component: selector, focus: selector };
|
|
3674
4483
|
});
|
|
3675
4484
|
}
|
|
@@ -3678,29 +4487,40 @@ export class InteractiveMode {
|
|
|
3678
4487
|
this.showLoginAuthTypeSelector();
|
|
3679
4488
|
return;
|
|
3680
4489
|
}
|
|
3681
|
-
|
|
4490
|
+
let providerOptions;
|
|
4491
|
+
try {
|
|
4492
|
+
providerOptions = await this.getLogoutProviderOptions();
|
|
4493
|
+
}
|
|
4494
|
+
catch (error) {
|
|
4495
|
+
this.showError(`Could not read stored credentials: ${error instanceof Error ? error.message : String(error)}`);
|
|
4496
|
+
return;
|
|
4497
|
+
}
|
|
3682
4498
|
if (providerOptions.length === 0) {
|
|
3683
|
-
this.showStatus("No
|
|
4499
|
+
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
3684
4500
|
return;
|
|
3685
4501
|
}
|
|
3686
4502
|
this.showSelector((done) => {
|
|
3687
|
-
const selector = new OAuthSelectorComponent(mode,
|
|
4503
|
+
const selector = new OAuthSelectorComponent(mode, providerOptions, async (providerId) => {
|
|
3688
4504
|
done();
|
|
3689
4505
|
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
3690
4506
|
if (!providerOption) {
|
|
3691
4507
|
return;
|
|
3692
4508
|
}
|
|
3693
4509
|
try {
|
|
3694
|
-
this.session.
|
|
3695
|
-
|
|
4510
|
+
await this.session.modelRuntime.logout(providerOption.id, {
|
|
4511
|
+
signal: AbortSignal.timeout(15_000),
|
|
4512
|
+
});
|
|
3696
4513
|
await this.updateAvailableProviderCount();
|
|
3697
4514
|
const message = providerOption.authType === "oauth"
|
|
3698
4515
|
? `Logged out of ${providerOption.name}`
|
|
3699
|
-
: `Removed API key for ${providerOption.name}
|
|
4516
|
+
: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;
|
|
3700
4517
|
this.showStatus(message);
|
|
3701
4518
|
}
|
|
3702
4519
|
catch (error) {
|
|
3703
|
-
|
|
4520
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4521
|
+
this.showError(error instanceof CredentialSynchronizationError
|
|
4522
|
+
? `Credentials removed for ${providerOption.name}, but local model state could not be synchronized: ${message}`
|
|
4523
|
+
: `Logout failed: ${message}`);
|
|
3704
4524
|
}
|
|
3705
4525
|
}, () => {
|
|
3706
4526
|
done();
|
|
@@ -3710,12 +4530,11 @@ export class InteractiveMode {
|
|
|
3710
4530
|
});
|
|
3711
4531
|
}
|
|
3712
4532
|
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
3713
|
-
this.session.modelRegistry.refresh();
|
|
3714
4533
|
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
3715
4534
|
let selectedModel;
|
|
3716
4535
|
let selectionError;
|
|
3717
4536
|
if (isUnknownModel(previousModel)) {
|
|
3718
|
-
const availableModels = this.session.
|
|
4537
|
+
const availableModels = this.session.modelRuntime.getAvailableSnapshot();
|
|
3719
4538
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
3720
4539
|
if (!hasDefaultModelProvider(providerId)) {
|
|
3721
4540
|
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
@@ -3758,21 +4577,35 @@ export class InteractiveMode {
|
|
|
3758
4577
|
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
3759
4578
|
}
|
|
3760
4579
|
}
|
|
4580
|
+
const controller = new AbortController();
|
|
4581
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
4582
|
+
void this.session.modelRuntime
|
|
4583
|
+
.refresh({ providers: [providerId], signal: controller.signal })
|
|
4584
|
+
.then((result) => {
|
|
4585
|
+
if (result.aborted) {
|
|
4586
|
+
this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
|
|
4587
|
+
}
|
|
4588
|
+
else if (result.errors.size > 0) {
|
|
4589
|
+
this.showWarning(`${actionLabel}, but its model catalog could not be refreshed; using cached models.`);
|
|
4590
|
+
}
|
|
4591
|
+
this.updateAvailableProviderCount();
|
|
4592
|
+
this.footer.invalidate();
|
|
4593
|
+
this.ui.requestRender();
|
|
4594
|
+
})
|
|
4595
|
+
.catch((error) => {
|
|
4596
|
+
this.showWarning(`${actionLabel}, but its model catalog could not be refreshed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4597
|
+
})
|
|
4598
|
+
.finally(() => clearTimeout(timeout));
|
|
3761
4599
|
}
|
|
3762
|
-
|
|
4600
|
+
showAmbientAuthDialog(providerOption) {
|
|
3763
4601
|
const restoreEditor = () => {
|
|
3764
4602
|
this.editorContainer.clear();
|
|
3765
4603
|
this.editorContainer.addChild(this.editor);
|
|
3766
4604
|
this.ui.setFocus(this.editor);
|
|
3767
4605
|
this.ui.requestRender();
|
|
3768
4606
|
};
|
|
3769
|
-
const dialog = new LoginDialogComponent(this.ui,
|
|
3770
|
-
dialog.showInfo([
|
|
3771
|
-
theme.fg("text", "Amazon Bedrock uses AWS credentials instead of a single API key."),
|
|
3772
|
-
theme.fg("text", "Configure an AWS profile, IAM keys, bearer token, or role-based credentials."),
|
|
3773
|
-
theme.fg("muted", "See:"),
|
|
3774
|
-
theme.fg("accent", ` ${path.join(getDocsPath(), "providers.md")}`),
|
|
3775
|
-
]);
|
|
4607
|
+
const dialog = new LoginDialogComponent(this.ui, providerOption.id, () => restoreEditor(), providerOption.name, `${providerOption.name} setup`);
|
|
4608
|
+
dialog.showInfo(`${providerOption.method?.name ?? "Authentication"} is configured outside ${APP_NAME}.`, [], true);
|
|
3776
4609
|
this.editorContainer.clear();
|
|
3777
4610
|
this.editorContainer.addChild(dialog);
|
|
3778
4611
|
this.ui.setFocus(dialog);
|
|
@@ -3783,6 +4616,13 @@ export class InteractiveMode {
|
|
|
3783
4616
|
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
3784
4617
|
// Completion handled below
|
|
3785
4618
|
}, providerName);
|
|
4619
|
+
if (providerId === "amazon-bedrock") {
|
|
4620
|
+
dialog.showDetails([
|
|
4621
|
+
theme.fg("text", "You can also use an AWS profile, IAM keys, or role-based credentials."),
|
|
4622
|
+
theme.fg("muted", "See:"),
|
|
4623
|
+
theme.fg("accent", ` ${path.join(getDocsPath(), "providers.md")}`),
|
|
4624
|
+
]);
|
|
4625
|
+
}
|
|
3786
4626
|
this.editorContainer.clear();
|
|
3787
4627
|
this.editorContainer.addChild(dialog);
|
|
3788
4628
|
this.ui.setFocus(dialog);
|
|
@@ -3794,46 +4634,105 @@ export class InteractiveMode {
|
|
|
3794
4634
|
this.ui.requestRender();
|
|
3795
4635
|
};
|
|
3796
4636
|
try {
|
|
3797
|
-
|
|
3798
|
-
if (!apiKey) {
|
|
3799
|
-
throw new Error("API key cannot be empty.");
|
|
3800
|
-
}
|
|
3801
|
-
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey });
|
|
4637
|
+
await this.loginProvider(dialog, providerId, "api_key");
|
|
3802
4638
|
restoreEditor();
|
|
3803
4639
|
await this.completeProviderAuthentication(providerId, providerName, "api_key", previousModel);
|
|
3804
4640
|
}
|
|
3805
4641
|
catch (error) {
|
|
3806
4642
|
restoreEditor();
|
|
3807
4643
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3808
|
-
if (
|
|
4644
|
+
if (error instanceof CredentialSynchronizationError) {
|
|
4645
|
+
this.showError(`Saved API key for ${providerName}, but local model state could not be synchronized: ${errorMsg}`);
|
|
4646
|
+
}
|
|
4647
|
+
else if (errorMsg !== "Login cancelled") {
|
|
3809
4648
|
this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
|
|
3810
4649
|
}
|
|
3811
4650
|
}
|
|
3812
4651
|
}
|
|
4652
|
+
showAuthSelect(dialog, prompt) {
|
|
4653
|
+
return new Promise((resolve, reject) => {
|
|
4654
|
+
const restoreDialog = () => {
|
|
4655
|
+
this.editorContainer.clear();
|
|
4656
|
+
this.editorContainer.addChild(dialog);
|
|
4657
|
+
this.ui.setFocus(dialog);
|
|
4658
|
+
this.ui.requestRender();
|
|
4659
|
+
};
|
|
4660
|
+
const labels = prompt.options.map((option) => option.label);
|
|
4661
|
+
const selector = new ExtensionSelectorComponent(prompt.message, labels, (optionLabel) => {
|
|
4662
|
+
restoreDialog();
|
|
4663
|
+
const id = prompt.options.find((option) => option.label === optionLabel)?.id;
|
|
4664
|
+
if (id)
|
|
4665
|
+
resolve(id);
|
|
4666
|
+
else
|
|
4667
|
+
reject(new Error("Login cancelled"));
|
|
4668
|
+
}, () => {
|
|
4669
|
+
restoreDialog();
|
|
4670
|
+
reject(new Error("Login cancelled"));
|
|
4671
|
+
});
|
|
4672
|
+
this.editorContainer.clear();
|
|
4673
|
+
this.editorContainer.addChild(selector);
|
|
4674
|
+
this.ui.setFocus(selector);
|
|
4675
|
+
this.ui.requestRender();
|
|
4676
|
+
});
|
|
4677
|
+
}
|
|
4678
|
+
async showAuthPrompt(dialog, prompt) {
|
|
4679
|
+
let response;
|
|
4680
|
+
if (prompt.type === "select") {
|
|
4681
|
+
response = this.showAuthSelect(dialog, prompt);
|
|
4682
|
+
}
|
|
4683
|
+
else if (prompt.type === "manual_code") {
|
|
4684
|
+
response = dialog.showManualInput(prompt.message);
|
|
4685
|
+
}
|
|
4686
|
+
else {
|
|
4687
|
+
response = dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
4688
|
+
}
|
|
4689
|
+
if (!prompt.signal)
|
|
4690
|
+
return response;
|
|
4691
|
+
if (prompt.signal.aborted)
|
|
4692
|
+
throw new Error("Login cancelled");
|
|
4693
|
+
const signal = prompt.signal;
|
|
4694
|
+
let onAbort;
|
|
4695
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
4696
|
+
onAbort = () => reject(new Error("Login cancelled"));
|
|
4697
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4698
|
+
});
|
|
4699
|
+
try {
|
|
4700
|
+
return await Promise.race([response, aborted]);
|
|
4701
|
+
}
|
|
4702
|
+
finally {
|
|
4703
|
+
if (onAbort)
|
|
4704
|
+
signal.removeEventListener("abort", onAbort);
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4707
|
+
notifyAuthDialog(dialog, event) {
|
|
4708
|
+
if (event.type === "auth_url") {
|
|
4709
|
+
dialog.showAuth(event.url, event.instructions);
|
|
4710
|
+
}
|
|
4711
|
+
else if (event.type === "device_code") {
|
|
4712
|
+
dialog.showDeviceCode(event);
|
|
4713
|
+
dialog.showWaiting("Waiting for authentication...");
|
|
4714
|
+
}
|
|
4715
|
+
else if (event.type === "info") {
|
|
4716
|
+
dialog.showInfo(event.message, event.links);
|
|
4717
|
+
}
|
|
4718
|
+
else {
|
|
4719
|
+
dialog.showProgress(event.message);
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
async loginProvider(dialog, providerId, method) {
|
|
4723
|
+
await this.session.modelRuntime.login(providerId, method, {
|
|
4724
|
+
signal: dialog.signal,
|
|
4725
|
+
prompt: (prompt) => this.showAuthPrompt(dialog, prompt),
|
|
4726
|
+
notify: (event) => this.notifyAuthDialog(dialog, event),
|
|
4727
|
+
});
|
|
4728
|
+
}
|
|
3813
4729
|
async showLoginDialog(providerId, providerName) {
|
|
3814
|
-
const providerInfo = this.session.modelRegistry.authStorage
|
|
3815
|
-
.getOAuthProviders()
|
|
3816
|
-
.find((provider) => provider.id === providerId);
|
|
3817
4730
|
const previousModel = this.session.model;
|
|
3818
|
-
|
|
3819
|
-
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
3820
|
-
// Create login dialog component
|
|
3821
|
-
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
3822
|
-
// Completion handled below
|
|
3823
|
-
}, providerName);
|
|
3824
|
-
// Show dialog in editor container
|
|
4731
|
+
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => { }, providerName);
|
|
3825
4732
|
this.editorContainer.clear();
|
|
3826
4733
|
this.editorContainer.addChild(dialog);
|
|
3827
4734
|
this.ui.setFocus(dialog);
|
|
3828
4735
|
this.ui.requestRender();
|
|
3829
|
-
// Promise for manual code input (racing with callback server)
|
|
3830
|
-
let manualCodeResolve;
|
|
3831
|
-
let manualCodeReject;
|
|
3832
|
-
const manualCodePromise = new Promise((resolve, reject) => {
|
|
3833
|
-
manualCodeResolve = resolve;
|
|
3834
|
-
manualCodeReject = reject;
|
|
3835
|
-
});
|
|
3836
|
-
// Restore editor helper
|
|
3837
4736
|
const restoreEditor = () => {
|
|
3838
4737
|
this.editorContainer.clear();
|
|
3839
4738
|
this.editorContainer.addChild(this.editor);
|
|
@@ -3841,49 +4740,17 @@ export class InteractiveMode {
|
|
|
3841
4740
|
this.ui.requestRender();
|
|
3842
4741
|
};
|
|
3843
4742
|
try {
|
|
3844
|
-
await this.
|
|
3845
|
-
onAuth: (info) => {
|
|
3846
|
-
dialog.showAuth(info.url, info.instructions);
|
|
3847
|
-
if (usesCallbackServer) {
|
|
3848
|
-
// Show input for manual paste, racing with callback
|
|
3849
|
-
dialog
|
|
3850
|
-
.showManualInput("Paste redirect URL below, or complete login in browser:")
|
|
3851
|
-
.then((value) => {
|
|
3852
|
-
if (value && manualCodeResolve) {
|
|
3853
|
-
manualCodeResolve(value);
|
|
3854
|
-
manualCodeResolve = undefined;
|
|
3855
|
-
}
|
|
3856
|
-
})
|
|
3857
|
-
.catch(() => {
|
|
3858
|
-
if (manualCodeReject) {
|
|
3859
|
-
manualCodeReject(new Error("Login cancelled"));
|
|
3860
|
-
manualCodeReject = undefined;
|
|
3861
|
-
}
|
|
3862
|
-
});
|
|
3863
|
-
}
|
|
3864
|
-
else if (providerId === "github-copilot") {
|
|
3865
|
-
// GitHub Copilot polls after onAuth
|
|
3866
|
-
dialog.showWaiting("Waiting for browser authentication...");
|
|
3867
|
-
}
|
|
3868
|
-
// For Anthropic: onPrompt is called immediately after
|
|
3869
|
-
},
|
|
3870
|
-
onPrompt: async (prompt) => {
|
|
3871
|
-
return dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
3872
|
-
},
|
|
3873
|
-
onProgress: (message) => {
|
|
3874
|
-
dialog.showProgress(message);
|
|
3875
|
-
},
|
|
3876
|
-
onManualCodeInput: () => manualCodePromise,
|
|
3877
|
-
signal: dialog.signal,
|
|
3878
|
-
});
|
|
3879
|
-
// Success
|
|
4743
|
+
await this.loginProvider(dialog, providerId, "oauth");
|
|
3880
4744
|
restoreEditor();
|
|
3881
4745
|
await this.completeProviderAuthentication(providerId, providerName, "oauth", previousModel);
|
|
3882
4746
|
}
|
|
3883
4747
|
catch (error) {
|
|
3884
4748
|
restoreEditor();
|
|
3885
4749
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3886
|
-
if (
|
|
4750
|
+
if (error instanceof CredentialSynchronizationError) {
|
|
4751
|
+
this.showError(`Logged in to ${providerName}, but local model state could not be synchronized: ${errorMsg}`);
|
|
4752
|
+
}
|
|
4753
|
+
else if (errorMsg !== "Login cancelled") {
|
|
3887
4754
|
this.showError(`Failed to login to ${providerName}: ${errorMsg}`);
|
|
3888
4755
|
}
|
|
3889
4756
|
}
|
|
@@ -3905,7 +4772,7 @@ export class InteractiveMode {
|
|
|
3905
4772
|
const borderColor = (s) => theme.fg("border", s);
|
|
3906
4773
|
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
3907
4774
|
reloadBox.addChild(new Spacer(1));
|
|
3908
|
-
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes..."), 1, 0));
|
|
4775
|
+
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes, and context files..."), 1, 0));
|
|
3909
4776
|
reloadBox.addChild(new Spacer(1));
|
|
3910
4777
|
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
3911
4778
|
const previousEditor = this.editor;
|
|
@@ -3920,47 +4787,50 @@ export class InteractiveMode {
|
|
|
3920
4787
|
this.ui.setFocus(editor);
|
|
3921
4788
|
this.ui.requestRender();
|
|
3922
4789
|
};
|
|
4790
|
+
let chatRestoredBeforeSessionStart = false;
|
|
4791
|
+
let reloadBoxDismissed = false;
|
|
4792
|
+
const restoreChatBeforeSessionStart = () => {
|
|
4793
|
+
if (chatRestoredBeforeSessionStart) {
|
|
4794
|
+
return;
|
|
4795
|
+
}
|
|
4796
|
+
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
4797
|
+
this.outputPad = this.settingsManager.getOutputPad();
|
|
4798
|
+
this.rebuildChatFromMessages();
|
|
4799
|
+
chatRestoredBeforeSessionStart = true;
|
|
4800
|
+
};
|
|
3923
4801
|
try {
|
|
3924
|
-
await this.session.reload();
|
|
4802
|
+
await this.session.reload({ beforeSessionStart: restoreChatBeforeSessionStart });
|
|
4803
|
+
restoreChatBeforeSessionStart();
|
|
3925
4804
|
this.keybindings.reload();
|
|
3926
4805
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
3927
4806
|
if (isExpandable(activeHeader)) {
|
|
3928
4807
|
activeHeader.setExpanded(this.toolOutputExpanded);
|
|
3929
4808
|
}
|
|
3930
4809
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
const themeResult = themeName ? setTheme(themeName, true) : { success: true };
|
|
3934
|
-
if (!themeResult.success) {
|
|
3935
|
-
this.showError(`Failed to load theme "${themeName}": ${themeResult.error}\nFell back to dark theme.`);
|
|
3936
|
-
}
|
|
3937
|
-
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
|
3938
|
-
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
3939
|
-
this.defaultEditor.setPaddingX(editorPaddingX);
|
|
3940
|
-
this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
|
|
3941
|
-
if (this.editor !== this.defaultEditor) {
|
|
3942
|
-
this.editor.setPaddingX?.(editorPaddingX);
|
|
3943
|
-
this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
|
|
3944
|
-
}
|
|
3945
|
-
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
3946
|
-
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
|
4810
|
+
await this.themeController.applyFromSettings();
|
|
4811
|
+
this.applyRuntimeSettings();
|
|
3947
4812
|
this.setupAutocompleteProvider();
|
|
3948
4813
|
const runner = this.session.extensionRunner;
|
|
3949
4814
|
this.setupExtensionShortcuts(runner);
|
|
3950
|
-
this.rebuildChatFromMessages();
|
|
3951
|
-
dismissReloadBox(this.editor);
|
|
3952
4815
|
this.showLoadedResources({
|
|
3953
4816
|
force: false,
|
|
3954
4817
|
showDiagnosticsWhenQuiet: true,
|
|
3955
4818
|
});
|
|
3956
|
-
const
|
|
4819
|
+
const savedImplicitProjectTrust = this.maybeSaveImplicitProjectTrustAfterReload();
|
|
4820
|
+
const modelsJsonError = this.session.modelRuntime.getError();
|
|
3957
4821
|
if (modelsJsonError) {
|
|
3958
4822
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
3959
4823
|
}
|
|
3960
|
-
this.showStatus(
|
|
4824
|
+
this.showStatus(savedImplicitProjectTrust
|
|
4825
|
+
? "Reloaded keybindings, extensions, skills, prompts, themes, and context files; saved project trust"
|
|
4826
|
+
: "Reloaded keybindings, extensions, skills, prompts, themes, and context files");
|
|
4827
|
+
dismissReloadBox(this.editor);
|
|
4828
|
+
reloadBoxDismissed = true;
|
|
3961
4829
|
}
|
|
3962
4830
|
catch (error) {
|
|
3963
|
-
|
|
4831
|
+
if (!reloadBoxDismissed) {
|
|
4832
|
+
dismissReloadBox(previousEditor);
|
|
4833
|
+
}
|
|
3964
4834
|
this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3965
4835
|
}
|
|
3966
4836
|
}
|
|
@@ -3972,7 +4842,9 @@ export class InteractiveMode {
|
|
|
3972
4842
|
this.showStatus(`Session exported to: ${filePath}`);
|
|
3973
4843
|
}
|
|
3974
4844
|
else {
|
|
3975
|
-
const filePath = await this.session.exportToHtml(outputPath
|
|
4845
|
+
const filePath = await this.session.exportToHtml(outputPath, {
|
|
4846
|
+
themeName: theme.name,
|
|
4847
|
+
});
|
|
3976
4848
|
this.showStatus(`Session exported to: ${filePath}`);
|
|
3977
4849
|
}
|
|
3978
4850
|
}
|
|
@@ -4017,17 +4889,12 @@ export class InteractiveMode {
|
|
|
4017
4889
|
return;
|
|
4018
4890
|
}
|
|
4019
4891
|
try {
|
|
4020
|
-
|
|
4021
|
-
this.loadingAnimation.stop();
|
|
4022
|
-
this.loadingAnimation = undefined;
|
|
4023
|
-
}
|
|
4024
|
-
this.statusContainer.clear();
|
|
4892
|
+
this.clearStatusIndicator();
|
|
4025
4893
|
const result = await this.runtimeHost.importFromJsonl(inputPath);
|
|
4026
4894
|
if (result.cancelled) {
|
|
4027
4895
|
this.showStatus("Import cancelled");
|
|
4028
4896
|
return;
|
|
4029
4897
|
}
|
|
4030
|
-
this.renderCurrentSessionState();
|
|
4031
4898
|
this.showStatus(`Session imported from: ${inputPath}`);
|
|
4032
4899
|
}
|
|
4033
4900
|
catch (error) {
|
|
@@ -4042,7 +4909,6 @@ export class InteractiveMode {
|
|
|
4042
4909
|
this.showStatus("Import cancelled");
|
|
4043
4910
|
return;
|
|
4044
4911
|
}
|
|
4045
|
-
this.renderCurrentSessionState();
|
|
4046
4912
|
this.showStatus(`Session imported from: ${inputPath}`);
|
|
4047
4913
|
return;
|
|
4048
4914
|
}
|
|
@@ -4069,7 +4935,7 @@ export class InteractiveMode {
|
|
|
4069
4935
|
// Export to a temp file
|
|
4070
4936
|
const tmpFile = path.join(os.tmpdir(), "session.html");
|
|
4071
4937
|
try {
|
|
4072
|
-
await this.session.exportToHtml(tmpFile);
|
|
4938
|
+
await this.session.exportToHtml(tmpFile, { themeName: theme.name });
|
|
4073
4939
|
}
|
|
4074
4940
|
catch (error) {
|
|
4075
4941
|
this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -4131,7 +4997,13 @@ export class InteractiveMode {
|
|
|
4131
4997
|
}
|
|
4132
4998
|
// Create the preview URL
|
|
4133
4999
|
const previewUrl = getShareViewerUrl(gistId);
|
|
4134
|
-
|
|
5000
|
+
if (previewUrl) {
|
|
5001
|
+
this.showStatus(`Share URL: ${previewUrl}\nGist: ${gistUrl}`);
|
|
5002
|
+
}
|
|
5003
|
+
else {
|
|
5004
|
+
this.showStatus(`Gist: ${gistUrl}`);
|
|
5005
|
+
this.showError(`No share viewer is configured, so no share URL was produced. Set ${ENV_SHARE_VIEWER_URL} to a share viewer base URL to enable share links.`);
|
|
5006
|
+
}
|
|
4135
5007
|
}
|
|
4136
5008
|
catch (error) {
|
|
4137
5009
|
if (!loader.signal.aborted) {
|
|
@@ -4140,7 +5012,7 @@ export class InteractiveMode {
|
|
|
4140
5012
|
}
|
|
4141
5013
|
}
|
|
4142
5014
|
}
|
|
4143
|
-
async handleCopyCommand() {
|
|
5015
|
+
async handleCopyCommand(options = {}) {
|
|
4144
5016
|
const text = this.session.getLastAssistantText();
|
|
4145
5017
|
if (!text) {
|
|
4146
5018
|
this.showError("No agent messages to copy yet.");
|
|
@@ -4148,7 +5020,12 @@ export class InteractiveMode {
|
|
|
4148
5020
|
}
|
|
4149
5021
|
try {
|
|
4150
5022
|
await copyToClipboard(text);
|
|
4151
|
-
|
|
5023
|
+
if (options.flashConfirmation && this.ui instanceof TuiAltScreen) {
|
|
5024
|
+
this.ui.flash("Copied!");
|
|
5025
|
+
}
|
|
5026
|
+
else {
|
|
5027
|
+
this.showStatus("Copied last agent message to clipboard");
|
|
5028
|
+
}
|
|
4152
5029
|
}
|
|
4153
5030
|
catch (error) {
|
|
4154
5031
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
@@ -4168,15 +5045,24 @@ export class InteractiveMode {
|
|
|
4168
5045
|
this.ui.requestRender();
|
|
4169
5046
|
return;
|
|
4170
5047
|
}
|
|
4171
|
-
this.
|
|
4172
|
-
this.
|
|
5048
|
+
this.session.setSessionName(name);
|
|
5049
|
+
const sessionName = this.sessionManager.getSessionName();
|
|
5050
|
+
if (sessionName !== name) {
|
|
5051
|
+
this.showWarning(`Session name was normalized from ${JSON.stringify(name)} to ${JSON.stringify(sessionName)}`);
|
|
5052
|
+
}
|
|
4173
5053
|
this.chatContainer.addChild(new Spacer(1));
|
|
4174
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${name}`), 1, 0));
|
|
5054
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${sessionName ?? name}`), 1, 0));
|
|
4175
5055
|
this.ui.requestRender();
|
|
4176
5056
|
}
|
|
4177
5057
|
handleSessionCommand() {
|
|
4178
5058
|
const stats = this.session.getSessionStats();
|
|
4179
5059
|
const sessionName = this.sessionManager.getSessionName();
|
|
5060
|
+
const entries = this.sessionManager.getEntries();
|
|
5061
|
+
const cacheWaste = computeCacheWaste(entries, this.session.modelRuntime);
|
|
5062
|
+
// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
|
|
5063
|
+
// resolves to a concrete responseModel). Usage without model attribution is
|
|
5064
|
+
// grouped separately so the breakdown reconciles with the session total.
|
|
5065
|
+
const usageBreakdown = getUsageCostBreakdown(entries);
|
|
4180
5066
|
let info = `${theme.bold("Session Info")}\n\n`;
|
|
4181
5067
|
if (sessionName) {
|
|
4182
5068
|
info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
|
|
@@ -4184,24 +5070,42 @@ export class InteractiveMode {
|
|
|
4184
5070
|
info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
|
|
4185
5071
|
info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
|
|
4186
5072
|
info += `${theme.bold("Messages")}\n`;
|
|
5073
|
+
info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n`;
|
|
4187
5074
|
info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
|
|
4188
5075
|
info += `${theme.fg("dim", "Assistant:")} ${stats.assistantMessages}\n`;
|
|
4189
|
-
info += `${theme.fg("dim", "
|
|
4190
|
-
info += `${theme.fg("dim", "Tool Results:")} ${stats.toolResults}\n`;
|
|
4191
|
-
info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n\n`;
|
|
5076
|
+
info += `${theme.fg("dim", "Tools:")} ${stats.toolCalls} calls, ${stats.toolResults} results\n\n`;
|
|
4192
5077
|
info += `${theme.bold("Tokens")}\n`;
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
}
|
|
4198
|
-
|
|
4199
|
-
|
|
5078
|
+
// "Input" is the full prompt volume. With cache activity, split it into
|
|
5079
|
+
// cached (served from cache) vs uncached (everything else) - the only
|
|
5080
|
+
// provider-independent split. Cache writes, where reported, are a detail
|
|
5081
|
+
// of the uncached portion.
|
|
5082
|
+
const { input, cacheRead, cacheWrite } = stats.tokens;
|
|
5083
|
+
const promptTokens = input + cacheRead + cacheWrite;
|
|
5084
|
+
info += `${theme.fg("dim", "Input:")} ${promptTokens.toLocaleString()}\n`;
|
|
5085
|
+
if (promptTokens > 0 && (cacheRead > 0 || cacheWrite > 0)) {
|
|
5086
|
+
const hitRate = theme.fg("dim", `(${((cacheRead / promptTokens) * 100).toFixed(1)}%)`);
|
|
5087
|
+
info += ` ${theme.fg("dim", "Cached:")} ${cacheRead.toLocaleString()} ${hitRate}\n`;
|
|
5088
|
+
const written = cacheWrite > 0 ? ` ${theme.fg("dim", `(${cacheWrite.toLocaleString()} written to cache)`)}` : "";
|
|
5089
|
+
info += ` ${theme.fg("dim", "Uncached:")} ${(input + cacheWrite).toLocaleString()}${written}\n`;
|
|
4200
5090
|
}
|
|
5091
|
+
info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
|
|
4201
5092
|
info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
|
|
4202
|
-
if (stats.cost > 0) {
|
|
5093
|
+
if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
|
|
4203
5094
|
info += `\n${theme.bold("Cost")}\n`;
|
|
4204
|
-
info += `${theme.fg("dim", "Total:")}
|
|
5095
|
+
info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
|
|
5096
|
+
if (usageBreakdown.length > 1) {
|
|
5097
|
+
for (const entry of usageBreakdown) {
|
|
5098
|
+
info += `\n ${theme.fg("dim", `${entry.key}:`)} $${entry.cost.toFixed(3)} ${theme.fg("dim", `(${formatTokens(entry.tokens)} tokens)`)}`;
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
5101
|
+
if (cacheWaste.missedTokens > 0) {
|
|
5102
|
+
const missLabel = cacheWaste.missCount === 1 ? "1 miss" : `${cacheWaste.missCount} misses`;
|
|
5103
|
+
const detail = `${cacheWaste.missedTokens.toLocaleString()} tokens, ${missLabel}`;
|
|
5104
|
+
info +=
|
|
5105
|
+
cacheWaste.missedCost >= 0.0001
|
|
5106
|
+
? `\n${theme.fg("dim", "Cache Re-billed:")} $${cacheWaste.missedCost.toFixed(3)} ${theme.fg("dim", `(${detail})`)}`
|
|
5107
|
+
: `\n${theme.fg("dim", "Cache Re-billed:")} ${detail}`;
|
|
5108
|
+
}
|
|
4205
5109
|
}
|
|
4206
5110
|
this.chatContainer.addChild(new Spacer(1));
|
|
4207
5111
|
this.chatContainer.addChild(new Text(info, 1, 0));
|
|
@@ -4213,7 +5117,7 @@ export class InteractiveMode {
|
|
|
4213
5117
|
const changelogMarkdown = allEntries.length > 0
|
|
4214
5118
|
? allEntries
|
|
4215
5119
|
.reverse()
|
|
4216
|
-
.map((e) => e.content)
|
|
5120
|
+
.map((e) => normalizeChangelogLinks(e.content, e))
|
|
4217
5121
|
.join("\n\n")
|
|
4218
5122
|
: "No changelog entries found.";
|
|
4219
5123
|
this.chatContainer.addChild(new Spacer(1));
|
|
@@ -4224,29 +5128,17 @@ export class InteractiveMode {
|
|
|
4224
5128
|
this.chatContainer.addChild(new DynamicBorder());
|
|
4225
5129
|
this.ui.requestRender();
|
|
4226
5130
|
}
|
|
4227
|
-
/**
|
|
4228
|
-
* Capitalize keybinding for display (e.g., "ctrl+c" -> "Ctrl+C").
|
|
4229
|
-
*/
|
|
4230
|
-
capitalizeKey(key) {
|
|
4231
|
-
return key
|
|
4232
|
-
.split("/")
|
|
4233
|
-
.map((k) => k
|
|
4234
|
-
.split("+")
|
|
4235
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
4236
|
-
.join("+"))
|
|
4237
|
-
.join("/");
|
|
4238
|
-
}
|
|
4239
5131
|
/**
|
|
4240
5132
|
* Get capitalized display string for an app keybinding action.
|
|
4241
5133
|
*/
|
|
4242
5134
|
getAppKeyDisplay(action) {
|
|
4243
|
-
return
|
|
5135
|
+
return keyDisplayText(action);
|
|
4244
5136
|
}
|
|
4245
5137
|
/**
|
|
4246
5138
|
* Get capitalized display string for an editor keybinding action.
|
|
4247
5139
|
*/
|
|
4248
5140
|
getEditorKeyDisplay(action) {
|
|
4249
|
-
return
|
|
5141
|
+
return keyDisplayText(action);
|
|
4250
5142
|
}
|
|
4251
5143
|
handleHotkeysCommand() {
|
|
4252
5144
|
// Navigation keybindings
|
|
@@ -4285,6 +5177,7 @@ export class InteractiveMode {
|
|
|
4285
5177
|
const toggleThinking = this.getAppKeyDisplay("app.thinking.toggle");
|
|
4286
5178
|
const externalEditor = this.getAppKeyDisplay("app.editor.external");
|
|
4287
5179
|
const cycleModelBackward = this.getAppKeyDisplay("app.model.cycleBackward");
|
|
5180
|
+
const copyMessage = this.getAppKeyDisplay("app.message.copy");
|
|
4288
5181
|
const followUp = this.getAppKeyDisplay("app.message.followUp");
|
|
4289
5182
|
const dequeue = this.getAppKeyDisplay("app.message.dequeue");
|
|
4290
5183
|
const pasteImage = this.getAppKeyDisplay("app.clipboard.pasteImage");
|
|
@@ -4292,7 +5185,7 @@ export class InteractiveMode {
|
|
|
4292
5185
|
**Navigation**
|
|
4293
5186
|
| Key | Action |
|
|
4294
5187
|
|-----|--------|
|
|
4295
|
-
| \`${cursorUp}\` / \`${cursorDown}\` / \`${cursorLeft}\` / \`${cursorRight}\` | Move cursor / browse history
|
|
5188
|
+
| \`${cursorUp}\` / \`${cursorDown}\` / \`${cursorLeft}\` / \`${cursorRight}\` | Move cursor / browse history |
|
|
4296
5189
|
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
|
|
4297
5190
|
| \`${cursorLineStart}\` | Start of line |
|
|
4298
5191
|
| \`${cursorLineEnd}\` | End of line |
|
|
@@ -4327,9 +5220,10 @@ export class InteractiveMode {
|
|
|
4327
5220
|
| \`${expandTools}\` | Toggle tool output expansion |
|
|
4328
5221
|
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
4329
5222
|
| \`${externalEditor}\` | Edit message in external editor |
|
|
5223
|
+
| \`${copyMessage}\` | Copy last assistant message |
|
|
4330
5224
|
| \`${followUp}\` | Queue follow-up message |
|
|
4331
5225
|
| \`${dequeue}\` | Restore queued messages |
|
|
4332
|
-
| \`${pasteImage}\` | Paste image from clipboard |
|
|
5226
|
+
| \`${pasteImage}\` | Paste image or text from clipboard |
|
|
4333
5227
|
| \`/\` | Slash commands |
|
|
4334
5228
|
| \`!\` | Run bash command |
|
|
4335
5229
|
| \`!!\` | Run bash command (excluded from context) |
|
|
@@ -4345,7 +5239,7 @@ export class InteractiveMode {
|
|
|
4345
5239
|
`;
|
|
4346
5240
|
for (const [key, shortcut] of shortcuts) {
|
|
4347
5241
|
const description = shortcut.description ?? shortcut.extensionPath;
|
|
4348
|
-
const keyDisplay = key
|
|
5242
|
+
const keyDisplay = formatKeyText(key, { capitalize: true });
|
|
4349
5243
|
hotkeys += `| \`${keyDisplay}\` | ${description} |\n`;
|
|
4350
5244
|
}
|
|
4351
5245
|
}
|
|
@@ -4358,17 +5252,12 @@ export class InteractiveMode {
|
|
|
4358
5252
|
this.ui.requestRender();
|
|
4359
5253
|
}
|
|
4360
5254
|
async handleClearCommand() {
|
|
4361
|
-
|
|
4362
|
-
this.loadingAnimation.stop();
|
|
4363
|
-
this.loadingAnimation = undefined;
|
|
4364
|
-
}
|
|
4365
|
-
this.statusContainer.clear();
|
|
5255
|
+
this.clearStatusIndicator();
|
|
4366
5256
|
try {
|
|
4367
5257
|
const result = await this.runtimeHost.newSession();
|
|
4368
5258
|
if (result.cancelled) {
|
|
4369
5259
|
return;
|
|
4370
5260
|
}
|
|
4371
|
-
this.renderCurrentSessionState();
|
|
4372
5261
|
this.chatContainer.addChild(new Spacer(1));
|
|
4373
5262
|
this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
|
|
4374
5263
|
this.ui.requestRender();
|
|
@@ -4409,11 +5298,6 @@ export class InteractiveMode {
|
|
|
4409
5298
|
this.chatContainer.addChild(new ArminComponent(this.ui));
|
|
4410
5299
|
this.ui.requestRender();
|
|
4411
5300
|
}
|
|
4412
|
-
handleDementedDelves() {
|
|
4413
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4414
|
-
this.chatContainer.addChild(new EarendilAnnouncementComponent());
|
|
4415
|
-
this.ui.requestRender();
|
|
4416
|
-
}
|
|
4417
5301
|
handleDaxnuts() {
|
|
4418
5302
|
this.chatContainer.addChild(new Spacer(1));
|
|
4419
5303
|
this.chatContainer.addChild(new DaxnutsComponent(this.ui));
|
|
@@ -4490,17 +5374,7 @@ export class InteractiveMode {
|
|
|
4490
5374
|
this.ui.requestRender();
|
|
4491
5375
|
}
|
|
4492
5376
|
async handleCompactCommand(customInstructions) {
|
|
4493
|
-
|
|
4494
|
-
const messageCount = entries.filter((e) => e.type === "message").length;
|
|
4495
|
-
if (messageCount < 2) {
|
|
4496
|
-
this.showWarning("Nothing to compact (no messages yet)");
|
|
4497
|
-
return;
|
|
4498
|
-
}
|
|
4499
|
-
if (this.loadingAnimation) {
|
|
4500
|
-
this.loadingAnimation.stop();
|
|
4501
|
-
this.loadingAnimation = undefined;
|
|
4502
|
-
}
|
|
4503
|
-
this.statusContainer.clear();
|
|
5377
|
+
this.clearStatusIndicator();
|
|
4504
5378
|
try {
|
|
4505
5379
|
await this.session.compact(customInstructions);
|
|
4506
5380
|
}
|
|
@@ -4508,15 +5382,13 @@ export class InteractiveMode {
|
|
|
4508
5382
|
// Ignore, will be emitted as an event
|
|
4509
5383
|
}
|
|
4510
5384
|
}
|
|
4511
|
-
stop() {
|
|
4512
|
-
this.
|
|
5385
|
+
stop(fullscreenExitOutput = this.settingsManager.getFullscreenExitOutput()) {
|
|
5386
|
+
this.disposeActiveSelector();
|
|
4513
5387
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
4514
5388
|
this.ui.terminal.setProgress(false);
|
|
4515
5389
|
}
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
this.loadingAnimation = undefined;
|
|
4519
|
-
}
|
|
5390
|
+
this.clearStatusIndicator();
|
|
5391
|
+
this.themeController.disableAutoSync();
|
|
4520
5392
|
this.clearExtensionTerminalInputListeners();
|
|
4521
5393
|
this.footer.dispose();
|
|
4522
5394
|
this.footerDataProvider.dispose();
|
|
@@ -4524,9 +5396,10 @@ export class InteractiveMode {
|
|
|
4524
5396
|
this.unsubscribe();
|
|
4525
5397
|
}
|
|
4526
5398
|
if (this.isInitialized) {
|
|
4527
|
-
this.
|
|
5399
|
+
this.stopInteractiveTui(fullscreenExitOutput);
|
|
4528
5400
|
this.isInitialized = false;
|
|
4529
5401
|
}
|
|
5402
|
+
this.unregisterSignalHandlers();
|
|
4530
5403
|
}
|
|
4531
5404
|
}
|
|
4532
5405
|
//# sourceMappingURL=interactive-mode.js.map
|