@sheason/pi-coding-agent 0.74.1-sheason.0 → 0.78.0-sheason.0.6.0-alpha.2
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 +256 -4
- package/README.md +16 -8
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js.map +1 -1
- package/dist/cli/args.d.ts +7 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +49 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts +2 -2
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +2 -3
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/cli/initial-message.d.ts +1 -1
- package/dist/cli/initial-message.d.ts.map +1 -1
- package/dist/cli/initial-message.js.map +1 -1
- package/dist/cli/list-models.d.ts +1 -1
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js.map +1 -1
- package/dist/cli/session-picker.d.ts +1 -1
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -6
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +61 -32
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-proxy.d.ts +268 -0
- package/dist/core/agent-session-proxy.d.ts.map +1 -0
- package/dist/core/agent-session-proxy.js +2 -0
- package/dist/core/agent-session-proxy.js.map +1 -0
- package/dist/core/agent-session-runtime.d.ts +10 -10
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +14 -14
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +8 -7
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +4 -2
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +60 -27
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +303 -177
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-guidance.d.ts.map +1 -1
- package/dist/core/auth-guidance.js.map +1 -1
- package/dist/core/auth-storage.d.ts +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +3 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +3 -3
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +5 -5
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +41 -37
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/index.d.ts +3 -3
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js.map +1 -1
- package/dist/core/export-html/index.d.ts +1 -1
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +8 -6
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/export-html/template.js +23 -6
- package/dist/core/export-html/tool-renderer.d.ts +2 -2
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/index.d.ts +8 -8
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +2 -2
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +17 -34
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +12 -7
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +36 -2
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +26 -24
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/wrapper.d.ts +2 -2
- package/dist/core/extensions/wrapper.d.ts.map +1 -1
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +3 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +4 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts +21 -0
- package/dist/core/http-dispatcher.d.ts.map +1 -0
- package/dist/core/http-dispatcher.js +48 -0
- package/dist/core/http-dispatcher.js.map +1 -0
- package/dist/core/index.d.ts +8 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/local-agent-session-proxy.d.ts +82 -0
- package/dist/core/local-agent-session-proxy.d.ts.map +1 -0
- package/dist/core/local-agent-session-proxy.js +531 -0
- package/dist/core/local-agent-session-proxy.js.map +1 -0
- package/dist/core/messages.d.ts +0 -9
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +0 -10
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry.d.ts +4 -4
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +72 -16
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/output-guard.d.ts +1 -0
- package/dist/core/output-guard.d.ts.map +1 -1
- package/dist/core/output-guard.js +52 -22
- package/dist/core/output-guard.js.map +1 -1
- package/dist/core/package-manager.d.ts +7 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +129 -64
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/prompt-templates.d.ts +1 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +12 -24
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/provider-display-names.d.ts.map +1 -1
- package/dist/core/provider-display-names.js +0 -1
- package/dist/core/provider-display-names.js.map +1 -1
- package/dist/core/resolve-config-value.d.ts +9 -1
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +134 -11
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/resource-loader.d.ts +13 -10
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +41 -33
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +15 -13
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +24 -17
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +20 -10
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +201 -106
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +5 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +31 -13
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +2 -2
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +10 -27
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/source-info.d.ts +1 -1
- package/dist/core/source-info.d.ts.map +1 -1
- package/dist/core/source-info.js.map +1 -1
- package/dist/core/system-prompt.d.ts +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +16 -9
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/telemetry.d.ts +1 -1
- package/dist/core/telemetry.d.ts.map +1 -1
- package/dist/core/telemetry.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -2
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +55 -54
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +3 -1
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +8 -1
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +5 -3
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +51 -91
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +27 -12
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/find.d.ts +2 -2
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +2 -3
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +2 -2
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +3 -3
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +17 -17
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +2 -2
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +10 -12
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +3 -1
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +9 -3
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/path-utils.d.ts +2 -0
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +39 -21
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/core/tools/read.d.ts +2 -2
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +15 -15
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +5 -2
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +17 -1
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/tools/truncate.d.ts.map +1 -1
- package/dist/core/tools/truncate.js +12 -2
- package/dist/core/tools/truncate.js.map +1 -1
- package/dist/core/tools/write.d.ts +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +25 -41
- package/dist/core/tools/write.js.map +1 -1
- package/dist/d-pi-worker.d.ts +12 -0
- package/dist/d-pi-worker.d.ts.map +1 -0
- package/dist/d-pi-worker.js +9 -0
- package/dist/d-pi-worker.js.map +1 -0
- package/dist/index.d.ts +30 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +100 -39
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +118 -1
- package/dist/migrations.js.map +1 -1
- package/dist/modes/connect/auth-headers.d.ts +2 -0
- package/dist/modes/connect/auth-headers.d.ts.map +1 -0
- package/dist/modes/connect/auth-headers.js +2 -0
- package/dist/modes/connect/auth-headers.js.map +1 -0
- package/dist/modes/connect/client-extension-sync.d.ts +13 -0
- package/dist/modes/connect/client-extension-sync.d.ts.map +1 -0
- package/dist/modes/connect/client-extension-sync.js +51 -0
- package/dist/modes/connect/client-extension-sync.js.map +1 -0
- package/dist/modes/connect/connect-mode.d.ts +6 -0
- package/dist/modes/connect/connect-mode.d.ts.map +1 -0
- package/dist/modes/connect/connect-mode.js +29 -0
- package/dist/modes/connect/connect-mode.js.map +1 -0
- package/dist/modes/connect/remote-agent-session-proxy.d.ts +81 -0
- package/dist/modes/connect/remote-agent-session-proxy.d.ts.map +1 -0
- package/dist/modes/connect/remote-agent-session-proxy.js +326 -0
- package/dist/modes/connect/remote-agent-session-proxy.js.map +1 -0
- package/dist/modes/connect/sse-client.d.ts +18 -0
- package/dist/modes/connect/sse-client.d.ts.map +1 -0
- package/dist/modes/connect/sse-client.js +90 -0
- package/dist/modes/connect/sse-client.js.map +1 -0
- package/dist/modes/index.d.ts +5 -5
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/armin.d.ts.map +1 -1
- package/dist/modes/interactive/components/armin.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts +4 -4
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +8 -5
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.d.ts +2 -2
- package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.js +2 -2
- package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +2 -2
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +0 -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.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.map +1 -1
- package/dist/modes/interactive/components/earendil-announcement.d.ts.map +1 -1
- package/dist/modes/interactive/components/earendil-announcement.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +14 -6
- 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.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +15 -4
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +126 -8
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +31 -31
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +7 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +28 -5
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +2 -2
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +3 -3
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +15 -0
- 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.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +53 -7
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1247 -205
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +5 -4
- package/dist/modes/interactive/theme/light.json +5 -4
- package/dist/modes/interactive/theme/theme.d.ts +22 -3
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +130 -69
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +8 -5
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +65 -8
- 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 +18 -4
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +5 -4
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/serve/api-handlers.d.ts +4 -0
- package/dist/modes/serve/api-handlers.d.ts.map +1 -0
- package/dist/modes/serve/api-handlers.js +324 -0
- package/dist/modes/serve/api-handlers.js.map +1 -0
- package/dist/modes/serve/http-server.d.ts +14 -0
- package/dist/modes/serve/http-server.d.ts.map +1 -0
- package/dist/modes/serve/http-server.js +94 -0
- package/dist/modes/serve/http-server.js.map +1 -0
- package/dist/modes/serve/serve-mode.d.ts +10 -0
- package/dist/modes/serve/serve-mode.d.ts.map +1 -0
- package/dist/modes/serve/serve-mode.js +217 -0
- package/dist/modes/serve/serve-mode.js.map +1 -0
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +62 -7
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/ansi.d.ts.map +1 -1
- package/dist/utils/ansi.js +47 -72
- package/dist/utils/ansi.js.map +1 -1
- package/dist/utils/changelog.d.ts +1 -1
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js.map +1 -1
- package/dist/utils/child-process.d.ts +5 -2
- package/dist/utils/child-process.d.ts.map +1 -1
- package/dist/utils/child-process.js +9 -7
- package/dist/utils/child-process.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +3 -1
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js +14 -8
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/deprecation.d.ts +4 -0
- package/dist/utils/deprecation.d.ts.map +1 -0
- package/dist/utils/deprecation.js +13 -0
- package/dist/utils/deprecation.js.map +1 -0
- package/dist/utils/exif-orientation.d.ts +1 -1
- package/dist/utils/exif-orientation.d.ts.map +1 -1
- package/dist/utils/exif-orientation.js.map +1 -1
- package/dist/utils/html.d.ts +7 -0
- package/dist/utils/html.d.ts.map +1 -0
- package/dist/utils/html.js +40 -0
- package/dist/utils/html.js.map +1 -0
- package/dist/utils/image-convert.d.ts.map +1 -1
- package/dist/utils/image-convert.js.map +1 -1
- package/dist/utils/image-resize-core.d.ts +30 -0
- package/dist/utils/image-resize-core.d.ts.map +1 -0
- package/dist/utils/image-resize-core.js +124 -0
- package/dist/utils/image-resize-core.js.map +1 -0
- package/dist/utils/image-resize-worker.d.ts +2 -0
- package/dist/utils/image-resize-worker.d.ts.map +1 -0
- package/dist/utils/image-resize-worker.js +31 -0
- package/dist/utils/image-resize-worker.js.map +1 -0
- package/dist/utils/image-resize.d.ts +7 -27
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +75 -131
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/json.d.ts +3 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +7 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/paths.d.ts +16 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +49 -7
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +6 -1
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts +12 -0
- package/dist/utils/syntax-highlight.d.ts.map +1 -0
- package/dist/utils/syntax-highlight.js +118 -0
- package/dist/utils/syntax-highlight.js.map +1 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +4 -1
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +2 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +9 -4
- package/dist/utils/version-check.js.map +1 -1
- package/dist/utils/windows-self-update.d.ts +3 -0
- package/dist/utils/windows-self-update.d.ts.map +1 -0
- package/dist/utils/windows-self-update.js +77 -0
- package/dist/utils/windows-self-update.js.map +1 -0
- package/docs/custom-provider.md +111 -21
- package/docs/development.md +1 -1
- package/docs/extensions.md +13 -7
- package/docs/index.md +13 -3
- package/docs/models.md +32 -13
- package/docs/packages.md +9 -6
- package/docs/providers.md +13 -5
- package/docs/quickstart.md +24 -1
- package/docs/rpc.md +2 -1
- package/docs/sdk.md +8 -0
- package/docs/session-format.md +1 -1
- package/docs/sessions.md +8 -0
- package/docs/settings.md +8 -6
- package/docs/skills.md +3 -4
- package/docs/terminal-setup.md +8 -0
- package/docs/termux.md +3 -3
- package/docs/tui.md +2 -2
- package/docs/usage.md +13 -2
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +2 -2
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +54 -3
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +1 -1
- package/examples/extensions/doom-overlay/doom-component.ts +2 -2
- package/examples/extensions/doom-overlay/index.ts +3 -3
- package/examples/extensions/git-merge-and-resolve.ts +115 -0
- package/examples/extensions/input-transform-streaming.ts +39 -0
- package/examples/extensions/overlay-qa-tests.ts +97 -66
- package/examples/extensions/overlay-test.ts +7 -4
- package/examples/extensions/plan-mode/index.ts +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +2 -2
- package/examples/extensions/subagent/README.md +3 -0
- package/examples/extensions/subagent/index.ts +42 -20
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +3 -3
- package/npm-shrinkwrap.json +1790 -0
- package/package.json +39 -32
- package/dist/utils/uuid.d.ts +0 -2
- package/dist/utils/uuid.d.ts.map +0 -1
- package/dist/utils/uuid.js +0 -40
- package/dist/utils/uuid.js.map +0 -1
|
@@ -7,14 +7,17 @@ import * as fs from "node:fs";
|
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { getProviders, } from "@sheason/pi-ai";
|
|
10
|
-
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@sheason/pi-tui";
|
|
10
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, SelectList, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@sheason/pi-tui";
|
|
11
|
+
import chalk from "chalk";
|
|
11
12
|
import { spawn, spawnSync } from "child_process";
|
|
12
13
|
import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
13
|
-
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
14
|
+
import { parseSkillBlock, } from "../../core/agent-session.js";
|
|
14
15
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
16
|
+
import { ExtensionRunner as ExtensionRunnerImpl } from "../../core/extensions/runner.js";
|
|
15
17
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
18
|
+
import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
|
|
16
19
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
17
|
-
import { createCompactionSummaryMessage
|
|
20
|
+
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
18
21
|
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
19
22
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
20
23
|
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
@@ -31,6 +34,7 @@ import { getPiUserAgent } from "../../utils/pi-user-agent.js";
|
|
|
31
34
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
32
35
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
33
36
|
import { checkForNewPiVersion } from "../../utils/version-check.js";
|
|
37
|
+
import { loadRemoteClientExtensions } from "../connect/client-extension-sync.js";
|
|
34
38
|
import { ArminComponent } from "./components/armin.js";
|
|
35
39
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
36
40
|
import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
@@ -46,7 +50,7 @@ import { EarendilAnnouncementComponent } from "./components/earendil-announcemen
|
|
|
46
50
|
import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
47
51
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
48
52
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
49
|
-
import { FooterComponent } from "./components/footer.js";
|
|
53
|
+
import { FooterComponent, formatTokens } from "./components/footer.js";
|
|
50
54
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
51
55
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
52
56
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
@@ -59,7 +63,7 @@ import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
|
59
63
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
60
64
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
61
65
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
62
|
-
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
66
|
+
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getSelectListTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
63
67
|
function isExpandable(obj) {
|
|
64
68
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
65
69
|
}
|
|
@@ -90,6 +94,27 @@ function isAnthropicSubscriptionAuthKey(apiKey) {
|
|
|
90
94
|
function isUnknownModel(model) {
|
|
91
95
|
return !!model && model.provider === "unknown" && model.id === "unknown" && model.api === "unknown";
|
|
92
96
|
}
|
|
97
|
+
function quoteIfNeeded(value) {
|
|
98
|
+
if (value.length > 0 && !/[^a-zA-Z0-9_\-./~:@]/.test(value)) {
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
102
|
+
}
|
|
103
|
+
export function formatResumeCommand(sessionManager) {
|
|
104
|
+
if (!process.stdout.isTTY)
|
|
105
|
+
return undefined;
|
|
106
|
+
if (!sessionManager.isPersisted())
|
|
107
|
+
return undefined;
|
|
108
|
+
const sessionFile = sessionManager.getSessionFile();
|
|
109
|
+
if (!sessionFile || !fs.existsSync(sessionFile))
|
|
110
|
+
return undefined;
|
|
111
|
+
const args = [APP_NAME];
|
|
112
|
+
if (!sessionManager.usesDefaultSessionDir()) {
|
|
113
|
+
args.push("--session-dir", quoteIfNeeded(sessionManager.getSessionDir()));
|
|
114
|
+
}
|
|
115
|
+
args.push("--session", sessionManager.getSessionId());
|
|
116
|
+
return args.join(" ");
|
|
117
|
+
}
|
|
93
118
|
function hasDefaultModelProvider(providerId) {
|
|
94
119
|
return providerId in defaultModelPerProvider;
|
|
95
120
|
}
|
|
@@ -105,7 +130,6 @@ export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProvi
|
|
|
105
130
|
return !oauthProviderIds.has(providerId);
|
|
106
131
|
}
|
|
107
132
|
export class InteractiveMode {
|
|
108
|
-
options;
|
|
109
133
|
runtimeHost;
|
|
110
134
|
ui;
|
|
111
135
|
chatContainer;
|
|
@@ -125,6 +149,7 @@ export class InteractiveMode {
|
|
|
125
149
|
version;
|
|
126
150
|
isInitialized = false;
|
|
127
151
|
onInputCallback;
|
|
152
|
+
pendingUserInputs = [];
|
|
128
153
|
loadingAnimation = undefined;
|
|
129
154
|
workingMessage = undefined;
|
|
130
155
|
workingVisible = true;
|
|
@@ -176,6 +201,8 @@ export class InteractiveMode {
|
|
|
176
201
|
extensionInput = undefined;
|
|
177
202
|
extensionEditor = undefined;
|
|
178
203
|
extensionTerminalInputUnsubscribers = new Set();
|
|
204
|
+
// Client-side extension runner for connect mode (undefined in local mode)
|
|
205
|
+
_clientExtensionRunner = undefined;
|
|
179
206
|
// Extension widgets (components rendered above/below the editor)
|
|
180
207
|
extensionWidgetsAbove = new Map();
|
|
181
208
|
extensionWidgetsBelow = new Map();
|
|
@@ -189,31 +216,61 @@ export class InteractiveMode {
|
|
|
189
216
|
builtInHeader = undefined;
|
|
190
217
|
// Custom header from extension (undefined = use built-in header)
|
|
191
218
|
customHeader = undefined;
|
|
192
|
-
|
|
219
|
+
options;
|
|
220
|
+
/** Proxy for connect mode — when set, core operations go through the proxy */
|
|
221
|
+
proxy;
|
|
222
|
+
/** Set the proxy after construction (used by connect mode for late binding) */
|
|
223
|
+
setProxy(proxy) {
|
|
224
|
+
this.proxy = proxy;
|
|
225
|
+
}
|
|
226
|
+
// Convenience accessors — return undefined when runtimeHost is not set (connect mode)
|
|
193
227
|
get session() {
|
|
194
|
-
return this.runtimeHost
|
|
228
|
+
return this.runtimeHost?.session;
|
|
195
229
|
}
|
|
196
230
|
get agent() {
|
|
197
|
-
return this.session
|
|
231
|
+
return this.session?.agent;
|
|
198
232
|
}
|
|
199
233
|
get sessionManager() {
|
|
200
|
-
return this.session
|
|
234
|
+
return this.session?.sessionManager;
|
|
201
235
|
}
|
|
202
236
|
get settingsManager() {
|
|
203
|
-
return this.session
|
|
237
|
+
return this.session?.settingsManager;
|
|
238
|
+
}
|
|
239
|
+
// Safe accessors that return defaults in connect mode
|
|
240
|
+
get showTerminalProgress() {
|
|
241
|
+
return this.runtimeHost ? this.settingsManager.getShowTerminalProgress() : false;
|
|
242
|
+
}
|
|
243
|
+
get showImages() {
|
|
244
|
+
return this.runtimeHost ? this.settingsManager.getShowImages() : false;
|
|
245
|
+
}
|
|
246
|
+
get imageWidthCells() {
|
|
247
|
+
return this.runtimeHost ? this.settingsManager.getImageWidthCells() : 80;
|
|
248
|
+
}
|
|
249
|
+
get currentCwd() {
|
|
250
|
+
return this.runtimeHost ? this.sessionManager.getCwd() : process.cwd();
|
|
204
251
|
}
|
|
205
252
|
constructor(runtimeHost, options = {}) {
|
|
206
|
-
this.options = options;
|
|
207
253
|
this.runtimeHost = runtimeHost;
|
|
208
|
-
this.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
this.runtimeHost
|
|
212
|
-
|
|
213
|
-
|
|
254
|
+
this.options = options;
|
|
255
|
+
this.proxy = options.proxy;
|
|
256
|
+
// Only register runtime callbacks when NOT using proxy (connect mode)
|
|
257
|
+
if (this.runtimeHost) {
|
|
258
|
+
this.runtimeHost.setBeforeSessionInvalidate(() => {
|
|
259
|
+
this.resetExtensionUI();
|
|
260
|
+
});
|
|
261
|
+
this.runtimeHost.setRebindSession(async () => {
|
|
262
|
+
await this.rebindCurrentSession();
|
|
263
|
+
});
|
|
264
|
+
}
|
|
214
265
|
this.version = VERSION;
|
|
215
|
-
|
|
216
|
-
|
|
266
|
+
// In connect mode (proxy set), use defaults for UI setup since session/settingsManager
|
|
267
|
+
// are not available. In normal mode, read from settingsManager.
|
|
268
|
+
const showHardwareCursor = this.runtimeHost ? this.settingsManager.getShowHardwareCursor() : false;
|
|
269
|
+
const clearOnShrink = this.runtimeHost ? this.settingsManager.getClearOnShrink() : true;
|
|
270
|
+
const editorPaddingX = this.runtimeHost ? this.settingsManager.getEditorPaddingX() : 0;
|
|
271
|
+
const autocompleteMaxVisible = this.runtimeHost ? this.settingsManager.getAutocompleteMaxVisible() : 8;
|
|
272
|
+
this.ui = new TUI(new ProcessTerminal(), showHardwareCursor);
|
|
273
|
+
this.ui.setClearOnShrink(clearOnShrink);
|
|
217
274
|
this.headerContainer = new Container();
|
|
218
275
|
this.chatContainer = new Container();
|
|
219
276
|
this.pendingMessagesContainer = new Container();
|
|
@@ -222,8 +279,6 @@ export class InteractiveMode {
|
|
|
222
279
|
this.widgetContainerBelow = new Container();
|
|
223
280
|
this.keybindings = KeybindingsManager.create();
|
|
224
281
|
setKeybindings(this.keybindings);
|
|
225
|
-
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
|
226
|
-
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
227
282
|
this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
|
|
228
283
|
paddingX: editorPaddingX,
|
|
229
284
|
autocompleteMaxVisible,
|
|
@@ -231,14 +286,19 @@ export class InteractiveMode {
|
|
|
231
286
|
this.editor = this.defaultEditor;
|
|
232
287
|
this.editorContainer = new Container();
|
|
233
288
|
this.editorContainer.addChild(this.editor);
|
|
234
|
-
this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
|
|
235
|
-
this.footer = new FooterComponent(this.session, this.footerDataProvider);
|
|
236
|
-
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
289
|
+
this.footerDataProvider = new FooterDataProvider(this.runtimeHost ? this.sessionManager.getCwd() : process.cwd());
|
|
290
|
+
this.footer = new FooterComponent(this.runtimeHost ? this.session : undefined, this.footerDataProvider);
|
|
291
|
+
this.footer.setAutoCompactEnabled(this.runtimeHost ? this.session.autoCompactionEnabled : false);
|
|
237
292
|
// Load hide thinking block setting
|
|
238
|
-
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
293
|
+
this.hideThinkingBlock = this.runtimeHost ? this.settingsManager.getHideThinkingBlock() : false;
|
|
239
294
|
// Register themes from resource loader and initialize
|
|
240
|
-
|
|
241
|
-
|
|
295
|
+
if (this.runtimeHost) {
|
|
296
|
+
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
297
|
+
initTheme(this.settingsManager.getTheme(), true);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
initTheme("dark", true);
|
|
301
|
+
}
|
|
242
302
|
}
|
|
243
303
|
getAutocompleteSourceTag(sourceInfo) {
|
|
244
304
|
if (!sourceInfo) {
|
|
@@ -319,8 +379,7 @@ export class InteractiveMode {
|
|
|
319
379
|
}));
|
|
320
380
|
// Convert extension commands to SlashCommand format
|
|
321
381
|
const builtinCommandNames = new Set(slashCommands.map((c) => c.name));
|
|
322
|
-
const extensionCommands = this.session.extensionRunner
|
|
323
|
-
.getRegisteredCommands()
|
|
382
|
+
const extensionCommands = this.session.extensionRunner.getRegisteredCommands()
|
|
324
383
|
.filter((cmd) => !builtinCommandNames.has(cmd.name))
|
|
325
384
|
.map((cmd) => ({
|
|
326
385
|
name: cmd.invocationName,
|
|
@@ -353,6 +412,57 @@ export class InteractiveMode {
|
|
|
353
412
|
this.editor.setAutocompleteProvider?.(provider);
|
|
354
413
|
}
|
|
355
414
|
}
|
|
415
|
+
/** Set up autocomplete for connect mode with a reduced command set */
|
|
416
|
+
setupConnectModeAutocomplete() {
|
|
417
|
+
// Fire-and-forget: fetch commands from server, build dynamic autocomplete.
|
|
418
|
+
// Falls back to builtin-only if the fetch fails.
|
|
419
|
+
void this._fetchAndSetConnectModeAutocomplete();
|
|
420
|
+
}
|
|
421
|
+
async _fetchAndSetConnectModeAutocomplete() {
|
|
422
|
+
try {
|
|
423
|
+
const commands = await this.proxy.fetchCommands();
|
|
424
|
+
this._applyConnectModeAutocomplete(commands);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
this._applyConnectModeAutocompleteFallback();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
_applyConnectModeAutocomplete(commands) {
|
|
431
|
+
const slashCommands = commands.map((cmd) => ({
|
|
432
|
+
name: cmd.name,
|
|
433
|
+
description: cmd.description,
|
|
434
|
+
...(cmd.argumentHint && { argumentHint: cmd.argumentHint }),
|
|
435
|
+
}));
|
|
436
|
+
const provider = new CombinedAutocompleteProvider(slashCommands, this.currentCwd);
|
|
437
|
+
this.autocompleteProvider = provider;
|
|
438
|
+
this.defaultEditor.setAutocompleteProvider(provider);
|
|
439
|
+
}
|
|
440
|
+
_applyConnectModeAutocompleteFallback() {
|
|
441
|
+
const fallbackCommands = [
|
|
442
|
+
{ name: "settings", description: "Open settings menu" },
|
|
443
|
+
{
|
|
444
|
+
name: "model",
|
|
445
|
+
description: "Switch model (e.g. /model provider/model-id)",
|
|
446
|
+
argumentHint: "<provider/model-id>",
|
|
447
|
+
},
|
|
448
|
+
{ name: "compact", description: "Manually compact the session context" },
|
|
449
|
+
{ name: "new", description: "Start a new session" },
|
|
450
|
+
{ name: "fork", description: "Create a new fork from a previous user message" },
|
|
451
|
+
{ name: "tree", description: "Navigate session tree (switch branches)" },
|
|
452
|
+
{ name: "clone", description: "Duplicate the current session at the current position" },
|
|
453
|
+
{ name: "resume", description: "Resume a different session" },
|
|
454
|
+
{ name: "name", description: "Set session display name" },
|
|
455
|
+
{ name: "copy", description: "Copy last agent message to clipboard" },
|
|
456
|
+
{ name: "reload", description: "Reload keybindings, extensions, skills, prompts, and themes" },
|
|
457
|
+
{ name: "hotkeys", description: "Show all keyboard shortcuts" },
|
|
458
|
+
{ name: "changelog", description: "Show changelog entries" },
|
|
459
|
+
{ name: "session", description: "Show session info and stats" },
|
|
460
|
+
{ name: "quit", description: "Quit" },
|
|
461
|
+
];
|
|
462
|
+
const provider = new CombinedAutocompleteProvider(fallbackCommands, this.currentCwd);
|
|
463
|
+
this.autocompleteProvider = provider;
|
|
464
|
+
this.defaultEditor.setAutocompleteProvider(provider);
|
|
465
|
+
}
|
|
356
466
|
showStartupNoticesIfNeeded() {
|
|
357
467
|
if (this.startupNoticesShown) {
|
|
358
468
|
return;
|
|
@@ -384,49 +494,81 @@ export class InteractiveMode {
|
|
|
384
494
|
return;
|
|
385
495
|
this.registerSignalHandlers();
|
|
386
496
|
// Load changelog (only show new entries, skip for resumed sessions)
|
|
387
|
-
|
|
497
|
+
// In connect mode, skip changelog since we don't have a local session
|
|
498
|
+
if (this.runtimeHost) {
|
|
499
|
+
this.changelogMarkdown = this.getChangelogForDisplay();
|
|
500
|
+
}
|
|
388
501
|
// Ensure fd and rg are available (downloads if missing, adds to PATH via getBinDir)
|
|
389
502
|
// Both are needed: fd for autocomplete, rg for grep tool and bash commands
|
|
390
503
|
const [fdPath] = await Promise.all([ensureTool("fd"), ensureTool("rg")]);
|
|
391
504
|
this.fdPath = fdPath;
|
|
505
|
+
if (this.runtimeHost &&
|
|
506
|
+
this.session.scopedModels.length > 0 &&
|
|
507
|
+
(this.options.verbose || !this.settingsManager.getQuietStartup())) {
|
|
508
|
+
const modelList = this.session.scopedModels.map((sm) => {
|
|
509
|
+
const thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : "";
|
|
510
|
+
return `${sm.model.id}${thinkingStr}`;
|
|
511
|
+
}).join(", ");
|
|
512
|
+
const cycleKeys = this.keybindings.getKeys("app.model.cycleForward");
|
|
513
|
+
const cycleHint = cycleKeys.length > 0
|
|
514
|
+
? theme.fg("muted", ` (${formatKeyText(cycleKeys.join("/"), { capitalize: true })} to cycle)`)
|
|
515
|
+
: "";
|
|
516
|
+
console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
|
|
517
|
+
}
|
|
392
518
|
// Add header container as first child
|
|
393
519
|
this.ui.addChild(this.headerContainer);
|
|
394
520
|
// Add header with keybindings from config (unless silenced)
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
521
|
+
// In connect mode, always show header since we don't have settingsManager
|
|
522
|
+
const showHeader = this.runtimeHost ? this.options.verbose || !this.settingsManager.getQuietStartup() : true;
|
|
523
|
+
if (showHeader) {
|
|
524
|
+
const bannerData = this.options.banner;
|
|
525
|
+
if (bannerData) {
|
|
526
|
+
// Connect mode: render banner from structured data sent by server
|
|
527
|
+
const logo = theme.bold(theme.fg("accent", bannerData.appName)) + theme.fg("dim", ` v${bannerData.version}`);
|
|
528
|
+
const renderHint = (hint) => theme.fg("dim", hint.key) + theme.fg("muted", ` ${hint.description}`);
|
|
529
|
+
const expandedInstructions = bannerData.expandedHints.map(renderHint).join("\n");
|
|
530
|
+
const compactInstructions = bannerData.compactHints.map(renderHint).join(theme.fg("muted", " · "));
|
|
531
|
+
const compactOnboarding = theme.fg("dim", bannerData.compactOnboarding);
|
|
532
|
+
const onboarding = theme.fg("dim", bannerData.onboarding);
|
|
533
|
+
this.builtInHeader = new ExpandableText(() => `${logo}\n${compactInstructions}\n${compactOnboarding}\n\n${onboarding}`, () => `${logo}\n${expandedInstructions}\n\n${onboarding}`, this.getStartupExpansionState(), 1, 0);
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
// Local mode: generate banner from keybindings + theme
|
|
537
|
+
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
|
|
538
|
+
// Build startup instructions using keybinding hint helpers
|
|
539
|
+
const hint = (keybinding, description) => keyHint(keybinding, description);
|
|
540
|
+
const expandedInstructions = [
|
|
541
|
+
hint("app.interrupt", "to interrupt"),
|
|
542
|
+
hint("app.clear", "to clear"),
|
|
543
|
+
rawKeyHint(`${keyText("app.clear")} twice`, "to exit"),
|
|
544
|
+
hint("app.exit", "to exit (empty)"),
|
|
545
|
+
hint("app.suspend", "to suspend"),
|
|
546
|
+
keyHint("tui.editor.deleteToLineEnd", "to delete to end"),
|
|
547
|
+
hint("app.thinking.cycle", "to cycle thinking level"),
|
|
548
|
+
rawKeyHint(`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`, "to cycle models"),
|
|
549
|
+
hint("app.model.select", "to select model"),
|
|
550
|
+
hint("app.tools.expand", "to expand tools"),
|
|
551
|
+
hint("app.thinking.toggle", "to expand thinking"),
|
|
552
|
+
hint("app.editor.external", "for external editor"),
|
|
553
|
+
rawKeyHint("/", "for commands"),
|
|
554
|
+
rawKeyHint("!", "to run bash"),
|
|
555
|
+
rawKeyHint("!!", "to run bash (no context)"),
|
|
556
|
+
hint("app.message.followUp", "to queue follow-up"),
|
|
557
|
+
hint("app.message.dequeue", "to edit all queued messages"),
|
|
558
|
+
hint("app.clipboard.pasteImage", "to paste image"),
|
|
559
|
+
rawKeyHint("drop files", "to attach"),
|
|
560
|
+
].join("\n");
|
|
561
|
+
const compactInstructions = [
|
|
562
|
+
hint("app.interrupt", "interrupt"),
|
|
563
|
+
rawKeyHint(`${keyText("app.clear")}/${keyText("app.exit")}`, "clear/exit"),
|
|
564
|
+
rawKeyHint("/", "commands"),
|
|
565
|
+
rawKeyHint("!", "bash"),
|
|
566
|
+
hint("app.tools.expand", "more"),
|
|
567
|
+
].join(theme.fg("muted", " · "));
|
|
568
|
+
const compactOnboarding = theme.fg("dim", `Press ${keyText("app.tools.expand")} to show full startup help and loaded resources.`);
|
|
569
|
+
const onboarding = theme.fg("dim", `Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.`);
|
|
570
|
+
this.builtInHeader = new ExpandableText(() => `${logo}\n${compactInstructions}\n${compactOnboarding}\n\n${onboarding}`, () => `${logo}\n${expandedInstructions}\n\n${onboarding}`, this.getStartupExpansionState(), 1, 0);
|
|
571
|
+
}
|
|
430
572
|
// Setup UI layout
|
|
431
573
|
this.headerContainer.addChild(new Spacer(1));
|
|
432
574
|
this.headerContainer.addChild(this.builtInHeader);
|
|
@@ -437,6 +579,50 @@ export class InteractiveMode {
|
|
|
437
579
|
this.builtInHeader = new Text("", 0, 0);
|
|
438
580
|
this.headerContainer.addChild(this.builtInHeader);
|
|
439
581
|
}
|
|
582
|
+
// In connect mode, render loaded resources from banner data into chat container
|
|
583
|
+
if (this.options.banner) {
|
|
584
|
+
const banner = this.options.banner;
|
|
585
|
+
// Loaded resource sections
|
|
586
|
+
for (const section of banner.loadedResources) {
|
|
587
|
+
const sectionHeader = theme.fg("mdHeading", `[${section.name}]`);
|
|
588
|
+
const compactBody = theme.fg("dim", ` ${section.compactList}`);
|
|
589
|
+
const expandedBody = theme.fg("dim", section.expandedList
|
|
590
|
+
.split("\n")
|
|
591
|
+
.map((l) => ` ${l}`)
|
|
592
|
+
.join("\n"));
|
|
593
|
+
const expandable = new ExpandableText(() => `${sectionHeader}\n${compactBody}`, () => `${sectionHeader}\n${expandedBody}`, this.getStartupExpansionState(), 0, 0);
|
|
594
|
+
this.chatContainer.addChild(expandable);
|
|
595
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
596
|
+
}
|
|
597
|
+
// Diagnostics (Skill conflicts, Prompt conflicts, etc.)
|
|
598
|
+
for (const diag of banner.diagnostics) {
|
|
599
|
+
const lines = [];
|
|
600
|
+
for (const entry of diag.entries) {
|
|
601
|
+
if (entry.type === "collision" && entry.collision) {
|
|
602
|
+
const c = entry.collision;
|
|
603
|
+
lines.push(` "${c.name}" collision:`);
|
|
604
|
+
lines.push(` ✓ auto (${c.winnerSource ?? "local"}) ${c.winnerPath}`);
|
|
605
|
+
lines.push(` ✗ ${c.loserPath} (skipped)`);
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
lines.push(` ${entry.message}`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (lines.length > 0) {
|
|
612
|
+
this.chatContainer.addChild(new Text(`${theme.fg("warning", `[${diag.label}]`)}\n${lines.join("\n")}`, 0, 0));
|
|
613
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
// What's New (changelog)
|
|
617
|
+
if (banner.changelogMarkdown) {
|
|
618
|
+
this.chatContainer.addChild(new DynamicBorder());
|
|
619
|
+
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
|
620
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
621
|
+
this.chatContainer.addChild(new Markdown(banner.changelogMarkdown.trim(), 1, 0, this.getMarkdownThemeWithSettings()));
|
|
622
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
623
|
+
this.chatContainer.addChild(new DynamicBorder());
|
|
624
|
+
}
|
|
625
|
+
}
|
|
440
626
|
this.ui.addChild(this.chatContainer);
|
|
441
627
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
442
628
|
this.ui.addChild(this.statusContainer);
|
|
@@ -452,9 +638,42 @@ export class InteractiveMode {
|
|
|
452
638
|
this.ui.start();
|
|
453
639
|
this.isInitialized = true;
|
|
454
640
|
// Initialize extensions first so resources are shown before messages
|
|
455
|
-
|
|
641
|
+
if (this.runtimeHost) {
|
|
642
|
+
await this.rebindCurrentSession();
|
|
643
|
+
}
|
|
644
|
+
else if (this.proxy) {
|
|
645
|
+
this.subscribeToAgent();
|
|
646
|
+
// Set initial footer snapshot from proxy
|
|
647
|
+
const snapshot = this.proxy.getSnapshot();
|
|
648
|
+
this.footer.setSnapshot(snapshot);
|
|
649
|
+
this.footerDataProvider.setCwd(snapshot.cwd);
|
|
650
|
+
this.footerDataProvider.setAvailableProviderCount(snapshot.availableProviderCount);
|
|
651
|
+
// If agent is already streaming, create the working loader
|
|
652
|
+
// (agent_start event fired before we subscribed)
|
|
653
|
+
if (snapshot.isStreaming) {
|
|
654
|
+
this.loadingAnimation = this.createWorkingLoader();
|
|
655
|
+
this.statusContainer.addChild(this.loadingAnimation);
|
|
656
|
+
}
|
|
657
|
+
// Apply remote settings to editor
|
|
658
|
+
if (snapshot.remoteSettings) {
|
|
659
|
+
const paddingX = snapshot.remoteSettings.editorPaddingX;
|
|
660
|
+
this.defaultEditor.setPaddingX(paddingX);
|
|
661
|
+
if (this.editor !== this.defaultEditor) {
|
|
662
|
+
this.editor.setPaddingX?.(paddingX);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
// Set up autocomplete for connect mode with a reduced command set
|
|
666
|
+
this.setupConnectModeAutocomplete();
|
|
667
|
+
// Load client-side extensions for UI (shortcuts, widgets, status, etc.)
|
|
668
|
+
await this.bindConnectModeExtensions();
|
|
669
|
+
}
|
|
456
670
|
// Render initial messages AFTER showing loaded resources
|
|
457
|
-
this.
|
|
671
|
+
if (this.runtimeHost) {
|
|
672
|
+
this.renderInitialMessages();
|
|
673
|
+
}
|
|
674
|
+
else if (this.proxy) {
|
|
675
|
+
this.renderInitialMessages();
|
|
676
|
+
}
|
|
458
677
|
// Set up theme file watcher
|
|
459
678
|
onThemeChange(() => {
|
|
460
679
|
this.ui.invalidate();
|
|
@@ -466,19 +685,27 @@ export class InteractiveMode {
|
|
|
466
685
|
this.ui.requestRender();
|
|
467
686
|
});
|
|
468
687
|
// Initialize available provider count for footer display
|
|
469
|
-
|
|
688
|
+
// In connect mode, skip since we don't have a model registry
|
|
689
|
+
if (this.runtimeHost) {
|
|
690
|
+
await this.updateAvailableProviderCount();
|
|
691
|
+
}
|
|
470
692
|
}
|
|
471
693
|
/**
|
|
472
694
|
* Update terminal title with session name and cwd.
|
|
473
695
|
*/
|
|
474
696
|
updateTerminalTitle() {
|
|
475
|
-
const cwdBasename = path.basename(this.
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
697
|
+
const cwdBasename = path.basename(this.currentCwd);
|
|
698
|
+
if (this.runtimeHost) {
|
|
699
|
+
const sessionName = this.sessionManager.getSessionName();
|
|
700
|
+
if (sessionName) {
|
|
701
|
+
this.ui.terminal.setTitle(`${APP_TITLE} - ${sessionName} - ${cwdBasename}`);
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
this.ui.terminal.setTitle(`${APP_TITLE} - ${cwdBasename}`);
|
|
705
|
+
}
|
|
479
706
|
}
|
|
480
707
|
else {
|
|
481
|
-
this.ui.terminal.setTitle(`${APP_TITLE} - ${cwdBasename}`);
|
|
708
|
+
this.ui.terminal.setTitle(`${APP_TITLE} (connect) - ${cwdBasename}`);
|
|
482
709
|
}
|
|
483
710
|
}
|
|
484
711
|
/**
|
|
@@ -487,51 +714,64 @@ export class InteractiveMode {
|
|
|
487
714
|
*/
|
|
488
715
|
async run() {
|
|
489
716
|
await this.init();
|
|
490
|
-
// Start version check asynchronously
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
717
|
+
// Start version check asynchronously (skip in connect mode)
|
|
718
|
+
if (this.runtimeHost) {
|
|
719
|
+
checkForNewPiVersion(this.version).then((newRelease) => {
|
|
720
|
+
if (newRelease) {
|
|
721
|
+
this.showNewVersionNotification(newRelease);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
// Start package update check asynchronously
|
|
725
|
+
this.checkForPackageUpdates().then((updates) => {
|
|
726
|
+
if (updates.length > 0) {
|
|
727
|
+
this.showPackageUpdateNotification(updates);
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
// Check tmux keyboard setup asynchronously
|
|
731
|
+
this.checkTmuxKeyboardSetup().then((warning) => {
|
|
732
|
+
if (warning) {
|
|
733
|
+
this.showWarning(warning);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
}
|
|
508
737
|
// Show startup warnings
|
|
509
738
|
const { migratedProviders, modelFallbackMessage, initialMessage, initialImages, initialMessages } = this.options;
|
|
510
739
|
if (migratedProviders && migratedProviders.length > 0) {
|
|
511
740
|
this.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(", ")}`);
|
|
512
741
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
742
|
+
if (this.runtimeHost) {
|
|
743
|
+
const modelsJsonError = this.session.modelRegistry.getError();
|
|
744
|
+
if (modelsJsonError) {
|
|
745
|
+
this.showError(`models.json error: ${modelsJsonError}`);
|
|
746
|
+
}
|
|
516
747
|
}
|
|
517
748
|
if (modelFallbackMessage) {
|
|
518
749
|
this.showWarning(modelFallbackMessage);
|
|
519
750
|
}
|
|
520
|
-
|
|
751
|
+
if (this.runtimeHost) {
|
|
752
|
+
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
753
|
+
}
|
|
521
754
|
// Process initial messages
|
|
522
755
|
if (initialMessage) {
|
|
523
756
|
try {
|
|
524
|
-
|
|
757
|
+
if (this.proxy) {
|
|
758
|
+
// Proxy uses a different image format; convert or skip images
|
|
759
|
+
await this.proxy.prompt(initialMessage);
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
await this.session.prompt(initialMessage, { images: initialImages });
|
|
763
|
+
}
|
|
525
764
|
}
|
|
526
765
|
catch (error) {
|
|
527
766
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
528
767
|
this.showError(errorMessage);
|
|
529
768
|
}
|
|
530
769
|
}
|
|
770
|
+
const cmdTarget = this.proxy ?? this.session;
|
|
531
771
|
if (initialMessages) {
|
|
532
772
|
for (const message of initialMessages) {
|
|
533
773
|
try {
|
|
534
|
-
await
|
|
774
|
+
await cmdTarget.prompt(message);
|
|
535
775
|
}
|
|
536
776
|
catch (error) {
|
|
537
777
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
@@ -543,7 +783,7 @@ export class InteractiveMode {
|
|
|
543
783
|
while (true) {
|
|
544
784
|
const userInput = await this.getUserInput();
|
|
545
785
|
try {
|
|
546
|
-
await
|
|
786
|
+
await cmdTarget.prompt(userInput);
|
|
547
787
|
}
|
|
548
788
|
catch (error) {
|
|
549
789
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
@@ -654,7 +894,7 @@ export class InteractiveMode {
|
|
|
654
894
|
getMarkdownThemeWithSettings() {
|
|
655
895
|
return {
|
|
656
896
|
...getMarkdownTheme(),
|
|
657
|
-
codeBlockIndent: this.settingsManager.getCodeBlockIndent(),
|
|
897
|
+
codeBlockIndent: this.runtimeHost ? this.settingsManager.getCodeBlockIndent() : " ",
|
|
658
898
|
};
|
|
659
899
|
}
|
|
660
900
|
// =========================================================================
|
|
@@ -1092,6 +1332,9 @@ export class InteractiveMode {
|
|
|
1092
1332
|
const uiContext = this.createExtensionUIContext();
|
|
1093
1333
|
await this.session.bindExtensions({
|
|
1094
1334
|
uiContext,
|
|
1335
|
+
abortHandler: () => {
|
|
1336
|
+
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1337
|
+
},
|
|
1095
1338
|
commandContextActions: {
|
|
1096
1339
|
waitForIdle: () => this.session.agent.waitForIdle(),
|
|
1097
1340
|
newSession: async (options) => {
|
|
@@ -1169,7 +1412,94 @@ export class InteractiveMode {
|
|
|
1169
1412
|
this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
|
|
1170
1413
|
this.showStartupNoticesIfNeeded();
|
|
1171
1414
|
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Load and bind client-side extensions for connect mode.
|
|
1417
|
+
* Extensions run with a real UI context but stub session/model access.
|
|
1418
|
+
* Tool and event registrations are no-ops (handled by the server).
|
|
1419
|
+
*/
|
|
1420
|
+
async bindConnectModeExtensions() {
|
|
1421
|
+
if (!this.proxy)
|
|
1422
|
+
return;
|
|
1423
|
+
const snapshot = this.proxy.getSnapshot();
|
|
1424
|
+
const remoteClientExtensionsUrl = this.options.remoteClientExtensionsUrl;
|
|
1425
|
+
if (!remoteClientExtensionsUrl)
|
|
1426
|
+
return;
|
|
1427
|
+
try {
|
|
1428
|
+
const cwd = snapshot.cwd || process.cwd();
|
|
1429
|
+
// Load server-synchronized client extensions. Connect mode intentionally
|
|
1430
|
+
// does not load local extension paths; the server is the source of truth.
|
|
1431
|
+
const result = await loadRemoteClientExtensions(remoteClientExtensionsUrl, cwd, this.options.remoteClientExtensionHeaders);
|
|
1432
|
+
if (result.extensions.length === 0)
|
|
1433
|
+
return;
|
|
1434
|
+
const runner = ExtensionRunnerImpl.createClientSide(result.extensions, result.runtime, cwd);
|
|
1435
|
+
// Bind core actions — most are no-ops since the server handles them.
|
|
1436
|
+
// Only UI-facing actions need real implementations.
|
|
1437
|
+
runner.bindCore({
|
|
1438
|
+
sendMessage: () => { },
|
|
1439
|
+
sendUserMessage: () => { },
|
|
1440
|
+
appendEntry: () => { },
|
|
1441
|
+
setSessionName: () => { },
|
|
1442
|
+
getSessionName: () => undefined,
|
|
1443
|
+
setLabel: () => { },
|
|
1444
|
+
getActiveTools: () => [],
|
|
1445
|
+
getAllTools: () => [],
|
|
1446
|
+
setActiveTools: () => { },
|
|
1447
|
+
refreshTools: () => { },
|
|
1448
|
+
getCommands: () => [],
|
|
1449
|
+
setModel: () => Promise.resolve(false),
|
|
1450
|
+
getThinkingLevel: () => "medium",
|
|
1451
|
+
setThinkingLevel: () => { },
|
|
1452
|
+
}, {
|
|
1453
|
+
getModel: () => undefined,
|
|
1454
|
+
isIdle: () => true,
|
|
1455
|
+
getSignal: () => undefined,
|
|
1456
|
+
abort: () => {
|
|
1457
|
+
this.proxy.abort();
|
|
1458
|
+
},
|
|
1459
|
+
hasPendingMessages: () => false,
|
|
1460
|
+
shutdown: () => {
|
|
1461
|
+
void this.shutdown();
|
|
1462
|
+
},
|
|
1463
|
+
getContextUsage: () => undefined,
|
|
1464
|
+
compact: () => { },
|
|
1465
|
+
getSystemPrompt: () => "",
|
|
1466
|
+
}, {
|
|
1467
|
+
registerProvider: () => { },
|
|
1468
|
+
unregisterProvider: () => { },
|
|
1469
|
+
});
|
|
1470
|
+
// Bind command context — delegate to proxy
|
|
1471
|
+
runner.bindCommandContext({
|
|
1472
|
+
waitForIdle: () => Promise.resolve(),
|
|
1473
|
+
newSession: async () => {
|
|
1474
|
+
await this.proxy.newSession();
|
|
1475
|
+
return { cancelled: false };
|
|
1476
|
+
},
|
|
1477
|
+
fork: async () => {
|
|
1478
|
+
await this.proxy.fork();
|
|
1479
|
+
return { cancelled: false };
|
|
1480
|
+
},
|
|
1481
|
+
navigateTree: async () => ({ cancelled: false }),
|
|
1482
|
+
switchSession: async () => ({ cancelled: false }),
|
|
1483
|
+
reload: async () => {
|
|
1484
|
+
await this.proxy.reload();
|
|
1485
|
+
},
|
|
1486
|
+
});
|
|
1487
|
+
// Set the real UI context so extensions can interact with the TUI
|
|
1488
|
+
const uiContext = this.createExtensionUIContext();
|
|
1489
|
+
runner.setUIContext(uiContext);
|
|
1490
|
+
// Set up extension shortcuts
|
|
1491
|
+
this.setupExtensionShortcuts(runner);
|
|
1492
|
+
this._clientExtensionRunner = runner;
|
|
1493
|
+
// Emit session_start so extensions can initialize their UI
|
|
1494
|
+
await runner.emit({ type: "session_start", reason: "startup" });
|
|
1495
|
+
}
|
|
1496
|
+
catch (err) {
|
|
1497
|
+
// Non-fatal: extensions are best-effort in connect mode
|
|
1498
|
+
process.stderr.write(`[connect] Failed to load client-side extensions: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1172
1501
|
applyRuntimeSettings() {
|
|
1502
|
+
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
1173
1503
|
this.footer.setSession(this.session);
|
|
1174
1504
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
1175
1505
|
this.footerDataProvider.setCwd(this.sessionManager.getCwd());
|
|
@@ -1215,7 +1545,7 @@ export class InteractiveMode {
|
|
|
1215
1545
|
* Get a registered tool definition by name (for custom rendering).
|
|
1216
1546
|
*/
|
|
1217
1547
|
getRegisteredToolDefinition(toolName) {
|
|
1218
|
-
return this.session
|
|
1548
|
+
return this.session?.getToolDefinition(toolName);
|
|
1219
1549
|
}
|
|
1220
1550
|
/**
|
|
1221
1551
|
* Set up keyboard shortcuts registered by extensions.
|
|
@@ -1234,7 +1564,9 @@ export class InteractiveMode {
|
|
|
1234
1564
|
model: this.session.model,
|
|
1235
1565
|
isIdle: () => !this.session.isStreaming,
|
|
1236
1566
|
signal: this.session.agent.signal,
|
|
1237
|
-
abort: () =>
|
|
1567
|
+
abort: () => {
|
|
1568
|
+
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1569
|
+
},
|
|
1238
1570
|
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
|
1239
1571
|
shutdown: () => {
|
|
1240
1572
|
this.shutdownRequested = true;
|
|
@@ -1397,6 +1729,11 @@ export class InteractiveMode {
|
|
|
1397
1729
|
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
|
|
1398
1730
|
}
|
|
1399
1731
|
this.setHiddenThinkingLabel();
|
|
1732
|
+
// Invalidate client-side extension runner (connect mode)
|
|
1733
|
+
if (this._clientExtensionRunner) {
|
|
1734
|
+
this._clientExtensionRunner.invalidate("Session replaced — extension context is stale");
|
|
1735
|
+
this._clientExtensionRunner = undefined;
|
|
1736
|
+
}
|
|
1400
1737
|
}
|
|
1401
1738
|
// Maximum total widget lines to prevent viewport overflow
|
|
1402
1739
|
static MAX_WIDGET_LINES = 10;
|
|
@@ -1509,8 +1846,54 @@ export class InteractiveMode {
|
|
|
1509
1846
|
}
|
|
1510
1847
|
/**
|
|
1511
1848
|
* Create the ExtensionUIContext for extensions.
|
|
1849
|
+
* In connect mode (proxy set), returns a headless implementation that rejects interactive dialogs.
|
|
1512
1850
|
*/
|
|
1513
1851
|
createExtensionUIContext() {
|
|
1852
|
+
// Connect mode: real UI for non-interactive operations, defaults for dialogs
|
|
1853
|
+
if (this.proxy) {
|
|
1854
|
+
return {
|
|
1855
|
+
// Interactive dialogs — select is real TUI, others return defaults
|
|
1856
|
+
select: (title, options, opts) => this.showExtensionSelector(title, options, opts),
|
|
1857
|
+
confirm: () => Promise.resolve(false),
|
|
1858
|
+
input: () => Promise.resolve(undefined),
|
|
1859
|
+
custom: () => Promise.resolve(undefined),
|
|
1860
|
+
editor: () => Promise.resolve(undefined),
|
|
1861
|
+
// Non-interactive UI — real TUI implementations
|
|
1862
|
+
notify: (message, type) => this.showExtensionNotify(message, type),
|
|
1863
|
+
onTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),
|
|
1864
|
+
setStatus: (key, text) => this.setExtensionStatus(key, text),
|
|
1865
|
+
setWorkingMessage: (message) => {
|
|
1866
|
+
this.workingMessage = message;
|
|
1867
|
+
if (this.loadingAnimation) {
|
|
1868
|
+
this.loadingAnimation.setMessage(message ?? this.defaultWorkingMessage);
|
|
1869
|
+
}
|
|
1870
|
+
},
|
|
1871
|
+
setWorkingVisible: (visible) => this.setWorkingVisible(visible),
|
|
1872
|
+
setWorkingIndicator: (options) => this.setWorkingIndicator(options),
|
|
1873
|
+
setHiddenThinkingLabel: (label) => this.setHiddenThinkingLabel(label),
|
|
1874
|
+
setWidget: (key, content, options) => this.setExtensionWidget(key, content, options),
|
|
1875
|
+
setFooter: (factory) => this.setExtensionFooter(factory),
|
|
1876
|
+
setHeader: (factory) => this.setExtensionHeader(factory),
|
|
1877
|
+
setTitle: (title) => this.ui.terminal.setTitle(title),
|
|
1878
|
+
pasteToEditor: (text) => this.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
|
|
1879
|
+
setEditorText: (text) => this.editor.setText(text),
|
|
1880
|
+
getEditorText: () => this.editor.getExpandedText?.() ?? this.editor.getText(),
|
|
1881
|
+
addAutocompleteProvider: (factory) => {
|
|
1882
|
+
this.autocompleteProviderWrappers.push(factory);
|
|
1883
|
+
this.setupAutocompleteProvider();
|
|
1884
|
+
},
|
|
1885
|
+
setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
|
|
1886
|
+
getEditorComponent: () => this.editorComponentFactory,
|
|
1887
|
+
get theme() {
|
|
1888
|
+
return theme;
|
|
1889
|
+
},
|
|
1890
|
+
getAllThemes: () => [],
|
|
1891
|
+
getTheme: () => undefined,
|
|
1892
|
+
setTheme: () => ({ success: false }),
|
|
1893
|
+
getToolsExpanded: () => this.toolOutputExpanded,
|
|
1894
|
+
setToolsExpanded: (expanded) => this.setToolsExpanded(expanded),
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1514
1897
|
return {
|
|
1515
1898
|
select: (title, options, opts) => this.showExtensionSelector(title, options, opts),
|
|
1516
1899
|
confirm: (title, message, opts) => this.showExtensionConfirm(title, message, opts),
|
|
@@ -1855,11 +2238,18 @@ export class InteractiveMode {
|
|
|
1855
2238
|
// Set up handlers on defaultEditor - they use this.editor for text access
|
|
1856
2239
|
// so they work correctly regardless of which editor is active
|
|
1857
2240
|
this.defaultEditor.onEscape = () => {
|
|
1858
|
-
|
|
2241
|
+
const isStreaming = this.proxy ? this.proxy.isStreaming : this.session.isStreaming;
|
|
2242
|
+
const isBashRunning = this.proxy ? this.proxy.isBashRunning : this.session.isBashRunning;
|
|
2243
|
+
if (isStreaming) {
|
|
1859
2244
|
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1860
2245
|
}
|
|
1861
|
-
else if (
|
|
1862
|
-
this.
|
|
2246
|
+
else if (isBashRunning) {
|
|
2247
|
+
if (this.proxy) {
|
|
2248
|
+
this.proxy.abortBash();
|
|
2249
|
+
}
|
|
2250
|
+
else {
|
|
2251
|
+
this.session.abortBash();
|
|
2252
|
+
}
|
|
1863
2253
|
}
|
|
1864
2254
|
else if (this.isBashMode) {
|
|
1865
2255
|
this.editor.setText("");
|
|
@@ -1868,7 +2258,7 @@ export class InteractiveMode {
|
|
|
1868
2258
|
}
|
|
1869
2259
|
else if (!this.editor.getText().trim()) {
|
|
1870
2260
|
// Double-escape with empty editor triggers /tree, /fork, or nothing based on setting
|
|
1871
|
-
const action = this.settingsManager
|
|
2261
|
+
const action = this.settingsManager?.getDoubleEscapeAction() ?? "tree";
|
|
1872
2262
|
if (action !== "none") {
|
|
1873
2263
|
const now = Date.now();
|
|
1874
2264
|
if (now - this.lastEscapeTime < 500) {
|
|
@@ -1923,7 +2313,16 @@ export class InteractiveMode {
|
|
|
1923
2313
|
if (!image) {
|
|
1924
2314
|
return;
|
|
1925
2315
|
}
|
|
1926
|
-
|
|
2316
|
+
if (this.proxy) {
|
|
2317
|
+
// Connect mode: send as base64 data URL via prompt images
|
|
2318
|
+
const base64 = Buffer.from(image.bytes).toString("base64");
|
|
2319
|
+
const dataUrl = `data:${image.mimeType};base64,${base64}`;
|
|
2320
|
+
const text = this.editor.getText().trim() || "Look at this image";
|
|
2321
|
+
await this.proxy.prompt(text, { images: [{ url: dataUrl, mediaType: image.mimeType }] });
|
|
2322
|
+
this.editor.setText("");
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
// Local mode: write to temp file and insert path
|
|
1927
2326
|
const tmpDir = os.tmpdir();
|
|
1928
2327
|
const ext = extensionForImageMimeType(image.mimeType) ?? "png";
|
|
1929
2328
|
const fileName = `pi-clipboard-${crypto.randomUUID()}.${ext}`;
|
|
@@ -1942,6 +2341,13 @@ export class InteractiveMode {
|
|
|
1942
2341
|
text = text.trim();
|
|
1943
2342
|
if (!text)
|
|
1944
2343
|
return;
|
|
2344
|
+
// In connect mode, handle commands via proxy
|
|
2345
|
+
if (this.proxy) {
|
|
2346
|
+
if (text.startsWith("/")) {
|
|
2347
|
+
await this.handleConnectModeCommand(text);
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
1945
2351
|
// Handle commands
|
|
1946
2352
|
if (text === "/settings") {
|
|
1947
2353
|
this.showSettingsSelector();
|
|
@@ -2067,6 +2473,11 @@ export class InteractiveMode {
|
|
|
2067
2473
|
}
|
|
2068
2474
|
// Handle bash command (! for normal, !! for excluded from context)
|
|
2069
2475
|
if (text.startsWith("!")) {
|
|
2476
|
+
if (this.proxy) {
|
|
2477
|
+
this.showWarning("Bash commands are not supported in connect mode.");
|
|
2478
|
+
this.editor.setText(text);
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2070
2481
|
const isExcluded = text.startsWith("!!");
|
|
2071
2482
|
const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
|
|
2072
2483
|
if (command) {
|
|
@@ -2083,8 +2494,8 @@ export class InteractiveMode {
|
|
|
2083
2494
|
}
|
|
2084
2495
|
}
|
|
2085
2496
|
// Queue input during compaction (extension commands execute immediately)
|
|
2086
|
-
if (this.session.isCompacting) {
|
|
2087
|
-
if (this.isExtensionCommand(text)) {
|
|
2497
|
+
if (this.proxy ? this.proxy.isCompacting : this.session.isCompacting) {
|
|
2498
|
+
if (!this.proxy && this.isExtensionCommand(text)) {
|
|
2088
2499
|
this.editor.addToHistory?.(text);
|
|
2089
2500
|
this.editor.setText("");
|
|
2090
2501
|
await this.session.prompt(text);
|
|
@@ -2096,10 +2507,15 @@ export class InteractiveMode {
|
|
|
2096
2507
|
}
|
|
2097
2508
|
// If streaming, use prompt() with steer behavior
|
|
2098
2509
|
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
2099
|
-
if (this.session.isStreaming) {
|
|
2510
|
+
if (this.proxy ? this.proxy.isStreaming : this.session.isStreaming) {
|
|
2100
2511
|
this.editor.addToHistory?.(text);
|
|
2101
2512
|
this.editor.setText("");
|
|
2102
|
-
|
|
2513
|
+
if (this.proxy) {
|
|
2514
|
+
this.proxy.steer(text);
|
|
2515
|
+
}
|
|
2516
|
+
else {
|
|
2517
|
+
await this.session.prompt(text, { streamingBehavior: "steer" });
|
|
2518
|
+
}
|
|
2103
2519
|
this.updatePendingMessagesDisplay();
|
|
2104
2520
|
this.ui.requestRender();
|
|
2105
2521
|
return;
|
|
@@ -2110,11 +2526,117 @@ export class InteractiveMode {
|
|
|
2110
2526
|
if (this.onInputCallback) {
|
|
2111
2527
|
this.onInputCallback(text);
|
|
2112
2528
|
}
|
|
2529
|
+
else {
|
|
2530
|
+
this.pendingUserInputs.push(text);
|
|
2531
|
+
}
|
|
2113
2532
|
this.editor.addToHistory?.(text);
|
|
2114
2533
|
};
|
|
2115
2534
|
}
|
|
2535
|
+
/** Handle slash commands in connect mode */
|
|
2536
|
+
async handleConnectModeCommand(text) {
|
|
2537
|
+
this.editor.setText("");
|
|
2538
|
+
const cmd = text.split(" ")[0];
|
|
2539
|
+
const arg = text.slice(cmd.length + 1).trim() || undefined;
|
|
2540
|
+
switch (cmd) {
|
|
2541
|
+
case "/quit":
|
|
2542
|
+
await this.shutdown();
|
|
2543
|
+
break;
|
|
2544
|
+
case "/compact":
|
|
2545
|
+
await this.proxy.compact();
|
|
2546
|
+
break;
|
|
2547
|
+
case "/model":
|
|
2548
|
+
if (arg) {
|
|
2549
|
+
this.proxy.setModel(arg);
|
|
2550
|
+
this.showStatus(`Model: ${arg}`);
|
|
2551
|
+
}
|
|
2552
|
+
else {
|
|
2553
|
+
this.showConnectModeModelSelector();
|
|
2554
|
+
}
|
|
2555
|
+
break;
|
|
2556
|
+
case "/scoped-models":
|
|
2557
|
+
this.showConnectModeScopedModelsSelector();
|
|
2558
|
+
break;
|
|
2559
|
+
case "/settings":
|
|
2560
|
+
this.showConnectModeSettingsSelector();
|
|
2561
|
+
break;
|
|
2562
|
+
case "/export":
|
|
2563
|
+
this.showStatus("Not available in connect mode");
|
|
2564
|
+
break;
|
|
2565
|
+
case "/import":
|
|
2566
|
+
this.showStatus("Not available in connect mode");
|
|
2567
|
+
break;
|
|
2568
|
+
case "/share":
|
|
2569
|
+
this.showStatus("Not available in connect mode");
|
|
2570
|
+
break;
|
|
2571
|
+
case "/login":
|
|
2572
|
+
this.showStatus("Not available in connect mode — configure auth on the server");
|
|
2573
|
+
break;
|
|
2574
|
+
case "/logout":
|
|
2575
|
+
this.showStatus("Not available in connect mode — configure auth on the server");
|
|
2576
|
+
break;
|
|
2577
|
+
case "/new":
|
|
2578
|
+
await this.proxy.newSession();
|
|
2579
|
+
// UI reset is handled by the session_replaced event
|
|
2580
|
+
break;
|
|
2581
|
+
case "/fork":
|
|
2582
|
+
this.showConnectModeForkSelector();
|
|
2583
|
+
break;
|
|
2584
|
+
case "/tree":
|
|
2585
|
+
this.showConnectModeTreeSelector();
|
|
2586
|
+
break;
|
|
2587
|
+
case "/clone":
|
|
2588
|
+
await this.proxy.fork();
|
|
2589
|
+
// UI reset is handled by the session_replaced event
|
|
2590
|
+
break;
|
|
2591
|
+
case "/resume":
|
|
2592
|
+
this.showConnectModeSessionSelector();
|
|
2593
|
+
break;
|
|
2594
|
+
case "/name":
|
|
2595
|
+
if (arg) {
|
|
2596
|
+
this.proxy.renameSession(arg);
|
|
2597
|
+
this.showStatus(`Session renamed to: ${arg}`);
|
|
2598
|
+
}
|
|
2599
|
+
else {
|
|
2600
|
+
this.showWarning("Usage: /name <session-name>");
|
|
2601
|
+
}
|
|
2602
|
+
break;
|
|
2603
|
+
case "/copy":
|
|
2604
|
+
this.handleConnectModeCopy();
|
|
2605
|
+
break;
|
|
2606
|
+
case "/reload":
|
|
2607
|
+
await this.proxy.reload();
|
|
2608
|
+
this.setupConnectModeAutocomplete();
|
|
2609
|
+
this.showStatus("Reloaded");
|
|
2610
|
+
break;
|
|
2611
|
+
case "/hotkeys":
|
|
2612
|
+
this.handleHotkeysCommand();
|
|
2613
|
+
break;
|
|
2614
|
+
case "/changelog":
|
|
2615
|
+
this.handleChangelogCommand();
|
|
2616
|
+
break;
|
|
2617
|
+
case "/session":
|
|
2618
|
+
this.showConnectModeSessionInfo();
|
|
2619
|
+
break;
|
|
2620
|
+
default: {
|
|
2621
|
+
// Check client-side extension runner first
|
|
2622
|
+
if (this._clientExtensionRunner) {
|
|
2623
|
+
const cmdName = cmd.startsWith("/") ? cmd.slice(1) : cmd;
|
|
2624
|
+
const resolvedCmd = this._clientExtensionRunner.getCommand(cmdName);
|
|
2625
|
+
if (resolvedCmd) {
|
|
2626
|
+
const ctx = this._clientExtensionRunner.createCommandContext();
|
|
2627
|
+
await resolvedCmd.handler(arg ?? "", ctx);
|
|
2628
|
+
break;
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
// Non-builtin commands (skill, prompt template, extension) are sent as prompts.
|
|
2632
|
+
// The server's agent-session handles skill expansion and prompt template resolution.
|
|
2633
|
+
await this.proxy.prompt(text);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2116
2637
|
subscribeToAgent() {
|
|
2117
|
-
this.
|
|
2638
|
+
const eventSource = this.proxy ?? this.session;
|
|
2639
|
+
this.unsubscribe = eventSource.subscribe(async (event) => {
|
|
2118
2640
|
await this.handleEvent(event);
|
|
2119
2641
|
});
|
|
2120
2642
|
}
|
|
@@ -2126,7 +2648,7 @@ export class InteractiveMode {
|
|
|
2126
2648
|
switch (event.type) {
|
|
2127
2649
|
case "agent_start":
|
|
2128
2650
|
this.pendingTools.clear();
|
|
2129
|
-
if (this.
|
|
2651
|
+
if (this.showTerminalProgress) {
|
|
2130
2652
|
this.ui.terminal.setProgress(true);
|
|
2131
2653
|
}
|
|
2132
2654
|
// Restore main escape handler if retry handler is still active
|
|
@@ -2189,9 +2711,9 @@ export class InteractiveMode {
|
|
|
2189
2711
|
if (content.type === "toolCall") {
|
|
2190
2712
|
if (!this.pendingTools.has(content.id)) {
|
|
2191
2713
|
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
|
|
2192
|
-
showImages: this.
|
|
2193
|
-
imageWidthCells: this.
|
|
2194
|
-
}, this.getRegisteredToolDefinition(content.name), this.ui, this.
|
|
2714
|
+
showImages: this.showImages,
|
|
2715
|
+
imageWidthCells: this.imageWidthCells,
|
|
2716
|
+
}, this.getRegisteredToolDefinition(content.name), this.ui, this.currentCwd);
|
|
2195
2717
|
component.setExpanded(this.toolOutputExpanded);
|
|
2196
2718
|
this.chatContainer.addChild(component);
|
|
2197
2719
|
this.pendingTools.set(content.id, component);
|
|
@@ -2214,7 +2736,7 @@ export class InteractiveMode {
|
|
|
2214
2736
|
this.streamingMessage = event.message;
|
|
2215
2737
|
let errorMessage;
|
|
2216
2738
|
if (this.streamingMessage.stopReason === "aborted") {
|
|
2217
|
-
const retryAttempt = this.session.retryAttempt;
|
|
2739
|
+
const retryAttempt = this.runtimeHost ? this.session.retryAttempt : 0;
|
|
2218
2740
|
errorMessage =
|
|
2219
2741
|
retryAttempt > 0
|
|
2220
2742
|
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
@@ -2250,9 +2772,9 @@ export class InteractiveMode {
|
|
|
2250
2772
|
let component = this.pendingTools.get(event.toolCallId);
|
|
2251
2773
|
if (!component) {
|
|
2252
2774
|
component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
|
|
2253
|
-
showImages: this.
|
|
2254
|
-
imageWidthCells: this.
|
|
2255
|
-
}, this.getRegisteredToolDefinition(event.toolName), this.ui, this.
|
|
2775
|
+
showImages: this.showImages,
|
|
2776
|
+
imageWidthCells: this.imageWidthCells,
|
|
2777
|
+
}, this.getRegisteredToolDefinition(event.toolName), this.ui, this.currentCwd);
|
|
2256
2778
|
component.setExpanded(this.toolOutputExpanded);
|
|
2257
2779
|
this.chatContainer.addChild(component);
|
|
2258
2780
|
this.pendingTools.set(event.toolCallId, component);
|
|
@@ -2279,7 +2801,7 @@ export class InteractiveMode {
|
|
|
2279
2801
|
break;
|
|
2280
2802
|
}
|
|
2281
2803
|
case "agent_end":
|
|
2282
|
-
if (this.
|
|
2804
|
+
if (this.showTerminalProgress) {
|
|
2283
2805
|
this.ui.terminal.setProgress(false);
|
|
2284
2806
|
}
|
|
2285
2807
|
if (this.loadingAnimation) {
|
|
@@ -2296,14 +2818,70 @@ export class InteractiveMode {
|
|
|
2296
2818
|
await this.checkShutdownRequested();
|
|
2297
2819
|
this.ui.requestRender();
|
|
2298
2820
|
break;
|
|
2821
|
+
case "turn_stats": {
|
|
2822
|
+
const { tps, output, input, cacheRead, cacheWrite, total, duration } = event;
|
|
2823
|
+
const parts = [];
|
|
2824
|
+
if (output > 0) {
|
|
2825
|
+
parts.push(`TPS ${tps.toFixed(1)} tok/s`);
|
|
2826
|
+
}
|
|
2827
|
+
parts.push(`out ${formatTokens(output)}`);
|
|
2828
|
+
parts.push(`in ${formatTokens(input)}`);
|
|
2829
|
+
if (cacheRead > 0 || cacheWrite > 0) {
|
|
2830
|
+
parts.push(`cache r/w ${formatTokens(cacheRead)}/${formatTokens(cacheWrite)}`);
|
|
2831
|
+
}
|
|
2832
|
+
parts.push(`total ${formatTokens(total)}`);
|
|
2833
|
+
parts.push(`${duration.toFixed(1)}s`);
|
|
2834
|
+
this.showStatus(parts.join(", "));
|
|
2835
|
+
break;
|
|
2836
|
+
}
|
|
2837
|
+
case "session_replaced": {
|
|
2838
|
+
// Server session was replaced (new/resume/fork) — reset UI and render new session
|
|
2839
|
+
this.chatContainer.clear();
|
|
2840
|
+
this.pendingMessagesContainer.clear();
|
|
2841
|
+
this.compactionQueuedMessages = [];
|
|
2842
|
+
this.pendingTools.clear();
|
|
2843
|
+
if (this.loadingAnimation) {
|
|
2844
|
+
this.loadingAnimation.stop();
|
|
2845
|
+
this.loadingAnimation = undefined;
|
|
2846
|
+
}
|
|
2847
|
+
this.statusContainer.clear();
|
|
2848
|
+
this.streamingComponent = undefined;
|
|
2849
|
+
this.streamingMessage = undefined;
|
|
2850
|
+
// Reset extension UI and reload client-side extensions
|
|
2851
|
+
this.resetExtensionUI();
|
|
2852
|
+
// Update footer with new session state
|
|
2853
|
+
const snapshot = this.proxy.getSnapshot();
|
|
2854
|
+
this.footer.setSnapshot(snapshot);
|
|
2855
|
+
this.footerDataProvider.setCwd(snapshot.cwd);
|
|
2856
|
+
this.footerDataProvider.setAvailableProviderCount(snapshot.availableProviderCount);
|
|
2857
|
+
// Re-bind client-side extensions for the new session
|
|
2858
|
+
void this.bindConnectModeExtensions();
|
|
2859
|
+
// Render messages from the new session
|
|
2860
|
+
this.renderInitialMessages();
|
|
2861
|
+
// Show confirmation
|
|
2862
|
+
const label = event.reason === "new"
|
|
2863
|
+
? "New session started"
|
|
2864
|
+
: event.reason === "resume"
|
|
2865
|
+
? "Session resumed"
|
|
2866
|
+
: "Forked to new session";
|
|
2867
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2868
|
+
this.chatContainer.addChild(new Text(theme.fg("accent", `✓ ${label}`), 1, 1));
|
|
2869
|
+
this.ui.requestRender();
|
|
2870
|
+
break;
|
|
2871
|
+
}
|
|
2299
2872
|
case "compaction_start": {
|
|
2300
|
-
if (this.
|
|
2873
|
+
if (this.showTerminalProgress) {
|
|
2301
2874
|
this.ui.terminal.setProgress(true);
|
|
2302
2875
|
}
|
|
2303
2876
|
// Keep editor active; submissions are queued during compaction.
|
|
2304
2877
|
this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
|
|
2305
2878
|
this.defaultEditor.onEscape = () => {
|
|
2306
|
-
this.
|
|
2879
|
+
if (this.proxy) {
|
|
2880
|
+
this.proxy.abort();
|
|
2881
|
+
}
|
|
2882
|
+
else {
|
|
2883
|
+
this.session.abortCompaction();
|
|
2884
|
+
}
|
|
2307
2885
|
};
|
|
2308
2886
|
this.statusContainer.clear();
|
|
2309
2887
|
const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
|
|
@@ -2316,7 +2894,7 @@ export class InteractiveMode {
|
|
|
2316
2894
|
break;
|
|
2317
2895
|
}
|
|
2318
2896
|
case "compaction_end": {
|
|
2319
|
-
if (this.
|
|
2897
|
+
if (this.showTerminalProgress) {
|
|
2320
2898
|
this.ui.terminal.setProgress(false);
|
|
2321
2899
|
}
|
|
2322
2900
|
if (this.autoCompactionEscapeHandler) {
|
|
@@ -2355,11 +2933,33 @@ export class InteractiveMode {
|
|
|
2355
2933
|
this.ui.requestRender();
|
|
2356
2934
|
break;
|
|
2357
2935
|
}
|
|
2936
|
+
case "state_update": {
|
|
2937
|
+
// Update footer snapshot for connect mode
|
|
2938
|
+
if (this.proxy && event.snapshot) {
|
|
2939
|
+
const currentSnapshot = this.proxy.getSnapshot();
|
|
2940
|
+
const merged = { ...currentSnapshot, ...event.snapshot };
|
|
2941
|
+
this.footer.setSnapshot(merged);
|
|
2942
|
+
if (event.snapshot.cwd !== undefined) {
|
|
2943
|
+
this.footerDataProvider.setCwd(event.snapshot.cwd);
|
|
2944
|
+
}
|
|
2945
|
+
if (event.snapshot.availableProviderCount !== undefined) {
|
|
2946
|
+
this.footerDataProvider.setAvailableProviderCount(event.snapshot.availableProviderCount);
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
this.footer.invalidate();
|
|
2950
|
+
this.ui.requestRender();
|
|
2951
|
+
break;
|
|
2952
|
+
}
|
|
2358
2953
|
case "auto_retry_start": {
|
|
2359
2954
|
// Set up escape to abort retry
|
|
2360
2955
|
this.retryEscapeHandler = this.defaultEditor.onEscape;
|
|
2361
2956
|
this.defaultEditor.onEscape = () => {
|
|
2362
|
-
this.
|
|
2957
|
+
if (this.proxy) {
|
|
2958
|
+
this.proxy.abort();
|
|
2959
|
+
}
|
|
2960
|
+
else {
|
|
2961
|
+
this.session.abortRetry();
|
|
2962
|
+
}
|
|
2363
2963
|
};
|
|
2364
2964
|
// Show retry indicator
|
|
2365
2965
|
this.statusContainer.clear();
|
|
@@ -2404,12 +3004,9 @@ export class InteractiveMode {
|
|
|
2404
3004
|
getUserMessageText(message) {
|
|
2405
3005
|
if (message.role !== "user")
|
|
2406
3006
|
return "";
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
const textBlocks = typeof content === "string"
|
|
2411
|
-
? [{ type: "text", text: content }]
|
|
2412
|
-
: content.filter((c) => c.type === "text");
|
|
3007
|
+
const textBlocks = typeof message.content === "string"
|
|
3008
|
+
? [{ type: "text", text: message.content }]
|
|
3009
|
+
: message.content.filter((c) => c.type === "text");
|
|
2413
3010
|
return textBlocks.map((c) => c.text).join("");
|
|
2414
3011
|
}
|
|
2415
3012
|
/**
|
|
@@ -2436,21 +3033,24 @@ export class InteractiveMode {
|
|
|
2436
3033
|
this.ui.requestRender();
|
|
2437
3034
|
}
|
|
2438
3035
|
addMessageToChat(message, options) {
|
|
2439
|
-
|
|
2440
|
-
switch (renderMessage.role) {
|
|
3036
|
+
switch (message.role) {
|
|
2441
3037
|
case "bashExecution": {
|
|
2442
|
-
const component = new BashExecutionComponent(
|
|
2443
|
-
if (
|
|
2444
|
-
component.appendOutput(
|
|
3038
|
+
const component = new BashExecutionComponent(message.command, this.ui, message.excludeFromContext);
|
|
3039
|
+
if (message.output) {
|
|
3040
|
+
component.appendOutput(message.output);
|
|
2445
3041
|
}
|
|
2446
|
-
component.setComplete(
|
|
3042
|
+
component.setComplete(message.exitCode, message.cancelled, message.truncated ? { truncated: true } : undefined, message.fullOutputPath);
|
|
2447
3043
|
this.chatContainer.addChild(component);
|
|
2448
3044
|
break;
|
|
2449
3045
|
}
|
|
2450
3046
|
case "custom": {
|
|
2451
|
-
if (
|
|
2452
|
-
|
|
2453
|
-
|
|
3047
|
+
if (message.display) {
|
|
3048
|
+
if (this.chatContainer.children.length > 0) {
|
|
3049
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3050
|
+
}
|
|
3051
|
+
const renderer = this.session?.extensionRunner.getMessageRenderer(message.customType) ??
|
|
3052
|
+
this._clientExtensionRunner?.getMessageRenderer(message.customType);
|
|
3053
|
+
const component = new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings());
|
|
2454
3054
|
component.setExpanded(this.toolOutputExpanded);
|
|
2455
3055
|
this.chatContainer.addChild(component);
|
|
2456
3056
|
}
|
|
@@ -2458,20 +3058,20 @@ export class InteractiveMode {
|
|
|
2458
3058
|
}
|
|
2459
3059
|
case "compactionSummary": {
|
|
2460
3060
|
this.chatContainer.addChild(new Spacer(1));
|
|
2461
|
-
const component = new CompactionSummaryMessageComponent(
|
|
3061
|
+
const component = new CompactionSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
|
|
2462
3062
|
component.setExpanded(this.toolOutputExpanded);
|
|
2463
3063
|
this.chatContainer.addChild(component);
|
|
2464
3064
|
break;
|
|
2465
3065
|
}
|
|
2466
3066
|
case "branchSummary": {
|
|
2467
3067
|
this.chatContainer.addChild(new Spacer(1));
|
|
2468
|
-
const component = new BranchSummaryMessageComponent(
|
|
3068
|
+
const component = new BranchSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
|
|
2469
3069
|
component.setExpanded(this.toolOutputExpanded);
|
|
2470
3070
|
this.chatContainer.addChild(component);
|
|
2471
3071
|
break;
|
|
2472
3072
|
}
|
|
2473
3073
|
case "user": {
|
|
2474
|
-
const textContent = this.getUserMessageText(
|
|
3074
|
+
const textContent = this.getUserMessageText(message);
|
|
2475
3075
|
if (textContent) {
|
|
2476
3076
|
if (this.chatContainer.children.length > 0) {
|
|
2477
3077
|
this.chatContainer.addChild(new Spacer(1));
|
|
@@ -2498,25 +3098,8 @@ export class InteractiveMode {
|
|
|
2498
3098
|
}
|
|
2499
3099
|
break;
|
|
2500
3100
|
}
|
|
2501
|
-
case "user-with-attachments": {
|
|
2502
|
-
const textContent = this.getTextContent(renderMessage.content);
|
|
2503
|
-
if (textContent) {
|
|
2504
|
-
if (this.chatContainer.children.length > 0) {
|
|
2505
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2506
|
-
}
|
|
2507
|
-
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());
|
|
2508
|
-
this.chatContainer.addChild(userComponent);
|
|
2509
|
-
if (options?.populateHistory) {
|
|
2510
|
-
this.editor.addToHistory?.(textContent);
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
break;
|
|
2514
|
-
}
|
|
2515
|
-
case "artifact": {
|
|
2516
|
-
break;
|
|
2517
|
-
}
|
|
2518
3101
|
case "assistant": {
|
|
2519
|
-
const assistantComponent = new AssistantMessageComponent(
|
|
3102
|
+
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
2520
3103
|
this.chatContainer.addChild(assistantComponent);
|
|
2521
3104
|
break;
|
|
2522
3105
|
}
|
|
@@ -2525,7 +3108,7 @@ export class InteractiveMode {
|
|
|
2525
3108
|
break;
|
|
2526
3109
|
}
|
|
2527
3110
|
default: {
|
|
2528
|
-
const _exhaustive =
|
|
3111
|
+
const _exhaustive = message;
|
|
2529
3112
|
}
|
|
2530
3113
|
}
|
|
2531
3114
|
}
|
|
@@ -2550,15 +3133,15 @@ export class InteractiveMode {
|
|
|
2550
3133
|
for (const content of message.content) {
|
|
2551
3134
|
if (content.type === "toolCall") {
|
|
2552
3135
|
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
|
|
2553
|
-
showImages: this.
|
|
2554
|
-
imageWidthCells: this.
|
|
2555
|
-
}, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager
|
|
3136
|
+
showImages: this.showImages,
|
|
3137
|
+
imageWidthCells: this.imageWidthCells,
|
|
3138
|
+
}, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager?.getCwd() ?? this.currentCwd);
|
|
2556
3139
|
component.setExpanded(this.toolOutputExpanded);
|
|
2557
3140
|
this.chatContainer.addChild(component);
|
|
2558
3141
|
if (message.stopReason === "aborted" || message.stopReason === "error") {
|
|
2559
3142
|
let errorMessage;
|
|
2560
3143
|
if (message.stopReason === "aborted") {
|
|
2561
|
-
const retryAttempt = this.session
|
|
3144
|
+
const retryAttempt = this.session?.retryAttempt ?? 0;
|
|
2562
3145
|
errorMessage =
|
|
2563
3146
|
retryAttempt > 0
|
|
2564
3147
|
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
@@ -2594,6 +3177,11 @@ export class InteractiveMode {
|
|
|
2594
3177
|
this.ui.requestRender();
|
|
2595
3178
|
}
|
|
2596
3179
|
renderInitialMessages() {
|
|
3180
|
+
if (this.proxy) {
|
|
3181
|
+
// Connect mode: render from proxy messages directly
|
|
3182
|
+
this.renderSessionContext({ messages: [...this.proxy.messages], thinkingLevel: "", model: null }, { updateFooter: true, populateHistory: true });
|
|
3183
|
+
return;
|
|
3184
|
+
}
|
|
2597
3185
|
// Get aligned messages and entries from session context
|
|
2598
3186
|
const context = this.sessionManager.buildSessionContext();
|
|
2599
3187
|
this.renderSessionContext(context, {
|
|
@@ -2609,6 +3197,10 @@ export class InteractiveMode {
|
|
|
2609
3197
|
}
|
|
2610
3198
|
}
|
|
2611
3199
|
async getUserInput() {
|
|
3200
|
+
const queuedInput = this.pendingUserInputs.shift();
|
|
3201
|
+
if (queuedInput !== undefined) {
|
|
3202
|
+
return queuedInput;
|
|
3203
|
+
}
|
|
2612
3204
|
return new Promise((resolve) => {
|
|
2613
3205
|
this.onInputCallback = (text) => {
|
|
2614
3206
|
this.onInputCallback = undefined;
|
|
@@ -2618,8 +3210,13 @@ export class InteractiveMode {
|
|
|
2618
3210
|
}
|
|
2619
3211
|
rebuildChatFromMessages() {
|
|
2620
3212
|
this.chatContainer.clear();
|
|
2621
|
-
|
|
2622
|
-
|
|
3213
|
+
if (this.proxy) {
|
|
3214
|
+
this.renderSessionContext({ messages: [...this.proxy.messages], thinkingLevel: "", model: null });
|
|
3215
|
+
}
|
|
3216
|
+
else {
|
|
3217
|
+
const context = this.sessionManager.buildSessionContext();
|
|
3218
|
+
this.renderSessionContext(context);
|
|
3219
|
+
}
|
|
2623
3220
|
}
|
|
2624
3221
|
// =========================================================================
|
|
2625
3222
|
// Key handlers
|
|
@@ -2644,16 +3241,44 @@ export class InteractiveMode {
|
|
|
2644
3241
|
* repaint the final frame while the process is exiting.
|
|
2645
3242
|
*/
|
|
2646
3243
|
isShuttingDown = false;
|
|
2647
|
-
async shutdown() {
|
|
3244
|
+
async shutdown(options) {
|
|
2648
3245
|
if (this.isShuttingDown)
|
|
2649
3246
|
return;
|
|
2650
3247
|
this.isShuttingDown = true;
|
|
2651
3248
|
this.unregisterSignalHandlers();
|
|
3249
|
+
if (options?.fromSignal) {
|
|
3250
|
+
// Signal-triggered shutdown (SIGTERM/SIGHUP). Emit extension cleanup
|
|
3251
|
+
// (session_shutdown) BEFORE touching the terminal. Extension teardown
|
|
3252
|
+
// such as removing sockets does not write to the tty, so it must not be
|
|
3253
|
+
// skipped if a later terminal-restore write fails on a dead or stalled
|
|
3254
|
+
// terminal. If the terminal is gone, the restore writes below emit EIO,
|
|
3255
|
+
// which the stdout/stderr error handler turns into emergencyTerminalExit;
|
|
3256
|
+
// the render loop is already idle, so this cannot hot-spin (see #4144).
|
|
3257
|
+
await this.runtimeHost.dispose();
|
|
3258
|
+
await this.ui.terminal.drainInput(1000);
|
|
3259
|
+
this.stop();
|
|
3260
|
+
process.exit(0);
|
|
3261
|
+
}
|
|
3262
|
+
// Interactive quit (Ctrl+D, Ctrl+C, /quit, extension shutdown()). Stop the
|
|
3263
|
+
// TUI before emitting shutdown events so extension UI cleanup cannot repaint
|
|
3264
|
+
// the final frame while the process is exiting.
|
|
2652
3265
|
// Drain any in-flight Kitty key release events before stopping.
|
|
2653
3266
|
// This prevents escape sequences from leaking to the parent shell over slow SSH.
|
|
2654
3267
|
await this.ui.terminal.drainInput(1000);
|
|
2655
3268
|
this.stop();
|
|
2656
|
-
|
|
3269
|
+
if (this.runtimeHost) {
|
|
3270
|
+
await this.runtimeHost.dispose();
|
|
3271
|
+
const sessionManager = this.sessionManager;
|
|
3272
|
+
if (sessionManager) {
|
|
3273
|
+
const resumeCommand = formatResumeCommand(sessionManager);
|
|
3274
|
+
if (resumeCommand) {
|
|
3275
|
+
process.stdout.write(`${chalk.dim("To resume this session:")} ${resumeCommand}\n`);
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
else if (this.proxy && "dispose" in this.proxy) {
|
|
3280
|
+
this.proxy.dispose();
|
|
3281
|
+
}
|
|
2657
3282
|
process.exit(0);
|
|
2658
3283
|
}
|
|
2659
3284
|
emergencyTerminalExit() {
|
|
@@ -2712,11 +3337,12 @@ export class InteractiveMode {
|
|
|
2712
3337
|
}
|
|
2713
3338
|
for (const signal of signals) {
|
|
2714
3339
|
const handler = () => {
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
3340
|
+
// SIGHUP no longer hard-exits: graceful shutdown emits session_shutdown
|
|
3341
|
+
// first, then attempts terminal restore. A genuinely dead terminal
|
|
3342
|
+
// surfaces as an EIO on the restore writes, which the stdout/stderr
|
|
3343
|
+
// error handler converts into emergencyTerminalExit (see #4144, #5080).
|
|
2718
3344
|
killTrackedDetachedChildren();
|
|
2719
|
-
void this.shutdown();
|
|
3345
|
+
void this.shutdown({ fromSignal: true });
|
|
2720
3346
|
};
|
|
2721
3347
|
process.prependListener(signal, handler);
|
|
2722
3348
|
this.signalCleanupHandlers.push(() => process.off(signal, handler));
|
|
@@ -2797,7 +3423,12 @@ export class InteractiveMode {
|
|
|
2797
3423
|
if (this.session.isStreaming) {
|
|
2798
3424
|
this.editor.addToHistory?.(text);
|
|
2799
3425
|
this.editor.setText("");
|
|
2800
|
-
|
|
3426
|
+
if (this.proxy) {
|
|
3427
|
+
this.proxy.followUp(text);
|
|
3428
|
+
}
|
|
3429
|
+
else {
|
|
3430
|
+
await this.session.prompt(text, { streamingBehavior: "followUp" });
|
|
3431
|
+
}
|
|
2801
3432
|
this.updatePendingMessagesDisplay();
|
|
2802
3433
|
this.ui.requestRender();
|
|
2803
3434
|
}
|
|
@@ -2821,12 +3452,18 @@ export class InteractiveMode {
|
|
|
2821
3452
|
this.editor.borderColor = theme.getBashModeBorderColor();
|
|
2822
3453
|
}
|
|
2823
3454
|
else {
|
|
2824
|
-
const level = this.
|
|
3455
|
+
const level = this.proxy ? this.proxy.thinkingLevel : this.session?.thinkingLevel || "off";
|
|
2825
3456
|
this.editor.borderColor = theme.getThinkingBorderColor(level);
|
|
2826
3457
|
}
|
|
2827
3458
|
this.ui.requestRender();
|
|
2828
3459
|
}
|
|
2829
3460
|
cycleThinkingLevel() {
|
|
3461
|
+
// Connect mode: no local session — route through the proxy. The proxy is
|
|
3462
|
+
// fire-and-forget; the resulting state change arrives via SSE.
|
|
3463
|
+
if (!this.session) {
|
|
3464
|
+
this.proxy.cycleThinkingLevel(1);
|
|
3465
|
+
return;
|
|
3466
|
+
}
|
|
2830
3467
|
const newLevel = this.session.cycleThinkingLevel();
|
|
2831
3468
|
if (newLevel === undefined) {
|
|
2832
3469
|
this.showStatus("Current model does not support thinking");
|
|
@@ -2839,9 +3476,19 @@ export class InteractiveMode {
|
|
|
2839
3476
|
}
|
|
2840
3477
|
async cycleModel(direction) {
|
|
2841
3478
|
try {
|
|
2842
|
-
|
|
3479
|
+
let result;
|
|
3480
|
+
if (this.session) {
|
|
3481
|
+
result = await this.session.cycleModel(direction);
|
|
3482
|
+
}
|
|
3483
|
+
else {
|
|
3484
|
+
// Connect mode: no local session — route through the proxy. The
|
|
3485
|
+
// proxy is fire-and-forget and returns void; the actual model
|
|
3486
|
+
// change is reflected via SSE.
|
|
3487
|
+
this.proxy.cycleModel(direction === "forward" ? 1 : -1);
|
|
3488
|
+
}
|
|
2843
3489
|
if (result === undefined) {
|
|
2844
|
-
const
|
|
3490
|
+
const scopedCount = this.session?.scopedModels.length ?? 0;
|
|
3491
|
+
const msg = scopedCount > 0 ? "Only one model in scope" : "Only one model available";
|
|
2845
3492
|
this.showStatus(msg);
|
|
2846
3493
|
}
|
|
2847
3494
|
else {
|
|
@@ -2886,7 +3533,7 @@ export class InteractiveMode {
|
|
|
2886
3533
|
}
|
|
2887
3534
|
this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
|
|
2888
3535
|
}
|
|
2889
|
-
openExternalEditor() {
|
|
3536
|
+
async openExternalEditor() {
|
|
2890
3537
|
// Determine editor (respect $VISUAL, then $EDITOR)
|
|
2891
3538
|
const editorCmd = process.env.VISUAL || process.env.EDITOR;
|
|
2892
3539
|
if (!editorCmd) {
|
|
@@ -2902,13 +3549,20 @@ export class InteractiveMode {
|
|
|
2902
3549
|
this.ui.stop();
|
|
2903
3550
|
// Split by space to support editor arguments (e.g., "code --wait")
|
|
2904
3551
|
const [editor, ...editorArgs] = editorCmd.split(" ");
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
3552
|
+
process.stdout.write(`Launching external editor: ${editorCmd}\nPi will resume when the editor exits.\n`);
|
|
3553
|
+
// Do not use spawnSync here. On Windows, synchronous child_process calls can keep
|
|
3554
|
+
// Node/libuv's console input read active after ui.stop() pauses stdin, racing
|
|
3555
|
+
// vim/nvim for the console input buffer until Ctrl+C cancels the pending read.
|
|
3556
|
+
const status = await new Promise((resolve) => {
|
|
3557
|
+
const child = spawn(editor, [...editorArgs, tmpFile], {
|
|
3558
|
+
stdio: "inherit",
|
|
3559
|
+
shell: process.platform === "win32",
|
|
3560
|
+
});
|
|
3561
|
+
child.on("error", () => resolve(null));
|
|
3562
|
+
child.on("close", (code) => resolve(code));
|
|
2909
3563
|
});
|
|
2910
3564
|
// On successful exit (status 0), replace editor content
|
|
2911
|
-
if (
|
|
3565
|
+
if (status === 0) {
|
|
2912
3566
|
const newContent = fs.readFileSync(tmpFile, "utf-8").replace(/\n$/, "");
|
|
2913
3567
|
this.editor.setText(newContent);
|
|
2914
3568
|
}
|
|
@@ -2938,6 +3592,7 @@ export class InteractiveMode {
|
|
|
2938
3592
|
showError(errorMessage) {
|
|
2939
3593
|
this.chatContainer.addChild(new Spacer(1));
|
|
2940
3594
|
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), 1, 0));
|
|
3595
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2941
3596
|
this.ui.requestRender();
|
|
2942
3597
|
}
|
|
2943
3598
|
showWarning(warningMessage) {
|
|
@@ -2945,17 +3600,26 @@ export class InteractiveMode {
|
|
|
2945
3600
|
this.chatContainer.addChild(new Text(theme.fg("warning", `Warning: ${warningMessage}`), 1, 0));
|
|
2946
3601
|
this.ui.requestRender();
|
|
2947
3602
|
}
|
|
2948
|
-
showNewVersionNotification(
|
|
3603
|
+
showNewVersionNotification(release) {
|
|
2949
3604
|
const action = theme.fg("accent", `${APP_NAME} update`);
|
|
2950
|
-
const updateInstruction = theme.fg("muted", `New version ${
|
|
2951
|
-
const changelogUrl = "https://
|
|
3605
|
+
const updateInstruction = theme.fg("muted", `New version ${release.version} is available. Run `) + action;
|
|
3606
|
+
const changelogUrl = "https://pi.dev/changelog";
|
|
2952
3607
|
const changelogLink = getCapabilities().hyperlinks
|
|
2953
3608
|
? hyperlink(theme.fg("accent", "open changelog"), changelogUrl)
|
|
2954
3609
|
: theme.fg("accent", changelogUrl);
|
|
2955
3610
|
const changelogLine = theme.fg("muted", "Changelog: ") + changelogLink;
|
|
3611
|
+
const note = release.note?.trim();
|
|
2956
3612
|
this.chatContainer.addChild(new Spacer(1));
|
|
2957
3613
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2958
|
-
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}
|
|
3614
|
+
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}`, 1, 0));
|
|
3615
|
+
if (note) {
|
|
3616
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3617
|
+
this.chatContainer.addChild(new Markdown(note, 1, 0, this.getMarkdownThemeWithSettings(), {
|
|
3618
|
+
color: (text) => theme.fg("muted", text),
|
|
3619
|
+
}));
|
|
3620
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3621
|
+
}
|
|
3622
|
+
this.chatContainer.addChild(new Text(changelogLine, 1, 0));
|
|
2959
3623
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2960
3624
|
this.ui.requestRender();
|
|
2961
3625
|
}
|
|
@@ -2974,13 +3638,15 @@ export class InteractiveMode {
|
|
|
2974
3638
|
* Combines session queue and compaction queue.
|
|
2975
3639
|
*/
|
|
2976
3640
|
getAllQueuedMessages() {
|
|
3641
|
+
const steeringSource = this.proxy ? this.proxy.steeringMessages : this.session.getSteeringMessages();
|
|
3642
|
+
const followUpSource = this.proxy ? this.proxy.followUpMessages : this.session.getFollowUpMessages();
|
|
2977
3643
|
return {
|
|
2978
3644
|
steering: [
|
|
2979
|
-
...
|
|
3645
|
+
...steeringSource,
|
|
2980
3646
|
...this.compactionQueuedMessages.filter((msg) => msg.mode === "steer").map((msg) => msg.text),
|
|
2981
3647
|
],
|
|
2982
3648
|
followUp: [
|
|
2983
|
-
...
|
|
3649
|
+
...followUpSource,
|
|
2984
3650
|
...this.compactionQueuedMessages.filter((msg) => msg.mode === "followUp").map((msg) => msg.text),
|
|
2985
3651
|
],
|
|
2986
3652
|
};
|
|
@@ -2990,7 +3656,9 @@ export class InteractiveMode {
|
|
|
2990
3656
|
* Clears both session queue and compaction queue.
|
|
2991
3657
|
*/
|
|
2992
3658
|
clearAllQueues() {
|
|
2993
|
-
const
|
|
3659
|
+
const sessionQueued = this.proxy
|
|
3660
|
+
? { steering: [...this.proxy.steeringMessages], followUp: [...this.proxy.followUpMessages] }
|
|
3661
|
+
: this.session.clearQueue();
|
|
2994
3662
|
const compactionSteering = this.compactionQueuedMessages
|
|
2995
3663
|
.filter((msg) => msg.mode === "steer")
|
|
2996
3664
|
.map((msg) => msg.text);
|
|
@@ -2999,8 +3667,8 @@ export class InteractiveMode {
|
|
|
2999
3667
|
.map((msg) => msg.text);
|
|
3000
3668
|
this.compactionQueuedMessages = [];
|
|
3001
3669
|
return {
|
|
3002
|
-
steering: [...steering, ...compactionSteering],
|
|
3003
|
-
followUp: [...followUp, ...compactionFollowUp],
|
|
3670
|
+
steering: [...sessionQueued.steering, ...compactionSteering],
|
|
3671
|
+
followUp: [...sessionQueued.followUp, ...compactionFollowUp],
|
|
3004
3672
|
};
|
|
3005
3673
|
}
|
|
3006
3674
|
updatePendingMessagesDisplay() {
|
|
@@ -3027,7 +3695,7 @@ export class InteractiveMode {
|
|
|
3027
3695
|
if (allQueued.length === 0) {
|
|
3028
3696
|
this.updatePendingMessagesDisplay();
|
|
3029
3697
|
if (options?.abort) {
|
|
3030
|
-
this.
|
|
3698
|
+
(this.proxy ?? this.session).abort();
|
|
3031
3699
|
}
|
|
3032
3700
|
return 0;
|
|
3033
3701
|
}
|
|
@@ -3037,7 +3705,7 @@ export class InteractiveMode {
|
|
|
3037
3705
|
this.editor.setText(combinedText);
|
|
3038
3706
|
this.updatePendingMessagesDisplay();
|
|
3039
3707
|
if (options?.abort) {
|
|
3040
|
-
this.
|
|
3708
|
+
(this.proxy ?? this.session).abort();
|
|
3041
3709
|
}
|
|
3042
3710
|
return allQueued.length;
|
|
3043
3711
|
}
|
|
@@ -3051,6 +3719,8 @@ export class InteractiveMode {
|
|
|
3051
3719
|
isExtensionCommand(text) {
|
|
3052
3720
|
if (!text.startsWith("/"))
|
|
3053
3721
|
return false;
|
|
3722
|
+
if (!this.session)
|
|
3723
|
+
return false;
|
|
3054
3724
|
const extensionRunner = this.session.extensionRunner;
|
|
3055
3725
|
const spaceIndex = text.indexOf(" ");
|
|
3056
3726
|
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
@@ -3064,11 +3734,31 @@ export class InteractiveMode {
|
|
|
3064
3734
|
this.compactionQueuedMessages = [];
|
|
3065
3735
|
this.updatePendingMessagesDisplay();
|
|
3066
3736
|
const restoreQueue = (error) => {
|
|
3067
|
-
this.session
|
|
3737
|
+
if (this.session) {
|
|
3738
|
+
this.session.clearQueue();
|
|
3739
|
+
}
|
|
3068
3740
|
this.compactionQueuedMessages = queuedMessages;
|
|
3069
3741
|
this.updatePendingMessagesDisplay();
|
|
3070
3742
|
this.showError(`Failed to send queued message${queuedMessages.length > 1 ? "s" : ""}: ${error instanceof Error ? error.message : String(error)}`);
|
|
3071
3743
|
};
|
|
3744
|
+
// Connect mode: send all queued messages through the proxy
|
|
3745
|
+
if (this.proxy) {
|
|
3746
|
+
try {
|
|
3747
|
+
for (const message of queuedMessages) {
|
|
3748
|
+
if (message.mode === "followUp") {
|
|
3749
|
+
this.proxy.followUp(message.text);
|
|
3750
|
+
}
|
|
3751
|
+
else {
|
|
3752
|
+
this.proxy.steer(message.text);
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
this.updatePendingMessagesDisplay();
|
|
3756
|
+
}
|
|
3757
|
+
catch (error) {
|
|
3758
|
+
restoreQueue(error);
|
|
3759
|
+
}
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3072
3762
|
try {
|
|
3073
3763
|
if (options?.willRetry) {
|
|
3074
3764
|
// When retry is pending, queue messages for the retry turn
|
|
@@ -3164,6 +3854,7 @@ export class InteractiveMode {
|
|
|
3164
3854
|
steeringMode: this.session.steeringMode,
|
|
3165
3855
|
followUpMode: this.session.followUpMode,
|
|
3166
3856
|
transport: this.settingsManager.getTransport(),
|
|
3857
|
+
httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
|
|
3167
3858
|
thinkingLevel: this.session.thinkingLevel,
|
|
3168
3859
|
availableThinkingLevels: this.session.getAvailableThinkingLevels(),
|
|
3169
3860
|
currentTheme: this.settingsManager.getTheme() || "dark",
|
|
@@ -3221,6 +3912,11 @@ export class InteractiveMode {
|
|
|
3221
3912
|
this.settingsManager.setTransport(transport);
|
|
3222
3913
|
this.session.agent.transport = transport;
|
|
3223
3914
|
},
|
|
3915
|
+
onHttpIdleTimeoutMsChange: (timeoutMs) => {
|
|
3916
|
+
this.settingsManager.setHttpIdleTimeoutMs(timeoutMs);
|
|
3917
|
+
configureHttpDispatcher(timeoutMs);
|
|
3918
|
+
this.showStatus(`HTTP idle timeout: ${formatHttpIdleTimeoutMs(timeoutMs)}`);
|
|
3919
|
+
},
|
|
3224
3920
|
onThinkingLevelChange: (level) => {
|
|
3225
3921
|
this.session.setThinkingLevel(level);
|
|
3226
3922
|
this.footer.invalidate();
|
|
@@ -3465,6 +4161,10 @@ export class InteractiveMode {
|
|
|
3465
4161
|
});
|
|
3466
4162
|
}
|
|
3467
4163
|
showUserMessageSelector() {
|
|
4164
|
+
if (!this.session) {
|
|
4165
|
+
this.showStatus("Fork selector not available in connect mode");
|
|
4166
|
+
return;
|
|
4167
|
+
}
|
|
3468
4168
|
const userMessages = this.session.getUserMessagesForForking();
|
|
3469
4169
|
if (userMessages.length === 0) {
|
|
3470
4170
|
this.showStatus("No messages to fork from");
|
|
@@ -3517,6 +4217,10 @@ export class InteractiveMode {
|
|
|
3517
4217
|
}
|
|
3518
4218
|
}
|
|
3519
4219
|
showTreeSelector(initialSelectedId) {
|
|
4220
|
+
if (!this.sessionManager) {
|
|
4221
|
+
this.showStatus("Tree selector not available in connect mode");
|
|
4222
|
+
return;
|
|
4223
|
+
}
|
|
3520
4224
|
const tree = this.sessionManager.getTree();
|
|
3521
4225
|
const realLeafId = this.sessionManager.getLeafId();
|
|
3522
4226
|
const initialFilterMode = this.settingsManager.getTreeFilterMode();
|
|
@@ -3620,7 +4324,9 @@ export class InteractiveMode {
|
|
|
3620
4324
|
}
|
|
3621
4325
|
showSessionSelector() {
|
|
3622
4326
|
this.showSelector((done) => {
|
|
3623
|
-
const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress),
|
|
4327
|
+
const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress), (onProgress) => this.sessionManager.usesDefaultSessionDir()
|
|
4328
|
+
? SessionManager.listAll(onProgress)
|
|
4329
|
+
: SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress), async (sessionPath) => {
|
|
3624
4330
|
done();
|
|
3625
4331
|
await this.handleResumeSession(sessionPath);
|
|
3626
4332
|
}, () => {
|
|
@@ -3642,6 +4348,343 @@ export class InteractiveMode {
|
|
|
3642
4348
|
return { component: selector, focus: selector };
|
|
3643
4349
|
});
|
|
3644
4350
|
}
|
|
4351
|
+
/** Show settings selector in connect mode using remote settings from snapshot */
|
|
4352
|
+
showConnectModeSettingsSelector() {
|
|
4353
|
+
const snapshot = this.proxy.getSnapshot();
|
|
4354
|
+
const rs = snapshot.remoteSettings;
|
|
4355
|
+
this.showSelector((done) => {
|
|
4356
|
+
const selector = new SettingsSelectorComponent({
|
|
4357
|
+
autoCompact: rs.autoCompact,
|
|
4358
|
+
showImages: rs.showImages,
|
|
4359
|
+
imageWidthCells: rs.imageWidthCells,
|
|
4360
|
+
autoResizeImages: rs.autoResizeImages,
|
|
4361
|
+
blockImages: rs.blockImages,
|
|
4362
|
+
enableSkillCommands: rs.enableSkillCommands,
|
|
4363
|
+
steeringMode: rs.steeringMode,
|
|
4364
|
+
followUpMode: rs.followUpMode,
|
|
4365
|
+
transport: rs.transport,
|
|
4366
|
+
httpIdleTimeoutMs: rs.httpIdleTimeoutMs,
|
|
4367
|
+
thinkingLevel: rs.thinkingLevel,
|
|
4368
|
+
availableThinkingLevels: [...rs.availableThinkingLevels],
|
|
4369
|
+
currentTheme: rs.currentTheme,
|
|
4370
|
+
availableThemes: [...rs.availableThemes],
|
|
4371
|
+
hideThinkingBlock: rs.hideThinkingBlock,
|
|
4372
|
+
collapseChangelog: rs.collapseChangelog,
|
|
4373
|
+
enableInstallTelemetry: rs.enableInstallTelemetry,
|
|
4374
|
+
doubleEscapeAction: rs.doubleEscapeAction,
|
|
4375
|
+
treeFilterMode: rs.treeFilterMode,
|
|
4376
|
+
showHardwareCursor: rs.showHardwareCursor,
|
|
4377
|
+
editorPaddingX: rs.editorPaddingX,
|
|
4378
|
+
autocompleteMaxVisible: rs.autocompleteMaxVisible,
|
|
4379
|
+
quietStartup: rs.quietStartup,
|
|
4380
|
+
clearOnShrink: rs.clearOnShrink,
|
|
4381
|
+
showTerminalProgress: rs.showTerminalProgress,
|
|
4382
|
+
warnings: rs.warnings,
|
|
4383
|
+
}, {
|
|
4384
|
+
onAutoCompactChange: (enabled) => {
|
|
4385
|
+
this.proxy.setAutoCompactEnabled(enabled);
|
|
4386
|
+
this.footer.setAutoCompactEnabled(enabled);
|
|
4387
|
+
},
|
|
4388
|
+
onShowImagesChange: (v) => {
|
|
4389
|
+
this.proxy.updateSettings({ showImages: v });
|
|
4390
|
+
},
|
|
4391
|
+
onImageWidthCellsChange: (v) => {
|
|
4392
|
+
this.proxy.updateSettings({ imageWidthCells: v });
|
|
4393
|
+
},
|
|
4394
|
+
onAutoResizeImagesChange: (v) => {
|
|
4395
|
+
this.proxy.updateSettings({ autoResizeImages: v });
|
|
4396
|
+
},
|
|
4397
|
+
onBlockImagesChange: (v) => {
|
|
4398
|
+
this.proxy.updateSettings({ blockImages: v });
|
|
4399
|
+
},
|
|
4400
|
+
onEnableSkillCommandsChange: (enabled) => {
|
|
4401
|
+
this.proxy.updateSettings({ enableSkillCommands: enabled });
|
|
4402
|
+
},
|
|
4403
|
+
onSteeringModeChange: (mode) => {
|
|
4404
|
+
this.proxy.setSteeringMode(mode);
|
|
4405
|
+
},
|
|
4406
|
+
onFollowUpModeChange: (mode) => {
|
|
4407
|
+
this.proxy.setFollowUpMode(mode);
|
|
4408
|
+
},
|
|
4409
|
+
onTransportChange: (v) => {
|
|
4410
|
+
this.proxy.updateSettings({ transport: v });
|
|
4411
|
+
},
|
|
4412
|
+
onHttpIdleTimeoutMsChange: (v) => {
|
|
4413
|
+
this.proxy.updateSettings({ httpIdleTimeoutMs: v });
|
|
4414
|
+
},
|
|
4415
|
+
onThinkingLevelChange: (level) => {
|
|
4416
|
+
this.proxy.setThinkingLevel(level);
|
|
4417
|
+
this.footer.invalidate();
|
|
4418
|
+
this.updateEditorBorderColor();
|
|
4419
|
+
},
|
|
4420
|
+
onThemeChange: (v) => {
|
|
4421
|
+
this.proxy.updateSettings({ theme: v });
|
|
4422
|
+
},
|
|
4423
|
+
onHideThinkingBlockChange: (v) => {
|
|
4424
|
+
this.proxy.updateSettings({ hideThinkingBlock: v });
|
|
4425
|
+
},
|
|
4426
|
+
onCollapseChangelogChange: (v) => {
|
|
4427
|
+
this.proxy.updateSettings({ collapseChangelog: v });
|
|
4428
|
+
},
|
|
4429
|
+
onEnableInstallTelemetryChange: (v) => {
|
|
4430
|
+
this.proxy.updateSettings({ enableInstallTelemetry: v });
|
|
4431
|
+
},
|
|
4432
|
+
onDoubleEscapeActionChange: (v) => {
|
|
4433
|
+
this.proxy.updateSettings({ doubleEscapeAction: v });
|
|
4434
|
+
},
|
|
4435
|
+
onTreeFilterModeChange: (v) => {
|
|
4436
|
+
this.proxy.updateSettings({ treeFilterMode: v });
|
|
4437
|
+
},
|
|
4438
|
+
onShowHardwareCursorChange: (v) => {
|
|
4439
|
+
this.proxy.updateSettings({ showHardwareCursor: v });
|
|
4440
|
+
},
|
|
4441
|
+
onEditorPaddingXChange: (v) => {
|
|
4442
|
+
this.proxy.updateSettings({ editorPaddingX: v });
|
|
4443
|
+
},
|
|
4444
|
+
onAutocompleteMaxVisibleChange: (v) => {
|
|
4445
|
+
this.proxy.updateSettings({ autocompleteMaxVisible: v });
|
|
4446
|
+
},
|
|
4447
|
+
onQuietStartupChange: (v) => {
|
|
4448
|
+
this.proxy.updateSettings({ quietStartup: v });
|
|
4449
|
+
},
|
|
4450
|
+
onClearOnShrinkChange: (v) => {
|
|
4451
|
+
this.proxy.updateSettings({ clearOnShrink: v });
|
|
4452
|
+
},
|
|
4453
|
+
onShowTerminalProgressChange: (v) => {
|
|
4454
|
+
this.proxy.updateSettings({ showTerminalProgress: v });
|
|
4455
|
+
},
|
|
4456
|
+
onWarningsChange: (v) => {
|
|
4457
|
+
this.proxy.updateSettings({ warnings: v });
|
|
4458
|
+
},
|
|
4459
|
+
onCancel: done,
|
|
4460
|
+
});
|
|
4461
|
+
return { component: selector, focus: selector };
|
|
4462
|
+
});
|
|
4463
|
+
}
|
|
4464
|
+
/** Show model selector in connect mode */
|
|
4465
|
+
async showConnectModeModelSelector() {
|
|
4466
|
+
try {
|
|
4467
|
+
const models = await this.proxy.fetchModels();
|
|
4468
|
+
if (models.length === 0) {
|
|
4469
|
+
this.showStatus("No models available");
|
|
4470
|
+
return;
|
|
4471
|
+
}
|
|
4472
|
+
const currentModelId = this.proxy.model;
|
|
4473
|
+
const items = models.map((m) => ({
|
|
4474
|
+
value: `${m.provider}/${m.id}`,
|
|
4475
|
+
label: `${m.id} [${m.provider}]`,
|
|
4476
|
+
description: m.name,
|
|
4477
|
+
}));
|
|
4478
|
+
this.showSelector((done) => {
|
|
4479
|
+
const selectList = new SelectList(items, Math.min(items.length, Math.floor(this.ui.terminal.rows / 2)), getSelectListTheme(), { minPrimaryColumnWidth: 20, maxPrimaryColumnWidth: 48 });
|
|
4480
|
+
// Preselect current model
|
|
4481
|
+
const currentIndex = items.findIndex((item) => item.value === currentModelId || item.label === currentModelId);
|
|
4482
|
+
if (currentIndex !== -1) {
|
|
4483
|
+
selectList.setSelectedIndex(currentIndex);
|
|
4484
|
+
}
|
|
4485
|
+
selectList.onSelect = (item) => {
|
|
4486
|
+
const modelId = item.value;
|
|
4487
|
+
this.proxy.setModel(modelId);
|
|
4488
|
+
done();
|
|
4489
|
+
this.showStatus(`Model: ${modelId}`);
|
|
4490
|
+
};
|
|
4491
|
+
selectList.onCancel = () => {
|
|
4492
|
+
done();
|
|
4493
|
+
this.ui.requestRender();
|
|
4494
|
+
};
|
|
4495
|
+
const container = new Container();
|
|
4496
|
+
container.addChild(new DynamicBorder());
|
|
4497
|
+
container.addChild(new Spacer(1));
|
|
4498
|
+
container.addChild(new Text(theme.fg("muted", "Select a model"), 0, 0));
|
|
4499
|
+
container.addChild(new Spacer(1));
|
|
4500
|
+
container.addChild(selectList);
|
|
4501
|
+
container.addChild(new DynamicBorder());
|
|
4502
|
+
return { component: container, focus: selectList };
|
|
4503
|
+
});
|
|
4504
|
+
}
|
|
4505
|
+
catch (e) {
|
|
4506
|
+
this.showError(`Failed to fetch models: ${e instanceof Error ? e.message : String(e)}`);
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
/** Show scoped models selector in connect mode */
|
|
4510
|
+
async showConnectModeScopedModelsSelector() {
|
|
4511
|
+
try {
|
|
4512
|
+
const items = await this.proxy.fetchModels();
|
|
4513
|
+
if (items.length === 0) {
|
|
4514
|
+
this.showStatus("No models available");
|
|
4515
|
+
return;
|
|
4516
|
+
}
|
|
4517
|
+
const snapshot = this.proxy.getSnapshot();
|
|
4518
|
+
const enabledModelIds = snapshot.scopedModelIds;
|
|
4519
|
+
// `ModelItemData` is now field-compatible with `Model<any>` (see
|
|
4520
|
+
// core/agent-session-proxy.ts), so the only gap is the strict
|
|
4521
|
+
// structural mismatch on `compat?` (optional) and `headers?` (optional)
|
|
4522
|
+
// and the union-typed `api` field. A two-step cast skips those
|
|
4523
|
+
// without touching upstream's strict `ModelsConfig.allModels` type.
|
|
4524
|
+
// TODO: upstream `ScopedModelsSelectorComponent` should accept
|
|
4525
|
+
// `ModelItemData` (or a `ModelDisplay` alias) directly.
|
|
4526
|
+
const allModels = items;
|
|
4527
|
+
this.showSelector((done) => {
|
|
4528
|
+
const selector = new ScopedModelsSelectorComponent({ allModels, enabledModelIds }, {
|
|
4529
|
+
onChange: (ids) => {
|
|
4530
|
+
this.proxy.setScopedModels(ids);
|
|
4531
|
+
},
|
|
4532
|
+
onPersist: (ids) => {
|
|
4533
|
+
// Mirror upstream: null or full coverage = clear filter.
|
|
4534
|
+
const patterns = ids === null || ids.length === allModels.length ? undefined : ids;
|
|
4535
|
+
this.proxy.setEnabledModels(patterns ? [...patterns] : undefined);
|
|
4536
|
+
this.showStatus("Model selection saved to settings");
|
|
4537
|
+
},
|
|
4538
|
+
onCancel: () => {
|
|
4539
|
+
done();
|
|
4540
|
+
this.ui.requestRender();
|
|
4541
|
+
},
|
|
4542
|
+
});
|
|
4543
|
+
return { component: selector, focus: selector };
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
catch (e) {
|
|
4547
|
+
this.showError(`Failed to fetch models: ${e instanceof Error ? e.message : String(e)}`);
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
/** Show fork selector in connect mode */
|
|
4551
|
+
async showConnectModeForkSelector() {
|
|
4552
|
+
try {
|
|
4553
|
+
const messages = await this.proxy.fetchUserMessages();
|
|
4554
|
+
if (messages.length === 0) {
|
|
4555
|
+
this.showStatus("No messages to fork from");
|
|
4556
|
+
return;
|
|
4557
|
+
}
|
|
4558
|
+
this.showSelector((done) => {
|
|
4559
|
+
const selector = new UserMessageSelectorComponent(messages.map((m) => ({ id: m.id, text: m.text })), async (entryId) => {
|
|
4560
|
+
try {
|
|
4561
|
+
await this.proxy.fork(entryId);
|
|
4562
|
+
done();
|
|
4563
|
+
this.showStatus("Forked to new session");
|
|
4564
|
+
}
|
|
4565
|
+
catch (error) {
|
|
4566
|
+
done();
|
|
4567
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
4568
|
+
}
|
|
4569
|
+
}, () => {
|
|
4570
|
+
done();
|
|
4571
|
+
this.ui.requestRender();
|
|
4572
|
+
});
|
|
4573
|
+
return { component: selector, focus: selector.getMessageList() };
|
|
4574
|
+
});
|
|
4575
|
+
}
|
|
4576
|
+
catch (e) {
|
|
4577
|
+
this.showError(`Failed to fetch messages: ${e instanceof Error ? e.message : String(e)}`);
|
|
4578
|
+
}
|
|
4579
|
+
}
|
|
4580
|
+
/** Show tree selector in connect mode */
|
|
4581
|
+
async showConnectModeTreeSelector() {
|
|
4582
|
+
try {
|
|
4583
|
+
const treeData = await this.proxy.fetchTree();
|
|
4584
|
+
if (treeData.length === 0) {
|
|
4585
|
+
this.showStatus("No entries in session");
|
|
4586
|
+
return;
|
|
4587
|
+
}
|
|
4588
|
+
const nodes = this._convertTreeDataToNodes(treeData);
|
|
4589
|
+
this.showSelector((done) => {
|
|
4590
|
+
const selector = new TreeSelectorComponent(nodes, null, this.ui.terminal.rows, async (entryId) => {
|
|
4591
|
+
try {
|
|
4592
|
+
await this.proxy.fork(entryId);
|
|
4593
|
+
done();
|
|
4594
|
+
this.showStatus("Navigated to branch");
|
|
4595
|
+
}
|
|
4596
|
+
catch (error) {
|
|
4597
|
+
done();
|
|
4598
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
4599
|
+
}
|
|
4600
|
+
}, () => {
|
|
4601
|
+
done();
|
|
4602
|
+
this.ui.requestRender();
|
|
4603
|
+
}, (entryId, label) => {
|
|
4604
|
+
this.proxy.setLabel(entryId, label);
|
|
4605
|
+
});
|
|
4606
|
+
return { component: selector, focus: selector };
|
|
4607
|
+
});
|
|
4608
|
+
}
|
|
4609
|
+
catch (e) {
|
|
4610
|
+
this.showError(`Failed to fetch tree: ${e instanceof Error ? e.message : String(e)}`);
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
/** Convert wire-format TreeNodeData to SessionTreeNode for TreeSelectorComponent */
|
|
4614
|
+
_convertTreeDataToNodes(data) {
|
|
4615
|
+
return data.map((node) => ({
|
|
4616
|
+
entry: { id: node.id, type: node.type, parentId: node.parentId, timestamp: node.timestamp },
|
|
4617
|
+
children: this._convertTreeDataToNodes(node.children),
|
|
4618
|
+
label: node.label,
|
|
4619
|
+
}));
|
|
4620
|
+
}
|
|
4621
|
+
/** Show session selector in connect mode */
|
|
4622
|
+
showConnectModeSessionSelector() {
|
|
4623
|
+
this.showSelector((done) => {
|
|
4624
|
+
const selector = new SessionSelectorComponent(async (_onProgress) => {
|
|
4625
|
+
const sessions = await this.proxy.getSessions();
|
|
4626
|
+
return sessions.map((s) => ({
|
|
4627
|
+
path: s.path,
|
|
4628
|
+
id: s.id,
|
|
4629
|
+
cwd: s.cwd,
|
|
4630
|
+
name: s.name,
|
|
4631
|
+
parentSessionPath: s.parentSessionPath,
|
|
4632
|
+
created: new Date(s.created),
|
|
4633
|
+
modified: new Date(s.modified),
|
|
4634
|
+
messageCount: s.messageCount,
|
|
4635
|
+
firstMessage: s.firstMessage,
|
|
4636
|
+
allMessagesText: "",
|
|
4637
|
+
}));
|
|
4638
|
+
}, async () => [], async (sessionPath) => {
|
|
4639
|
+
try {
|
|
4640
|
+
await this.proxy.switchSession(sessionPath);
|
|
4641
|
+
done();
|
|
4642
|
+
this.showStatus("Switched session");
|
|
4643
|
+
}
|
|
4644
|
+
catch (error) {
|
|
4645
|
+
done();
|
|
4646
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
4647
|
+
}
|
|
4648
|
+
}, () => {
|
|
4649
|
+
done();
|
|
4650
|
+
this.ui.requestRender();
|
|
4651
|
+
}, () => {
|
|
4652
|
+
void this.shutdown();
|
|
4653
|
+
}, () => this.ui.requestRender());
|
|
4654
|
+
return { component: selector, focus: selector };
|
|
4655
|
+
});
|
|
4656
|
+
}
|
|
4657
|
+
/** Copy last agent message in connect mode */
|
|
4658
|
+
handleConnectModeCopy() {
|
|
4659
|
+
const msgs = this.proxy.messages;
|
|
4660
|
+
const lastAssistant = [...msgs].reverse().find((m) => m.role === "assistant");
|
|
4661
|
+
if (!lastAssistant) {
|
|
4662
|
+
this.showStatus("No assistant message to copy");
|
|
4663
|
+
return;
|
|
4664
|
+
}
|
|
4665
|
+
const text = lastAssistant.content
|
|
4666
|
+
.filter((p) => p.type === "text")
|
|
4667
|
+
.map((p) => p.text)
|
|
4668
|
+
.join("\n");
|
|
4669
|
+
if (text) {
|
|
4670
|
+
void copyToClipboard(text);
|
|
4671
|
+
this.showStatus("Copied to clipboard");
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
/** Show session info in connect mode */
|
|
4675
|
+
showConnectModeSessionInfo() {
|
|
4676
|
+
const snapshot = this.proxy.getSnapshot();
|
|
4677
|
+
const parts = [
|
|
4678
|
+
`Model: ${snapshot.model}`,
|
|
4679
|
+
`Session: ${snapshot.sessionName ?? "(unnamed)"}`,
|
|
4680
|
+
`File: ${snapshot.sessionFile ?? "N/A"}`,
|
|
4681
|
+
`CWD: ${snapshot.cwd}`,
|
|
4682
|
+
`Messages: ${snapshot.messages.length}`,
|
|
4683
|
+
`Context: ${snapshot.contextUsage.percent !== null ? `${snapshot.contextUsage.percent.toFixed(1)}%` : "?"} / ${snapshot.contextUsage.contextWindow}`,
|
|
4684
|
+
`Tokens: ↑${snapshot.tokenUsage.input} ↓${snapshot.tokenUsage.output} $${snapshot.tokenUsage.cost.toFixed(3)}`,
|
|
4685
|
+
];
|
|
4686
|
+
this.showStatus(parts.join(" • "));
|
|
4687
|
+
}
|
|
3645
4688
|
async handleResumeSession(sessionPath, options) {
|
|
3646
4689
|
if (this.loadingAnimation) {
|
|
3647
4690
|
this.loadingAnimation.stop();
|
|
@@ -3923,9 +4966,7 @@ export class InteractiveMode {
|
|
|
3923
4966
|
});
|
|
3924
4967
|
}
|
|
3925
4968
|
async showLoginDialog(providerId, providerName) {
|
|
3926
|
-
const providerInfo = this.session.modelRegistry.authStorage
|
|
3927
|
-
.getOAuthProviders()
|
|
3928
|
-
.find((provider) => provider.id === providerId);
|
|
4969
|
+
const providerInfo = this.session.modelRegistry.authStorage.getOAuthProviders().find((provider) => provider.id === providerId);
|
|
3929
4970
|
const previousModel = this.session.model;
|
|
3930
4971
|
// Providers that use callback servers (can paste redirect URL)
|
|
3931
4972
|
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
@@ -3973,12 +5014,12 @@ export class InteractiveMode {
|
|
|
3973
5014
|
}
|
|
3974
5015
|
});
|
|
3975
5016
|
}
|
|
3976
|
-
else if (providerId === "github-copilot") {
|
|
3977
|
-
// GitHub Copilot polls after onAuth
|
|
3978
|
-
dialog.showWaiting("Waiting for browser authentication...");
|
|
3979
|
-
}
|
|
3980
5017
|
// For Anthropic: onPrompt is called immediately after
|
|
3981
5018
|
},
|
|
5019
|
+
onDeviceCode: (info) => {
|
|
5020
|
+
dialog.showDeviceCode(info);
|
|
5021
|
+
dialog.showWaiting("Waiting for authentication...");
|
|
5022
|
+
},
|
|
3982
5023
|
onPrompt: async (prompt) => {
|
|
3983
5024
|
return dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
3984
5025
|
},
|
|
@@ -4035,6 +5076,7 @@ export class InteractiveMode {
|
|
|
4035
5076
|
};
|
|
4036
5077
|
try {
|
|
4037
5078
|
await this.session.reload();
|
|
5079
|
+
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
4038
5080
|
this.keybindings.reload();
|
|
4039
5081
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
4040
5082
|
if (isExpandable(activeHeader)) {
|
|
@@ -4610,7 +5652,7 @@ export class InteractiveMode {
|
|
|
4610
5652
|
}
|
|
4611
5653
|
stop() {
|
|
4612
5654
|
this.unregisterSignalHandlers();
|
|
4613
|
-
if (this.settingsManager
|
|
5655
|
+
if (this.settingsManager?.getShowTerminalProgress()) {
|
|
4614
5656
|
this.ui.terminal.setProgress(false);
|
|
4615
5657
|
}
|
|
4616
5658
|
if (this.loadingAnimation) {
|