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