@phi-code-admin/phi-code 0.74.3 → 0.75.1
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 +1186 -4
- package/README.md +478 -379
- package/dist/bun/cli.d.ts +3 -0
- package/dist/bun/cli.d.ts.map +1 -0
- package/dist/bun/cli.js +9 -0
- package/dist/bun/cli.js.map +1 -0
- package/dist/bun/register-bedrock.d.ts +2 -0
- package/dist/bun/register-bedrock.d.ts.map +1 -0
- package/dist/bun/register-bedrock.js +4 -0
- package/dist/bun/register-bedrock.js.map +1 -0
- package/dist/bun/restore-sandbox-env.d.ts +13 -0
- package/dist/bun/restore-sandbox-env.d.ts.map +1 -0
- package/dist/bun/restore-sandbox-env.js +32 -0
- package/dist/bun/restore-sandbox-env.js.map +1 -0
- package/dist/cli/args.d.ts +12 -7
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +87 -45
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +4 -0
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/cli/initial-message.d.ts +18 -0
- package/dist/cli/initial-message.d.ts.map +1 -0
- package/dist/cli/initial-message.js +22 -0
- package/dist/cli/initial-message.js.map +1 -0
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +7 -1
- package/dist/cli/list-models.js.map +1 -1
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js +2 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -5
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +226 -30
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +117 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -0
- package/dist/core/agent-session-runtime.js +300 -0
- package/dist/core/agent-session-runtime.js.map +1 -0
- package/dist/core/agent-session-services.d.ts +86 -0
- package/dist/core/agent-session-services.d.ts.map +1 -0
- package/dist/core/agent-session-services.js +117 -0
- package/dist/core/agent-session-services.js.map +1 -0
- package/dist/core/agent-session.d.ts +63 -82
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +674 -628
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/api-key-store.d.ts +87 -0
- package/dist/core/api-key-store.d.ts.map +1 -0
- package/dist/core/api-key-store.js +168 -0
- package/dist/core/api-key-store.js.map +1 -0
- package/dist/core/auth-guidance.d.ts +5 -0
- package/dist/core/auth-guidance.d.ts.map +1 -0
- package/dist/core/auth-guidance.js +21 -0
- package/dist/core/auth-guidance.js.map +1 -0
- package/dist/core/auth-storage.d.ts +12 -5
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +34 -8
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts +0 -15
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +28 -129
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +2 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +3 -2
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +4 -4
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +32 -27
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/utils.d.ts.map +1 -1
- package/dist/core/compaction/utils.js.map +1 -1
- package/dist/core/config-watcher.d.ts +47 -0
- package/dist/core/config-watcher.d.ts.map +1 -0
- package/dist/core/config-watcher.js +135 -0
- package/dist/core/config-watcher.js.map +1 -0
- package/dist/core/default-models.json +80 -0
- package/dist/core/defaults.d.ts.map +1 -1
- package/dist/core/diagnostics.d.ts.map +1 -1
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +7 -3
- package/dist/core/exec.js.map +1 -1
- 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 +7 -4
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +15 -13
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/export-html/template.css +112 -17
- package/dist/core/export-html/template.html +1 -0
- package/dist/core/export-html/template.js +312 -64
- package/dist/core/export-html/tool-renderer.d.ts +9 -10
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +61 -16
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/index.d.ts +5 -4
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +2 -2
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +0 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +98 -18
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +27 -14
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +299 -115
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +200 -44
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +10 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/wrapper.d.ts +4 -11
- package/dist/core/extensions/wrapper.d.ts.map +1 -1
- package/dist/core/extensions/wrapper.js +7 -87
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +22 -2
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +225 -49
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/index.d.ts +5 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts +348 -50
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +276 -132
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry.d.ts +41 -5
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +316 -136
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +6 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +70 -37
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/output-guard.d.ts +6 -0
- package/dist/core/output-guard.d.ts.map +1 -0
- package/dist/core/output-guard.js +59 -0
- package/dist/core/output-guard.js.map +1 -0
- package/dist/core/package-manager.d.ts +49 -7
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +655 -122
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/prompt-templates.d.ts +12 -10
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +37 -38
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/provider-display-names.d.ts +2 -0
- package/dist/core/provider-display-names.d.ts.map +1 -0
- package/dist/core/provider-display-names.js +33 -0
- package/dist/core/provider-display-names.js.map +1 -0
- package/dist/core/resolve-config-value.d.ts +6 -0
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +75 -8
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/resource-loader.d.ts +18 -8
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +217 -123
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +25 -8
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +84 -37
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-cwd.d.ts +19 -0
- package/dist/core/session-cwd.d.ts.map +1 -0
- package/dist/core/session-cwd.js +38 -0
- package/dist/core/session-cwd.js.map +1 -0
- package/dist/core/session-manager.d.ts +11 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +42 -27
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +34 -5
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +113 -13
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +13 -11
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +59 -19
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts +2 -3
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +9 -6
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/source-info.d.ts +18 -0
- package/dist/core/source-info.d.ts.map +1 -0
- package/dist/core/source-info.js +19 -0
- package/dist/core/source-info.js.map +1 -0
- package/dist/core/system-prompt.d.ts +3 -3
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +16 -55
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/telemetry.d.ts +3 -0
- package/dist/core/telemetry.d.ts.map +1 -0
- package/dist/core/telemetry.js +9 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/core/timings.d.ts +1 -0
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +6 -0
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/bash.d.ts +27 -14
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +301 -208
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +23 -1
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +154 -59
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +22 -12
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +243 -65
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts +6 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/core/tools/file-mutation-queue.js +37 -0
- package/dist/core/tools/file-mutation-queue.js.map +1 -0
- package/dist/core/tools/find.d.ts +10 -14
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +202 -110
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +14 -22
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +100 -35
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +27 -60
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +96 -45
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +8 -11
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +66 -15
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +50 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -0
- package/dist/core/tools/output-accumulator.js +178 -0
- package/dist/core/tools/output-accumulator.js.map +1 -0
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +1 -1
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/core/tools/read.d.ts +9 -13
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +175 -52
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +21 -0
- package/dist/core/tools/render-utils.d.ts.map +1 -0
- package/dist/core/tools/render-utils.js +49 -0
- package/dist/core/tools/render-utils.js.map +1 -0
- package/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -0
- package/dist/core/tools/tool-definition-wrapper.js +34 -0
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -0
- package/dist/core/tools/truncate.d.ts.map +1 -1
- package/dist/core/tools/truncate.js.map +1 -1
- package/dist/core/tools/write.d.ts +8 -11
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +167 -32
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +12 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -10
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +5 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +326 -404
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts +2 -2
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +24 -4
- package/dist/migrations.js.map +1 -1
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/armin.d.ts.map +1 -1
- package/dist/modes/interactive/components/armin.js +10 -6
- package/dist/modes/interactive/components/armin.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +5 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +32 -3
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +0 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +31 -12
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js +7 -1
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +5 -3
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +5 -3
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +49 -16
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.js +5 -0
- package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +14 -7
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +6 -1
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
- package/dist/modes/interactive/components/daxnuts.js +8 -6
- 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.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +1 -0
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
- package/dist/modes/interactive/components/earendil-announcement.d.ts.map +1 -0
- package/dist/modes/interactive/components/earendil-announcement.js +40 -0
- package/dist/modes/interactive/components/earendil-announcement.js.map +1 -0
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +16 -10
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-input.js +13 -7
- package/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +18 -11
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +7 -2
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts +8 -36
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +23 -48
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +5 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +35 -14
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +41 -22
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +18 -6
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +104 -31
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +5 -12
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +61 -42
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +2 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +109 -73
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +9 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +84 -4
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.js +6 -1
- package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +5 -3
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/theme-selector.js +7 -1
- package/dist/modes/interactive/components/theme-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +6 -1
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +20 -34
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +158 -636
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +21 -2
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +224 -52
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts +2 -2
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +20 -16
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +1 -0
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +8 -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.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +67 -39
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1587 -710
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -1
- package/dist/modes/interactive/theme/light.json +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +3 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +101 -72
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts +2 -2
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +107 -77
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/jsonl.d.ts +17 -0
- package/dist/modes/rpc/jsonl.d.ts.map +1 -0
- package/dist/modes/rpc/jsonl.js +49 -0
- package/dist/modes/rpc/jsonl.js.map +1 -0
- package/dist/modes/rpc/rpc-client.d.ts +8 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +22 -16
- 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 +184 -94
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +14 -4
- 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 +4 -0
- package/dist/package-manager-cli.d.ts.map +1 -0
- package/dist/package-manager-cli.js +460 -0
- package/dist/package-manager-cli.js.map +1 -0
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js.map +1 -1
- package/dist/utils/child-process.d.ts +12 -0
- package/dist/utils/child-process.d.ts.map +1 -0
- package/dist/utils/child-process.js +86 -0
- package/dist/utils/child-process.js.map +1 -0
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +94 -11
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +1 -0
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/clipboard.d.ts +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +96 -46
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/exif-orientation.d.ts +5 -0
- package/dist/utils/exif-orientation.d.ts.map +1 -0
- package/dist/utils/exif-orientation.js +158 -0
- package/dist/utils/exif-orientation.js.map +1 -0
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/fs-watch.d.ts +5 -0
- package/dist/utils/fs-watch.d.ts.map +1 -0
- package/dist/utils/fs-watch.js +25 -0
- package/dist/utils/fs-watch.js.map +1 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/image-convert.d.ts.map +1 -1
- package/dist/utils/image-convert.js +5 -1
- package/dist/utils/image-convert.js.map +1 -1
- package/dist/utils/image-resize.d.ts +5 -5
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +51 -95
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/mime.d.ts.map +1 -1
- package/dist/utils/mime.js.map +1 -1
- package/dist/utils/paths.d.ts +16 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +50 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/photon.d.ts.map +1 -1
- package/dist/utils/photon.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 +10 -6
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +29 -25
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/sleep.d.ts.map +1 -1
- package/dist/utils/sleep.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +11 -6
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +14 -0
- package/dist/utils/version-check.d.ts.map +1 -0
- package/dist/utils/version-check.js +77 -0
- package/dist/utils/version-check.js.map +1 -0
- package/docs/compaction.md +394 -0
- package/docs/custom-provider.md +646 -0
- package/docs/development.md +71 -0
- package/docs/docs.json +148 -0
- package/docs/extensions.md +2596 -0
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/exy.png +0 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/index.md +70 -0
- package/docs/json.md +82 -0
- package/docs/keybindings.md +197 -0
- package/docs/models.md +474 -0
- package/docs/packages.md +223 -0
- package/docs/prompt-templates.md +88 -0
- package/docs/providers.md +243 -0
- package/docs/quickstart.md +142 -0
- package/docs/rpc.md +1407 -0
- package/docs/sdk.md +1149 -0
- package/docs/session-format.md +412 -0
- package/docs/sessions.md +137 -0
- package/docs/settings.md +279 -0
- package/docs/shell-aliases.md +13 -0
- package/docs/skills.md +232 -0
- package/docs/terminal-setup.md +106 -0
- package/docs/termux.md +127 -0
- package/docs/themes.md +295 -0
- package/docs/tmux.md +61 -0
- package/docs/tui.md +918 -0
- package/docs/usage.md +277 -0
- package/docs/windows.md +17 -0
- package/examples/README.md +25 -0
- package/examples/extensions/README.md +208 -0
- package/examples/extensions/auto-commit-on-exit.ts +49 -0
- package/examples/extensions/bash-spawn-hook.ts +30 -0
- package/examples/extensions/bookmark.ts +50 -0
- package/examples/extensions/border-status-editor.ts +150 -0
- package/examples/extensions/built-in-tool-renderer.ts +249 -0
- package/examples/extensions/claude-rules.ts +86 -0
- package/examples/extensions/commands.ts +72 -0
- package/examples/extensions/confirm-destructive.ts +59 -0
- package/examples/extensions/custom-compaction.ts +127 -0
- package/examples/extensions/custom-footer.ts +64 -0
- package/examples/extensions/custom-header.ts +73 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/examples/extensions/custom-provider-anthropic/package.json +19 -0
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
- package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
- package/examples/extensions/dirty-repo-guard.ts +56 -0
- package/examples/extensions/doom-overlay/README.md +46 -0
- package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +152 -0
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
- package/examples/extensions/doom-overlay/doom-component.ts +132 -0
- package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
- package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
- package/examples/extensions/doom-overlay/index.ts +74 -0
- package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
- package/examples/extensions/dynamic-resources/SKILL.md +8 -0
- package/examples/extensions/dynamic-resources/dynamic.json +79 -0
- package/examples/extensions/dynamic-resources/dynamic.md +5 -0
- package/examples/extensions/dynamic-resources/index.ts +15 -0
- package/examples/extensions/dynamic-tools.ts +74 -0
- package/examples/extensions/event-bus.ts +43 -0
- package/examples/extensions/file-trigger.ts +41 -0
- package/examples/extensions/git-checkpoint.ts +53 -0
- package/examples/extensions/github-issue-autocomplete.ts +185 -0
- package/examples/extensions/handoff.ts +191 -0
- package/examples/extensions/hello.ts +26 -0
- package/examples/extensions/hidden-thinking-label.ts +53 -0
- package/examples/extensions/inline-bash.ts +94 -0
- package/examples/extensions/input-transform.ts +43 -0
- package/examples/extensions/interactive-shell.ts +196 -0
- package/examples/extensions/mac-system-theme.ts +47 -0
- package/examples/extensions/message-renderer.ts +59 -0
- package/examples/extensions/minimal-mode.ts +426 -0
- package/examples/extensions/modal-editor.ts +85 -0
- package/examples/extensions/model-status.ts +31 -0
- package/examples/extensions/notify.ts +55 -0
- package/examples/extensions/overlay-qa-tests.ts +1348 -0
- package/examples/extensions/overlay-test.ts +150 -0
- package/examples/extensions/permission-gate.ts +34 -0
- package/examples/extensions/pirate.ts +47 -0
- package/examples/extensions/plan-mode/README.md +65 -0
- package/examples/extensions/plan-mode/index.ts +340 -0
- package/examples/extensions/plan-mode/utils.ts +168 -0
- package/examples/extensions/preset.ts +430 -0
- package/examples/extensions/prompt-customizer.ts +97 -0
- package/examples/extensions/protected-paths.ts +30 -0
- package/examples/extensions/provider-payload.ts +18 -0
- package/examples/extensions/qna.ts +122 -0
- package/examples/extensions/question.ts +264 -0
- package/examples/extensions/questionnaire.ts +427 -0
- package/examples/extensions/rainbow-editor.ts +88 -0
- package/examples/extensions/reload-runtime.ts +37 -0
- package/examples/extensions/rpc-demo.ts +118 -0
- package/examples/extensions/sandbox/index.ts +321 -0
- package/examples/extensions/sandbox/package-lock.json +92 -0
- package/examples/extensions/sandbox/package.json +19 -0
- package/examples/extensions/send-user-message.ts +97 -0
- package/examples/extensions/session-name.ts +27 -0
- package/examples/extensions/shutdown-command.ts +63 -0
- package/examples/extensions/snake.ts +343 -0
- package/examples/extensions/space-invaders.ts +560 -0
- package/examples/extensions/ssh.ts +220 -0
- package/examples/extensions/status-line.ts +32 -0
- package/examples/extensions/structured-output.ts +65 -0
- package/examples/extensions/subagent/README.md +172 -0
- package/examples/extensions/subagent/agents/planner.md +37 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/subagent/agents/scout.md +50 -0
- package/examples/extensions/subagent/agents/worker.md +24 -0
- package/examples/extensions/subagent/agents.ts +126 -0
- package/examples/extensions/subagent/index.ts +987 -0
- package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
- package/examples/extensions/subagent/prompts/implement.md +10 -0
- package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
- package/examples/extensions/summarize.ts +206 -0
- package/examples/extensions/system-prompt-header.ts +17 -0
- package/examples/extensions/tic-tac-toe.ts +1008 -0
- package/examples/extensions/timed-confirm.ts +70 -0
- package/examples/extensions/titlebar-spinner.ts +58 -0
- package/examples/extensions/todo.ts +297 -0
- package/examples/extensions/tool-override.ts +144 -0
- package/examples/extensions/tools.ts +141 -0
- package/examples/extensions/trigger-compact.ts +50 -0
- package/examples/extensions/truncated-tool.ts +195 -0
- package/examples/extensions/widget-placement.ts +9 -0
- package/examples/extensions/with-deps/index.ts +32 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +22 -0
- package/examples/extensions/working-indicator.ts +123 -0
- package/examples/extensions/working-message-test.ts +25 -0
- package/examples/rpc-extension-ui.ts +632 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +62 -0
- package/examples/sdk/04-skills.ts +55 -0
- package/examples/sdk/05-tools.ts +44 -0
- package/examples/sdk/06-extensions.ts +90 -0
- package/examples/sdk/07-context-files.ts +42 -0
- package/examples/sdk/08-prompt-templates.ts +51 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +48 -0
- package/examples/sdk/10-settings.ts +53 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +73 -0
- package/examples/sdk/13-session-runtime.ts +67 -0
- package/examples/sdk/README.md +147 -0
- package/extensions/phi/init.ts +15 -1
- package/extensions/phi/keys.ts +186 -0
- package/extensions/phi/providers/alibaba.ts +126 -0
- package/extensions/phi/providers/opencode-go.ts +204 -0
- package/extensions/phi/setup.ts +692 -0
- package/extensions/phi/smart-router.ts +8 -0
- package/package.json +17 -12
- package/scripts/copy-assets.sh +0 -0
- package/scripts/migrate-sessions.sh +0 -0
|
@@ -6,35 +6,48 @@ 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 { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "phi-code-tui";
|
|
10
9
|
import { spawn, spawnSync } from "child_process";
|
|
11
|
-
import {
|
|
10
|
+
import { getProviders, } from "phi-code-ai";
|
|
11
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "phi-code-tui";
|
|
12
|
+
import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
12
13
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
14
|
+
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
13
15
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
14
16
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
15
17
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
16
|
-
import { resolveModelScope } from "../../core/model-resolver.js";
|
|
18
|
+
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
19
|
+
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
20
|
+
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
21
|
+
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
17
22
|
import { SessionManager } from "../../core/session-manager.js";
|
|
18
23
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
24
|
+
import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
|
|
19
25
|
import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
|
|
20
26
|
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
21
27
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
28
|
+
import { parseGitUrl } from "../../utils/git.js";
|
|
29
|
+
import { getCwdRelativePath } from "../../utils/paths.js";
|
|
30
|
+
import { getPiUserAgent } from "../../utils/pi-user-agent.js";
|
|
31
|
+
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
22
32
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
33
|
+
import { checkForNewPiVersion } from "../../utils/version-check.js";
|
|
23
34
|
import { ArminComponent } from "./components/armin.js";
|
|
24
35
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
25
36
|
import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
26
37
|
import { BorderedLoader } from "./components/bordered-loader.js";
|
|
27
38
|
import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
|
|
28
39
|
import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
|
|
40
|
+
import { CountdownTimer } from "./components/countdown-timer.js";
|
|
29
41
|
import { CustomEditor } from "./components/custom-editor.js";
|
|
30
42
|
import { CustomMessageComponent } from "./components/custom-message.js";
|
|
31
43
|
import { DaxnutsComponent } from "./components/daxnuts.js";
|
|
32
44
|
import { DynamicBorder } from "./components/dynamic-border.js";
|
|
45
|
+
import { EarendilAnnouncementComponent } from "./components/earendil-announcement.js";
|
|
33
46
|
import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
34
47
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
35
48
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
36
49
|
import { FooterComponent } from "./components/footer.js";
|
|
37
|
-
import {
|
|
50
|
+
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
38
51
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
39
52
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
40
53
|
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
@@ -46,12 +59,140 @@ import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
|
46
59
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
47
60
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
48
61
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
49
|
-
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, Theme, theme, } from "./theme/theme.js";
|
|
62
|
+
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
50
63
|
function isExpandable(obj) {
|
|
51
64
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
52
65
|
}
|
|
66
|
+
class ExpandableText extends Text {
|
|
67
|
+
getCollapsedText;
|
|
68
|
+
getExpandedText;
|
|
69
|
+
constructor(getCollapsedText, getExpandedText, expanded = false, paddingX = 0, paddingY = 0) {
|
|
70
|
+
super(expanded ? getExpandedText() : getCollapsedText(), paddingX, paddingY);
|
|
71
|
+
this.getCollapsedText = getCollapsedText;
|
|
72
|
+
this.getExpandedText = getExpandedText;
|
|
73
|
+
}
|
|
74
|
+
setExpanded(expanded) {
|
|
75
|
+
this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
|
|
79
|
+
function isDeadTerminalError(error) {
|
|
80
|
+
if (!error || typeof error !== "object" || !("code" in error)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const code = error.code;
|
|
84
|
+
return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
|
|
85
|
+
}
|
|
86
|
+
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.";
|
|
87
|
+
function isAnthropicSubscriptionAuthKey(apiKey) {
|
|
88
|
+
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
|
89
|
+
}
|
|
90
|
+
function isUnknownModel(model) {
|
|
91
|
+
return !!model && model.provider === "unknown" && model.id === "unknown" && model.api === "unknown";
|
|
92
|
+
}
|
|
93
|
+
function hasDefaultModelProvider(providerId) {
|
|
94
|
+
return providerId in defaultModelPerProvider;
|
|
95
|
+
}
|
|
96
|
+
const BEDROCK_PROVIDER_ID = "amazon-bedrock";
|
|
97
|
+
const BUILT_IN_MODEL_PROVIDERS = new Set(getProviders());
|
|
98
|
+
export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProviderIds = BUILT_IN_MODEL_PROVIDERS) {
|
|
99
|
+
if (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (builtInProviderIds.has(providerId)) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return !oauthProviderIds.has(providerId);
|
|
106
|
+
}
|
|
53
107
|
export class InteractiveMode {
|
|
108
|
+
options;
|
|
109
|
+
runtimeHost;
|
|
110
|
+
ui;
|
|
111
|
+
chatContainer;
|
|
112
|
+
pendingMessagesContainer;
|
|
113
|
+
statusContainer;
|
|
114
|
+
defaultEditor;
|
|
115
|
+
editor;
|
|
116
|
+
editorComponentFactory;
|
|
117
|
+
autocompleteProvider;
|
|
118
|
+
autocompleteProviderWrappers = [];
|
|
119
|
+
fdPath;
|
|
120
|
+
editorContainer;
|
|
121
|
+
footer;
|
|
122
|
+
footerDataProvider;
|
|
123
|
+
// Stored so the same manager can be injected into custom editors, selectors, and extension UI.
|
|
124
|
+
keybindings;
|
|
125
|
+
version;
|
|
126
|
+
isInitialized = false;
|
|
127
|
+
onInputCallback;
|
|
128
|
+
loadingAnimation = undefined;
|
|
129
|
+
workingMessage = undefined;
|
|
130
|
+
workingVisible = true;
|
|
131
|
+
workingIndicatorOptions = undefined;
|
|
132
|
+
defaultWorkingMessage = "Working...";
|
|
133
|
+
defaultHiddenThinkingLabel = "Thinking...";
|
|
134
|
+
hiddenThinkingLabel = this.defaultHiddenThinkingLabel;
|
|
135
|
+
lastSigintTime = 0;
|
|
136
|
+
lastEscapeTime = 0;
|
|
137
|
+
changelogMarkdown = undefined;
|
|
138
|
+
startupNoticesShown = false;
|
|
139
|
+
anthropicSubscriptionWarningShown = false;
|
|
140
|
+
// Status line tracking (for mutating immediately-sequential status updates)
|
|
141
|
+
lastStatusSpacer = undefined;
|
|
142
|
+
lastStatusText = undefined;
|
|
143
|
+
// Streaming message tracking
|
|
144
|
+
streamingComponent = undefined;
|
|
145
|
+
streamingMessage = undefined;
|
|
146
|
+
// Tool execution tracking: toolCallId -> component
|
|
147
|
+
pendingTools = new Map();
|
|
148
|
+
// Tool output expansion state
|
|
149
|
+
toolOutputExpanded = false;
|
|
150
|
+
// Thinking block visibility state
|
|
151
|
+
hideThinkingBlock = false;
|
|
152
|
+
// Skill commands: command name -> skill file path
|
|
153
|
+
skillCommands = new Map();
|
|
154
|
+
// Agent subscription unsubscribe function
|
|
155
|
+
unsubscribe;
|
|
156
|
+
signalCleanupHandlers = [];
|
|
157
|
+
// Track if editor is in bash mode (text starts with !)
|
|
158
|
+
isBashMode = false;
|
|
159
|
+
// Track current bash execution component
|
|
160
|
+
bashComponent = undefined;
|
|
161
|
+
// Track pending bash components (shown in pending area, moved to chat on submit)
|
|
162
|
+
pendingBashComponents = [];
|
|
163
|
+
// Auto-compaction state
|
|
164
|
+
autoCompactionLoader = undefined;
|
|
165
|
+
autoCompactionEscapeHandler;
|
|
166
|
+
// Auto-retry state
|
|
167
|
+
retryLoader = undefined;
|
|
168
|
+
retryCountdown = undefined;
|
|
169
|
+
retryEscapeHandler;
|
|
170
|
+
// Messages queued while compaction is running
|
|
171
|
+
compactionQueuedMessages = [];
|
|
172
|
+
// Shutdown state
|
|
173
|
+
shutdownRequested = false;
|
|
174
|
+
// Extension UI state
|
|
175
|
+
extensionSelector = undefined;
|
|
176
|
+
extensionInput = undefined;
|
|
177
|
+
extensionEditor = undefined;
|
|
178
|
+
extensionTerminalInputUnsubscribers = new Set();
|
|
179
|
+
// Extension widgets (components rendered above/below the editor)
|
|
180
|
+
extensionWidgetsAbove = new Map();
|
|
181
|
+
extensionWidgetsBelow = new Map();
|
|
182
|
+
widgetContainerAbove;
|
|
183
|
+
widgetContainerBelow;
|
|
184
|
+
// Custom footer from extension (undefined = use built-in footer)
|
|
185
|
+
customFooter = undefined;
|
|
186
|
+
// Header container that holds the built-in or custom header
|
|
187
|
+
headerContainer;
|
|
188
|
+
// Built-in header (logo + keybinding hints + changelog)
|
|
189
|
+
builtInHeader = undefined;
|
|
190
|
+
// Custom header from extension (undefined = use built-in header)
|
|
191
|
+
customHeader = undefined;
|
|
54
192
|
// Convenience accessors
|
|
193
|
+
get session() {
|
|
194
|
+
return this.runtimeHost.session;
|
|
195
|
+
}
|
|
55
196
|
get agent() {
|
|
56
197
|
return this.session.agent;
|
|
57
198
|
}
|
|
@@ -61,63 +202,15 @@ export class InteractiveMode {
|
|
|
61
202
|
get settingsManager() {
|
|
62
203
|
return this.session.settingsManager;
|
|
63
204
|
}
|
|
64
|
-
constructor(
|
|
205
|
+
constructor(runtimeHost, options = {}) {
|
|
65
206
|
this.options = options;
|
|
66
|
-
this.
|
|
67
|
-
this.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Status line tracking (for mutating immediately-sequential status updates)
|
|
74
|
-
this.lastStatusSpacer = undefined;
|
|
75
|
-
this.lastStatusText = undefined;
|
|
76
|
-
// Streaming message tracking
|
|
77
|
-
this.streamingComponent = undefined;
|
|
78
|
-
this.streamingMessage = undefined;
|
|
79
|
-
// Tool execution tracking: toolCallId -> component
|
|
80
|
-
this.pendingTools = new Map();
|
|
81
|
-
// Tool output expansion state
|
|
82
|
-
this.toolOutputExpanded = false;
|
|
83
|
-
// Thinking block visibility state
|
|
84
|
-
this.hideThinkingBlock = false;
|
|
85
|
-
// Skill commands: command name -> skill file path
|
|
86
|
-
this.skillCommands = new Map();
|
|
87
|
-
// Track if editor is in bash mode (text starts with !)
|
|
88
|
-
this.isBashMode = false;
|
|
89
|
-
// Track current bash execution component
|
|
90
|
-
this.bashComponent = undefined;
|
|
91
|
-
// Track pending bash components (shown in pending area, moved to chat on submit)
|
|
92
|
-
this.pendingBashComponents = [];
|
|
93
|
-
// Auto-compaction state
|
|
94
|
-
this.autoCompactionLoader = undefined;
|
|
95
|
-
// Auto-retry state
|
|
96
|
-
this.retryLoader = undefined;
|
|
97
|
-
// Messages queued while compaction is running
|
|
98
|
-
this.compactionQueuedMessages = [];
|
|
99
|
-
// Shutdown state
|
|
100
|
-
this.shutdownRequested = false;
|
|
101
|
-
// Extension UI state
|
|
102
|
-
this.extensionSelector = undefined;
|
|
103
|
-
this.extensionInput = undefined;
|
|
104
|
-
this.extensionEditor = undefined;
|
|
105
|
-
this.extensionTerminalInputUnsubscribers = new Set();
|
|
106
|
-
// Extension widgets (components rendered above/below the editor)
|
|
107
|
-
this.extensionWidgetsAbove = new Map();
|
|
108
|
-
this.extensionWidgetsBelow = new Map();
|
|
109
|
-
// Custom footer from extension (undefined = use built-in footer)
|
|
110
|
-
this.customFooter = undefined;
|
|
111
|
-
// Built-in header (logo + keybinding hints + changelog)
|
|
112
|
-
this.builtInHeader = undefined;
|
|
113
|
-
// Custom header from extension (undefined = use built-in header)
|
|
114
|
-
this.customHeader = undefined;
|
|
115
|
-
/**
|
|
116
|
-
* Gracefully shutdown the agent.
|
|
117
|
-
* Emits shutdown event to extensions, then exits.
|
|
118
|
-
*/
|
|
119
|
-
this.isShuttingDown = false;
|
|
120
|
-
this.session = session;
|
|
207
|
+
this.runtimeHost = runtimeHost;
|
|
208
|
+
this.runtimeHost.setBeforeSessionInvalidate(() => {
|
|
209
|
+
this.resetExtensionUI();
|
|
210
|
+
});
|
|
211
|
+
this.runtimeHost.setRebindSession(async () => {
|
|
212
|
+
await this.rebindCurrentSession();
|
|
213
|
+
});
|
|
121
214
|
this.version = VERSION;
|
|
122
215
|
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
|
|
123
216
|
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
|
@@ -128,6 +221,7 @@ export class InteractiveMode {
|
|
|
128
221
|
this.widgetContainerAbove = new Container();
|
|
129
222
|
this.widgetContainerBelow = new Container();
|
|
130
223
|
this.keybindings = KeybindingsManager.create();
|
|
224
|
+
setKeybindings(this.keybindings);
|
|
131
225
|
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
|
132
226
|
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
133
227
|
this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
|
|
@@ -137,16 +231,55 @@ export class InteractiveMode {
|
|
|
137
231
|
this.editor = this.defaultEditor;
|
|
138
232
|
this.editorContainer = new Container();
|
|
139
233
|
this.editorContainer.addChild(this.editor);
|
|
140
|
-
this.footerDataProvider = new FooterDataProvider();
|
|
141
|
-
this.footer = new FooterComponent(session, this.footerDataProvider);
|
|
142
|
-
this.footer.setAutoCompactEnabled(session.autoCompactionEnabled);
|
|
234
|
+
this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
|
|
235
|
+
this.footer = new FooterComponent(this.session, this.footerDataProvider);
|
|
236
|
+
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
143
237
|
// Load hide thinking block setting
|
|
144
238
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
145
239
|
// Register themes from resource loader and initialize
|
|
146
240
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
147
241
|
initTheme(this.settingsManager.getTheme(), true);
|
|
148
242
|
}
|
|
149
|
-
|
|
243
|
+
getAutocompleteSourceTag(sourceInfo) {
|
|
244
|
+
if (!sourceInfo) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
const scopePrefix = sourceInfo.scope === "user" ? "u" : sourceInfo.scope === "project" ? "p" : "t";
|
|
248
|
+
const source = sourceInfo.source.trim();
|
|
249
|
+
if (source === "auto" || source === "local" || source === "cli") {
|
|
250
|
+
return scopePrefix;
|
|
251
|
+
}
|
|
252
|
+
if (source.startsWith("npm:")) {
|
|
253
|
+
return `${scopePrefix}:${source}`;
|
|
254
|
+
}
|
|
255
|
+
const gitSource = parseGitUrl(source);
|
|
256
|
+
if (gitSource) {
|
|
257
|
+
const ref = gitSource.ref ? `@${gitSource.ref}` : "";
|
|
258
|
+
return `${scopePrefix}:git:${gitSource.host}/${gitSource.path}${ref}`;
|
|
259
|
+
}
|
|
260
|
+
return scopePrefix;
|
|
261
|
+
}
|
|
262
|
+
prefixAutocompleteDescription(description, sourceInfo) {
|
|
263
|
+
const sourceTag = this.getAutocompleteSourceTag(sourceInfo);
|
|
264
|
+
if (!sourceTag) {
|
|
265
|
+
return description;
|
|
266
|
+
}
|
|
267
|
+
return description ? `[${sourceTag}] ${description}` : `[${sourceTag}]`;
|
|
268
|
+
}
|
|
269
|
+
getBuiltInCommandConflictDiagnostics(extensionRunner) {
|
|
270
|
+
const builtinNames = new Set(BUILTIN_SLASH_COMMANDS.map((command) => command.name));
|
|
271
|
+
return extensionRunner
|
|
272
|
+
.getRegisteredCommands()
|
|
273
|
+
.filter((command) => builtinNames.has(command.name))
|
|
274
|
+
.map((command) => ({
|
|
275
|
+
type: "warning",
|
|
276
|
+
message: command.invocationName === command.name
|
|
277
|
+
? `Extension command '/${command.name}' conflicts with built-in interactive command. Skipping in autocomplete.`
|
|
278
|
+
: `Extension command '/${command.name}' conflicts with built-in interactive command. Available as '/${command.invocationName}'.`,
|
|
279
|
+
path: command.sourceInfo.path,
|
|
280
|
+
}));
|
|
281
|
+
}
|
|
282
|
+
createBaseAutocompleteProvider() {
|
|
150
283
|
// Define commands for autocomplete
|
|
151
284
|
const slashCommands = BUILTIN_SLASH_COMMANDS.map((command) => ({
|
|
152
285
|
name: command.name,
|
|
@@ -181,13 +314,17 @@ export class InteractiveMode {
|
|
|
181
314
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
182
315
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
183
316
|
name: cmd.name,
|
|
184
|
-
description: cmd.description,
|
|
317
|
+
description: this.prefixAutocompleteDescription(cmd.description, cmd.sourceInfo),
|
|
318
|
+
...(cmd.argumentHint && { argumentHint: cmd.argumentHint }),
|
|
185
319
|
}));
|
|
186
320
|
// Convert extension commands to SlashCommand format
|
|
187
321
|
const builtinCommandNames = new Set(slashCommands.map((c) => c.name));
|
|
188
|
-
const extensionCommands =
|
|
189
|
-
|
|
190
|
-
|
|
322
|
+
const extensionCommands = this.session.extensionRunner
|
|
323
|
+
.getRegisteredCommands()
|
|
324
|
+
.filter((cmd) => !builtinCommandNames.has(cmd.name))
|
|
325
|
+
.map((cmd) => ({
|
|
326
|
+
name: cmd.invocationName,
|
|
327
|
+
description: this.prefixAutocompleteDescription(cmd.description, cmd.sourceInfo),
|
|
191
328
|
getArgumentCompletions: cmd.getArgumentCompletions,
|
|
192
329
|
}));
|
|
193
330
|
// Build skill commands from session.skills (if enabled)
|
|
@@ -197,19 +334,55 @@ export class InteractiveMode {
|
|
|
197
334
|
for (const skill of this.session.resourceLoader.getSkills().skills) {
|
|
198
335
|
const commandName = `skill:${skill.name}`;
|
|
199
336
|
this.skillCommands.set(commandName, skill.filePath);
|
|
200
|
-
skillCommandList.push({
|
|
337
|
+
skillCommandList.push({
|
|
338
|
+
name: commandName,
|
|
339
|
+
description: this.prefixAutocompleteDescription(skill.description, skill.sourceInfo),
|
|
340
|
+
});
|
|
201
341
|
}
|
|
202
342
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
343
|
+
return new CombinedAutocompleteProvider([...slashCommands, ...templateCommands, ...extensionCommands, ...skillCommandList], this.sessionManager.getCwd(), this.fdPath);
|
|
344
|
+
}
|
|
345
|
+
setupAutocompleteProvider() {
|
|
346
|
+
let provider = this.createBaseAutocompleteProvider();
|
|
347
|
+
for (const wrapProvider of this.autocompleteProviderWrappers) {
|
|
348
|
+
provider = wrapProvider(provider);
|
|
349
|
+
}
|
|
350
|
+
this.autocompleteProvider = provider;
|
|
351
|
+
this.defaultEditor.setAutocompleteProvider(provider);
|
|
206
352
|
if (this.editor !== this.defaultEditor) {
|
|
207
|
-
this.editor.setAutocompleteProvider?.(
|
|
353
|
+
this.editor.setAutocompleteProvider?.(provider);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
showStartupNoticesIfNeeded() {
|
|
357
|
+
if (this.startupNoticesShown) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
this.startupNoticesShown = true;
|
|
361
|
+
if (!this.changelogMarkdown) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (this.chatContainer.children.length > 0) {
|
|
365
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
366
|
+
}
|
|
367
|
+
this.chatContainer.addChild(new DynamicBorder());
|
|
368
|
+
if (this.settingsManager.getCollapseChangelog()) {
|
|
369
|
+
const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
|
|
370
|
+
const latestVersion = versionMatch ? versionMatch[1] : this.version;
|
|
371
|
+
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
|
|
372
|
+
this.chatContainer.addChild(new Text(condensedText, 1, 0));
|
|
208
373
|
}
|
|
374
|
+
else {
|
|
375
|
+
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
|
376
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
377
|
+
this.chatContainer.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0, this.getMarkdownThemeWithSettings()));
|
|
378
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
379
|
+
}
|
|
380
|
+
this.chatContainer.addChild(new DynamicBorder());
|
|
209
381
|
}
|
|
210
382
|
async init() {
|
|
211
383
|
if (this.isInitialized)
|
|
212
384
|
return;
|
|
385
|
+
this.registerSignalHandlers();
|
|
213
386
|
// Load changelog (only show new entries, skip for resumed sessions)
|
|
214
387
|
this.changelogMarkdown = this.getChangelogForDisplay();
|
|
215
388
|
// Ensure fd and rg are available (downloads if missing, adds to PATH via getBinDir)
|
|
@@ -218,102 +391,99 @@ export class InteractiveMode {
|
|
|
218
391
|
this.fdPath = fdPath;
|
|
219
392
|
// Add header container as first child
|
|
220
393
|
this.ui.addChild(this.headerContainer);
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
];
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
this.headerContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
|
310
|
-
this.headerContainer.addChild(new Spacer(1));
|
|
311
|
-
this.headerContainer.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0, this.getMarkdownThemeWithSettings()));
|
|
312
|
-
this.headerContainer.addChild(new Spacer(1));
|
|
313
|
-
}
|
|
314
|
-
this.headerContainer.addChild(new DynamicBorder());
|
|
315
|
-
}
|
|
394
|
+
// Phi Code gradient ASCII logo (yellow -> orange -> pink -> magenta).
|
|
395
|
+
// ALWAYS displayed at startup, independent of quietStartup setting.
|
|
396
|
+
// quietStartup only gates the keyboard shortcuts panel below.
|
|
397
|
+
const gradientColors = [
|
|
398
|
+
[255, 220, 50],
|
|
399
|
+
[255, 165, 50],
|
|
400
|
+
[255, 100, 100],
|
|
401
|
+
[230, 80, 150],
|
|
402
|
+
[200, 50, 180],
|
|
403
|
+
[180, 50, 220],
|
|
404
|
+
];
|
|
405
|
+
const applyGradient = (line) => {
|
|
406
|
+
const chars = [...line];
|
|
407
|
+
const visibleChars = chars.filter((c) => c !== " ");
|
|
408
|
+
const totalVisible = visibleChars.length;
|
|
409
|
+
let visibleIdx = 0;
|
|
410
|
+
return chars
|
|
411
|
+
.map((c) => {
|
|
412
|
+
if (c === " ")
|
|
413
|
+
return c;
|
|
414
|
+
const t = totalVisible > 1 ? visibleIdx / (totalVisible - 1) : 0;
|
|
415
|
+
visibleIdx++;
|
|
416
|
+
const segLen = gradientColors.length - 1;
|
|
417
|
+
const seg = Math.min(Math.floor(t * segLen), segLen - 1);
|
|
418
|
+
const local = t * segLen - seg;
|
|
419
|
+
const [r1, g1, b1] = gradientColors[seg];
|
|
420
|
+
const [r2, g2, b2] = gradientColors[seg + 1];
|
|
421
|
+
const r = Math.round(r1 + (r2 - r1) * local);
|
|
422
|
+
const g = Math.round(g1 + (g2 - g1) * local);
|
|
423
|
+
const b = Math.round(b1 + (b2 - b1) * local);
|
|
424
|
+
return `\x1b[38;2;${r};${g};${b}m${c}\x1b[0m`;
|
|
425
|
+
})
|
|
426
|
+
.join("");
|
|
427
|
+
};
|
|
428
|
+
const asciiLines = [
|
|
429
|
+
" ██████╗ ██╗ ██╗██╗",
|
|
430
|
+
" ██╔══██╗██║ ██║██║",
|
|
431
|
+
" ██████╔╝███████║██║",
|
|
432
|
+
" ██╔═══╝ ██╔══██║██║",
|
|
433
|
+
" ██║ ██║ ██║██║",
|
|
434
|
+
" ╚═╝ ╚═╝ ╚═╝╚═╝",
|
|
435
|
+
];
|
|
436
|
+
const asciiLogo = asciiLines.map((line) => applyGradient(line)).join("\n");
|
|
437
|
+
const phiLabel = applyGradient(`φ ${APP_NAME.toUpperCase()}`);
|
|
438
|
+
const logo = asciiLogo +
|
|
439
|
+
"\n " +
|
|
440
|
+
phiLabel +
|
|
441
|
+
theme.fg("dim", ` v${this.version}`) +
|
|
442
|
+
theme.fg("dim", " — The Ultimate Coding Agent");
|
|
443
|
+
// Show keyboard hints panel only when not silenced (quietStartup)
|
|
444
|
+
if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
|
|
445
|
+
// Build startup instructions using keybinding hint helpers
|
|
446
|
+
const hint = (keybinding, description) => keyHint(keybinding, description);
|
|
447
|
+
const expandedInstructions = [
|
|
448
|
+
hint("app.interrupt", "to interrupt"),
|
|
449
|
+
hint("app.clear", "to clear"),
|
|
450
|
+
rawKeyHint(`${keyText("app.clear")} twice`, "to exit"),
|
|
451
|
+
hint("app.exit", "to exit (empty)"),
|
|
452
|
+
hint("app.suspend", "to suspend"),
|
|
453
|
+
keyHint("tui.editor.deleteToLineEnd", "to delete to end"),
|
|
454
|
+
hint("app.thinking.cycle", "to cycle thinking level"),
|
|
455
|
+
rawKeyHint(`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`, "to cycle models"),
|
|
456
|
+
hint("app.model.select", "to select model"),
|
|
457
|
+
hint("app.tools.expand", "to expand tools"),
|
|
458
|
+
hint("app.thinking.toggle", "to expand thinking"),
|
|
459
|
+
hint("app.editor.external", "for external editor"),
|
|
460
|
+
rawKeyHint("/", "for commands"),
|
|
461
|
+
rawKeyHint("!", "to run bash"),
|
|
462
|
+
rawKeyHint("!!", "to run bash (no context)"),
|
|
463
|
+
hint("app.message.followUp", "to queue follow-up"),
|
|
464
|
+
hint("app.message.dequeue", "to edit all queued messages"),
|
|
465
|
+
hint("app.clipboard.pasteImage", "to paste image"),
|
|
466
|
+
rawKeyHint("drop files", "to attach"),
|
|
467
|
+
].join("\n");
|
|
468
|
+
const compactInstructions = [
|
|
469
|
+
hint("app.interrupt", "interrupt"),
|
|
470
|
+
rawKeyHint(`${keyText("app.clear")}/${keyText("app.exit")}`, "clear/exit"),
|
|
471
|
+
rawKeyHint("/", "commands"),
|
|
472
|
+
rawKeyHint("!", "bash"),
|
|
473
|
+
hint("app.tools.expand", "more"),
|
|
474
|
+
].join(theme.fg("muted", " · "));
|
|
475
|
+
const compactOnboarding = theme.fg("dim", `Press ${keyText("app.tools.expand")} to show full startup help and loaded resources.`);
|
|
476
|
+
const onboarding = theme.fg("dim", `Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.`);
|
|
477
|
+
this.builtInHeader = new ExpandableText(() => `${logo}\n${compactInstructions}\n${compactOnboarding}\n\n${onboarding}`, () => `${logo}\n${expandedInstructions}\n\n${onboarding}`, this.getStartupExpansionState(), 1, 0);
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
// quietStartup: show only the logo, no keyboard hints panel
|
|
481
|
+
this.builtInHeader = new Text(logo, 1, 0);
|
|
316
482
|
}
|
|
483
|
+
// Setup UI layout (always show the header with logo)
|
|
484
|
+
this.headerContainer.addChild(new Spacer(1));
|
|
485
|
+
this.headerContainer.addChild(this.builtInHeader);
|
|
486
|
+
this.headerContainer.addChild(new Spacer(1));
|
|
317
487
|
this.ui.addChild(this.chatContainer);
|
|
318
488
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
319
489
|
this.ui.addChild(this.statusContainer);
|
|
@@ -325,17 +495,13 @@ export class InteractiveMode {
|
|
|
325
495
|
this.ui.setFocus(this.editor);
|
|
326
496
|
this.setupKeyHandlers();
|
|
327
497
|
this.setupEditorSubmitHandler();
|
|
498
|
+
// Start the UI before initializing extensions so session_start handlers can use interactive dialogs
|
|
499
|
+
this.ui.start();
|
|
500
|
+
this.isInitialized = true;
|
|
328
501
|
// Initialize extensions first so resources are shown before messages
|
|
329
|
-
await this.
|
|
502
|
+
await this.rebindCurrentSession();
|
|
330
503
|
// Render initial messages AFTER showing loaded resources
|
|
331
504
|
this.renderInitialMessages();
|
|
332
|
-
// Start the UI
|
|
333
|
-
this.ui.start();
|
|
334
|
-
this.isInitialized = true;
|
|
335
|
-
// Set terminal title
|
|
336
|
-
this.updateTerminalTitle();
|
|
337
|
-
// Subscribe to agent events
|
|
338
|
-
this.subscribeToAgent();
|
|
339
505
|
// Set up theme file watcher
|
|
340
506
|
onThemeChange(() => {
|
|
341
507
|
this.ui.invalidate();
|
|
@@ -353,13 +519,13 @@ export class InteractiveMode {
|
|
|
353
519
|
* Update terminal title with session name and cwd.
|
|
354
520
|
*/
|
|
355
521
|
updateTerminalTitle() {
|
|
356
|
-
const cwdBasename = path.basename(
|
|
522
|
+
const cwdBasename = path.basename(this.sessionManager.getCwd());
|
|
357
523
|
const sessionName = this.sessionManager.getSessionName();
|
|
358
524
|
if (sessionName) {
|
|
359
|
-
this.ui.terminal.setTitle(
|
|
525
|
+
this.ui.terminal.setTitle(`${APP_TITLE} - ${sessionName} - ${cwdBasename}`);
|
|
360
526
|
}
|
|
361
527
|
else {
|
|
362
|
-
this.ui.terminal.setTitle(
|
|
528
|
+
this.ui.terminal.setTitle(`${APP_TITLE} - ${cwdBasename}`);
|
|
363
529
|
}
|
|
364
530
|
}
|
|
365
531
|
/**
|
|
@@ -369,11 +535,23 @@ export class InteractiveMode {
|
|
|
369
535
|
async run() {
|
|
370
536
|
await this.init();
|
|
371
537
|
// Start version check asynchronously
|
|
372
|
-
this.
|
|
538
|
+
checkForNewPiVersion(this.version).then((newVersion) => {
|
|
373
539
|
if (newVersion) {
|
|
374
540
|
this.showNewVersionNotification(newVersion);
|
|
375
541
|
}
|
|
376
542
|
});
|
|
543
|
+
// Start package update check asynchronously
|
|
544
|
+
this.checkForPackageUpdates().then((updates) => {
|
|
545
|
+
if (updates.length > 0) {
|
|
546
|
+
this.showPackageUpdateNotification(updates);
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
// Check tmux keyboard setup asynchronously
|
|
550
|
+
this.checkTmuxKeyboardSetup().then((warning) => {
|
|
551
|
+
if (warning) {
|
|
552
|
+
this.showWarning(warning);
|
|
553
|
+
}
|
|
554
|
+
});
|
|
377
555
|
// Show startup warnings
|
|
378
556
|
const { migratedProviders, modelFallbackMessage, initialMessage, initialImages, initialMessages } = this.options;
|
|
379
557
|
if (migratedProviders && migratedProviders.length > 0) {
|
|
@@ -386,6 +564,7 @@ export class InteractiveMode {
|
|
|
386
564
|
if (modelFallbackMessage) {
|
|
387
565
|
this.showWarning(modelFallbackMessage);
|
|
388
566
|
}
|
|
567
|
+
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
389
568
|
// Process initial messages
|
|
390
569
|
if (initialMessage) {
|
|
391
570
|
try {
|
|
@@ -419,28 +598,63 @@ export class InteractiveMode {
|
|
|
419
598
|
}
|
|
420
599
|
}
|
|
421
600
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
if (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE)
|
|
427
|
-
return undefined;
|
|
601
|
+
async checkForPackageUpdates() {
|
|
602
|
+
if (process.env.PI_OFFLINE) {
|
|
603
|
+
return [];
|
|
604
|
+
}
|
|
428
605
|
try {
|
|
429
|
-
const
|
|
430
|
-
|
|
606
|
+
const packageManager = new DefaultPackageManager({
|
|
607
|
+
cwd: this.sessionManager.getCwd(),
|
|
608
|
+
agentDir: getAgentDir(),
|
|
609
|
+
settingsManager: this.settingsManager,
|
|
431
610
|
});
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const data = (await response.json());
|
|
435
|
-
const latestVersion = data.version;
|
|
436
|
-
if (latestVersion && latestVersion !== this.version) {
|
|
437
|
-
return latestVersion;
|
|
438
|
-
}
|
|
439
|
-
return undefined;
|
|
611
|
+
const updates = await packageManager.checkForAvailableUpdates();
|
|
612
|
+
return updates.map((update) => update.displayName);
|
|
440
613
|
}
|
|
441
614
|
catch {
|
|
615
|
+
return [];
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
async checkTmuxKeyboardSetup() {
|
|
619
|
+
if (!process.env.TMUX)
|
|
620
|
+
return undefined;
|
|
621
|
+
const runTmuxShow = (option) => {
|
|
622
|
+
return new Promise((resolve) => {
|
|
623
|
+
const proc = spawn("tmux", ["show", "-gv", option], {
|
|
624
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
625
|
+
});
|
|
626
|
+
let stdout = "";
|
|
627
|
+
const timer = setTimeout(() => {
|
|
628
|
+
proc.kill();
|
|
629
|
+
resolve(undefined);
|
|
630
|
+
}, 2000);
|
|
631
|
+
proc.stdout?.on("data", (data) => {
|
|
632
|
+
stdout += data.toString();
|
|
633
|
+
});
|
|
634
|
+
proc.on("error", () => {
|
|
635
|
+
clearTimeout(timer);
|
|
636
|
+
resolve(undefined);
|
|
637
|
+
});
|
|
638
|
+
proc.on("close", (code) => {
|
|
639
|
+
clearTimeout(timer);
|
|
640
|
+
resolve(code === 0 ? stdout.trim() : undefined);
|
|
641
|
+
});
|
|
642
|
+
});
|
|
643
|
+
};
|
|
644
|
+
const [extendedKeys, extendedKeysFormat] = await Promise.all([
|
|
645
|
+
runTmuxShow("extended-keys"),
|
|
646
|
+
runTmuxShow("extended-keys-format"),
|
|
647
|
+
]);
|
|
648
|
+
// If we couldn't query tmux (timeout, sandbox, etc.), don't warn
|
|
649
|
+
if (extendedKeys === undefined)
|
|
442
650
|
return undefined;
|
|
651
|
+
if (extendedKeys !== "on" && extendedKeys !== "always") {
|
|
652
|
+
return "tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart tmux.";
|
|
653
|
+
}
|
|
654
|
+
if (extendedKeysFormat === "xterm") {
|
|
655
|
+
return "tmux extended-keys-format is xterm. Pi works best with csi-u. Add `set -g extended-keys-format csi-u` to ~/.tmux.conf and restart tmux.";
|
|
443
656
|
}
|
|
657
|
+
return undefined;
|
|
444
658
|
}
|
|
445
659
|
/**
|
|
446
660
|
* Get changelog entries to display on startup.
|
|
@@ -455,19 +669,35 @@ export class InteractiveMode {
|
|
|
455
669
|
const changelogPath = getChangelogPath();
|
|
456
670
|
const entries = parseChangelog(changelogPath);
|
|
457
671
|
if (!lastVersion) {
|
|
458
|
-
// Fresh install -
|
|
672
|
+
// Fresh install - record the version, send telemetry, don't show changelog
|
|
459
673
|
this.settingsManager.setLastChangelogVersion(VERSION);
|
|
674
|
+
this.reportInstallTelemetry(VERSION);
|
|
460
675
|
return undefined;
|
|
461
676
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
677
|
+
const newEntries = getNewEntries(entries, lastVersion);
|
|
678
|
+
if (newEntries.length > 0) {
|
|
679
|
+
this.settingsManager.setLastChangelogVersion(VERSION);
|
|
680
|
+
this.reportInstallTelemetry(VERSION);
|
|
681
|
+
return newEntries.map((e) => e.content).join("\n\n");
|
|
468
682
|
}
|
|
469
683
|
return undefined;
|
|
470
684
|
}
|
|
685
|
+
reportInstallTelemetry(version) {
|
|
686
|
+
if (process.env.PI_OFFLINE) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (!isInstallTelemetryEnabled(this.settingsManager)) {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
void fetch(`https://pi.dev/api/report-install?version=${encodeURIComponent(version)}`, {
|
|
693
|
+
headers: {
|
|
694
|
+
"User-Agent": getPiUserAgent(version),
|
|
695
|
+
},
|
|
696
|
+
signal: AbortSignal.timeout(5000),
|
|
697
|
+
})
|
|
698
|
+
.then(() => undefined)
|
|
699
|
+
.catch(() => undefined);
|
|
700
|
+
}
|
|
471
701
|
getMarkdownThemeWithSettings() {
|
|
472
702
|
return {
|
|
473
703
|
...getMarkdownTheme(),
|
|
@@ -486,24 +716,139 @@ export class InteractiveMode {
|
|
|
486
716
|
}
|
|
487
717
|
return result;
|
|
488
718
|
}
|
|
719
|
+
formatExtensionDisplayPath(path) {
|
|
720
|
+
let result = this.formatDisplayPath(path);
|
|
721
|
+
result = result.replace(/\/index\.ts$/, "").replace(/\/index\.js$/, "");
|
|
722
|
+
return result;
|
|
723
|
+
}
|
|
724
|
+
formatContextPath(p) {
|
|
725
|
+
const cwd = path.resolve(this.sessionManager.getCwd());
|
|
726
|
+
const absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(cwd, p);
|
|
727
|
+
const relativePath = getCwdRelativePath(absolutePath, cwd);
|
|
728
|
+
if (relativePath !== undefined) {
|
|
729
|
+
return relativePath;
|
|
730
|
+
}
|
|
731
|
+
return this.formatDisplayPath(absolutePath);
|
|
732
|
+
}
|
|
733
|
+
getStartupExpansionState() {
|
|
734
|
+
return this.options.verbose || this.toolOutputExpanded;
|
|
735
|
+
}
|
|
489
736
|
/**
|
|
490
737
|
* Get a short path relative to the package root for display.
|
|
491
738
|
*/
|
|
492
|
-
getShortPath(fullPath,
|
|
493
|
-
|
|
739
|
+
getShortPath(fullPath, sourceInfo) {
|
|
740
|
+
const baseDir = sourceInfo?.baseDir;
|
|
741
|
+
if (baseDir && this.isPackageSource(sourceInfo)) {
|
|
742
|
+
const relativePath = path.relative(path.resolve(baseDir), path.resolve(fullPath));
|
|
743
|
+
if (relativePath &&
|
|
744
|
+
relativePath !== "." &&
|
|
745
|
+
!relativePath.startsWith("..") &&
|
|
746
|
+
!relativePath.startsWith(`..${path.sep}`) &&
|
|
747
|
+
!path.isAbsolute(relativePath)) {
|
|
748
|
+
return relativePath.replace(/\\/g, "/");
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
const source = sourceInfo?.source ?? "";
|
|
494
752
|
const npmMatch = fullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/);
|
|
495
753
|
if (npmMatch && source.startsWith("npm:")) {
|
|
496
754
|
return npmMatch[2];
|
|
497
755
|
}
|
|
498
|
-
// For git packages, show path relative to repo root
|
|
499
756
|
const gitMatch = fullPath.match(/git\/[^/]+\/[^/]+\/(.*)/);
|
|
500
757
|
if (gitMatch && source.startsWith("git:")) {
|
|
501
758
|
return gitMatch[1];
|
|
502
759
|
}
|
|
503
|
-
// For local/auto, just use formatDisplayPath
|
|
504
760
|
return this.formatDisplayPath(fullPath);
|
|
505
761
|
}
|
|
506
|
-
|
|
762
|
+
getCompactPathLabel(resourcePath, sourceInfo) {
|
|
763
|
+
const shortPath = this.getShortPath(resourcePath, sourceInfo);
|
|
764
|
+
const normalizedPath = shortPath.replace(/\\/g, "/");
|
|
765
|
+
const segments = normalizedPath.split("/").filter((segment) => segment.length > 0 && segment !== "~");
|
|
766
|
+
if (segments.length > 0) {
|
|
767
|
+
return segments[segments.length - 1];
|
|
768
|
+
}
|
|
769
|
+
return shortPath;
|
|
770
|
+
}
|
|
771
|
+
getCompactPackageSourceLabel(sourceInfo) {
|
|
772
|
+
const source = sourceInfo?.source ?? "";
|
|
773
|
+
if (source.startsWith("npm:")) {
|
|
774
|
+
return source.slice("npm:".length) || source;
|
|
775
|
+
}
|
|
776
|
+
const gitSource = parseGitUrl(source);
|
|
777
|
+
if (gitSource) {
|
|
778
|
+
return gitSource.path || source;
|
|
779
|
+
}
|
|
780
|
+
return source;
|
|
781
|
+
}
|
|
782
|
+
getCompactExtensionLabel(resourcePath, sourceInfo) {
|
|
783
|
+
if (!this.isPackageSource(sourceInfo)) {
|
|
784
|
+
return this.getCompactPathLabel(resourcePath, sourceInfo);
|
|
785
|
+
}
|
|
786
|
+
const sourceLabel = this.getCompactPackageSourceLabel(sourceInfo);
|
|
787
|
+
if (!sourceLabel) {
|
|
788
|
+
return this.getCompactPathLabel(resourcePath, sourceInfo);
|
|
789
|
+
}
|
|
790
|
+
const shortPath = this.getShortPath(resourcePath, sourceInfo).replace(/\\/g, "/");
|
|
791
|
+
const packagePath = shortPath.startsWith("extensions/") ? shortPath.slice("extensions/".length) : shortPath;
|
|
792
|
+
const parsedPath = path.posix.parse(packagePath);
|
|
793
|
+
if (parsedPath.name === "index") {
|
|
794
|
+
return !parsedPath.dir || parsedPath.dir === "." ? sourceLabel : `${sourceLabel}:${parsedPath.dir}`;
|
|
795
|
+
}
|
|
796
|
+
return `${sourceLabel}:${packagePath}`;
|
|
797
|
+
}
|
|
798
|
+
getCompactDisplayPathSegments(resourcePath) {
|
|
799
|
+
return this.formatDisplayPath(resourcePath)
|
|
800
|
+
.replace(/\\/g, "/")
|
|
801
|
+
.split("/")
|
|
802
|
+
.filter((segment) => segment.length > 0 && segment !== "~");
|
|
803
|
+
}
|
|
804
|
+
getCompactNonPackageExtensionLabel(resourcePath, index, allPaths) {
|
|
805
|
+
const segments = allPaths[index]?.segments;
|
|
806
|
+
if (!segments || segments.length === 0) {
|
|
807
|
+
return this.getCompactPathLabel(resourcePath);
|
|
808
|
+
}
|
|
809
|
+
for (let segmentCount = 1; segmentCount <= segments.length; segmentCount += 1) {
|
|
810
|
+
const candidate = segments.slice(-segmentCount).join("/");
|
|
811
|
+
const isUnique = allPaths.every((item, itemIndex) => {
|
|
812
|
+
if (itemIndex === index) {
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
return item.segments.slice(-segmentCount).join("/") !== candidate;
|
|
816
|
+
});
|
|
817
|
+
if (isUnique) {
|
|
818
|
+
return candidate;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return segments.join("/");
|
|
822
|
+
}
|
|
823
|
+
getCompactExtensionLabels(extensions) {
|
|
824
|
+
const nonPackageExtensions = extensions
|
|
825
|
+
.map((extension) => {
|
|
826
|
+
const segments = this.getCompactDisplayPathSegments(extension.path);
|
|
827
|
+
const lastSegment = segments[segments.length - 1];
|
|
828
|
+
if (segments.length > 1 && (lastSegment === "index.ts" || lastSegment === "index.js")) {
|
|
829
|
+
segments.pop();
|
|
830
|
+
}
|
|
831
|
+
return {
|
|
832
|
+
path: extension.path,
|
|
833
|
+
sourceInfo: extension.sourceInfo,
|
|
834
|
+
segments,
|
|
835
|
+
};
|
|
836
|
+
})
|
|
837
|
+
.filter((extension) => !this.isPackageSource(extension.sourceInfo));
|
|
838
|
+
return extensions.map((extension) => {
|
|
839
|
+
if (this.isPackageSource(extension.sourceInfo)) {
|
|
840
|
+
return this.getCompactExtensionLabel(extension.path, extension.sourceInfo);
|
|
841
|
+
}
|
|
842
|
+
const nonPackageIndex = nonPackageExtensions.findIndex((item) => item.path === extension.path);
|
|
843
|
+
if (nonPackageIndex === -1) {
|
|
844
|
+
return this.getCompactPathLabel(extension.path, extension.sourceInfo);
|
|
845
|
+
}
|
|
846
|
+
return this.getCompactNonPackageExtensionLabel(extension.path, nonPackageIndex, nonPackageExtensions);
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
getDisplaySourceInfo(sourceInfo) {
|
|
850
|
+
const source = sourceInfo?.source ?? "local";
|
|
851
|
+
const scope = sourceInfo?.scope ?? "project";
|
|
507
852
|
if (source === "local") {
|
|
508
853
|
if (scope === "user") {
|
|
509
854
|
return { label: "user", color: "muted" };
|
|
@@ -522,7 +867,9 @@ export class InteractiveMode {
|
|
|
522
867
|
const scopeLabel = scope === "user" ? "user" : scope === "project" ? "project" : scope === "temporary" ? "temp" : undefined;
|
|
523
868
|
return { label: source, scopeLabel, color: "accent" };
|
|
524
869
|
}
|
|
525
|
-
getScopeGroup(
|
|
870
|
+
getScopeGroup(sourceInfo) {
|
|
871
|
+
const source = sourceInfo?.source ?? "local";
|
|
872
|
+
const scope = sourceInfo?.scope ?? "project";
|
|
526
873
|
if (source === "cli" || scope === "temporary")
|
|
527
874
|
return "path";
|
|
528
875
|
if (scope === "user")
|
|
@@ -531,28 +878,27 @@ export class InteractiveMode {
|
|
|
531
878
|
return "project";
|
|
532
879
|
return "path";
|
|
533
880
|
}
|
|
534
|
-
isPackageSource(
|
|
881
|
+
isPackageSource(sourceInfo) {
|
|
882
|
+
const source = sourceInfo?.source ?? "";
|
|
535
883
|
return source.startsWith("npm:") || source.startsWith("git:");
|
|
536
884
|
}
|
|
537
|
-
buildScopeGroups(
|
|
885
|
+
buildScopeGroups(items) {
|
|
538
886
|
const groups = {
|
|
539
887
|
user: { scope: "user", paths: [], packages: new Map() },
|
|
540
888
|
project: { scope: "project", paths: [], packages: new Map() },
|
|
541
889
|
path: { scope: "path", paths: [], packages: new Map() },
|
|
542
890
|
};
|
|
543
|
-
for (const
|
|
544
|
-
const
|
|
545
|
-
const source = meta?.source ?? "local";
|
|
546
|
-
const scope = meta?.scope ?? "project";
|
|
547
|
-
const groupKey = this.getScopeGroup(source, scope);
|
|
891
|
+
for (const item of items) {
|
|
892
|
+
const groupKey = this.getScopeGroup(item.sourceInfo);
|
|
548
893
|
const group = groups[groupKey];
|
|
549
|
-
|
|
894
|
+
const source = item.sourceInfo?.source ?? "local";
|
|
895
|
+
if (this.isPackageSource(item.sourceInfo)) {
|
|
550
896
|
const list = group.packages.get(source) ?? [];
|
|
551
|
-
list.push(
|
|
897
|
+
list.push(item);
|
|
552
898
|
group.packages.set(source, list);
|
|
553
899
|
}
|
|
554
900
|
else {
|
|
555
|
-
group.paths.push(
|
|
901
|
+
group.paths.push(item);
|
|
556
902
|
}
|
|
557
903
|
}
|
|
558
904
|
return [groups.project, groups.user, groups.path].filter((group) => group.paths.length > 0 || group.packages.size > 0);
|
|
@@ -561,57 +907,44 @@ export class InteractiveMode {
|
|
|
561
907
|
const lines = [];
|
|
562
908
|
for (const group of groups) {
|
|
563
909
|
lines.push(` ${theme.fg("accent", group.scope)}`);
|
|
564
|
-
const sortedPaths = [...group.paths].sort((a, b) => a.localeCompare(b));
|
|
565
|
-
for (const
|
|
566
|
-
lines.push(theme.fg("dim", ` ${options.formatPath(
|
|
910
|
+
const sortedPaths = [...group.paths].sort((a, b) => a.path.localeCompare(b.path));
|
|
911
|
+
for (const item of sortedPaths) {
|
|
912
|
+
lines.push(theme.fg("dim", ` ${options.formatPath(item)}`));
|
|
567
913
|
}
|
|
568
914
|
const sortedPackages = Array.from(group.packages.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
569
|
-
for (const [source,
|
|
915
|
+
for (const [source, items] of sortedPackages) {
|
|
570
916
|
lines.push(` ${theme.fg("mdLink", source)}`);
|
|
571
|
-
const sortedPackagePaths = [...
|
|
572
|
-
for (const
|
|
573
|
-
lines.push(theme.fg("dim", ` ${options.formatPackagePath(
|
|
917
|
+
const sortedPackagePaths = [...items].sort((a, b) => a.path.localeCompare(b.path));
|
|
918
|
+
for (const item of sortedPackagePaths) {
|
|
919
|
+
lines.push(theme.fg("dim", ` ${options.formatPackagePath(item, source)}`));
|
|
574
920
|
}
|
|
575
921
|
}
|
|
576
922
|
}
|
|
577
923
|
return lines.join("\n");
|
|
578
924
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
* Package manager stores metadata for directories, but we display file paths.
|
|
582
|
-
*/
|
|
583
|
-
findMetadata(p, metadata) {
|
|
584
|
-
// Try exact match first
|
|
585
|
-
const exact = metadata.get(p);
|
|
925
|
+
findSourceInfoForPath(p, sourceInfos) {
|
|
926
|
+
const exact = sourceInfos.get(p);
|
|
586
927
|
if (exact)
|
|
587
928
|
return exact;
|
|
588
|
-
// Try parent directories (package manager stores directory paths)
|
|
589
929
|
let current = p;
|
|
590
930
|
while (current.includes("/")) {
|
|
591
931
|
current = current.substring(0, current.lastIndexOf("/"));
|
|
592
|
-
const parent =
|
|
932
|
+
const parent = sourceInfos.get(current);
|
|
593
933
|
if (parent)
|
|
594
934
|
return parent;
|
|
595
935
|
}
|
|
596
936
|
return undefined;
|
|
597
937
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const meta = this.findMetadata(p, metadata);
|
|
603
|
-
if (meta) {
|
|
604
|
-
const shortPath = this.getShortPath(p, meta.source);
|
|
605
|
-
const { label, scopeLabel } = this.getDisplaySourceInfo(meta.source, meta.scope);
|
|
938
|
+
formatPathWithSource(p, sourceInfo) {
|
|
939
|
+
if (sourceInfo) {
|
|
940
|
+
const shortPath = this.getShortPath(p, sourceInfo);
|
|
941
|
+
const { label, scopeLabel } = this.getDisplaySourceInfo(sourceInfo);
|
|
606
942
|
const labelText = scopeLabel ? `${label} (${scopeLabel})` : label;
|
|
607
943
|
return `${labelText} ${shortPath}`;
|
|
608
944
|
}
|
|
609
945
|
return this.formatDisplayPath(p);
|
|
610
946
|
}
|
|
611
|
-
|
|
612
|
-
* Format resource diagnostics with nice collision display using metadata.
|
|
613
|
-
*/
|
|
614
|
-
formatDiagnostics(diagnostics, metadata) {
|
|
947
|
+
formatDiagnostics(diagnostics, sourceInfos) {
|
|
615
948
|
const lines = [];
|
|
616
949
|
// Group collision diagnostics by name
|
|
617
950
|
const collisions = new Map();
|
|
@@ -632,21 +965,17 @@ export class InteractiveMode {
|
|
|
632
965
|
if (!first)
|
|
633
966
|
continue;
|
|
634
967
|
lines.push(theme.fg("warning", ` "${name}" collision:`));
|
|
635
|
-
|
|
636
|
-
lines.push(theme.fg("dim", ` ${theme.fg("success", "✓")} ${this.formatPathWithSource(first.winnerPath, metadata)}`));
|
|
637
|
-
// Show all losers
|
|
968
|
+
lines.push(theme.fg("dim", ` ${theme.fg("success", "✓")} ${this.formatPathWithSource(first.winnerPath, this.findSourceInfoForPath(first.winnerPath, sourceInfos))}`));
|
|
638
969
|
for (const d of collisionList) {
|
|
639
970
|
if (d.collision) {
|
|
640
|
-
lines.push(theme.fg("dim", ` ${theme.fg("warning", "✗")} ${this.formatPathWithSource(d.collision.loserPath,
|
|
971
|
+
lines.push(theme.fg("dim", ` ${theme.fg("warning", "✗")} ${this.formatPathWithSource(d.collision.loserPath, this.findSourceInfoForPath(d.collision.loserPath, sourceInfos))} (skipped)`));
|
|
641
972
|
}
|
|
642
973
|
}
|
|
643
974
|
}
|
|
644
|
-
// Format other diagnostics (skill name collisions, parse errors, etc.)
|
|
645
975
|
for (const d of otherDiagnostics) {
|
|
646
976
|
if (d.path) {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${sourceInfo}`));
|
|
977
|
+
const formattedPath = this.formatPathWithSource(d.path, this.findSourceInfoForPath(d.path, sourceInfos));
|
|
978
|
+
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${formattedPath}`));
|
|
650
979
|
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${d.message}`));
|
|
651
980
|
}
|
|
652
981
|
else {
|
|
@@ -661,11 +990,48 @@ export class InteractiveMode {
|
|
|
661
990
|
if (!showListing && !showDiagnostics) {
|
|
662
991
|
return;
|
|
663
992
|
}
|
|
664
|
-
const metadata = this.session.resourceLoader.getPathMetadata();
|
|
665
993
|
const sectionHeader = (name, color = "mdHeading") => theme.fg(color, `[${name}]`);
|
|
994
|
+
const formatCompactList = (items, options) => {
|
|
995
|
+
const labels = items.map((item) => item.trim()).filter((item) => item.length > 0);
|
|
996
|
+
if (options?.sort !== false) {
|
|
997
|
+
labels.sort((a, b) => a.localeCompare(b));
|
|
998
|
+
}
|
|
999
|
+
return theme.fg("dim", ` ${labels.join(", ")}`);
|
|
1000
|
+
};
|
|
1001
|
+
const addLoadedSection = (name, collapsedBody, expandedBody = collapsedBody, color = "mdHeading") => {
|
|
1002
|
+
const section = new ExpandableText(() => `${sectionHeader(name, color)}\n${collapsedBody}`, () => `${sectionHeader(name, color)}\n${expandedBody}`, this.getStartupExpansionState(), 0, 0);
|
|
1003
|
+
this.chatContainer.addChild(section);
|
|
1004
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
1005
|
+
};
|
|
666
1006
|
const skillsResult = this.session.resourceLoader.getSkills();
|
|
667
1007
|
const promptsResult = this.session.resourceLoader.getPrompts();
|
|
668
1008
|
const themesResult = this.session.resourceLoader.getThemes();
|
|
1009
|
+
const extensions = options?.extensions ??
|
|
1010
|
+
this.session.resourceLoader.getExtensions().extensions.map((extension) => ({
|
|
1011
|
+
path: extension.path,
|
|
1012
|
+
sourceInfo: extension.sourceInfo,
|
|
1013
|
+
}));
|
|
1014
|
+
const sourceInfos = new Map();
|
|
1015
|
+
for (const extension of extensions) {
|
|
1016
|
+
if (extension.sourceInfo) {
|
|
1017
|
+
sourceInfos.set(extension.path, extension.sourceInfo);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
for (const skill of skillsResult.skills) {
|
|
1021
|
+
if (skill.sourceInfo) {
|
|
1022
|
+
sourceInfos.set(skill.filePath, skill.sourceInfo);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
for (const prompt of promptsResult.prompts) {
|
|
1026
|
+
if (prompt.sourceInfo) {
|
|
1027
|
+
sourceInfos.set(prompt.filePath, prompt.sourceInfo);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
for (const loadedTheme of themesResult.themes) {
|
|
1031
|
+
if (loadedTheme.sourcePath && loadedTheme.sourceInfo) {
|
|
1032
|
+
sourceInfos.set(loadedTheme.sourcePath, loadedTheme.sourceInfo);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
669
1035
|
if (showListing) {
|
|
670
1036
|
const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
|
|
671
1037
|
if (contextFiles.length > 0) {
|
|
@@ -673,72 +1039,71 @@ export class InteractiveMode {
|
|
|
673
1039
|
const contextList = contextFiles
|
|
674
1040
|
.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`))
|
|
675
1041
|
.join("\n");
|
|
676
|
-
|
|
677
|
-
|
|
1042
|
+
const contextCompactList = formatCompactList(contextFiles.map((contextFile) => this.formatContextPath(contextFile.path)), { sort: false });
|
|
1043
|
+
addLoadedSection("Context", contextCompactList, contextList);
|
|
678
1044
|
}
|
|
679
1045
|
const skills = skillsResult.skills;
|
|
680
1046
|
if (skills.length > 0) {
|
|
681
|
-
const
|
|
682
|
-
const groups = this.buildScopeGroups(skillPaths, metadata);
|
|
1047
|
+
const groups = this.buildScopeGroups(skills.map((skill) => ({ path: skill.filePath, sourceInfo: skill.sourceInfo })));
|
|
683
1048
|
const skillList = this.formatScopeGroups(groups, {
|
|
684
|
-
formatPath: (
|
|
685
|
-
formatPackagePath: (
|
|
1049
|
+
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
1050
|
+
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
686
1051
|
});
|
|
687
|
-
|
|
688
|
-
|
|
1052
|
+
const skillCompactList = formatCompactList(skills.map((skill) => skill.name));
|
|
1053
|
+
addLoadedSection("Skills", skillCompactList, skillList);
|
|
689
1054
|
}
|
|
690
1055
|
const templates = this.session.promptTemplates;
|
|
691
1056
|
if (templates.length > 0) {
|
|
692
|
-
const
|
|
693
|
-
const groups = this.buildScopeGroups(templatePaths, metadata);
|
|
1057
|
+
const groups = this.buildScopeGroups(templates.map((template) => ({ path: template.filePath, sourceInfo: template.sourceInfo })));
|
|
694
1058
|
const templateByPath = new Map(templates.map((t) => [t.filePath, t]));
|
|
695
1059
|
const templateList = this.formatScopeGroups(groups, {
|
|
696
|
-
formatPath: (
|
|
697
|
-
const template = templateByPath.get(
|
|
698
|
-
return template ? `/${template.name}` : this.formatDisplayPath(
|
|
1060
|
+
formatPath: (item) => {
|
|
1061
|
+
const template = templateByPath.get(item.path);
|
|
1062
|
+
return template ? `/${template.name}` : this.formatDisplayPath(item.path);
|
|
699
1063
|
},
|
|
700
|
-
formatPackagePath: (
|
|
701
|
-
const template = templateByPath.get(
|
|
702
|
-
return template ? `/${template.name}` : this.formatDisplayPath(
|
|
1064
|
+
formatPackagePath: (item) => {
|
|
1065
|
+
const template = templateByPath.get(item.path);
|
|
1066
|
+
return template ? `/${template.name}` : this.formatDisplayPath(item.path);
|
|
703
1067
|
},
|
|
704
1068
|
});
|
|
705
|
-
|
|
706
|
-
|
|
1069
|
+
const promptCompactList = formatCompactList(templates.map((template) => `/${template.name}`));
|
|
1070
|
+
addLoadedSection("Prompts", promptCompactList, templateList);
|
|
707
1071
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const groups = this.buildScopeGroups(extensionPaths, metadata);
|
|
1072
|
+
if (extensions.length > 0) {
|
|
1073
|
+
const groups = this.buildScopeGroups(extensions);
|
|
711
1074
|
const extList = this.formatScopeGroups(groups, {
|
|
712
|
-
formatPath: (
|
|
713
|
-
formatPackagePath: (
|
|
1075
|
+
formatPath: (item) => this.formatExtensionDisplayPath(item.path),
|
|
1076
|
+
formatPackagePath: (item) => this.formatExtensionDisplayPath(this.getShortPath(item.path, item.sourceInfo)),
|
|
714
1077
|
});
|
|
715
|
-
this.
|
|
716
|
-
|
|
1078
|
+
const extensionCompactList = formatCompactList(this.getCompactExtensionLabels(extensions));
|
|
1079
|
+
addLoadedSection("Extensions", extensionCompactList, extList, "mdHeading");
|
|
717
1080
|
}
|
|
718
1081
|
// Show loaded themes (excluding built-in)
|
|
719
1082
|
const loadedThemes = themesResult.themes;
|
|
720
1083
|
const customThemes = loadedThemes.filter((t) => t.sourcePath);
|
|
721
1084
|
if (customThemes.length > 0) {
|
|
722
|
-
const
|
|
723
|
-
|
|
1085
|
+
const groups = this.buildScopeGroups(customThemes.map((loadedTheme) => ({
|
|
1086
|
+
path: loadedTheme.sourcePath,
|
|
1087
|
+
sourceInfo: loadedTheme.sourceInfo,
|
|
1088
|
+
})));
|
|
724
1089
|
const themeList = this.formatScopeGroups(groups, {
|
|
725
|
-
formatPath: (
|
|
726
|
-
formatPackagePath: (
|
|
1090
|
+
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
1091
|
+
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
727
1092
|
});
|
|
728
|
-
|
|
729
|
-
|
|
1093
|
+
const themeCompactList = formatCompactList(customThemes.map((loadedTheme) => loadedTheme.name ?? this.getCompactPathLabel(loadedTheme.sourcePath, loadedTheme.sourceInfo)));
|
|
1094
|
+
addLoadedSection("Themes", themeCompactList, themeList);
|
|
730
1095
|
}
|
|
731
1096
|
}
|
|
732
1097
|
if (showDiagnostics) {
|
|
733
1098
|
const skillDiagnostics = skillsResult.diagnostics;
|
|
734
1099
|
if (skillDiagnostics.length > 0) {
|
|
735
|
-
const warningLines = this.formatDiagnostics(skillDiagnostics,
|
|
1100
|
+
const warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);
|
|
736
1101
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
|
|
737
1102
|
this.chatContainer.addChild(new Spacer(1));
|
|
738
1103
|
}
|
|
739
1104
|
const promptDiagnostics = promptsResult.diagnostics;
|
|
740
1105
|
if (promptDiagnostics.length > 0) {
|
|
741
|
-
const warningLines = this.formatDiagnostics(promptDiagnostics,
|
|
1106
|
+
const warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);
|
|
742
1107
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
|
|
743
1108
|
this.chatContainer.addChild(new Spacer(1));
|
|
744
1109
|
}
|
|
@@ -749,18 +1114,19 @@ export class InteractiveMode {
|
|
|
749
1114
|
extensionDiagnostics.push({ type: "error", message: error.error, path: error.path });
|
|
750
1115
|
}
|
|
751
1116
|
}
|
|
752
|
-
const commandDiagnostics = this.session.extensionRunner
|
|
1117
|
+
const commandDiagnostics = this.session.extensionRunner.getCommandDiagnostics();
|
|
753
1118
|
extensionDiagnostics.push(...commandDiagnostics);
|
|
754
|
-
|
|
1119
|
+
extensionDiagnostics.push(...this.getBuiltInCommandConflictDiagnostics(this.session.extensionRunner));
|
|
1120
|
+
const shortcutDiagnostics = this.session.extensionRunner.getShortcutDiagnostics();
|
|
755
1121
|
extensionDiagnostics.push(...shortcutDiagnostics);
|
|
756
1122
|
if (extensionDiagnostics.length > 0) {
|
|
757
|
-
const warningLines = this.formatDiagnostics(extensionDiagnostics,
|
|
1123
|
+
const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
|
|
758
1124
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
|
|
759
1125
|
this.chatContainer.addChild(new Spacer(1));
|
|
760
1126
|
}
|
|
761
1127
|
const themeDiagnostics = themesResult.diagnostics;
|
|
762
1128
|
if (themeDiagnostics.length > 0) {
|
|
763
|
-
const warningLines = this.formatDiagnostics(themeDiagnostics,
|
|
1129
|
+
const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
|
|
764
1130
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
|
765
1131
|
this.chatContainer.addChild(new Spacer(1));
|
|
766
1132
|
}
|
|
@@ -769,7 +1135,7 @@ export class InteractiveMode {
|
|
|
769
1135
|
/**
|
|
770
1136
|
* Initialize the extension system with TUI-based UI context.
|
|
771
1137
|
*/
|
|
772
|
-
async
|
|
1138
|
+
async bindCurrentSessionExtensions() {
|
|
773
1139
|
const uiContext = this.createExtensionUIContext();
|
|
774
1140
|
await this.session.bindExtensions({
|
|
775
1141
|
uiContext,
|
|
@@ -781,33 +1147,31 @@ export class InteractiveMode {
|
|
|
781
1147
|
this.loadingAnimation = undefined;
|
|
782
1148
|
}
|
|
783
1149
|
this.statusContainer.clear();
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1150
|
+
try {
|
|
1151
|
+
const result = await this.runtimeHost.newSession(options);
|
|
1152
|
+
if (!result.cancelled) {
|
|
1153
|
+
this.renderCurrentSessionState();
|
|
1154
|
+
this.ui.requestRender();
|
|
1155
|
+
}
|
|
1156
|
+
return result;
|
|
1157
|
+
}
|
|
1158
|
+
catch (error) {
|
|
1159
|
+
return this.handleFatalRuntimeError("Failed to create session", error);
|
|
788
1160
|
}
|
|
789
|
-
// Clear UI state
|
|
790
|
-
this.chatContainer.clear();
|
|
791
|
-
this.pendingMessagesContainer.clear();
|
|
792
|
-
this.compactionQueuedMessages = [];
|
|
793
|
-
this.streamingComponent = undefined;
|
|
794
|
-
this.streamingMessage = undefined;
|
|
795
|
-
this.pendingTools.clear();
|
|
796
|
-
// Render any messages added via setup, or show empty session
|
|
797
|
-
this.renderInitialMessages();
|
|
798
|
-
this.ui.requestRender();
|
|
799
|
-
return { cancelled: false };
|
|
800
1161
|
},
|
|
801
|
-
fork: async (entryId) => {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
1162
|
+
fork: async (entryId, options) => {
|
|
1163
|
+
try {
|
|
1164
|
+
const result = await this.runtimeHost.fork(entryId, options);
|
|
1165
|
+
if (!result.cancelled) {
|
|
1166
|
+
this.renderCurrentSessionState();
|
|
1167
|
+
this.editor.setText(result.selectedText ?? "");
|
|
1168
|
+
this.showStatus("Forked to new session");
|
|
1169
|
+
}
|
|
1170
|
+
return { cancelled: result.cancelled };
|
|
1171
|
+
}
|
|
1172
|
+
catch (error) {
|
|
1173
|
+
return this.handleFatalRuntimeError("Failed to fork session", error);
|
|
805
1174
|
}
|
|
806
|
-
this.chatContainer.clear();
|
|
807
|
-
this.renderInitialMessages();
|
|
808
|
-
this.editor.setText(result.selectedText);
|
|
809
|
-
this.showStatus("Forked to new session");
|
|
810
|
-
return { cancelled: false };
|
|
811
1175
|
},
|
|
812
1176
|
navigateTree: async (targetId, options) => {
|
|
813
1177
|
const result = await this.session.navigateTree(targetId, {
|
|
@@ -825,11 +1189,11 @@ export class InteractiveMode {
|
|
|
825
1189
|
this.editor.setText(result.editorText);
|
|
826
1190
|
}
|
|
827
1191
|
this.showStatus("Navigated to selected point");
|
|
1192
|
+
void this.flushCompactionQueue({ willRetry: false });
|
|
828
1193
|
return { cancelled: false };
|
|
829
1194
|
},
|
|
830
|
-
switchSession: async (sessionPath) => {
|
|
831
|
-
|
|
832
|
-
return { cancelled: false };
|
|
1195
|
+
switchSession: async (sessionPath, options) => {
|
|
1196
|
+
return this.handleResumeSession(sessionPath, options);
|
|
833
1197
|
},
|
|
834
1198
|
reload: async () => {
|
|
835
1199
|
await this.handleReloadCommand();
|
|
@@ -846,22 +1210,59 @@ export class InteractiveMode {
|
|
|
846
1210
|
},
|
|
847
1211
|
});
|
|
848
1212
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
849
|
-
this.
|
|
1213
|
+
this.setupAutocompleteProvider();
|
|
850
1214
|
const extensionRunner = this.session.extensionRunner;
|
|
851
|
-
if (!extensionRunner) {
|
|
852
|
-
this.showLoadedResources({ extensionPaths: [], force: false });
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
1215
|
this.setupExtensionShortcuts(extensionRunner);
|
|
856
|
-
this.showLoadedResources({
|
|
1216
|
+
this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
|
|
1217
|
+
this.showStartupNoticesIfNeeded();
|
|
1218
|
+
}
|
|
1219
|
+
applyRuntimeSettings() {
|
|
1220
|
+
this.footer.setSession(this.session);
|
|
1221
|
+
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
1222
|
+
this.footerDataProvider.setCwd(this.sessionManager.getCwd());
|
|
1223
|
+
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
1224
|
+
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
1225
|
+
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
|
1226
|
+
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
|
1227
|
+
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
1228
|
+
this.defaultEditor.setPaddingX(editorPaddingX);
|
|
1229
|
+
this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
|
|
1230
|
+
if (this.editor !== this.defaultEditor) {
|
|
1231
|
+
this.editor.setPaddingX?.(editorPaddingX);
|
|
1232
|
+
this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
async rebindCurrentSession() {
|
|
1236
|
+
this.unsubscribe?.();
|
|
1237
|
+
this.unsubscribe = undefined;
|
|
1238
|
+
this.applyRuntimeSettings();
|
|
1239
|
+
await this.bindCurrentSessionExtensions();
|
|
1240
|
+
this.subscribeToAgent();
|
|
1241
|
+
await this.updateAvailableProviderCount();
|
|
1242
|
+
this.updateEditorBorderColor();
|
|
1243
|
+
this.updateTerminalTitle();
|
|
1244
|
+
}
|
|
1245
|
+
async handleFatalRuntimeError(prefix, error) {
|
|
1246
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1247
|
+
this.showError(`${prefix}: ${message}`);
|
|
1248
|
+
stopThemeWatcher();
|
|
1249
|
+
this.stop();
|
|
1250
|
+
process.exit(1);
|
|
1251
|
+
}
|
|
1252
|
+
renderCurrentSessionState() {
|
|
1253
|
+
this.chatContainer.clear();
|
|
1254
|
+
this.pendingMessagesContainer.clear();
|
|
1255
|
+
this.compactionQueuedMessages = [];
|
|
1256
|
+
this.streamingComponent = undefined;
|
|
1257
|
+
this.streamingMessage = undefined;
|
|
1258
|
+
this.pendingTools.clear();
|
|
1259
|
+
this.renderInitialMessages();
|
|
857
1260
|
}
|
|
858
1261
|
/**
|
|
859
1262
|
* Get a registered tool definition by name (for custom rendering).
|
|
860
1263
|
*/
|
|
861
1264
|
getRegisteredToolDefinition(toolName) {
|
|
862
|
-
|
|
863
|
-
const registeredTool = tools.find((t) => t.definition.name === toolName);
|
|
864
|
-
return registeredTool?.definition;
|
|
1265
|
+
return this.session.getToolDefinition(toolName);
|
|
865
1266
|
}
|
|
866
1267
|
/**
|
|
867
1268
|
* Set up keyboard shortcuts registered by extensions.
|
|
@@ -874,11 +1275,12 @@ export class InteractiveMode {
|
|
|
874
1275
|
const createContext = () => ({
|
|
875
1276
|
ui: this.createExtensionUIContext(),
|
|
876
1277
|
hasUI: true,
|
|
877
|
-
cwd:
|
|
1278
|
+
cwd: this.sessionManager.getCwd(),
|
|
878
1279
|
sessionManager: this.sessionManager,
|
|
879
1280
|
modelRegistry: this.session.modelRegistry,
|
|
880
1281
|
model: this.session.model,
|
|
881
1282
|
isIdle: () => !this.session.isStreaming,
|
|
1283
|
+
signal: this.session.agent.signal,
|
|
882
1284
|
abort: () => this.session.abort(),
|
|
883
1285
|
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
|
884
1286
|
shutdown: () => {
|
|
@@ -888,10 +1290,8 @@ export class InteractiveMode {
|
|
|
888
1290
|
compact: (options) => {
|
|
889
1291
|
void (async () => {
|
|
890
1292
|
try {
|
|
891
|
-
const result = await this.
|
|
892
|
-
|
|
893
|
-
options?.onComplete?.(result);
|
|
894
|
-
}
|
|
1293
|
+
const result = await this.session.compact(options?.customInstructions);
|
|
1294
|
+
options?.onComplete?.(result);
|
|
895
1295
|
}
|
|
896
1296
|
catch (error) {
|
|
897
1297
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
@@ -923,6 +1323,50 @@ export class InteractiveMode {
|
|
|
923
1323
|
this.footerDataProvider.setExtensionStatus(key, text);
|
|
924
1324
|
this.ui.requestRender();
|
|
925
1325
|
}
|
|
1326
|
+
getWorkingLoaderMessage() {
|
|
1327
|
+
return this.workingMessage ?? this.defaultWorkingMessage;
|
|
1328
|
+
}
|
|
1329
|
+
createWorkingLoader() {
|
|
1330
|
+
return new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.getWorkingLoaderMessage(), this.workingIndicatorOptions);
|
|
1331
|
+
}
|
|
1332
|
+
stopWorkingLoader() {
|
|
1333
|
+
if (this.loadingAnimation) {
|
|
1334
|
+
this.loadingAnimation.stop();
|
|
1335
|
+
this.loadingAnimation = undefined;
|
|
1336
|
+
}
|
|
1337
|
+
this.statusContainer.clear();
|
|
1338
|
+
}
|
|
1339
|
+
setWorkingVisible(visible) {
|
|
1340
|
+
this.workingVisible = visible;
|
|
1341
|
+
if (!visible) {
|
|
1342
|
+
this.stopWorkingLoader();
|
|
1343
|
+
this.ui.requestRender();
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
if (this.session.isStreaming && !this.loadingAnimation) {
|
|
1347
|
+
this.statusContainer.clear();
|
|
1348
|
+
this.loadingAnimation = this.createWorkingLoader();
|
|
1349
|
+
this.statusContainer.addChild(this.loadingAnimation);
|
|
1350
|
+
}
|
|
1351
|
+
this.ui.requestRender();
|
|
1352
|
+
}
|
|
1353
|
+
setWorkingIndicator(options) {
|
|
1354
|
+
this.workingIndicatorOptions = options;
|
|
1355
|
+
this.loadingAnimation?.setIndicator(options);
|
|
1356
|
+
this.ui.requestRender();
|
|
1357
|
+
}
|
|
1358
|
+
setHiddenThinkingLabel(label) {
|
|
1359
|
+
this.hiddenThinkingLabel = label ?? this.defaultHiddenThinkingLabel;
|
|
1360
|
+
for (const child of this.chatContainer.children) {
|
|
1361
|
+
if (child instanceof AssistantMessageComponent) {
|
|
1362
|
+
child.setHiddenThinkingLabel(this.hiddenThinkingLabel);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
if (this.streamingComponent) {
|
|
1366
|
+
this.streamingComponent.setHiddenThinkingLabel(this.hiddenThinkingLabel);
|
|
1367
|
+
}
|
|
1368
|
+
this.ui.requestRender();
|
|
1369
|
+
}
|
|
926
1370
|
/**
|
|
927
1371
|
* Set an extension widget (string array or custom component).
|
|
928
1372
|
*/
|
|
@@ -988,15 +1432,21 @@ export class InteractiveMode {
|
|
|
988
1432
|
this.clearExtensionWidgets();
|
|
989
1433
|
this.footerDataProvider.clearExtensionStatuses();
|
|
990
1434
|
this.footer.invalidate();
|
|
1435
|
+
this.autocompleteProviderWrappers = [];
|
|
991
1436
|
this.setCustomEditorComponent(undefined);
|
|
1437
|
+
this.setupAutocompleteProvider();
|
|
992
1438
|
this.defaultEditor.onExtensionShortcut = undefined;
|
|
993
1439
|
this.updateTerminalTitle();
|
|
1440
|
+
this.workingMessage = undefined;
|
|
1441
|
+
this.workingVisible = true;
|
|
1442
|
+
this.setWorkingIndicator();
|
|
994
1443
|
if (this.loadingAnimation) {
|
|
995
|
-
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${
|
|
1444
|
+
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
|
|
996
1445
|
}
|
|
1446
|
+
this.setHiddenThinkingLabel();
|
|
997
1447
|
}
|
|
998
1448
|
// Maximum total widget lines to prevent viewport overflow
|
|
999
|
-
static
|
|
1449
|
+
static MAX_WIDGET_LINES = 10;
|
|
1000
1450
|
/**
|
|
1001
1451
|
* Render all extension widgets to the widget container.
|
|
1002
1452
|
*/
|
|
@@ -1067,6 +1517,9 @@ export class InteractiveMode {
|
|
|
1067
1517
|
if (factory) {
|
|
1068
1518
|
// Create and add custom header
|
|
1069
1519
|
this.customHeader = factory(this.ui, theme);
|
|
1520
|
+
if (isExpandable(this.customHeader)) {
|
|
1521
|
+
this.customHeader.setExpanded(this.toolOutputExpanded);
|
|
1522
|
+
}
|
|
1070
1523
|
if (index !== -1) {
|
|
1071
1524
|
this.headerContainer.children[index] = this.customHeader;
|
|
1072
1525
|
}
|
|
@@ -1078,6 +1531,9 @@ export class InteractiveMode {
|
|
|
1078
1531
|
else {
|
|
1079
1532
|
// Restore built-in header
|
|
1080
1533
|
this.customHeader = undefined;
|
|
1534
|
+
if (isExpandable(this.builtInHeader)) {
|
|
1535
|
+
this.builtInHeader.setExpanded(this.toolOutputExpanded);
|
|
1536
|
+
}
|
|
1081
1537
|
if (index !== -1) {
|
|
1082
1538
|
this.headerContainer.children[index] = this.builtInHeader;
|
|
1083
1539
|
}
|
|
@@ -1110,19 +1566,14 @@ export class InteractiveMode {
|
|
|
1110
1566
|
onTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),
|
|
1111
1567
|
setStatus: (key, text) => this.setExtensionStatus(key, text),
|
|
1112
1568
|
setWorkingMessage: (message) => {
|
|
1569
|
+
this.workingMessage = message;
|
|
1113
1570
|
if (this.loadingAnimation) {
|
|
1114
|
-
|
|
1115
|
-
this.loadingAnimation.setMessage(message);
|
|
1116
|
-
}
|
|
1117
|
-
else {
|
|
1118
|
-
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
else {
|
|
1122
|
-
// Queue message for when loadingAnimation is created (handles agent_start race)
|
|
1123
|
-
this.pendingWorkingMessage = message;
|
|
1571
|
+
this.loadingAnimation.setMessage(message ?? this.defaultWorkingMessage);
|
|
1124
1572
|
}
|
|
1125
1573
|
},
|
|
1574
|
+
setWorkingVisible: (visible) => this.setWorkingVisible(visible),
|
|
1575
|
+
setWorkingIndicator: (options) => this.setWorkingIndicator(options),
|
|
1576
|
+
setHiddenThinkingLabel: (label) => this.setHiddenThinkingLabel(label),
|
|
1126
1577
|
setWidget: (key, content, options) => this.setExtensionWidget(key, content, options),
|
|
1127
1578
|
setFooter: (factory) => this.setExtensionFooter(factory),
|
|
1128
1579
|
setHeader: (factory) => this.setExtensionHeader(factory),
|
|
@@ -1130,9 +1581,14 @@ export class InteractiveMode {
|
|
|
1130
1581
|
custom: (factory, options) => this.showExtensionCustom(factory, options),
|
|
1131
1582
|
pasteToEditor: (text) => this.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
|
|
1132
1583
|
setEditorText: (text) => this.editor.setText(text),
|
|
1133
|
-
getEditorText: () => this.editor.getText(),
|
|
1584
|
+
getEditorText: () => this.editor.getExpandedText?.() ?? this.editor.getText(),
|
|
1134
1585
|
editor: (title, prefill) => this.showExtensionEditor(title, prefill),
|
|
1586
|
+
addAutocompleteProvider: (factory) => {
|
|
1587
|
+
this.autocompleteProviderWrappers.push(factory);
|
|
1588
|
+
this.setupAutocompleteProvider();
|
|
1589
|
+
},
|
|
1135
1590
|
setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
|
|
1591
|
+
getEditorComponent: () => this.editorComponentFactory,
|
|
1136
1592
|
get theme() {
|
|
1137
1593
|
return theme;
|
|
1138
1594
|
},
|
|
@@ -1204,6 +1660,10 @@ export class InteractiveMode {
|
|
|
1204
1660
|
const result = await this.showExtensionSelector(`${title}\n${message}`, ["Yes", "No"], opts);
|
|
1205
1661
|
return result === "Yes";
|
|
1206
1662
|
}
|
|
1663
|
+
async promptForMissingSessionCwd(error) {
|
|
1664
|
+
const confirmed = await this.showExtensionConfirm("Session cwd not found", formatMissingSessionCwdPrompt(error.issue));
|
|
1665
|
+
return confirmed ? error.issue.fallbackCwd : undefined;
|
|
1666
|
+
}
|
|
1207
1667
|
/**
|
|
1208
1668
|
* Show a text input for extensions.
|
|
1209
1669
|
*/
|
|
@@ -1277,6 +1737,7 @@ export class InteractiveMode {
|
|
|
1277
1737
|
* Pass undefined to restore the default editor.
|
|
1278
1738
|
*/
|
|
1279
1739
|
setCustomEditorComponent(factory) {
|
|
1740
|
+
this.editorComponentFactory = factory;
|
|
1280
1741
|
// Save text from current editor before switching
|
|
1281
1742
|
const currentText = this.editor.getText();
|
|
1282
1743
|
this.editorContainer.clear();
|
|
@@ -1441,7 +1902,7 @@ export class InteractiveMode {
|
|
|
1441
1902
|
// Set up handlers on defaultEditor - they use this.editor for text access
|
|
1442
1903
|
// so they work correctly regardless of which editor is active
|
|
1443
1904
|
this.defaultEditor.onEscape = () => {
|
|
1444
|
-
if (this.
|
|
1905
|
+
if (this.session.isStreaming) {
|
|
1445
1906
|
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1446
1907
|
}
|
|
1447
1908
|
else if (this.session.isBashRunning) {
|
|
@@ -1473,24 +1934,24 @@ export class InteractiveMode {
|
|
|
1473
1934
|
}
|
|
1474
1935
|
};
|
|
1475
1936
|
// Register app action handlers
|
|
1476
|
-
this.defaultEditor.onAction("clear", () => this.handleCtrlC());
|
|
1937
|
+
this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
|
|
1477
1938
|
this.defaultEditor.onCtrlD = () => this.handleCtrlD();
|
|
1478
|
-
this.defaultEditor.onAction("suspend", () => this.handleCtrlZ());
|
|
1479
|
-
this.defaultEditor.onAction("
|
|
1480
|
-
this.defaultEditor.onAction("
|
|
1481
|
-
this.defaultEditor.onAction("
|
|
1939
|
+
this.defaultEditor.onAction("app.suspend", () => this.handleCtrlZ());
|
|
1940
|
+
this.defaultEditor.onAction("app.thinking.cycle", () => this.cycleThinkingLevel());
|
|
1941
|
+
this.defaultEditor.onAction("app.model.cycleForward", () => this.cycleModel("forward"));
|
|
1942
|
+
this.defaultEditor.onAction("app.model.cycleBackward", () => this.cycleModel("backward"));
|
|
1482
1943
|
// Global debug handler on TUI (works regardless of focus)
|
|
1483
1944
|
this.ui.onDebug = () => this.handleDebugCommand();
|
|
1484
|
-
this.defaultEditor.onAction("
|
|
1485
|
-
this.defaultEditor.onAction("
|
|
1486
|
-
this.defaultEditor.onAction("
|
|
1487
|
-
this.defaultEditor.onAction("
|
|
1488
|
-
this.defaultEditor.onAction("followUp", () => this.handleFollowUp());
|
|
1489
|
-
this.defaultEditor.onAction("dequeue", () => this.handleDequeue());
|
|
1490
|
-
this.defaultEditor.onAction("
|
|
1491
|
-
this.defaultEditor.onAction("tree", () => this.showTreeSelector());
|
|
1492
|
-
this.defaultEditor.onAction("fork", () => this.showUserMessageSelector());
|
|
1493
|
-
this.defaultEditor.onAction("resume", () => this.showSessionSelector());
|
|
1945
|
+
this.defaultEditor.onAction("app.model.select", () => this.showModelSelector());
|
|
1946
|
+
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
1947
|
+
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
1948
|
+
this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
|
|
1949
|
+
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
1950
|
+
this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
|
|
1951
|
+
this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
|
|
1952
|
+
this.defaultEditor.onAction("app.session.tree", () => this.showTreeSelector());
|
|
1953
|
+
this.defaultEditor.onAction("app.session.fork", () => this.showUserMessageSelector());
|
|
1954
|
+
this.defaultEditor.onAction("app.session.resume", () => this.showSessionSelector());
|
|
1494
1955
|
this.defaultEditor.onChange = (text) => {
|
|
1495
1956
|
const wasBashMode = this.isBashMode;
|
|
1496
1957
|
this.isBashMode = text.trimStart().startsWith("!");
|
|
@@ -1545,18 +2006,23 @@ export class InteractiveMode {
|
|
|
1545
2006
|
await this.handleModelCommand(searchTerm);
|
|
1546
2007
|
return;
|
|
1547
2008
|
}
|
|
1548
|
-
if (text.startsWith("/export")) {
|
|
2009
|
+
if (text === "/export" || text.startsWith("/export ")) {
|
|
1549
2010
|
await this.handleExportCommand(text);
|
|
1550
2011
|
this.editor.setText("");
|
|
1551
2012
|
return;
|
|
1552
2013
|
}
|
|
2014
|
+
if (text === "/import" || text.startsWith("/import ")) {
|
|
2015
|
+
await this.handleImportCommand(text);
|
|
2016
|
+
this.editor.setText("");
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
1553
2019
|
if (text === "/share") {
|
|
1554
2020
|
await this.handleShareCommand();
|
|
1555
2021
|
this.editor.setText("");
|
|
1556
2022
|
return;
|
|
1557
2023
|
}
|
|
1558
2024
|
if (text === "/copy") {
|
|
1559
|
-
this.handleCopyCommand();
|
|
2025
|
+
await this.handleCopyCommand();
|
|
1560
2026
|
this.editor.setText("");
|
|
1561
2027
|
return;
|
|
1562
2028
|
}
|
|
@@ -1585,6 +2051,11 @@ export class InteractiveMode {
|
|
|
1585
2051
|
this.editor.setText("");
|
|
1586
2052
|
return;
|
|
1587
2053
|
}
|
|
2054
|
+
if (text === "/clone") {
|
|
2055
|
+
this.editor.setText("");
|
|
2056
|
+
await this.handleCloneCommand();
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
1588
2059
|
if (text === "/tree") {
|
|
1589
2060
|
this.showTreeSelector();
|
|
1590
2061
|
this.editor.setText("");
|
|
@@ -1626,6 +2097,11 @@ export class InteractiveMode {
|
|
|
1626
2097
|
this.editor.setText("");
|
|
1627
2098
|
return;
|
|
1628
2099
|
}
|
|
2100
|
+
if (text === "/dementedelves") {
|
|
2101
|
+
this.handleDementedDelves();
|
|
2102
|
+
this.editor.setText("");
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
1629
2105
|
if (text === "/resume") {
|
|
1630
2106
|
this.showSessionSelector();
|
|
1631
2107
|
this.editor.setText("");
|
|
@@ -1696,31 +2172,44 @@ export class InteractiveMode {
|
|
|
1696
2172
|
this.footer.invalidate();
|
|
1697
2173
|
switch (event.type) {
|
|
1698
2174
|
case "agent_start":
|
|
2175
|
+
this.pendingTools.clear();
|
|
2176
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2177
|
+
this.ui.terminal.setProgress(true);
|
|
2178
|
+
}
|
|
1699
2179
|
// Restore main escape handler if retry handler is still active
|
|
1700
2180
|
// (retry success event fires later, but we need main handler now)
|
|
1701
2181
|
if (this.retryEscapeHandler) {
|
|
1702
2182
|
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
1703
2183
|
this.retryEscapeHandler = undefined;
|
|
1704
2184
|
}
|
|
2185
|
+
if (this.retryCountdown) {
|
|
2186
|
+
this.retryCountdown.dispose();
|
|
2187
|
+
this.retryCountdown = undefined;
|
|
2188
|
+
}
|
|
1705
2189
|
if (this.retryLoader) {
|
|
1706
2190
|
this.retryLoader.stop();
|
|
1707
2191
|
this.retryLoader = undefined;
|
|
1708
2192
|
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
this.loadingAnimation = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.defaultWorkingMessage);
|
|
1714
|
-
this.statusContainer.addChild(this.loadingAnimation);
|
|
1715
|
-
// Apply any pending working message queued before loader existed
|
|
1716
|
-
if (this.pendingWorkingMessage !== undefined) {
|
|
1717
|
-
if (this.pendingWorkingMessage) {
|
|
1718
|
-
this.loadingAnimation.setMessage(this.pendingWorkingMessage);
|
|
1719
|
-
}
|
|
1720
|
-
this.pendingWorkingMessage = undefined;
|
|
2193
|
+
this.stopWorkingLoader();
|
|
2194
|
+
if (this.workingVisible) {
|
|
2195
|
+
this.loadingAnimation = this.createWorkingLoader();
|
|
2196
|
+
this.statusContainer.addChild(this.loadingAnimation);
|
|
1721
2197
|
}
|
|
1722
2198
|
this.ui.requestRender();
|
|
1723
2199
|
break;
|
|
2200
|
+
case "queue_update":
|
|
2201
|
+
this.updatePendingMessagesDisplay();
|
|
2202
|
+
this.ui.requestRender();
|
|
2203
|
+
break;
|
|
2204
|
+
case "session_info_changed":
|
|
2205
|
+
this.updateTerminalTitle();
|
|
2206
|
+
this.footer.invalidate();
|
|
2207
|
+
this.ui.requestRender();
|
|
2208
|
+
break;
|
|
2209
|
+
case "thinking_level_changed":
|
|
2210
|
+
this.footer.invalidate();
|
|
2211
|
+
this.updateEditorBorderColor();
|
|
2212
|
+
break;
|
|
1724
2213
|
case "message_start":
|
|
1725
2214
|
if (event.message.role === "custom") {
|
|
1726
2215
|
this.addMessageToChat(event.message);
|
|
@@ -1732,7 +2221,7 @@ export class InteractiveMode {
|
|
|
1732
2221
|
this.ui.requestRender();
|
|
1733
2222
|
}
|
|
1734
2223
|
else if (event.message.role === "assistant") {
|
|
1735
|
-
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings());
|
|
2224
|
+
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
1736
2225
|
this.streamingMessage = event.message;
|
|
1737
2226
|
this.chatContainer.addChild(this.streamingComponent);
|
|
1738
2227
|
this.streamingComponent.updateContent(this.streamingMessage);
|
|
@@ -1746,9 +2235,10 @@ export class InteractiveMode {
|
|
|
1746
2235
|
for (const content of this.streamingMessage.content) {
|
|
1747
2236
|
if (content.type === "toolCall") {
|
|
1748
2237
|
if (!this.pendingTools.has(content.id)) {
|
|
1749
|
-
const component = new ToolExecutionComponent(content.name, content.arguments, {
|
|
2238
|
+
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
|
|
1750
2239
|
showImages: this.settingsManager.getShowImages(),
|
|
1751
|
-
|
|
2240
|
+
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
2241
|
+
}, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
|
|
1752
2242
|
component.setExpanded(this.toolOutputExpanded);
|
|
1753
2243
|
this.chatContainer.addChild(component);
|
|
1754
2244
|
this.pendingTools.set(content.id, component);
|
|
@@ -1804,15 +2294,18 @@ export class InteractiveMode {
|
|
|
1804
2294
|
this.ui.requestRender();
|
|
1805
2295
|
break;
|
|
1806
2296
|
case "tool_execution_start": {
|
|
1807
|
-
|
|
1808
|
-
|
|
2297
|
+
let component = this.pendingTools.get(event.toolCallId);
|
|
2298
|
+
if (!component) {
|
|
2299
|
+
component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
|
|
1809
2300
|
showImages: this.settingsManager.getShowImages(),
|
|
1810
|
-
|
|
2301
|
+
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
2302
|
+
}, this.getRegisteredToolDefinition(event.toolName), this.ui, this.sessionManager.getCwd());
|
|
1811
2303
|
component.setExpanded(this.toolOutputExpanded);
|
|
1812
2304
|
this.chatContainer.addChild(component);
|
|
1813
2305
|
this.pendingTools.set(event.toolCallId, component);
|
|
1814
|
-
this.ui.requestRender();
|
|
1815
2306
|
}
|
|
2307
|
+
component.markExecutionStarted();
|
|
2308
|
+
this.ui.requestRender();
|
|
1816
2309
|
break;
|
|
1817
2310
|
}
|
|
1818
2311
|
case "tool_execution_update": {
|
|
@@ -1833,6 +2326,9 @@ export class InteractiveMode {
|
|
|
1833
2326
|
break;
|
|
1834
2327
|
}
|
|
1835
2328
|
case "agent_end":
|
|
2329
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2330
|
+
this.ui.terminal.setProgress(false);
|
|
2331
|
+
}
|
|
1836
2332
|
if (this.loadingAnimation) {
|
|
1837
2333
|
this.loadingAnimation.stop();
|
|
1838
2334
|
this.loadingAnimation = undefined;
|
|
@@ -1847,54 +2343,60 @@ export class InteractiveMode {
|
|
|
1847
2343
|
await this.checkShutdownRequested();
|
|
1848
2344
|
this.ui.requestRender();
|
|
1849
2345
|
break;
|
|
1850
|
-
case "
|
|
2346
|
+
case "compaction_start": {
|
|
2347
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2348
|
+
this.ui.terminal.setProgress(true);
|
|
2349
|
+
}
|
|
1851
2350
|
// Keep editor active; submissions are queued during compaction.
|
|
1852
|
-
// Set up escape to abort auto-compaction
|
|
1853
2351
|
this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
|
|
1854
2352
|
this.defaultEditor.onEscape = () => {
|
|
1855
2353
|
this.session.abortCompaction();
|
|
1856
2354
|
};
|
|
1857
|
-
// Show compacting indicator with reason
|
|
1858
2355
|
this.statusContainer.clear();
|
|
1859
|
-
const
|
|
1860
|
-
|
|
2356
|
+
const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
|
|
2357
|
+
const label = event.reason === "manual"
|
|
2358
|
+
? `Compacting context... ${cancelHint}`
|
|
2359
|
+
: `${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... ${cancelHint}`;
|
|
2360
|
+
this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
|
|
1861
2361
|
this.statusContainer.addChild(this.autoCompactionLoader);
|
|
1862
2362
|
this.ui.requestRender();
|
|
1863
2363
|
break;
|
|
1864
2364
|
}
|
|
1865
|
-
case "
|
|
1866
|
-
|
|
2365
|
+
case "compaction_end": {
|
|
2366
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2367
|
+
this.ui.terminal.setProgress(false);
|
|
2368
|
+
}
|
|
1867
2369
|
if (this.autoCompactionEscapeHandler) {
|
|
1868
2370
|
this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
|
|
1869
2371
|
this.autoCompactionEscapeHandler = undefined;
|
|
1870
2372
|
}
|
|
1871
|
-
// Stop loader
|
|
1872
2373
|
if (this.autoCompactionLoader) {
|
|
1873
2374
|
this.autoCompactionLoader.stop();
|
|
1874
2375
|
this.autoCompactionLoader = undefined;
|
|
1875
2376
|
this.statusContainer.clear();
|
|
1876
2377
|
}
|
|
1877
|
-
// Handle result
|
|
1878
2378
|
if (event.aborted) {
|
|
1879
|
-
|
|
2379
|
+
if (event.reason === "manual") {
|
|
2380
|
+
this.showError("Compaction cancelled");
|
|
2381
|
+
}
|
|
2382
|
+
else {
|
|
2383
|
+
this.showStatus("Auto-compaction cancelled");
|
|
2384
|
+
}
|
|
1880
2385
|
}
|
|
1881
2386
|
else if (event.result) {
|
|
1882
|
-
// Rebuild chat to show compacted state
|
|
1883
2387
|
this.chatContainer.clear();
|
|
1884
2388
|
this.rebuildChatFromMessages();
|
|
1885
|
-
|
|
1886
|
-
this.addMessageToChat({
|
|
1887
|
-
role: "compactionSummary",
|
|
1888
|
-
tokensBefore: event.result.tokensBefore,
|
|
1889
|
-
summary: event.result.summary,
|
|
1890
|
-
timestamp: Date.now(),
|
|
1891
|
-
});
|
|
2389
|
+
this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString()));
|
|
1892
2390
|
this.footer.invalidate();
|
|
1893
2391
|
}
|
|
1894
2392
|
else if (event.errorMessage) {
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
2393
|
+
if (event.reason === "manual") {
|
|
2394
|
+
this.showError(event.errorMessage);
|
|
2395
|
+
}
|
|
2396
|
+
else {
|
|
2397
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2398
|
+
this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
|
|
2399
|
+
}
|
|
1898
2400
|
}
|
|
1899
2401
|
void this.flushCompactionQueue({ willRetry: event.willRetry });
|
|
1900
2402
|
this.ui.requestRender();
|
|
@@ -1908,8 +2410,14 @@ export class InteractiveMode {
|
|
|
1908
2410
|
};
|
|
1909
2411
|
// Show retry indicator
|
|
1910
2412
|
this.statusContainer.clear();
|
|
1911
|
-
|
|
1912
|
-
|
|
2413
|
+
this.retryCountdown?.dispose();
|
|
2414
|
+
const retryMessage = (seconds) => `Retrying (${event.attempt}/${event.maxAttempts}) in ${seconds}s... (${keyText("app.interrupt")} to cancel)`;
|
|
2415
|
+
this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), retryMessage(Math.ceil(event.delayMs / 1000)));
|
|
2416
|
+
this.retryCountdown = new CountdownTimer(event.delayMs, this.ui, (seconds) => {
|
|
2417
|
+
this.retryLoader?.setMessage(retryMessage(seconds));
|
|
2418
|
+
}, () => {
|
|
2419
|
+
this.retryCountdown = undefined;
|
|
2420
|
+
});
|
|
1913
2421
|
this.statusContainer.addChild(this.retryLoader);
|
|
1914
2422
|
this.ui.requestRender();
|
|
1915
2423
|
break;
|
|
@@ -1920,6 +2428,10 @@ export class InteractiveMode {
|
|
|
1920
2428
|
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
1921
2429
|
this.retryEscapeHandler = undefined;
|
|
1922
2430
|
}
|
|
2431
|
+
if (this.retryCountdown) {
|
|
2432
|
+
this.retryCountdown.dispose();
|
|
2433
|
+
this.retryCountdown = undefined;
|
|
2434
|
+
}
|
|
1923
2435
|
// Stop loader
|
|
1924
2436
|
if (this.retryLoader) {
|
|
1925
2437
|
this.retryLoader.stop();
|
|
@@ -1980,7 +2492,7 @@ export class InteractiveMode {
|
|
|
1980
2492
|
}
|
|
1981
2493
|
case "custom": {
|
|
1982
2494
|
if (message.display) {
|
|
1983
|
-
const renderer = this.session.extensionRunner
|
|
2495
|
+
const renderer = this.session.extensionRunner.getMessageRenderer(message.customType);
|
|
1984
2496
|
const component = new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings());
|
|
1985
2497
|
component.setExpanded(this.toolOutputExpanded);
|
|
1986
2498
|
this.chatContainer.addChild(component);
|
|
@@ -2004,10 +2516,12 @@ export class InteractiveMode {
|
|
|
2004
2516
|
case "user": {
|
|
2005
2517
|
const textContent = this.getUserMessageText(message);
|
|
2006
2518
|
if (textContent) {
|
|
2519
|
+
if (this.chatContainer.children.length > 0) {
|
|
2520
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2521
|
+
}
|
|
2007
2522
|
const skillBlock = parseSkillBlock(textContent);
|
|
2008
2523
|
if (skillBlock) {
|
|
2009
2524
|
// Render skill block (collapsible)
|
|
2010
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2011
2525
|
const component = new SkillInvocationMessageComponent(skillBlock, this.getMarkdownThemeWithSettings());
|
|
2012
2526
|
component.setExpanded(this.toolOutputExpanded);
|
|
2013
2527
|
this.chatContainer.addChild(component);
|
|
@@ -2028,7 +2542,7 @@ export class InteractiveMode {
|
|
|
2028
2542
|
break;
|
|
2029
2543
|
}
|
|
2030
2544
|
case "assistant": {
|
|
2031
|
-
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings());
|
|
2545
|
+
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
2032
2546
|
this.chatContainer.addChild(assistantComponent);
|
|
2033
2547
|
break;
|
|
2034
2548
|
}
|
|
@@ -2049,6 +2563,7 @@ export class InteractiveMode {
|
|
|
2049
2563
|
*/
|
|
2050
2564
|
renderSessionContext(sessionContext, options = {}) {
|
|
2051
2565
|
this.pendingTools.clear();
|
|
2566
|
+
const renderedPendingTools = new Map();
|
|
2052
2567
|
if (options.updateFooter) {
|
|
2053
2568
|
this.footer.invalidate();
|
|
2054
2569
|
this.updateEditorBorderColor();
|
|
@@ -2060,7 +2575,10 @@ export class InteractiveMode {
|
|
|
2060
2575
|
// Render tool call components
|
|
2061
2576
|
for (const content of message.content) {
|
|
2062
2577
|
if (content.type === "toolCall") {
|
|
2063
|
-
const component = new ToolExecutionComponent(content.name, content.
|
|
2578
|
+
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
|
|
2579
|
+
showImages: this.settingsManager.getShowImages(),
|
|
2580
|
+
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
2581
|
+
}, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
|
|
2064
2582
|
component.setExpanded(this.toolOutputExpanded);
|
|
2065
2583
|
this.chatContainer.addChild(component);
|
|
2066
2584
|
if (message.stopReason === "aborted" || message.stopReason === "error") {
|
|
@@ -2078,17 +2596,17 @@ export class InteractiveMode {
|
|
|
2078
2596
|
component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
|
|
2079
2597
|
}
|
|
2080
2598
|
else {
|
|
2081
|
-
|
|
2599
|
+
renderedPendingTools.set(content.id, component);
|
|
2082
2600
|
}
|
|
2083
2601
|
}
|
|
2084
2602
|
}
|
|
2085
2603
|
}
|
|
2086
2604
|
else if (message.role === "toolResult") {
|
|
2087
2605
|
// Match tool results to pending tool components
|
|
2088
|
-
const component =
|
|
2606
|
+
const component = renderedPendingTools.get(message.toolCallId);
|
|
2089
2607
|
if (component) {
|
|
2090
2608
|
component.updateResult(message);
|
|
2091
|
-
|
|
2609
|
+
renderedPendingTools.delete(message.toolCallId);
|
|
2092
2610
|
}
|
|
2093
2611
|
}
|
|
2094
2612
|
else {
|
|
@@ -2096,7 +2614,9 @@ export class InteractiveMode {
|
|
|
2096
2614
|
this.addMessageToChat(message, options);
|
|
2097
2615
|
}
|
|
2098
2616
|
}
|
|
2099
|
-
|
|
2617
|
+
for (const [toolCallId, component] of renderedPendingTools) {
|
|
2618
|
+
this.pendingTools.set(toolCallId, component);
|
|
2619
|
+
}
|
|
2100
2620
|
this.ui.requestRender();
|
|
2101
2621
|
}
|
|
2102
2622
|
renderInitialMessages() {
|
|
@@ -2144,26 +2664,32 @@ export class InteractiveMode {
|
|
|
2144
2664
|
// Only called when editor is empty (enforced by CustomEditor)
|
|
2145
2665
|
void this.shutdown();
|
|
2146
2666
|
}
|
|
2667
|
+
/**
|
|
2668
|
+
* Gracefully shutdown the agent.
|
|
2669
|
+
* Stops the TUI before emitting shutdown events so extension UI cleanup cannot
|
|
2670
|
+
* repaint the final frame while the process is exiting.
|
|
2671
|
+
*/
|
|
2672
|
+
isShuttingDown = false;
|
|
2147
2673
|
async shutdown() {
|
|
2148
2674
|
if (this.isShuttingDown)
|
|
2149
2675
|
return;
|
|
2150
2676
|
this.isShuttingDown = true;
|
|
2151
|
-
|
|
2152
|
-
const extensionRunner = this.session.extensionRunner;
|
|
2153
|
-
if (extensionRunner?.hasHandlers("session_shutdown")) {
|
|
2154
|
-
await extensionRunner.emit({
|
|
2155
|
-
type: "session_shutdown",
|
|
2156
|
-
});
|
|
2157
|
-
}
|
|
2158
|
-
// Wait for any pending renders to complete
|
|
2159
|
-
// requestRender() uses process.nextTick(), so we wait one tick
|
|
2160
|
-
await new Promise((resolve) => process.nextTick(resolve));
|
|
2677
|
+
this.unregisterSignalHandlers();
|
|
2161
2678
|
// Drain any in-flight Kitty key release events before stopping.
|
|
2162
2679
|
// This prevents escape sequences from leaking to the parent shell over slow SSH.
|
|
2163
2680
|
await this.ui.terminal.drainInput(1000);
|
|
2164
2681
|
this.stop();
|
|
2682
|
+
await this.runtimeHost.dispose();
|
|
2165
2683
|
process.exit(0);
|
|
2166
2684
|
}
|
|
2685
|
+
emergencyTerminalExit() {
|
|
2686
|
+
this.isShuttingDown = true;
|
|
2687
|
+
this.unregisterSignalHandlers();
|
|
2688
|
+
killTrackedDetachedChildren();
|
|
2689
|
+
// The terminal is gone. Do not run normal shutdown because TUI and
|
|
2690
|
+
// extension cleanup can write restore sequences and re-trigger EIO.
|
|
2691
|
+
process.exit(129);
|
|
2692
|
+
}
|
|
2167
2693
|
/**
|
|
2168
2694
|
* Check if shutdown was requested and perform shutdown if so.
|
|
2169
2695
|
*/
|
|
@@ -2172,21 +2698,71 @@ export class InteractiveMode {
|
|
|
2172
2698
|
return;
|
|
2173
2699
|
await this.shutdown();
|
|
2174
2700
|
}
|
|
2701
|
+
registerSignalHandlers() {
|
|
2702
|
+
this.unregisterSignalHandlers();
|
|
2703
|
+
const signals = ["SIGTERM"];
|
|
2704
|
+
if (process.platform !== "win32") {
|
|
2705
|
+
signals.push("SIGHUP");
|
|
2706
|
+
}
|
|
2707
|
+
for (const signal of signals) {
|
|
2708
|
+
const handler = () => {
|
|
2709
|
+
if (signal === "SIGHUP") {
|
|
2710
|
+
this.emergencyTerminalExit();
|
|
2711
|
+
}
|
|
2712
|
+
killTrackedDetachedChildren();
|
|
2713
|
+
void this.shutdown();
|
|
2714
|
+
};
|
|
2715
|
+
process.prependListener(signal, handler);
|
|
2716
|
+
this.signalCleanupHandlers.push(() => process.off(signal, handler));
|
|
2717
|
+
}
|
|
2718
|
+
const terminalErrorHandler = (error) => {
|
|
2719
|
+
if (isDeadTerminalError(error)) {
|
|
2720
|
+
this.emergencyTerminalExit();
|
|
2721
|
+
}
|
|
2722
|
+
throw error;
|
|
2723
|
+
};
|
|
2724
|
+
process.stdout.on("error", terminalErrorHandler);
|
|
2725
|
+
process.stderr.on("error", terminalErrorHandler);
|
|
2726
|
+
this.signalCleanupHandlers.push(() => process.stdout.off("error", terminalErrorHandler));
|
|
2727
|
+
this.signalCleanupHandlers.push(() => process.stderr.off("error", terminalErrorHandler));
|
|
2728
|
+
}
|
|
2729
|
+
unregisterSignalHandlers() {
|
|
2730
|
+
for (const cleanup of this.signalCleanupHandlers) {
|
|
2731
|
+
cleanup();
|
|
2732
|
+
}
|
|
2733
|
+
this.signalCleanupHandlers = [];
|
|
2734
|
+
}
|
|
2175
2735
|
handleCtrlZ() {
|
|
2736
|
+
if (process.platform === "win32") {
|
|
2737
|
+
this.showStatus("Suspend to background is not supported on Windows");
|
|
2738
|
+
return;
|
|
2739
|
+
}
|
|
2740
|
+
// Keep the event loop alive while suspended. Without this, stopping the TUI
|
|
2741
|
+
// can leave Node with no ref'ed handles, causing the process to exit on fg
|
|
2742
|
+
// before the SIGCONT handler gets a chance to restore the terminal.
|
|
2743
|
+
const suspendKeepAlive = setInterval(() => { }, 2 ** 30);
|
|
2176
2744
|
// Ignore SIGINT while suspended so Ctrl+C in the terminal does not
|
|
2177
2745
|
// kill the backgrounded process. The handler is removed on resume.
|
|
2178
2746
|
const ignoreSigint = () => { };
|
|
2179
2747
|
process.on("SIGINT", ignoreSigint);
|
|
2180
2748
|
// Set up handler to restore TUI when resumed
|
|
2181
2749
|
process.once("SIGCONT", () => {
|
|
2750
|
+
clearInterval(suspendKeepAlive);
|
|
2182
2751
|
process.removeListener("SIGINT", ignoreSigint);
|
|
2183
2752
|
this.ui.start();
|
|
2184
2753
|
this.ui.requestRender(true);
|
|
2185
2754
|
});
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2755
|
+
try {
|
|
2756
|
+
// Stop the TUI (restore terminal to normal mode)
|
|
2757
|
+
this.ui.stop();
|
|
2758
|
+
// Send SIGTSTP to process group (pid=0 means all processes in group)
|
|
2759
|
+
process.kill(0, "SIGTSTP");
|
|
2760
|
+
}
|
|
2761
|
+
catch (error) {
|
|
2762
|
+
clearInterval(suspendKeepAlive);
|
|
2763
|
+
process.removeListener("SIGINT", ignoreSigint);
|
|
2764
|
+
throw error;
|
|
2765
|
+
}
|
|
2190
2766
|
}
|
|
2191
2767
|
async handleFollowUp() {
|
|
2192
2768
|
const text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
|
|
@@ -2215,6 +2791,7 @@ export class InteractiveMode {
|
|
|
2215
2791
|
}
|
|
2216
2792
|
// If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
|
|
2217
2793
|
else if (this.editor.onSubmit) {
|
|
2794
|
+
this.editor.setText("");
|
|
2218
2795
|
this.editor.onSubmit(text);
|
|
2219
2796
|
}
|
|
2220
2797
|
}
|
|
@@ -2260,6 +2837,7 @@ export class InteractiveMode {
|
|
|
2260
2837
|
this.updateEditorBorderColor();
|
|
2261
2838
|
const thinkingStr = result.model.reasoning && result.thinkingLevel !== "off" ? ` (thinking: ${result.thinkingLevel})` : "";
|
|
2262
2839
|
this.showStatus(`Switched to ${result.model.name || result.model.id}${thinkingStr}`);
|
|
2840
|
+
void this.maybeWarnAboutAnthropicSubscriptionAuth(result.model);
|
|
2263
2841
|
}
|
|
2264
2842
|
}
|
|
2265
2843
|
catch (error) {
|
|
@@ -2271,6 +2849,10 @@ export class InteractiveMode {
|
|
|
2271
2849
|
}
|
|
2272
2850
|
setToolsExpanded(expanded) {
|
|
2273
2851
|
this.toolOutputExpanded = expanded;
|
|
2852
|
+
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
2853
|
+
if (isExpandable(activeHeader)) {
|
|
2854
|
+
activeHeader.setExpanded(expanded);
|
|
2855
|
+
}
|
|
2274
2856
|
for (const child of this.chatContainer.children) {
|
|
2275
2857
|
if (isExpandable(child)) {
|
|
2276
2858
|
child.setExpanded(expanded);
|
|
@@ -2311,6 +2893,7 @@ export class InteractiveMode {
|
|
|
2311
2893
|
// Spawn editor synchronously with inherited stdio for interactive editing
|
|
2312
2894
|
const result = spawnSync(editor, [...editorArgs, tmpFile], {
|
|
2313
2895
|
stdio: "inherit",
|
|
2896
|
+
shell: process.platform === "win32",
|
|
2314
2897
|
});
|
|
2315
2898
|
// On successful exit (status 0), replace editor content
|
|
2316
2899
|
if (result.status === 0) {
|
|
@@ -2351,16 +2934,29 @@ export class InteractiveMode {
|
|
|
2351
2934
|
this.ui.requestRender();
|
|
2352
2935
|
}
|
|
2353
2936
|
showNewVersionNotification(newVersion) {
|
|
2354
|
-
const action = theme.fg("accent",
|
|
2355
|
-
const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. `) + action;
|
|
2356
|
-
const changelogUrl =
|
|
2357
|
-
const
|
|
2937
|
+
const action = theme.fg("accent", `${APP_NAME} update`);
|
|
2938
|
+
const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. Run `) + action;
|
|
2939
|
+
const changelogUrl = "https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md";
|
|
2940
|
+
const changelogLink = getCapabilities().hyperlinks
|
|
2941
|
+
? hyperlink(theme.fg("accent", "open changelog"), changelogUrl)
|
|
2942
|
+
: theme.fg("accent", changelogUrl);
|
|
2943
|
+
const changelogLine = theme.fg("muted", "Changelog: ") + changelogLink;
|
|
2358
2944
|
this.chatContainer.addChild(new Spacer(1));
|
|
2359
2945
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2360
2946
|
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}\n${changelogLine}`, 1, 0));
|
|
2361
2947
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2362
2948
|
this.ui.requestRender();
|
|
2363
2949
|
}
|
|
2950
|
+
showPackageUpdateNotification(packages) {
|
|
2951
|
+
const action = theme.fg("accent", `${APP_NAME} update`);
|
|
2952
|
+
const updateInstruction = theme.fg("muted", "Package updates are available. Run ") + action;
|
|
2953
|
+
const packageLines = packages.map((pkg) => `- ${pkg}`).join("\n");
|
|
2954
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2955
|
+
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2956
|
+
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Package Updates Available"))}\n${updateInstruction}\n${theme.fg("muted", "Packages:")}\n${packageLines}`, 1, 0));
|
|
2957
|
+
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2958
|
+
this.ui.requestRender();
|
|
2959
|
+
}
|
|
2364
2960
|
/**
|
|
2365
2961
|
* Get all queued messages (read-only).
|
|
2366
2962
|
* Combines session queue and compaction queue.
|
|
@@ -2408,7 +3004,7 @@ export class InteractiveMode {
|
|
|
2408
3004
|
const text = theme.fg("dim", `Follow-up: ${message}`);
|
|
2409
3005
|
this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
|
|
2410
3006
|
}
|
|
2411
|
-
const dequeueHint = this.getAppKeyDisplay("dequeue");
|
|
3007
|
+
const dequeueHint = this.getAppKeyDisplay("app.message.dequeue");
|
|
2412
3008
|
const hintText = theme.fg("dim", `↳ ${dequeueHint} to edit all queued messages`);
|
|
2413
3009
|
this.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
|
|
2414
3010
|
}
|
|
@@ -2444,8 +3040,6 @@ export class InteractiveMode {
|
|
|
2444
3040
|
if (!text.startsWith("/"))
|
|
2445
3041
|
return false;
|
|
2446
3042
|
const extensionRunner = this.session.extensionRunner;
|
|
2447
|
-
if (!extensionRunner)
|
|
2448
|
-
return false;
|
|
2449
3043
|
const spaceIndex = text.indexOf(" ");
|
|
2450
3044
|
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
2451
3045
|
return !!extensionRunner.getCommand(commandName);
|
|
@@ -2551,6 +3145,7 @@ export class InteractiveMode {
|
|
|
2551
3145
|
const selector = new SettingsSelectorComponent({
|
|
2552
3146
|
autoCompact: this.session.autoCompactionEnabled,
|
|
2553
3147
|
showImages: this.settingsManager.getShowImages(),
|
|
3148
|
+
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
2554
3149
|
autoResizeImages: this.settingsManager.getImageAutoResize(),
|
|
2555
3150
|
blockImages: this.settingsManager.getBlockImages(),
|
|
2556
3151
|
enableSkillCommands: this.settingsManager.getEnableSkillCommands(),
|
|
@@ -2563,6 +3158,7 @@ export class InteractiveMode {
|
|
|
2563
3158
|
availableThemes: getAvailableThemes(),
|
|
2564
3159
|
hideThinkingBlock: this.hideThinkingBlock,
|
|
2565
3160
|
collapseChangelog: this.settingsManager.getCollapseChangelog(),
|
|
3161
|
+
enableInstallTelemetry: this.settingsManager.getEnableInstallTelemetry(),
|
|
2566
3162
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
2567
3163
|
treeFilterMode: this.settingsManager.getTreeFilterMode(),
|
|
2568
3164
|
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
@@ -2570,6 +3166,8 @@ export class InteractiveMode {
|
|
|
2570
3166
|
autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
|
|
2571
3167
|
quietStartup: this.settingsManager.getQuietStartup(),
|
|
2572
3168
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
3169
|
+
showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
|
|
3170
|
+
warnings: this.settingsManager.getWarnings(),
|
|
2573
3171
|
}, {
|
|
2574
3172
|
onAutoCompactChange: (enabled) => {
|
|
2575
3173
|
this.session.setAutoCompactionEnabled(enabled);
|
|
@@ -2583,6 +3181,14 @@ export class InteractiveMode {
|
|
|
2583
3181
|
}
|
|
2584
3182
|
}
|
|
2585
3183
|
},
|
|
3184
|
+
onImageWidthCellsChange: (width) => {
|
|
3185
|
+
this.settingsManager.setImageWidthCells(width);
|
|
3186
|
+
for (const child of this.chatContainer.children) {
|
|
3187
|
+
if (child instanceof ToolExecutionComponent) {
|
|
3188
|
+
child.setImageWidthCells(width);
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
},
|
|
2586
3192
|
onAutoResizeImagesChange: (enabled) => {
|
|
2587
3193
|
this.settingsManager.setImageAutoResize(enabled);
|
|
2588
3194
|
},
|
|
@@ -2591,7 +3197,7 @@ export class InteractiveMode {
|
|
|
2591
3197
|
},
|
|
2592
3198
|
onEnableSkillCommandsChange: (enabled) => {
|
|
2593
3199
|
this.settingsManager.setEnableSkillCommands(enabled);
|
|
2594
|
-
this.
|
|
3200
|
+
this.setupAutocompleteProvider();
|
|
2595
3201
|
},
|
|
2596
3202
|
onSteeringModeChange: (mode) => {
|
|
2597
3203
|
this.session.setSteeringMode(mode);
|
|
@@ -2601,7 +3207,7 @@ export class InteractiveMode {
|
|
|
2601
3207
|
},
|
|
2602
3208
|
onTransportChange: (transport) => {
|
|
2603
3209
|
this.settingsManager.setTransport(transport);
|
|
2604
|
-
this.session.agent.
|
|
3210
|
+
this.session.agent.transport = transport;
|
|
2605
3211
|
},
|
|
2606
3212
|
onThinkingLevelChange: (level) => {
|
|
2607
3213
|
this.session.setThinkingLevel(level);
|
|
@@ -2637,6 +3243,9 @@ export class InteractiveMode {
|
|
|
2637
3243
|
onCollapseChangelogChange: (collapsed) => {
|
|
2638
3244
|
this.settingsManager.setCollapseChangelog(collapsed);
|
|
2639
3245
|
},
|
|
3246
|
+
onEnableInstallTelemetryChange: (enabled) => {
|
|
3247
|
+
this.settingsManager.setEnableInstallTelemetry(enabled);
|
|
3248
|
+
},
|
|
2640
3249
|
onQuietStartupChange: (enabled) => {
|
|
2641
3250
|
this.settingsManager.setQuietStartup(enabled);
|
|
2642
3251
|
},
|
|
@@ -2668,6 +3277,12 @@ export class InteractiveMode {
|
|
|
2668
3277
|
this.settingsManager.setClearOnShrink(enabled);
|
|
2669
3278
|
this.ui.setClearOnShrink(enabled);
|
|
2670
3279
|
},
|
|
3280
|
+
onShowTerminalProgressChange: (enabled) => {
|
|
3281
|
+
this.settingsManager.setShowTerminalProgress(enabled);
|
|
3282
|
+
},
|
|
3283
|
+
onWarningsChange: (warnings) => {
|
|
3284
|
+
this.settingsManager.setWarnings(warnings);
|
|
3285
|
+
},
|
|
2671
3286
|
onCancel: () => {
|
|
2672
3287
|
done();
|
|
2673
3288
|
this.ui.requestRender();
|
|
@@ -2688,6 +3303,7 @@ export class InteractiveMode {
|
|
|
2688
3303
|
this.footer.invalidate();
|
|
2689
3304
|
this.updateEditorBorderColor();
|
|
2690
3305
|
this.showStatus(`Model: ${model.id}`);
|
|
3306
|
+
void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
|
|
2691
3307
|
this.checkDaxnutsEasterEgg(model);
|
|
2692
3308
|
}
|
|
2693
3309
|
catch (error) {
|
|
@@ -2698,28 +3314,8 @@ export class InteractiveMode {
|
|
|
2698
3314
|
this.showModelSelector(searchTerm);
|
|
2699
3315
|
}
|
|
2700
3316
|
async findExactModelMatch(searchTerm) {
|
|
2701
|
-
const term = searchTerm.trim();
|
|
2702
|
-
if (!term)
|
|
2703
|
-
return undefined;
|
|
2704
|
-
let targetProvider;
|
|
2705
|
-
let targetModelId = "";
|
|
2706
|
-
if (term.includes("/")) {
|
|
2707
|
-
const parts = term.split("/", 2);
|
|
2708
|
-
targetProvider = parts[0]?.trim().toLowerCase();
|
|
2709
|
-
targetModelId = parts[1]?.trim().toLowerCase() ?? "";
|
|
2710
|
-
}
|
|
2711
|
-
else {
|
|
2712
|
-
targetModelId = term.toLowerCase();
|
|
2713
|
-
}
|
|
2714
|
-
if (!targetModelId)
|
|
2715
|
-
return undefined;
|
|
2716
3317
|
const models = await this.getModelCandidates();
|
|
2717
|
-
|
|
2718
|
-
const idMatch = item.id.toLowerCase() === targetModelId;
|
|
2719
|
-
const providerMatch = !targetProvider || item.provider.toLowerCase() === targetProvider;
|
|
2720
|
-
return idMatch && providerMatch;
|
|
2721
|
-
});
|
|
2722
|
-
return exactMatches.length === 1 ? exactMatches[0] : undefined;
|
|
3318
|
+
return findExactModelReferenceMatch(searchTerm, models);
|
|
2723
3319
|
}
|
|
2724
3320
|
async getModelCandidates() {
|
|
2725
3321
|
if (this.session.scopedModels.length > 0) {
|
|
@@ -2739,6 +3335,34 @@ export class InteractiveMode {
|
|
|
2739
3335
|
const uniqueProviders = new Set(models.map((m) => m.provider));
|
|
2740
3336
|
this.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);
|
|
2741
3337
|
}
|
|
3338
|
+
async maybeWarnAboutAnthropicSubscriptionAuth(model = this.session.model) {
|
|
3339
|
+
if (this.settingsManager.getWarnings().anthropicExtraUsage === false) {
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
if (this.anthropicSubscriptionWarningShown) {
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
if (!model || model.provider !== "anthropic") {
|
|
3346
|
+
return;
|
|
3347
|
+
}
|
|
3348
|
+
const storedCredential = this.session.modelRegistry.authStorage.get("anthropic");
|
|
3349
|
+
if (storedCredential?.type === "oauth") {
|
|
3350
|
+
this.anthropicSubscriptionWarningShown = true;
|
|
3351
|
+
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
3352
|
+
return;
|
|
3353
|
+
}
|
|
3354
|
+
try {
|
|
3355
|
+
const apiKey = await this.session.modelRegistry.getApiKeyForProvider(model.provider);
|
|
3356
|
+
if (!isAnthropicSubscriptionAuthKey(apiKey)) {
|
|
3357
|
+
return;
|
|
3358
|
+
}
|
|
3359
|
+
this.anthropicSubscriptionWarningShown = true;
|
|
3360
|
+
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
3361
|
+
}
|
|
3362
|
+
catch {
|
|
3363
|
+
// Ignore auth lookup failures for warning-only checks.
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
2742
3366
|
showModelSelector(initialSearchInput) {
|
|
2743
3367
|
this.showSelector((done) => {
|
|
2744
3368
|
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRegistry, this.session.scopedModels, async (model) => {
|
|
@@ -2748,6 +3372,7 @@ export class InteractiveMode {
|
|
|
2748
3372
|
this.updateEditorBorderColor();
|
|
2749
3373
|
done();
|
|
2750
3374
|
this.showStatus(`Model: ${model.id}`);
|
|
3375
|
+
void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
|
|
2751
3376
|
this.checkDaxnutsEasterEgg(model);
|
|
2752
3377
|
}
|
|
2753
3378
|
catch (error) {
|
|
@@ -2773,33 +3398,24 @@ export class InteractiveMode {
|
|
|
2773
3398
|
const sessionScopedModels = this.session.scopedModels;
|
|
2774
3399
|
const hasSessionScope = sessionScopedModels.length > 0;
|
|
2775
3400
|
// Build enabled model IDs from session state or settings
|
|
2776
|
-
|
|
2777
|
-
let hasFilter = false;
|
|
3401
|
+
let currentEnabledIds = null;
|
|
2778
3402
|
if (hasSessionScope) {
|
|
2779
3403
|
// Use current session's scoped models
|
|
2780
|
-
|
|
2781
|
-
enabledModelIds.add(`${sm.model.provider}/${sm.model.id}`);
|
|
2782
|
-
}
|
|
2783
|
-
hasFilter = true;
|
|
3404
|
+
currentEnabledIds = sessionScopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
2784
3405
|
}
|
|
2785
3406
|
else {
|
|
2786
3407
|
// Fall back to settings
|
|
2787
3408
|
const patterns = this.settingsManager.getEnabledModels();
|
|
2788
3409
|
if (patterns !== undefined && patterns.length > 0) {
|
|
2789
|
-
hasFilter = true;
|
|
2790
3410
|
const scopedModels = await resolveModelScope(patterns, this.session.modelRegistry);
|
|
2791
|
-
|
|
2792
|
-
enabledModelIds.add(`${sm.model.provider}/${sm.model.id}`);
|
|
2793
|
-
}
|
|
3411
|
+
currentEnabledIds = scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
2794
3412
|
}
|
|
2795
3413
|
}
|
|
2796
|
-
// Track current enabled state (session-only until persisted)
|
|
2797
|
-
const currentEnabledIds = new Set(enabledModelIds);
|
|
2798
|
-
let currentHasFilter = hasFilter;
|
|
2799
3414
|
// Helper to update session's scoped models (session-only, no persist)
|
|
2800
3415
|
const updateSessionModels = async (enabledIds) => {
|
|
2801
|
-
|
|
2802
|
-
|
|
3416
|
+
currentEnabledIds = enabledIds === null ? null : [...enabledIds];
|
|
3417
|
+
if (enabledIds && enabledIds.length > 0 && enabledIds.length < allModels.length) {
|
|
3418
|
+
const newScopedModels = await resolveModelScope(enabledIds, this.session.modelRegistry);
|
|
2803
3419
|
this.session.setScopedModels(newScopedModels.map((sm) => ({
|
|
2804
3420
|
model: sm.model,
|
|
2805
3421
|
thinkingLevel: sm.thinkingLevel,
|
|
@@ -2816,49 +3432,16 @@ export class InteractiveMode {
|
|
|
2816
3432
|
const selector = new ScopedModelsSelectorComponent({
|
|
2817
3433
|
allModels,
|
|
2818
3434
|
enabledModelIds: currentEnabledIds,
|
|
2819
|
-
hasEnabledModelsFilter: currentHasFilter,
|
|
2820
3435
|
}, {
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
currentEnabledIds.add(modelId);
|
|
2824
|
-
}
|
|
2825
|
-
else {
|
|
2826
|
-
currentEnabledIds.delete(modelId);
|
|
2827
|
-
}
|
|
2828
|
-
currentHasFilter = true;
|
|
2829
|
-
await updateSessionModels(currentEnabledIds);
|
|
2830
|
-
},
|
|
2831
|
-
onEnableAll: async (allModelIds) => {
|
|
2832
|
-
currentEnabledIds.clear();
|
|
2833
|
-
for (const id of allModelIds) {
|
|
2834
|
-
currentEnabledIds.add(id);
|
|
2835
|
-
}
|
|
2836
|
-
currentHasFilter = false;
|
|
2837
|
-
await updateSessionModels(currentEnabledIds);
|
|
2838
|
-
},
|
|
2839
|
-
onClearAll: async () => {
|
|
2840
|
-
currentEnabledIds.clear();
|
|
2841
|
-
currentHasFilter = true;
|
|
2842
|
-
await updateSessionModels(currentEnabledIds);
|
|
2843
|
-
},
|
|
2844
|
-
onToggleProvider: async (_provider, modelIds, enabled) => {
|
|
2845
|
-
for (const id of modelIds) {
|
|
2846
|
-
if (enabled) {
|
|
2847
|
-
currentEnabledIds.add(id);
|
|
2848
|
-
}
|
|
2849
|
-
else {
|
|
2850
|
-
currentEnabledIds.delete(id);
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
currentHasFilter = true;
|
|
2854
|
-
await updateSessionModels(currentEnabledIds);
|
|
3436
|
+
onChange: async (enabledIds) => {
|
|
3437
|
+
await updateSessionModels(enabledIds);
|
|
2855
3438
|
},
|
|
2856
3439
|
onPersist: (enabledIds) => {
|
|
2857
3440
|
// Persist to settings
|
|
2858
|
-
const newPatterns = enabledIds.length === allModels.length
|
|
3441
|
+
const newPatterns = enabledIds === null || enabledIds.length === allModels.length
|
|
2859
3442
|
? undefined // All enabled = clear filter
|
|
2860
3443
|
: enabledIds;
|
|
2861
|
-
this.settingsManager.setEnabledModels(newPatterns);
|
|
3444
|
+
this.settingsManager.setEnabledModels(newPatterns ? [...newPatterns] : undefined);
|
|
2862
3445
|
this.showStatus("Model selection saved to settings");
|
|
2863
3446
|
},
|
|
2864
3447
|
onCancel: () => {
|
|
@@ -2875,27 +3458,52 @@ export class InteractiveMode {
|
|
|
2875
3458
|
this.showStatus("No messages to fork from");
|
|
2876
3459
|
return;
|
|
2877
3460
|
}
|
|
3461
|
+
const initialSelectedId = userMessages[userMessages.length - 1]?.entryId;
|
|
2878
3462
|
this.showSelector((done) => {
|
|
2879
3463
|
const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ id: m.entryId, text: m.text })), async (entryId) => {
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
3464
|
+
try {
|
|
3465
|
+
const result = await this.runtimeHost.fork(entryId);
|
|
3466
|
+
if (result.cancelled) {
|
|
3467
|
+
done();
|
|
3468
|
+
this.ui.requestRender();
|
|
3469
|
+
return;
|
|
3470
|
+
}
|
|
3471
|
+
this.renderCurrentSessionState();
|
|
3472
|
+
this.editor.setText(result.selectedText ?? "");
|
|
2883
3473
|
done();
|
|
2884
|
-
this.
|
|
2885
|
-
|
|
3474
|
+
this.showStatus("Forked to new session");
|
|
3475
|
+
}
|
|
3476
|
+
catch (error) {
|
|
3477
|
+
done();
|
|
3478
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
2886
3479
|
}
|
|
2887
|
-
this.chatContainer.clear();
|
|
2888
|
-
this.renderInitialMessages();
|
|
2889
|
-
this.editor.setText(result.selectedText);
|
|
2890
|
-
done();
|
|
2891
|
-
this.showStatus("Branched to new session");
|
|
2892
3480
|
}, () => {
|
|
2893
3481
|
done();
|
|
2894
3482
|
this.ui.requestRender();
|
|
2895
|
-
});
|
|
3483
|
+
}, initialSelectedId);
|
|
2896
3484
|
return { component: selector, focus: selector.getMessageList() };
|
|
2897
3485
|
});
|
|
2898
3486
|
}
|
|
3487
|
+
async handleCloneCommand() {
|
|
3488
|
+
const leafId = this.sessionManager.getLeafId();
|
|
3489
|
+
if (!leafId) {
|
|
3490
|
+
this.showStatus("Nothing to clone yet");
|
|
3491
|
+
return;
|
|
3492
|
+
}
|
|
3493
|
+
try {
|
|
3494
|
+
const result = await this.runtimeHost.fork(leafId, { position: "at" });
|
|
3495
|
+
if (result.cancelled) {
|
|
3496
|
+
this.ui.requestRender();
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3499
|
+
this.renderCurrentSessionState();
|
|
3500
|
+
this.editor.setText("");
|
|
3501
|
+
this.showStatus("Cloned to new session");
|
|
3502
|
+
}
|
|
3503
|
+
catch (error) {
|
|
3504
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
2899
3507
|
showTreeSelector(initialSelectedId) {
|
|
2900
3508
|
const tree = this.sessionManager.getTree();
|
|
2901
3509
|
const realLeafId = this.sessionManager.getLeafId();
|
|
@@ -2950,7 +3558,7 @@ export class InteractiveMode {
|
|
|
2950
3558
|
this.session.abortBranchSummary();
|
|
2951
3559
|
};
|
|
2952
3560
|
this.chatContainer.addChild(new Spacer(1));
|
|
2953
|
-
summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${
|
|
3561
|
+
summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${keyText("app.interrupt")} to cancel)`);
|
|
2954
3562
|
this.statusContainer.addChild(summaryLoader);
|
|
2955
3563
|
this.ui.requestRender();
|
|
2956
3564
|
}
|
|
@@ -2976,6 +3584,7 @@ export class InteractiveMode {
|
|
|
2976
3584
|
this.editor.setText(result.editorText);
|
|
2977
3585
|
}
|
|
2978
3586
|
this.showStatus("Navigated to selected point");
|
|
3587
|
+
void this.flushCompactionQueue({ willRetry: false });
|
|
2979
3588
|
}
|
|
2980
3589
|
catch (error) {
|
|
2981
3590
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
@@ -3021,73 +3630,297 @@ export class InteractiveMode {
|
|
|
3021
3630
|
return { component: selector, focus: selector };
|
|
3022
3631
|
});
|
|
3023
3632
|
}
|
|
3024
|
-
async handleResumeSession(sessionPath) {
|
|
3025
|
-
// Stop loading animation
|
|
3633
|
+
async handleResumeSession(sessionPath, options) {
|
|
3026
3634
|
if (this.loadingAnimation) {
|
|
3027
3635
|
this.loadingAnimation.stop();
|
|
3028
3636
|
this.loadingAnimation = undefined;
|
|
3029
3637
|
}
|
|
3030
3638
|
this.statusContainer.clear();
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3639
|
+
try {
|
|
3640
|
+
const result = await this.runtimeHost.switchSession(sessionPath, {
|
|
3641
|
+
withSession: options?.withSession,
|
|
3642
|
+
});
|
|
3643
|
+
if (result.cancelled) {
|
|
3644
|
+
return result;
|
|
3645
|
+
}
|
|
3646
|
+
this.renderCurrentSessionState();
|
|
3647
|
+
this.showStatus("Resumed session");
|
|
3648
|
+
return result;
|
|
3649
|
+
}
|
|
3650
|
+
catch (error) {
|
|
3651
|
+
if (error instanceof MissingSessionCwdError) {
|
|
3652
|
+
const selectedCwd = await this.promptForMissingSessionCwd(error);
|
|
3653
|
+
if (!selectedCwd) {
|
|
3654
|
+
this.showStatus("Resume cancelled");
|
|
3655
|
+
return { cancelled: true };
|
|
3656
|
+
}
|
|
3657
|
+
const result = await this.runtimeHost.switchSession(sessionPath, {
|
|
3658
|
+
cwdOverride: selectedCwd,
|
|
3659
|
+
withSession: options?.withSession,
|
|
3660
|
+
});
|
|
3661
|
+
if (result.cancelled) {
|
|
3662
|
+
return result;
|
|
3663
|
+
}
|
|
3664
|
+
this.renderCurrentSessionState();
|
|
3665
|
+
this.showStatus("Resumed session in current cwd");
|
|
3666
|
+
return result;
|
|
3667
|
+
}
|
|
3668
|
+
return this.handleFatalRuntimeError("Failed to resume session", error);
|
|
3669
|
+
}
|
|
3043
3670
|
}
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3671
|
+
getLoginProviderOptions(authType) {
|
|
3672
|
+
const authStorage = this.session.modelRegistry.authStorage;
|
|
3673
|
+
const oauthProviders = authStorage.getOAuthProviders();
|
|
3674
|
+
const oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));
|
|
3675
|
+
const options = oauthProviders.map((provider) => ({
|
|
3676
|
+
id: provider.id,
|
|
3677
|
+
name: provider.name,
|
|
3678
|
+
authType: "oauth",
|
|
3679
|
+
}));
|
|
3680
|
+
const modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));
|
|
3681
|
+
for (const providerId of modelProviders) {
|
|
3682
|
+
if (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {
|
|
3683
|
+
continue;
|
|
3684
|
+
}
|
|
3685
|
+
options.push({
|
|
3686
|
+
id: providerId,
|
|
3687
|
+
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
3688
|
+
authType: "api_key",
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
const filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;
|
|
3692
|
+
return filteredOptions.sort((a, b) => a.name.localeCompare(b.name));
|
|
3693
|
+
}
|
|
3694
|
+
getLogoutProviderOptions() {
|
|
3695
|
+
const authStorage = this.session.modelRegistry.authStorage;
|
|
3696
|
+
const options = [];
|
|
3697
|
+
for (const providerId of authStorage.list()) {
|
|
3698
|
+
const credential = authStorage.get(providerId);
|
|
3699
|
+
if (!credential) {
|
|
3700
|
+
continue;
|
|
3051
3701
|
}
|
|
3702
|
+
options.push({
|
|
3703
|
+
id: providerId,
|
|
3704
|
+
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
3705
|
+
authType: credential.type,
|
|
3706
|
+
});
|
|
3707
|
+
}
|
|
3708
|
+
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
3709
|
+
}
|
|
3710
|
+
showLoginAuthTypeSelector() {
|
|
3711
|
+
const subscriptionLabel = "Use a subscription";
|
|
3712
|
+
const apiKeyLabel = "Use an API key";
|
|
3713
|
+
this.showSelector((done) => {
|
|
3714
|
+
const selector = new ExtensionSelectorComponent("Select authentication method:", [subscriptionLabel, apiKeyLabel], (option) => {
|
|
3715
|
+
done();
|
|
3716
|
+
const authType = option === subscriptionLabel ? "oauth" : "api_key";
|
|
3717
|
+
this.showLoginProviderSelector(authType);
|
|
3718
|
+
}, () => {
|
|
3719
|
+
done();
|
|
3720
|
+
this.ui.requestRender();
|
|
3721
|
+
});
|
|
3722
|
+
return { component: selector, focus: selector };
|
|
3723
|
+
});
|
|
3724
|
+
}
|
|
3725
|
+
showLoginProviderSelector(authType) {
|
|
3726
|
+
const providerOptions = this.getLoginProviderOptions(authType);
|
|
3727
|
+
if (providerOptions.length === 0) {
|
|
3728
|
+
this.showStatus(authType === "oauth" ? "No subscription providers available." : "No API key providers available.");
|
|
3729
|
+
return;
|
|
3730
|
+
}
|
|
3731
|
+
this.showSelector((done) => {
|
|
3732
|
+
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
3733
|
+
done();
|
|
3734
|
+
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
3735
|
+
if (!providerOption) {
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
if (providerOption.authType === "oauth") {
|
|
3739
|
+
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
3740
|
+
}
|
|
3741
|
+
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
3742
|
+
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
3743
|
+
}
|
|
3744
|
+
else {
|
|
3745
|
+
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
3746
|
+
}
|
|
3747
|
+
}, () => {
|
|
3748
|
+
done();
|
|
3749
|
+
this.showLoginAuthTypeSelector();
|
|
3750
|
+
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId));
|
|
3751
|
+
return { component: selector, focus: selector };
|
|
3752
|
+
});
|
|
3753
|
+
}
|
|
3754
|
+
async showOAuthSelector(mode) {
|
|
3755
|
+
if (mode === "login") {
|
|
3756
|
+
this.showLoginAuthTypeSelector();
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
const providerOptions = this.getLogoutProviderOptions();
|
|
3760
|
+
if (providerOptions.length === 0) {
|
|
3761
|
+
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
3762
|
+
return;
|
|
3052
3763
|
}
|
|
3053
3764
|
this.showSelector((done) => {
|
|
3054
|
-
const selector = new OAuthSelectorComponent(mode, this.session.modelRegistry.authStorage, async (providerId) => {
|
|
3765
|
+
const selector = new OAuthSelectorComponent(mode, this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
3055
3766
|
done();
|
|
3056
|
-
|
|
3057
|
-
|
|
3767
|
+
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
3768
|
+
if (!providerOption) {
|
|
3769
|
+
return;
|
|
3770
|
+
}
|
|
3771
|
+
try {
|
|
3772
|
+
this.session.modelRegistry.authStorage.logout(providerOption.id);
|
|
3773
|
+
this.session.modelRegistry.refresh();
|
|
3774
|
+
await this.updateAvailableProviderCount();
|
|
3775
|
+
const message = providerOption.authType === "oauth"
|
|
3776
|
+
? `Logged out of ${providerOption.name}`
|
|
3777
|
+
: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;
|
|
3778
|
+
this.showStatus(message);
|
|
3779
|
+
}
|
|
3780
|
+
catch (error) {
|
|
3781
|
+
this.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3782
|
+
}
|
|
3783
|
+
}, () => {
|
|
3784
|
+
done();
|
|
3785
|
+
this.ui.requestRender();
|
|
3786
|
+
});
|
|
3787
|
+
return { component: selector, focus: selector };
|
|
3788
|
+
});
|
|
3789
|
+
}
|
|
3790
|
+
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
3791
|
+
this.session.modelRegistry.refresh();
|
|
3792
|
+
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
3793
|
+
let selectedModel;
|
|
3794
|
+
let selectionError;
|
|
3795
|
+
if (isUnknownModel(previousModel)) {
|
|
3796
|
+
const availableModels = this.session.modelRegistry.getAvailable();
|
|
3797
|
+
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
3798
|
+
if (!hasDefaultModelProvider(providerId)) {
|
|
3799
|
+
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
3800
|
+
}
|
|
3801
|
+
else if (providerModels.length === 0) {
|
|
3802
|
+
selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
|
|
3803
|
+
}
|
|
3804
|
+
else {
|
|
3805
|
+
const defaultModelId = defaultModelPerProvider[providerId];
|
|
3806
|
+
selectedModel = providerModels.find((model) => model.id === defaultModelId);
|
|
3807
|
+
if (!selectedModel) {
|
|
3808
|
+
selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
|
|
3058
3809
|
}
|
|
3059
3810
|
else {
|
|
3060
|
-
// Logout flow
|
|
3061
|
-
const providerInfo = this.session.modelRegistry.authStorage
|
|
3062
|
-
.getOAuthProviders()
|
|
3063
|
-
.find((p) => p.id === providerId);
|
|
3064
|
-
const providerName = providerInfo?.name || providerId;
|
|
3065
3811
|
try {
|
|
3066
|
-
this.session.
|
|
3067
|
-
this.session.modelRegistry.refresh();
|
|
3068
|
-
await this.updateAvailableProviderCount();
|
|
3069
|
-
this.showStatus(`Logged out of ${providerName}`);
|
|
3812
|
+
await this.session.setModel(selectedModel);
|
|
3070
3813
|
}
|
|
3071
3814
|
catch (error) {
|
|
3072
|
-
|
|
3815
|
+
selectedModel = undefined;
|
|
3816
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3817
|
+
selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
|
|
3073
3818
|
}
|
|
3074
3819
|
}
|
|
3075
|
-
}
|
|
3076
|
-
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
await this.updateAvailableProviderCount();
|
|
3823
|
+
this.footer.invalidate();
|
|
3824
|
+
this.updateEditorBorderColor();
|
|
3825
|
+
if (selectedModel) {
|
|
3826
|
+
this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
|
|
3827
|
+
void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
|
|
3828
|
+
this.checkDaxnutsEasterEgg(selectedModel);
|
|
3829
|
+
}
|
|
3830
|
+
else {
|
|
3831
|
+
this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
|
|
3832
|
+
if (selectionError) {
|
|
3833
|
+
this.showError(selectionError);
|
|
3834
|
+
}
|
|
3835
|
+
else {
|
|
3836
|
+
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
showBedrockSetupDialog(providerId, providerName) {
|
|
3841
|
+
const restoreEditor = () => {
|
|
3842
|
+
this.editorContainer.clear();
|
|
3843
|
+
this.editorContainer.addChild(this.editor);
|
|
3844
|
+
this.ui.setFocus(this.editor);
|
|
3845
|
+
this.ui.requestRender();
|
|
3846
|
+
};
|
|
3847
|
+
const dialog = new LoginDialogComponent(this.ui, providerId, () => restoreEditor(), providerName, "Amazon Bedrock setup");
|
|
3848
|
+
dialog.showInfo([
|
|
3849
|
+
theme.fg("text", "Amazon Bedrock uses AWS credentials instead of a single API key."),
|
|
3850
|
+
theme.fg("text", "Configure an AWS profile, IAM keys, bearer token, or role-based credentials."),
|
|
3851
|
+
theme.fg("muted", "See:"),
|
|
3852
|
+
theme.fg("accent", ` ${path.join(getDocsPath(), "providers.md")}`),
|
|
3853
|
+
]);
|
|
3854
|
+
this.editorContainer.clear();
|
|
3855
|
+
this.editorContainer.addChild(dialog);
|
|
3856
|
+
this.ui.setFocus(dialog);
|
|
3857
|
+
this.ui.requestRender();
|
|
3858
|
+
}
|
|
3859
|
+
async showApiKeyLoginDialog(providerId, providerName) {
|
|
3860
|
+
const previousModel = this.session.model;
|
|
3861
|
+
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
3862
|
+
// Completion handled below
|
|
3863
|
+
}, providerName);
|
|
3864
|
+
this.editorContainer.clear();
|
|
3865
|
+
this.editorContainer.addChild(dialog);
|
|
3866
|
+
this.ui.setFocus(dialog);
|
|
3867
|
+
this.ui.requestRender();
|
|
3868
|
+
const restoreEditor = () => {
|
|
3869
|
+
this.editorContainer.clear();
|
|
3870
|
+
this.editorContainer.addChild(this.editor);
|
|
3871
|
+
this.ui.setFocus(this.editor);
|
|
3872
|
+
this.ui.requestRender();
|
|
3873
|
+
};
|
|
3874
|
+
try {
|
|
3875
|
+
const apiKey = (await dialog.showPrompt("Enter API key:")).trim();
|
|
3876
|
+
if (!apiKey) {
|
|
3877
|
+
throw new Error("API key cannot be empty.");
|
|
3878
|
+
}
|
|
3879
|
+
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey });
|
|
3880
|
+
restoreEditor();
|
|
3881
|
+
await this.completeProviderAuthentication(providerId, providerName, "api_key", previousModel);
|
|
3882
|
+
}
|
|
3883
|
+
catch (error) {
|
|
3884
|
+
restoreEditor();
|
|
3885
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3886
|
+
if (errorMsg !== "Login cancelled") {
|
|
3887
|
+
this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
showOAuthLoginSelect(dialog, prompt) {
|
|
3892
|
+
return new Promise((resolve) => {
|
|
3893
|
+
const restoreDialog = () => {
|
|
3894
|
+
this.editorContainer.clear();
|
|
3895
|
+
this.editorContainer.addChild(dialog);
|
|
3896
|
+
this.ui.setFocus(dialog);
|
|
3077
3897
|
this.ui.requestRender();
|
|
3898
|
+
};
|
|
3899
|
+
const labels = prompt.options.map((option) => option.label);
|
|
3900
|
+
const selector = new ExtensionSelectorComponent(prompt.message, labels, (optionLabel) => {
|
|
3901
|
+
restoreDialog();
|
|
3902
|
+
resolve(prompt.options.find((option) => option.label === optionLabel)?.id);
|
|
3903
|
+
}, () => {
|
|
3904
|
+
restoreDialog();
|
|
3905
|
+
resolve(undefined);
|
|
3078
3906
|
});
|
|
3079
|
-
|
|
3907
|
+
this.editorContainer.clear();
|
|
3908
|
+
this.editorContainer.addChild(selector);
|
|
3909
|
+
this.ui.setFocus(selector);
|
|
3910
|
+
this.ui.requestRender();
|
|
3080
3911
|
});
|
|
3081
3912
|
}
|
|
3082
|
-
async showLoginDialog(providerId) {
|
|
3083
|
-
const providerInfo = this.session.modelRegistry.authStorage
|
|
3084
|
-
|
|
3913
|
+
async showLoginDialog(providerId, providerName) {
|
|
3914
|
+
const providerInfo = this.session.modelRegistry.authStorage
|
|
3915
|
+
.getOAuthProviders()
|
|
3916
|
+
.find((provider) => provider.id === providerId);
|
|
3917
|
+
const previousModel = this.session.model;
|
|
3085
3918
|
// Providers that use callback servers (can paste redirect URL)
|
|
3086
3919
|
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
3087
3920
|
// Create login dialog component
|
|
3088
3921
|
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
3089
3922
|
// Completion handled below
|
|
3090
|
-
});
|
|
3923
|
+
}, providerName);
|
|
3091
3924
|
// Show dialog in editor container
|
|
3092
3925
|
this.editorContainer.clear();
|
|
3093
3926
|
this.editorContainer.addChild(dialog);
|
|
@@ -3140,14 +3973,13 @@ export class InteractiveMode {
|
|
|
3140
3973
|
onProgress: (message) => {
|
|
3141
3974
|
dialog.showProgress(message);
|
|
3142
3975
|
},
|
|
3976
|
+
onSelect: (prompt) => this.showOAuthLoginSelect(dialog, prompt),
|
|
3143
3977
|
onManualCodeInput: () => manualCodePromise,
|
|
3144
3978
|
signal: dialog.signal,
|
|
3145
3979
|
});
|
|
3146
3980
|
// Success
|
|
3147
3981
|
restoreEditor();
|
|
3148
|
-
this.
|
|
3149
|
-
await this.updateAvailableProviderCount();
|
|
3150
|
-
this.showStatus(`Logged in to ${providerName}. Credentials saved to ${getAuthPath()}`);
|
|
3982
|
+
await this.completeProviderAuthentication(providerId, providerName, "oauth", previousModel);
|
|
3151
3983
|
}
|
|
3152
3984
|
catch (error) {
|
|
3153
3985
|
restoreEditor();
|
|
@@ -3170,16 +4002,20 @@ export class InteractiveMode {
|
|
|
3170
4002
|
return;
|
|
3171
4003
|
}
|
|
3172
4004
|
this.resetExtensionUI();
|
|
3173
|
-
const
|
|
3174
|
-
|
|
3175
|
-
|
|
4005
|
+
const reloadBox = new Container();
|
|
4006
|
+
const borderColor = (s) => theme.fg("border", s);
|
|
4007
|
+
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
4008
|
+
reloadBox.addChild(new Spacer(1));
|
|
4009
|
+
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes..."), 1, 0));
|
|
4010
|
+
reloadBox.addChild(new Spacer(1));
|
|
4011
|
+
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
3176
4012
|
const previousEditor = this.editor;
|
|
3177
4013
|
this.editorContainer.clear();
|
|
3178
|
-
this.editorContainer.addChild(
|
|
3179
|
-
this.ui.setFocus(
|
|
3180
|
-
this.ui.requestRender();
|
|
3181
|
-
|
|
3182
|
-
|
|
4014
|
+
this.editorContainer.addChild(reloadBox);
|
|
4015
|
+
this.ui.setFocus(reloadBox);
|
|
4016
|
+
this.ui.requestRender(true);
|
|
4017
|
+
await new Promise((resolve) => process.nextTick(resolve));
|
|
4018
|
+
const dismissReloadBox = (editor) => {
|
|
3183
4019
|
this.editorContainer.clear();
|
|
3184
4020
|
this.editorContainer.addChild(editor);
|
|
3185
4021
|
this.ui.setFocus(editor);
|
|
@@ -3187,6 +4023,11 @@ export class InteractiveMode {
|
|
|
3187
4023
|
};
|
|
3188
4024
|
try {
|
|
3189
4025
|
await this.session.reload();
|
|
4026
|
+
this.keybindings.reload();
|
|
4027
|
+
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
4028
|
+
if (isExpandable(activeHeader)) {
|
|
4029
|
+
activeHeader.setExpanded(this.toolOutputExpanded);
|
|
4030
|
+
}
|
|
3190
4031
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
3191
4032
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
3192
4033
|
const themeName = this.settingsManager.getTheme();
|
|
@@ -3204,15 +4045,12 @@ export class InteractiveMode {
|
|
|
3204
4045
|
}
|
|
3205
4046
|
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
3206
4047
|
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
|
3207
|
-
this.
|
|
4048
|
+
this.setupAutocompleteProvider();
|
|
3208
4049
|
const runner = this.session.extensionRunner;
|
|
3209
|
-
|
|
3210
|
-
this.setupExtensionShortcuts(runner);
|
|
3211
|
-
}
|
|
4050
|
+
this.setupExtensionShortcuts(runner);
|
|
3212
4051
|
this.rebuildChatFromMessages();
|
|
3213
|
-
|
|
4052
|
+
dismissReloadBox(this.editor);
|
|
3214
4053
|
this.showLoadedResources({
|
|
3215
|
-
extensionPaths: runner?.getExtensionPaths() ?? [],
|
|
3216
4054
|
force: false,
|
|
3217
4055
|
showDiagnosticsWhenQuiet: true,
|
|
3218
4056
|
});
|
|
@@ -3220,24 +4058,102 @@ export class InteractiveMode {
|
|
|
3220
4058
|
if (modelsJsonError) {
|
|
3221
4059
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
3222
4060
|
}
|
|
3223
|
-
this.showStatus("Reloaded extensions, skills, prompts, themes");
|
|
4061
|
+
this.showStatus("Reloaded keybindings, extensions, skills, prompts, themes");
|
|
3224
4062
|
}
|
|
3225
4063
|
catch (error) {
|
|
3226
|
-
|
|
4064
|
+
dismissReloadBox(previousEditor);
|
|
3227
4065
|
this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3228
4066
|
}
|
|
3229
4067
|
}
|
|
3230
4068
|
async handleExportCommand(text) {
|
|
3231
|
-
const
|
|
3232
|
-
const outputPath = parts.length > 1 ? parts[1] : undefined;
|
|
4069
|
+
const outputPath = this.getPathCommandArgument(text, "/export");
|
|
3233
4070
|
try {
|
|
3234
|
-
|
|
3235
|
-
|
|
4071
|
+
if (outputPath?.endsWith(".jsonl")) {
|
|
4072
|
+
const filePath = this.session.exportToJsonl(outputPath);
|
|
4073
|
+
this.showStatus(`Session exported to: ${filePath}`);
|
|
4074
|
+
}
|
|
4075
|
+
else {
|
|
4076
|
+
const filePath = await this.session.exportToHtml(outputPath);
|
|
4077
|
+
this.showStatus(`Session exported to: ${filePath}`);
|
|
4078
|
+
}
|
|
3236
4079
|
}
|
|
3237
4080
|
catch (error) {
|
|
3238
4081
|
this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3239
4082
|
}
|
|
3240
4083
|
}
|
|
4084
|
+
getPathCommandArgument(text, command) {
|
|
4085
|
+
if (text === command) {
|
|
4086
|
+
return undefined;
|
|
4087
|
+
}
|
|
4088
|
+
if (!text.startsWith(`${command} `)) {
|
|
4089
|
+
return undefined;
|
|
4090
|
+
}
|
|
4091
|
+
const argsString = text.slice(command.length + 1).trimStart();
|
|
4092
|
+
if (!argsString) {
|
|
4093
|
+
return undefined;
|
|
4094
|
+
}
|
|
4095
|
+
const firstChar = argsString[0];
|
|
4096
|
+
if (firstChar === '"' || firstChar === "'") {
|
|
4097
|
+
const closingQuoteIndex = argsString.indexOf(firstChar, 1);
|
|
4098
|
+
if (closingQuoteIndex < 0) {
|
|
4099
|
+
return undefined;
|
|
4100
|
+
}
|
|
4101
|
+
return argsString.slice(1, closingQuoteIndex);
|
|
4102
|
+
}
|
|
4103
|
+
const firstWhitespaceIndex = argsString.search(/\s/);
|
|
4104
|
+
if (firstWhitespaceIndex < 0) {
|
|
4105
|
+
return argsString;
|
|
4106
|
+
}
|
|
4107
|
+
return argsString.slice(0, firstWhitespaceIndex);
|
|
4108
|
+
}
|
|
4109
|
+
async handleImportCommand(text) {
|
|
4110
|
+
const inputPath = this.getPathCommandArgument(text, "/import");
|
|
4111
|
+
if (!inputPath) {
|
|
4112
|
+
this.showError("Usage: /import <path.jsonl>");
|
|
4113
|
+
return;
|
|
4114
|
+
}
|
|
4115
|
+
const confirmed = await this.showExtensionConfirm("Import session", `Replace current session with ${inputPath}?`);
|
|
4116
|
+
if (!confirmed) {
|
|
4117
|
+
this.showStatus("Import cancelled");
|
|
4118
|
+
return;
|
|
4119
|
+
}
|
|
4120
|
+
try {
|
|
4121
|
+
if (this.loadingAnimation) {
|
|
4122
|
+
this.loadingAnimation.stop();
|
|
4123
|
+
this.loadingAnimation = undefined;
|
|
4124
|
+
}
|
|
4125
|
+
this.statusContainer.clear();
|
|
4126
|
+
const result = await this.runtimeHost.importFromJsonl(inputPath);
|
|
4127
|
+
if (result.cancelled) {
|
|
4128
|
+
this.showStatus("Import cancelled");
|
|
4129
|
+
return;
|
|
4130
|
+
}
|
|
4131
|
+
this.renderCurrentSessionState();
|
|
4132
|
+
this.showStatus(`Session imported from: ${inputPath}`);
|
|
4133
|
+
}
|
|
4134
|
+
catch (error) {
|
|
4135
|
+
if (error instanceof MissingSessionCwdError) {
|
|
4136
|
+
const selectedCwd = await this.promptForMissingSessionCwd(error);
|
|
4137
|
+
if (!selectedCwd) {
|
|
4138
|
+
this.showStatus("Import cancelled");
|
|
4139
|
+
return;
|
|
4140
|
+
}
|
|
4141
|
+
const result = await this.runtimeHost.importFromJsonl(inputPath, selectedCwd);
|
|
4142
|
+
if (result.cancelled) {
|
|
4143
|
+
this.showStatus("Import cancelled");
|
|
4144
|
+
return;
|
|
4145
|
+
}
|
|
4146
|
+
this.renderCurrentSessionState();
|
|
4147
|
+
this.showStatus(`Session imported from: ${inputPath}`);
|
|
4148
|
+
return;
|
|
4149
|
+
}
|
|
4150
|
+
if (error instanceof SessionImportFileNotFoundError) {
|
|
4151
|
+
this.showError(`Failed to import session: ${error.message}`);
|
|
4152
|
+
return;
|
|
4153
|
+
}
|
|
4154
|
+
await this.handleFatalRuntimeError("Failed to import session", error);
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
3241
4157
|
async handleShareCommand() {
|
|
3242
4158
|
// Check if gh is available and logged in
|
|
3243
4159
|
try {
|
|
@@ -3325,14 +4241,14 @@ export class InteractiveMode {
|
|
|
3325
4241
|
}
|
|
3326
4242
|
}
|
|
3327
4243
|
}
|
|
3328
|
-
handleCopyCommand() {
|
|
4244
|
+
async handleCopyCommand() {
|
|
3329
4245
|
const text = this.session.getLastAssistantText();
|
|
3330
4246
|
if (!text) {
|
|
3331
4247
|
this.showError("No agent messages to copy yet.");
|
|
3332
4248
|
return;
|
|
3333
4249
|
}
|
|
3334
4250
|
try {
|
|
3335
|
-
copyToClipboard(text);
|
|
4251
|
+
await copyToClipboard(text);
|
|
3336
4252
|
this.showStatus("Copied last agent message to clipboard");
|
|
3337
4253
|
}
|
|
3338
4254
|
catch (error) {
|
|
@@ -3353,8 +4269,7 @@ export class InteractiveMode {
|
|
|
3353
4269
|
this.ui.requestRender();
|
|
3354
4270
|
return;
|
|
3355
4271
|
}
|
|
3356
|
-
this.
|
|
3357
|
-
this.updateTerminalTitle();
|
|
4272
|
+
this.session.setSessionName(name);
|
|
3358
4273
|
this.chatContainer.addChild(new Spacer(1));
|
|
3359
4274
|
this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${name}`), 1, 0));
|
|
3360
4275
|
this.ui.requestRender();
|
|
@@ -3409,69 +4324,63 @@ export class InteractiveMode {
|
|
|
3409
4324
|
this.chatContainer.addChild(new DynamicBorder());
|
|
3410
4325
|
this.ui.requestRender();
|
|
3411
4326
|
}
|
|
3412
|
-
/**
|
|
3413
|
-
* Capitalize keybinding for display (e.g., "ctrl+c" -> "Ctrl+C").
|
|
3414
|
-
*/
|
|
3415
|
-
capitalizeKey(key) {
|
|
3416
|
-
return key
|
|
3417
|
-
.split("/")
|
|
3418
|
-
.map((k) => k
|
|
3419
|
-
.split("+")
|
|
3420
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
3421
|
-
.join("+"))
|
|
3422
|
-
.join("/");
|
|
3423
|
-
}
|
|
3424
4327
|
/**
|
|
3425
4328
|
* Get capitalized display string for an app keybinding action.
|
|
3426
4329
|
*/
|
|
3427
4330
|
getAppKeyDisplay(action) {
|
|
3428
|
-
return
|
|
4331
|
+
return keyDisplayText(action);
|
|
3429
4332
|
}
|
|
3430
4333
|
/**
|
|
3431
4334
|
* Get capitalized display string for an editor keybinding action.
|
|
3432
4335
|
*/
|
|
3433
4336
|
getEditorKeyDisplay(action) {
|
|
3434
|
-
return
|
|
4337
|
+
return keyDisplayText(action);
|
|
3435
4338
|
}
|
|
3436
4339
|
handleHotkeysCommand() {
|
|
3437
4340
|
// Navigation keybindings
|
|
3438
|
-
const
|
|
3439
|
-
const
|
|
3440
|
-
const
|
|
3441
|
-
const
|
|
3442
|
-
const
|
|
3443
|
-
const
|
|
3444
|
-
const
|
|
3445
|
-
const
|
|
4341
|
+
const cursorUp = this.getEditorKeyDisplay("tui.editor.cursorUp");
|
|
4342
|
+
const cursorDown = this.getEditorKeyDisplay("tui.editor.cursorDown");
|
|
4343
|
+
const cursorLeft = this.getEditorKeyDisplay("tui.editor.cursorLeft");
|
|
4344
|
+
const cursorRight = this.getEditorKeyDisplay("tui.editor.cursorRight");
|
|
4345
|
+
const cursorWordLeft = this.getEditorKeyDisplay("tui.editor.cursorWordLeft");
|
|
4346
|
+
const cursorWordRight = this.getEditorKeyDisplay("tui.editor.cursorWordRight");
|
|
4347
|
+
const cursorLineStart = this.getEditorKeyDisplay("tui.editor.cursorLineStart");
|
|
4348
|
+
const cursorLineEnd = this.getEditorKeyDisplay("tui.editor.cursorLineEnd");
|
|
4349
|
+
const jumpForward = this.getEditorKeyDisplay("tui.editor.jumpForward");
|
|
4350
|
+
const jumpBackward = this.getEditorKeyDisplay("tui.editor.jumpBackward");
|
|
4351
|
+
const pageUp = this.getEditorKeyDisplay("tui.editor.pageUp");
|
|
4352
|
+
const pageDown = this.getEditorKeyDisplay("tui.editor.pageDown");
|
|
3446
4353
|
// Editing keybindings
|
|
3447
|
-
const submit = this.getEditorKeyDisplay("submit");
|
|
3448
|
-
const newLine = this.getEditorKeyDisplay("newLine");
|
|
3449
|
-
const deleteWordBackward = this.getEditorKeyDisplay("deleteWordBackward");
|
|
3450
|
-
const deleteWordForward = this.getEditorKeyDisplay("deleteWordForward");
|
|
3451
|
-
const deleteToLineStart = this.getEditorKeyDisplay("deleteToLineStart");
|
|
3452
|
-
const deleteToLineEnd = this.getEditorKeyDisplay("deleteToLineEnd");
|
|
3453
|
-
const yank = this.getEditorKeyDisplay("yank");
|
|
3454
|
-
const yankPop = this.getEditorKeyDisplay("yankPop");
|
|
3455
|
-
const undo = this.getEditorKeyDisplay("undo");
|
|
3456
|
-
const tab = this.getEditorKeyDisplay("tab");
|
|
4354
|
+
const submit = this.getEditorKeyDisplay("tui.input.submit");
|
|
4355
|
+
const newLine = this.getEditorKeyDisplay("tui.input.newLine");
|
|
4356
|
+
const deleteWordBackward = this.getEditorKeyDisplay("tui.editor.deleteWordBackward");
|
|
4357
|
+
const deleteWordForward = this.getEditorKeyDisplay("tui.editor.deleteWordForward");
|
|
4358
|
+
const deleteToLineStart = this.getEditorKeyDisplay("tui.editor.deleteToLineStart");
|
|
4359
|
+
const deleteToLineEnd = this.getEditorKeyDisplay("tui.editor.deleteToLineEnd");
|
|
4360
|
+
const yank = this.getEditorKeyDisplay("tui.editor.yank");
|
|
4361
|
+
const yankPop = this.getEditorKeyDisplay("tui.editor.yankPop");
|
|
4362
|
+
const undo = this.getEditorKeyDisplay("tui.editor.undo");
|
|
4363
|
+
const tab = this.getEditorKeyDisplay("tui.input.tab");
|
|
3457
4364
|
// App keybindings
|
|
3458
|
-
const interrupt = this.getAppKeyDisplay("interrupt");
|
|
3459
|
-
const clear = this.getAppKeyDisplay("clear");
|
|
3460
|
-
const exit = this.getAppKeyDisplay("exit");
|
|
3461
|
-
const suspend = this.getAppKeyDisplay("suspend");
|
|
3462
|
-
const cycleThinkingLevel = this.getAppKeyDisplay("
|
|
3463
|
-
const cycleModelForward = this.getAppKeyDisplay("
|
|
3464
|
-
const selectModel = this.getAppKeyDisplay("
|
|
3465
|
-
const expandTools = this.getAppKeyDisplay("
|
|
3466
|
-
const toggleThinking = this.getAppKeyDisplay("
|
|
3467
|
-
const externalEditor = this.getAppKeyDisplay("
|
|
3468
|
-
const
|
|
3469
|
-
const
|
|
4365
|
+
const interrupt = this.getAppKeyDisplay("app.interrupt");
|
|
4366
|
+
const clear = this.getAppKeyDisplay("app.clear");
|
|
4367
|
+
const exit = this.getAppKeyDisplay("app.exit");
|
|
4368
|
+
const suspend = this.getAppKeyDisplay("app.suspend");
|
|
4369
|
+
const cycleThinkingLevel = this.getAppKeyDisplay("app.thinking.cycle");
|
|
4370
|
+
const cycleModelForward = this.getAppKeyDisplay("app.model.cycleForward");
|
|
4371
|
+
const selectModel = this.getAppKeyDisplay("app.model.select");
|
|
4372
|
+
const expandTools = this.getAppKeyDisplay("app.tools.expand");
|
|
4373
|
+
const toggleThinking = this.getAppKeyDisplay("app.thinking.toggle");
|
|
4374
|
+
const externalEditor = this.getAppKeyDisplay("app.editor.external");
|
|
4375
|
+
const cycleModelBackward = this.getAppKeyDisplay("app.model.cycleBackward");
|
|
4376
|
+
const followUp = this.getAppKeyDisplay("app.message.followUp");
|
|
4377
|
+
const dequeue = this.getAppKeyDisplay("app.message.dequeue");
|
|
4378
|
+
const pasteImage = this.getAppKeyDisplay("app.clipboard.pasteImage");
|
|
3470
4379
|
let hotkeys = `
|
|
3471
4380
|
**Navigation**
|
|
3472
4381
|
| Key | Action |
|
|
3473
4382
|
|-----|--------|
|
|
3474
|
-
| \`
|
|
4383
|
+
| \`${cursorUp}\` / \`${cursorDown}\` / \`${cursorLeft}\` / \`${cursorRight}\` | Move cursor / browse history (Up when empty) |
|
|
3475
4384
|
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
|
|
3476
4385
|
| \`${cursorLineStart}\` | Start of line |
|
|
3477
4386
|
| \`${cursorLineEnd}\` | End of line |
|
|
@@ -3501,33 +4410,31 @@ export class InteractiveMode {
|
|
|
3501
4410
|
| \`${exit}\` | Exit (when editor is empty) |
|
|
3502
4411
|
| \`${suspend}\` | Suspend to background |
|
|
3503
4412
|
| \`${cycleThinkingLevel}\` | Cycle thinking level |
|
|
3504
|
-
| \`${cycleModelForward}\` | Cycle models |
|
|
4413
|
+
| \`${cycleModelForward}\` / \`${cycleModelBackward}\` | Cycle models |
|
|
3505
4414
|
| \`${selectModel}\` | Open model selector |
|
|
3506
4415
|
| \`${expandTools}\` | Toggle tool output expansion |
|
|
3507
4416
|
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
3508
4417
|
| \`${externalEditor}\` | Edit message in external editor |
|
|
3509
4418
|
| \`${followUp}\` | Queue follow-up message |
|
|
3510
4419
|
| \`${dequeue}\` | Restore queued messages |
|
|
3511
|
-
| \`
|
|
4420
|
+
| \`${pasteImage}\` | Paste image from clipboard |
|
|
3512
4421
|
| \`/\` | Slash commands |
|
|
3513
4422
|
| \`!\` | Run bash command |
|
|
3514
4423
|
| \`!!\` | Run bash command (excluded from context) |
|
|
3515
4424
|
`;
|
|
3516
4425
|
// Add extension-registered shortcuts
|
|
3517
4426
|
const extensionRunner = this.session.extensionRunner;
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
hotkeys += `
|
|
4427
|
+
const shortcuts = extensionRunner.getShortcuts(this.keybindings.getEffectiveConfig());
|
|
4428
|
+
if (shortcuts.size > 0) {
|
|
4429
|
+
hotkeys += `
|
|
3522
4430
|
**Extensions**
|
|
3523
4431
|
| Key | Action |
|
|
3524
4432
|
|-----|--------|
|
|
3525
4433
|
`;
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
}
|
|
4434
|
+
for (const [key, shortcut] of shortcuts) {
|
|
4435
|
+
const description = shortcut.description ?? shortcut.extensionPath;
|
|
4436
|
+
const keyDisplay = formatKeyText(key, { capitalize: true });
|
|
4437
|
+
hotkeys += `| \`${keyDisplay}\` | ${description} |\n`;
|
|
3531
4438
|
}
|
|
3532
4439
|
}
|
|
3533
4440
|
this.chatContainer.addChild(new Spacer(1));
|
|
@@ -3539,25 +4446,24 @@ export class InteractiveMode {
|
|
|
3539
4446
|
this.ui.requestRender();
|
|
3540
4447
|
}
|
|
3541
4448
|
async handleClearCommand() {
|
|
3542
|
-
// Stop loading animation
|
|
3543
4449
|
if (this.loadingAnimation) {
|
|
3544
4450
|
this.loadingAnimation.stop();
|
|
3545
4451
|
this.loadingAnimation = undefined;
|
|
3546
4452
|
}
|
|
3547
4453
|
this.statusContainer.clear();
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
4454
|
+
try {
|
|
4455
|
+
const result = await this.runtimeHost.newSession();
|
|
4456
|
+
if (result.cancelled) {
|
|
4457
|
+
return;
|
|
4458
|
+
}
|
|
4459
|
+
this.renderCurrentSessionState();
|
|
4460
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
4461
|
+
this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
|
|
4462
|
+
this.ui.requestRender();
|
|
4463
|
+
}
|
|
4464
|
+
catch (error) {
|
|
4465
|
+
await this.handleFatalRuntimeError("Failed to create session", error);
|
|
4466
|
+
}
|
|
3561
4467
|
}
|
|
3562
4468
|
handleDebugCommand() {
|
|
3563
4469
|
const width = this.ui.terminal.columns;
|
|
@@ -3591,6 +4497,11 @@ export class InteractiveMode {
|
|
|
3591
4497
|
this.chatContainer.addChild(new ArminComponent(this.ui));
|
|
3592
4498
|
this.ui.requestRender();
|
|
3593
4499
|
}
|
|
4500
|
+
handleDementedDelves() {
|
|
4501
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
4502
|
+
this.chatContainer.addChild(new EarendilAnnouncementComponent());
|
|
4503
|
+
this.ui.requestRender();
|
|
4504
|
+
}
|
|
3594
4505
|
handleDaxnuts() {
|
|
3595
4506
|
this.chatContainer.addChild(new Spacer(1));
|
|
3596
4507
|
this.chatContainer.addChild(new DaxnutsComponent(this.ui));
|
|
@@ -3604,14 +4515,12 @@ export class InteractiveMode {
|
|
|
3604
4515
|
async handleBashCommand(command, excludeFromContext = false) {
|
|
3605
4516
|
const extensionRunner = this.session.extensionRunner;
|
|
3606
4517
|
// Emit user_bash event to let extensions intercept
|
|
3607
|
-
const eventResult = extensionRunner
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
})
|
|
3614
|
-
: undefined;
|
|
4518
|
+
const eventResult = await extensionRunner.emitUserBash({
|
|
4519
|
+
type: "user_bash",
|
|
4520
|
+
command,
|
|
4521
|
+
excludeFromContext,
|
|
4522
|
+
cwd: this.sessionManager.getCwd(),
|
|
4523
|
+
});
|
|
3615
4524
|
// If extension returned a full result, use it directly
|
|
3616
4525
|
if (eventResult?.result) {
|
|
3617
4526
|
const result = eventResult.result;
|
|
@@ -3675,55 +4584,23 @@ export class InteractiveMode {
|
|
|
3675
4584
|
this.showWarning("Nothing to compact (no messages yet)");
|
|
3676
4585
|
return;
|
|
3677
4586
|
}
|
|
3678
|
-
await this.executeCompaction(customInstructions, false);
|
|
3679
|
-
}
|
|
3680
|
-
async executeCompaction(customInstructions, isAuto = false) {
|
|
3681
|
-
// Stop loading animation
|
|
3682
4587
|
if (this.loadingAnimation) {
|
|
3683
4588
|
this.loadingAnimation.stop();
|
|
3684
4589
|
this.loadingAnimation = undefined;
|
|
3685
4590
|
}
|
|
3686
4591
|
this.statusContainer.clear();
|
|
3687
|
-
// Set up escape handler during compaction
|
|
3688
|
-
const originalOnEscape = this.defaultEditor.onEscape;
|
|
3689
|
-
this.defaultEditor.onEscape = () => {
|
|
3690
|
-
this.session.abortCompaction();
|
|
3691
|
-
};
|
|
3692
|
-
// Show compacting status
|
|
3693
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
3694
|
-
const cancelHint = `(${appKey(this.keybindings, "interrupt")} to cancel)`;
|
|
3695
|
-
const label = isAuto ? `Auto-compacting context... ${cancelHint}` : `Compacting context... ${cancelHint}`;
|
|
3696
|
-
const compactingLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
|
|
3697
|
-
this.statusContainer.addChild(compactingLoader);
|
|
3698
|
-
this.ui.requestRender();
|
|
3699
|
-
let result;
|
|
3700
4592
|
try {
|
|
3701
|
-
|
|
3702
|
-
// Rebuild UI
|
|
3703
|
-
this.rebuildChatFromMessages();
|
|
3704
|
-
// Add compaction component at bottom so user sees it without scrolling
|
|
3705
|
-
const msg = createCompactionSummaryMessage(result.summary, result.tokensBefore, new Date().toISOString());
|
|
3706
|
-
this.addMessageToChat(msg);
|
|
3707
|
-
this.footer.invalidate();
|
|
4593
|
+
await this.session.compact(customInstructions);
|
|
3708
4594
|
}
|
|
3709
|
-
catch
|
|
3710
|
-
|
|
3711
|
-
if (message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError")) {
|
|
3712
|
-
this.showError("Compaction cancelled");
|
|
3713
|
-
}
|
|
3714
|
-
else {
|
|
3715
|
-
this.showError(`Compaction failed: ${message}`);
|
|
3716
|
-
}
|
|
3717
|
-
}
|
|
3718
|
-
finally {
|
|
3719
|
-
compactingLoader.stop();
|
|
3720
|
-
this.statusContainer.clear();
|
|
3721
|
-
this.defaultEditor.onEscape = originalOnEscape;
|
|
4595
|
+
catch {
|
|
4596
|
+
// Ignore, will be emitted as an event
|
|
3722
4597
|
}
|
|
3723
|
-
void this.flushCompactionQueue({ willRetry: false });
|
|
3724
|
-
return result;
|
|
3725
4598
|
}
|
|
3726
4599
|
stop() {
|
|
4600
|
+
this.unregisterSignalHandlers();
|
|
4601
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
4602
|
+
this.ui.terminal.setProgress(false);
|
|
4603
|
+
}
|
|
3727
4604
|
if (this.loadingAnimation) {
|
|
3728
4605
|
this.loadingAnimation.stop();
|
|
3729
4606
|
this.loadingAnimation = undefined;
|