@averyyy/pi-coding-agent 0.82.1-piclient.9 → 0.84.1-piclient.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +272 -0
- package/README.md +6 -1
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +27 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/auth-check.d.ts +19 -0
- package/dist/cli/auth-check.d.ts.map +1 -0
- package/dist/cli/auth-check.js +50 -0
- package/dist/cli/auth-check.js.map +1 -0
- package/dist/cli/auth-command.d.ts +24 -0
- package/dist/cli/auth-command.d.ts.map +1 -0
- package/dist/cli/auth-command.js +102 -0
- package/dist/cli/auth-command.js.map +1 -0
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +2 -2
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/credential-print.d.ts +13 -0
- package/dist/cli/credential-print.d.ts.map +1 -0
- package/dist/cli/credential-print.js +76 -0
- package/dist/cli/credential-print.js.map +1 -0
- package/dist/cli/experimental/auth.d.ts +16 -0
- package/dist/cli/experimental/auth.d.ts.map +1 -0
- package/dist/cli/experimental/auth.js +13 -0
- package/dist/cli/experimental/auth.js.map +1 -0
- package/dist/cli/experimental/cli.d.ts +6 -0
- package/dist/cli/experimental/cli.d.ts.map +1 -0
- package/dist/cli/experimental/cli.js +5 -0
- package/dist/cli/experimental/cli.js.map +1 -0
- package/dist/cli/experimental/command-options.d.ts +17 -0
- package/dist/cli/experimental/command-options.d.ts.map +1 -0
- package/dist/cli/experimental/command-options.js +35 -0
- package/dist/cli/experimental/command-options.js.map +1 -0
- package/dist/cli/experimental/command.d.ts +63 -0
- package/dist/cli/experimental/command.d.ts.map +1 -0
- package/dist/cli/experimental/command.js +130 -0
- package/dist/cli/experimental/command.js.map +1 -0
- package/dist/cli/experimental/commands/client.d.ts +13 -0
- package/dist/cli/experimental/commands/client.d.ts.map +1 -0
- package/dist/cli/experimental/commands/client.js +25 -0
- package/dist/cli/experimental/commands/client.js.map +1 -0
- package/dist/cli/experimental/commands/pi.d.ts +15 -0
- package/dist/cli/experimental/commands/pi.d.ts.map +1 -0
- package/dist/cli/experimental/commands/pi.js +28 -0
- package/dist/cli/experimental/commands/pi.js.map +1 -0
- package/dist/cli/experimental/commands/server.d.ts +13 -0
- package/dist/cli/experimental/commands/server.d.ts.map +1 -0
- package/dist/cli/experimental/commands/server.js +25 -0
- package/dist/cli/experimental/commands/server.js.map +1 -0
- package/dist/cli/experimental/transport-address.d.ts +10 -0
- package/dist/cli/experimental/transport-address.d.ts.map +1 -0
- package/dist/cli/experimental/transport-address.js +38 -0
- package/dist/cli/experimental/transport-address.js.map +1 -0
- package/dist/cli/list-models.d.ts +1 -1
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +2 -2
- package/dist/cli/list-models.js.map +1 -1
- package/dist/cli/startup-ui.d.ts +1 -1
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +2 -2
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/remote-session.d.ts +53 -0
- package/dist/client/remote-session.d.ts.map +1 -0
- package/dist/client/remote-session.js +340 -0
- package/dist/client/remote-session.js.map +1 -0
- package/dist/client/transcript.d.ts +12 -0
- package/dist/client/transcript.d.ts.map +1 -0
- package/dist/client/transcript.js +98 -0
- package/dist/client/transcript.js.map +1 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +3 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +3 -12
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +74 -65
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +25 -9
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +237 -39
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- 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.map +1 -1
- package/dist/core/extensions/loader.js +43 -20
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +3 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +10 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +33 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +10 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +5 -0
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/keybindings.d.ts +44 -4
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/model-config.d.ts +29 -5
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +4 -0
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts +5 -3
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +13 -10
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +4 -3
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +42 -10
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +32 -13
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +233 -73
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/models-store.d.ts +12 -7
- package/dist/core/models-store.d.ts.map +1 -1
- package/dist/core/models-store.js +83 -14
- package/dist/core/models-store.js.map +1 -1
- package/dist/core/package-manager.d.ts +4 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +76 -42
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/pi-manifest.d.ts +8 -0
- package/dist/core/pi-manifest.d.ts.map +1 -0
- package/dist/core/pi-manifest.js +25 -0
- package/dist/core/pi-manifest.js.map +1 -0
- package/dist/core/pi-server-client.d.ts +2 -2
- package/dist/core/pi-server-client.d.ts.map +1 -1
- package/dist/core/pi-server-client.js +2 -0
- package/dist/core/pi-server-client.js.map +1 -1
- package/dist/core/provider-composer.d.ts +4 -1
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +36 -15
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +73 -67
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader.d.ts +15 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +64 -10
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-credentials.d.ts +5 -5
- package/dist/core/runtime-credentials.d.ts.map +1 -1
- package/dist/core/runtime-credentials.js +11 -8
- package/dist/core/runtime-credentials.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +3 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +12 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +40 -16
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts +4 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +6 -4
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts +4 -0
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +11 -7
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts +7 -0
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +16 -19
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +4 -0
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +5 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts +4 -0
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +5 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts +4 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +7 -3
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +6 -3
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/write.d.ts +4 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +6 -2
- package/dist/core/tools/write.js.map +1 -1
- package/dist/extensions/llama/index.d.ts.map +1 -1
- package/dist/extensions/llama/index.js +11 -2
- package/dist/extensions/llama/index.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +22 -8
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +94 -6
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +5 -2
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +13 -4
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +7 -0
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +2 -2
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/markdown-transform.d.ts +3 -0
- package/dist/modes/interactive/components/markdown-transform.d.ts.map +1 -0
- package/dist/modes/interactive/components/markdown-transform.js +19 -0
- package/dist/modes/interactive/components/markdown-transform.js.map +1 -0
- package/dist/modes/interactive/components/mermaid.d.ts +11 -0
- package/dist/modes/interactive/components/mermaid.d.ts.map +1 -0
- package/dist/modes/interactive/components/mermaid.js +75 -0
- package/dist/modes/interactive/components/mermaid.js.map +1 -0
- package/dist/modes/interactive/components/model-selector.d.ts +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +20 -5
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +4 -0
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +26 -0
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +8 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +30 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +3 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +9 -2
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +7 -5
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +36 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +481 -150
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -0
- package/dist/modes/interactive/theme/light.json +1 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +3 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +10 -1
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +5 -1
- package/dist/modes/interactive/theme/theme.d.ts +2 -2
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +24 -6
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +28 -0
- package/dist/modes/json-event.d.ts.map +1 -0
- package/dist/modes/json-event.js +12 -0
- package/dist/modes/json-event.js.map +1 -0
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +12 -2
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +5 -4
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +17 -3
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +11 -9
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/rpc-entry.d.ts.map +1 -1
- package/dist/rpc-entry.js +1 -0
- package/dist/rpc-entry.js.map +1 -1
- package/dist/server/create-harness.d.ts +26 -0
- package/dist/server/create-harness.d.ts.map +1 -0
- package/dist/server/create-harness.js +104 -0
- package/dist/server/create-harness.js.map +1 -0
- package/dist/utils/abort.d.ts +5 -0
- package/dist/utils/abort.d.ts.map +1 -0
- package/dist/utils/abort.js +48 -0
- package/dist/utils/abort.js.map +1 -0
- package/dist/utils/clipboard.d.ts +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +22 -2
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/management-http.d.ts +23 -0
- package/dist/utils/management-http.d.ts.map +1 -0
- package/dist/utils/management-http.js +50 -0
- package/dist/utils/management-http.js.map +1 -0
- package/dist/utils/paths.d.ts +3 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +23 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/tool-result-images.d.ts +19 -0
- package/dist/utils/tool-result-images.d.ts.map +1 -0
- package/dist/utils/tool-result-images.js +45 -0
- package/dist/utils/tool-result-images.js.map +1 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +4 -6
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +4 -0
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +20 -2
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +2 -2
- package/docs/custom-provider.md +16 -8
- package/docs/extensions.md +33 -6
- package/docs/json.md +19 -14
- package/docs/keybindings.md +46 -14
- package/docs/models.md +24 -3
- package/docs/providers.md +10 -1
- package/docs/quickstart.md +2 -0
- package/docs/rpc.md +13 -11
- package/docs/sdk.md +21 -2
- package/docs/security.md +1 -1
- package/docs/session-format.md +2 -0
- package/docs/settings.md +4 -1
- package/docs/themes.md +5 -3
- package/docs/usage.md +7 -0
- package/examples/extensions/custom-compaction.ts +1 -16
- package/examples/extensions/custom-provider-anthropic/index.ts +10 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/doom-overlay/wad-finder.ts +7 -3
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/handoff.ts +2 -11
- package/examples/extensions/qna.ts +3 -7
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/summarize.ts +7 -18
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/rpc-extension-ui.ts +11 -2
- package/examples/sdk/02-custom-model.ts +1 -2
- package/examples/sdk/09-api-keys-and-oauth.ts +1 -1
- package/examples/sdk/12-full-control.ts +3 -1
- package/examples/sdk/README.md +1 -1
- package/package.json +14 -7
|
@@ -6,7 +6,8 @@ import * as crypto from "node:crypto";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import
|
|
9
|
+
import * as TuiLayouts from "@earendil-works/pi-tui";
|
|
10
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@earendil-works/pi-tui";
|
|
10
11
|
import chalk from "chalk";
|
|
11
12
|
import { spawn, spawnSync } from "child_process";
|
|
12
13
|
import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
@@ -16,7 +17,8 @@ import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, }
|
|
|
16
17
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
17
18
|
import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
|
|
18
19
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
19
|
-
import { defaultModelPerProvider, findExactModelReferenceMatch,
|
|
20
|
+
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScopeFromModels, } from "../../core/model-resolver.js";
|
|
21
|
+
import { CredentialSynchronizationError } from "../../core/model-runtime.js";
|
|
20
22
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
21
23
|
import { getPiClientUpdateMarkerPath, PI_CLIENT_RUNTIME_RELOAD_EXIT_CODE, readPiClientUpdateMarker, writePiClientRuntimeReloadState, } from "../../core/pi-client-runtime-update.js";
|
|
22
24
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
@@ -29,6 +31,7 @@ import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelo
|
|
|
29
31
|
import { copyToClipboard, readClipboardText } from "../../utils/clipboard.js";
|
|
30
32
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
31
33
|
import { parseGitUrl } from "../../utils/git.js";
|
|
34
|
+
import { openBrowser } from "../../utils/open-browser.js";
|
|
32
35
|
import { getCwdRelativePath } from "../../utils/paths.js";
|
|
33
36
|
import { getPiUserAgent } from "../../utils/pi-user-agent.js";
|
|
34
37
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
@@ -52,6 +55,7 @@ import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
|
52
55
|
import { FooterComponent, formatTokens } from "./components/footer.js";
|
|
53
56
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
54
57
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
58
|
+
import { createMermaidMarkdownTransformer } from "./components/mermaid.js";
|
|
55
59
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
56
60
|
import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
|
|
57
61
|
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
@@ -161,11 +165,58 @@ function formatLoginProviderCompletionDescription(provider) {
|
|
|
161
165
|
const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
|
|
162
166
|
return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
|
|
163
167
|
}
|
|
168
|
+
/** Composition root for selecting the interactive terminal renderer. */
|
|
169
|
+
export function createInteractiveTui(options) {
|
|
170
|
+
const terminal = options.terminal ?? new ProcessTerminal();
|
|
171
|
+
if (options.tuiMode === "fullscreen") {
|
|
172
|
+
return new TuiAltScreen(terminal, options.showHardwareCursor, options.logDirectory, {
|
|
173
|
+
openUrl: openBrowser,
|
|
174
|
+
onRightClickPaste: options.onRightClickPaste,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return new TuiMainScreen(terminal, options.showHardwareCursor, options.logDirectory);
|
|
178
|
+
}
|
|
179
|
+
/** Stable reference for components while InteractiveMode replaces the active renderer. */
|
|
180
|
+
export function createInteractiveTuiReference(getTui) {
|
|
181
|
+
return new Proxy({}, {
|
|
182
|
+
get: (_target, property) => {
|
|
183
|
+
const tui = getTui();
|
|
184
|
+
const value = Reflect.get(tui, property, tui);
|
|
185
|
+
if (typeof value !== "function")
|
|
186
|
+
return value;
|
|
187
|
+
let methodTui = tui;
|
|
188
|
+
let method = value;
|
|
189
|
+
return (...args) => {
|
|
190
|
+
const currentTui = getTui();
|
|
191
|
+
if (currentTui !== methodTui) {
|
|
192
|
+
const currentMethod = Reflect.get(currentTui, property, currentTui);
|
|
193
|
+
if (typeof currentMethod !== "function") {
|
|
194
|
+
throw new TypeError(`TUI property ${String(property)} is not callable`);
|
|
195
|
+
}
|
|
196
|
+
methodTui = currentTui;
|
|
197
|
+
method = currentMethod;
|
|
198
|
+
}
|
|
199
|
+
return Reflect.apply(method, methodTui, args);
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
set: (_target, property, value) => {
|
|
203
|
+
const tui = getTui();
|
|
204
|
+
return Reflect.set(tui, property, value, tui);
|
|
205
|
+
},
|
|
206
|
+
has: (_target, property) => Reflect.has(getTui(), property),
|
|
207
|
+
getPrototypeOf: () => Reflect.getPrototypeOf(getTui()),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
164
210
|
export class InteractiveMode {
|
|
165
211
|
runtimeHost;
|
|
212
|
+
renderer;
|
|
166
213
|
ui;
|
|
214
|
+
mainScreenRenderState;
|
|
167
215
|
loadedResourcesContainer;
|
|
168
216
|
chatContainer;
|
|
217
|
+
documentContainer;
|
|
218
|
+
transcriptScrollView;
|
|
219
|
+
fullscreenLayoutRoot;
|
|
169
220
|
pendingMessagesContainer;
|
|
170
221
|
statusContainer;
|
|
171
222
|
defaultEditor;
|
|
@@ -175,7 +226,10 @@ export class InteractiveMode {
|
|
|
175
226
|
autocompleteProviderWrappers = [];
|
|
176
227
|
fdPath;
|
|
177
228
|
editorContainer;
|
|
229
|
+
activeSelectorToken;
|
|
230
|
+
activeSelectorDispose;
|
|
178
231
|
footer;
|
|
232
|
+
footerContainer;
|
|
179
233
|
footerDataProvider;
|
|
180
234
|
// Stored so the same manager can be injected into custom editors, selectors, and extension UI.
|
|
181
235
|
keybindings;
|
|
@@ -209,6 +263,10 @@ export class InteractiveMode {
|
|
|
209
263
|
// Thinking block visibility state
|
|
210
264
|
hideThinkingBlock = false;
|
|
211
265
|
outputPad = 1;
|
|
266
|
+
mermaidMarkdownTransformer = createMermaidMarkdownTransformer({
|
|
267
|
+
getMode: () => this.settingsManager.getMermaidRenderingMode(),
|
|
268
|
+
theme,
|
|
269
|
+
});
|
|
212
270
|
// Skill commands: command name -> skill file path
|
|
213
271
|
skillCommands = new Map();
|
|
214
272
|
// Agent subscription unsubscribe function
|
|
@@ -232,7 +290,7 @@ export class InteractiveMode {
|
|
|
232
290
|
extensionSelector = undefined;
|
|
233
291
|
extensionInput = undefined;
|
|
234
292
|
extensionEditor = undefined;
|
|
235
|
-
|
|
293
|
+
extensionTerminalInputSubscriptions = new Set();
|
|
236
294
|
// Extension widgets (components rendered above/below the editor)
|
|
237
295
|
extensionWidgetsAbove = new Map();
|
|
238
296
|
extensionWidgetsBelow = new Map();
|
|
@@ -247,6 +305,9 @@ export class InteractiveMode {
|
|
|
247
305
|
// Custom header from extension (undefined = use built-in header)
|
|
248
306
|
customHeader = undefined;
|
|
249
307
|
options;
|
|
308
|
+
onRightClickPaste = () => {
|
|
309
|
+
void this.handleRightClickPaste();
|
|
310
|
+
};
|
|
250
311
|
autoTrustOnReloadCwd;
|
|
251
312
|
themeController;
|
|
252
313
|
// Convenience accessors
|
|
@@ -264,7 +325,8 @@ export class InteractiveMode {
|
|
|
264
325
|
}
|
|
265
326
|
constructor(runtimeHost, options = {}) {
|
|
266
327
|
this.runtimeHost = runtimeHost;
|
|
267
|
-
|
|
328
|
+
const tuiMode = options.tuiMode ?? this.settingsManager.getTuiMode();
|
|
329
|
+
this.options = { ...options, tuiMode };
|
|
268
330
|
this.autoTrustOnReloadCwd = options.autoTrustOnReloadCwd;
|
|
269
331
|
this.runtimeHost.setBeforeSessionInvalidate(() => {
|
|
270
332
|
this.resetExtensionUI();
|
|
@@ -273,11 +335,21 @@ export class InteractiveMode {
|
|
|
273
335
|
await this.rebindCurrentSession({ renderBeforeBind: true });
|
|
274
336
|
});
|
|
275
337
|
this.version = VERSION;
|
|
276
|
-
this.
|
|
338
|
+
this.renderer = createInteractiveTui({
|
|
339
|
+
tuiMode,
|
|
340
|
+
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
341
|
+
logDirectory: getAgentDir(),
|
|
342
|
+
onRightClickPaste: this.onRightClickPaste,
|
|
343
|
+
});
|
|
344
|
+
this.ui = createInteractiveTuiReference(() => this.renderer);
|
|
277
345
|
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
|
278
346
|
this.headerContainer = new Container();
|
|
279
347
|
this.loadedResourcesContainer = new Container();
|
|
280
348
|
this.chatContainer = new Container();
|
|
349
|
+
this.documentContainer = new Container();
|
|
350
|
+
this.documentContainer.addChild(this.headerContainer);
|
|
351
|
+
this.documentContainer.addChild(this.loadedResourcesContainer);
|
|
352
|
+
this.documentContainer.addChild(this.chatContainer);
|
|
281
353
|
this.pendingMessagesContainer = new Container();
|
|
282
354
|
this.statusContainer = new Container();
|
|
283
355
|
this.widgetContainerAbove = new Container();
|
|
@@ -296,6 +368,8 @@ export class InteractiveMode {
|
|
|
296
368
|
this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
|
|
297
369
|
this.footer = new FooterComponent(this.session, this.footerDataProvider);
|
|
298
370
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
371
|
+
this.footerContainer = new Container();
|
|
372
|
+
this.footerContainer.addChild(this.footer);
|
|
299
373
|
// Load hide thinking block setting
|
|
300
374
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
301
375
|
this.outputPad = this.settingsManager.getOutputPad();
|
|
@@ -351,11 +425,10 @@ export class InteractiveMode {
|
|
|
351
425
|
}));
|
|
352
426
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
353
427
|
if (modelCommand) {
|
|
354
|
-
modelCommand.getArgumentCompletions =
|
|
355
|
-
// Get available models (scoped or from registry)
|
|
428
|
+
modelCommand.getArgumentCompletions = (prefix) => {
|
|
356
429
|
const models = this.session.scopedModels.length > 0
|
|
357
430
|
? this.session.scopedModels.map((s) => s.model)
|
|
358
|
-
:
|
|
431
|
+
: this.session.modelRuntime.getAvailableSnapshot();
|
|
359
432
|
if (models.length === 0)
|
|
360
433
|
return null;
|
|
361
434
|
// Create items with provider/id format
|
|
@@ -456,6 +529,73 @@ export class InteractiveMode {
|
|
|
456
529
|
}
|
|
457
530
|
this.chatContainer.addChild(new DynamicBorder());
|
|
458
531
|
}
|
|
532
|
+
mountInteractiveTui(tui, components) {
|
|
533
|
+
for (const component of components)
|
|
534
|
+
tui.addChild(component);
|
|
535
|
+
if (TuiLayouts.isViewportTUI(tui)) {
|
|
536
|
+
if (!this.fullscreenLayoutRoot)
|
|
537
|
+
throw new Error("Fullscreen layout is not initialized");
|
|
538
|
+
tui.setLayoutRoot(this.fullscreenLayoutRoot);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
stopInteractiveTui() {
|
|
542
|
+
if (this.renderer.mode === "fullscreen") {
|
|
543
|
+
while (this.renderer.hasOverlayEntries)
|
|
544
|
+
this.renderer.hideOverlay();
|
|
545
|
+
this.switchTuiMode("regular", false, false);
|
|
546
|
+
this.renderer.renderNow();
|
|
547
|
+
}
|
|
548
|
+
this.ui.stop();
|
|
549
|
+
}
|
|
550
|
+
switchTuiMode(mode, restoreProgress = true, startRenderer = true) {
|
|
551
|
+
const previousUi = this.renderer;
|
|
552
|
+
if (mode === previousUi.mode)
|
|
553
|
+
return true;
|
|
554
|
+
if (previousUi.hasOverlayEntries)
|
|
555
|
+
return false;
|
|
556
|
+
const components = [...previousUi.children];
|
|
557
|
+
const focus = previousUi.getFocusedComponent();
|
|
558
|
+
const terminal = previousUi.terminal;
|
|
559
|
+
const showHardwareCursor = previousUi.getShowHardwareCursor();
|
|
560
|
+
const clearOnShrink = previousUi.getClearOnShrink();
|
|
561
|
+
const onDebug = previousUi.onDebug;
|
|
562
|
+
if (previousUi instanceof TuiMainScreen) {
|
|
563
|
+
this.mainScreenRenderState = previousUi.captureRenderState();
|
|
564
|
+
}
|
|
565
|
+
previousUi.stop({ preserveScreen: true });
|
|
566
|
+
previousUi.setFocus(null);
|
|
567
|
+
previousUi.clear();
|
|
568
|
+
if (TuiLayouts.isViewportTUI(previousUi))
|
|
569
|
+
previousUi.setLayoutRoot(undefined);
|
|
570
|
+
const nextUi = createInteractiveTui({
|
|
571
|
+
tuiMode: mode,
|
|
572
|
+
showHardwareCursor,
|
|
573
|
+
logDirectory: getAgentDir(),
|
|
574
|
+
terminal,
|
|
575
|
+
onRightClickPaste: this.onRightClickPaste,
|
|
576
|
+
});
|
|
577
|
+
nextUi.setClearOnShrink(clearOnShrink);
|
|
578
|
+
nextUi.onDebug = onDebug;
|
|
579
|
+
if (nextUi instanceof TuiMainScreen && this.mainScreenRenderState) {
|
|
580
|
+
nextUi.restoreRenderState(this.mainScreenRenderState);
|
|
581
|
+
}
|
|
582
|
+
this.renderer = nextUi;
|
|
583
|
+
this.options.tuiMode = mode;
|
|
584
|
+
this.mountInteractiveTui(nextUi, components);
|
|
585
|
+
nextUi.invalidate();
|
|
586
|
+
nextUi.setFocus(focus);
|
|
587
|
+
if (!startRenderer)
|
|
588
|
+
return true;
|
|
589
|
+
nextUi.start();
|
|
590
|
+
this.themeController.rebindTui();
|
|
591
|
+
this.rebindExtensionTerminalInputListeners();
|
|
592
|
+
if (restoreProgress &&
|
|
593
|
+
this.settingsManager.getShowTerminalProgress() &&
|
|
594
|
+
(this.session.isStreaming || this.session.isCompacting)) {
|
|
595
|
+
terminal.setProgress(true);
|
|
596
|
+
}
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
459
599
|
async init() {
|
|
460
600
|
if (this.isInitialized)
|
|
461
601
|
return;
|
|
@@ -479,18 +619,36 @@ export class InteractiveMode {
|
|
|
479
619
|
: "";
|
|
480
620
|
console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
|
|
481
621
|
}
|
|
482
|
-
//
|
|
483
|
-
// Keep loaded resources before chat so restored session messages never precede them.
|
|
484
|
-
this.ui.addChild(this.headerContainer);
|
|
485
|
-
this.ui.addChild(this.loadedResourcesContainer);
|
|
486
|
-
this.ui.addChild(this.chatContainer);
|
|
487
|
-
this.ui.addChild(this.pendingMessagesContainer);
|
|
488
|
-
this.ui.addChild(this.statusContainer);
|
|
622
|
+
// Keep one component tree and remount it when changing renderers.
|
|
489
623
|
this.renderWidgets(); // Initialize with default spacer
|
|
490
|
-
this.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
624
|
+
this.transcriptScrollView = new TuiLayouts.ScrollView(this.documentContainer, {
|
|
625
|
+
follow: "end",
|
|
626
|
+
primary: true,
|
|
627
|
+
overscroll: "chain",
|
|
628
|
+
scrollbar: this.settingsManager.getFullscreenScrollbar(),
|
|
629
|
+
scrollbarStyle: (text) => theme.bg("scrollbarThumb", text),
|
|
630
|
+
});
|
|
631
|
+
const dock = new TuiLayouts.VStack([
|
|
632
|
+
{ component: this.pendingMessagesContainer, shrink: 1, minSize: 0 },
|
|
633
|
+
{ component: this.statusContainer, shrink: 1, minSize: 0 },
|
|
634
|
+
{ component: this.widgetContainerAbove, shrink: 1, minSize: 0 },
|
|
635
|
+
{ component: this.editorContainer, shrink: 1, minSize: 3 },
|
|
636
|
+
{ component: this.widgetContainerBelow, shrink: 1, minSize: 0 },
|
|
637
|
+
{ component: this.footerContainer, shrink: 1, minSize: 1 },
|
|
638
|
+
]);
|
|
639
|
+
this.fullscreenLayoutRoot = new TuiLayouts.VStack([
|
|
640
|
+
{ component: this.transcriptScrollView, basis: 0, grow: 1, shrink: 1, minSize: 1 },
|
|
641
|
+
{ component: dock, basis: "auto", grow: 0, shrink: 1, minSize: 1 },
|
|
642
|
+
]);
|
|
643
|
+
this.mountInteractiveTui(this.renderer, [
|
|
644
|
+
this.documentContainer,
|
|
645
|
+
this.pendingMessagesContainer,
|
|
646
|
+
this.statusContainer,
|
|
647
|
+
this.widgetContainerAbove,
|
|
648
|
+
this.editorContainer,
|
|
649
|
+
this.widgetContainerBelow,
|
|
650
|
+
this.footerContainer,
|
|
651
|
+
]);
|
|
494
652
|
this.ui.setFocus(this.editor);
|
|
495
653
|
this.setupKeyHandlers();
|
|
496
654
|
this.setupEditorSubmitHandler();
|
|
@@ -582,10 +740,13 @@ export class InteractiveMode {
|
|
|
582
740
|
async run() {
|
|
583
741
|
await this.init();
|
|
584
742
|
if (!process.env.PI_OFFLINE) {
|
|
743
|
+
const controller = new AbortController();
|
|
744
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
585
745
|
void this.session.modelRuntime
|
|
586
|
-
.refresh()
|
|
746
|
+
.refresh({ signal: controller.signal })
|
|
587
747
|
.then(() => this.updateAvailableProviderCount())
|
|
588
|
-
.catch(() => { })
|
|
748
|
+
.catch(() => { })
|
|
749
|
+
.finally(() => clearTimeout(timeout));
|
|
589
750
|
}
|
|
590
751
|
// Start version check asynchronously
|
|
591
752
|
checkForNewPiVersion(this.version).then((newRelease) => {
|
|
@@ -769,13 +930,14 @@ export class InteractiveMode {
|
|
|
769
930
|
// Extension System
|
|
770
931
|
// =========================================================================
|
|
771
932
|
formatDisplayPath(p) {
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
933
|
+
const normalizedPath = p.replaceAll("\\", "/");
|
|
934
|
+
const normalizedHome = os.homedir().replaceAll("\\", "/").replace(/\/+$/, "");
|
|
935
|
+
if (normalizedPath === normalizedHome)
|
|
936
|
+
return "~";
|
|
937
|
+
if (normalizedPath.startsWith(`${normalizedHome}/`)) {
|
|
938
|
+
return `~/${normalizedPath.slice(normalizedHome.length + 1)}`;
|
|
777
939
|
}
|
|
778
|
-
return
|
|
940
|
+
return p;
|
|
779
941
|
}
|
|
780
942
|
formatExtensionDisplayPath(path) {
|
|
781
943
|
let result = this.formatDisplayPath(path);
|
|
@@ -1106,7 +1268,12 @@ export class InteractiveMode {
|
|
|
1106
1268
|
}
|
|
1107
1269
|
}
|
|
1108
1270
|
if (showListing) {
|
|
1109
|
-
const
|
|
1271
|
+
const systemPromptSource = this.session.resourceLoader.getSystemPromptSource();
|
|
1272
|
+
const contextFiles = [
|
|
1273
|
+
...(systemPromptSource ? [systemPromptSource] : []),
|
|
1274
|
+
...this.session.resourceLoader.getAppendSystemPromptSources(),
|
|
1275
|
+
...this.session.resourceLoader.getAgentsFiles().agentsFiles,
|
|
1276
|
+
];
|
|
1110
1277
|
if (contextFiles.length > 0) {
|
|
1111
1278
|
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1112
1279
|
const contextList = contextFiles
|
|
@@ -1283,8 +1450,12 @@ export class InteractiveMode {
|
|
|
1283
1450
|
this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
|
|
1284
1451
|
this.showStartupNoticesIfNeeded();
|
|
1285
1452
|
}
|
|
1453
|
+
applyFullscreenScrollbarSetting() {
|
|
1454
|
+
this.transcriptScrollView?.setScrollbar(this.settingsManager.getFullscreenScrollbar());
|
|
1455
|
+
}
|
|
1286
1456
|
applyRuntimeSettings() {
|
|
1287
1457
|
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
1458
|
+
this.applyFullscreenScrollbarSetting();
|
|
1288
1459
|
this.footer.setSession(this.session);
|
|
1289
1460
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
1290
1461
|
this.footerDataProvider.setCwd(this.sessionManager.getCwd());
|
|
@@ -1306,16 +1477,19 @@ export class InteractiveMode {
|
|
|
1306
1477
|
}
|
|
1307
1478
|
}
|
|
1308
1479
|
async rebindCurrentSession(options = {}) {
|
|
1480
|
+
const session = this.session;
|
|
1309
1481
|
this.unsubscribe?.();
|
|
1310
1482
|
this.unsubscribe = undefined;
|
|
1311
1483
|
this.applyRuntimeSettings();
|
|
1312
1484
|
if (options.renderBeforeBind) {
|
|
1313
1485
|
this.renderCurrentSessionState();
|
|
1314
1486
|
this.subscribeToAgent();
|
|
1315
|
-
await this.bindCurrentSessionExtensions();
|
|
1316
1487
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1488
|
+
await this.bindCurrentSessionExtensions();
|
|
1489
|
+
if (this.session !== session) {
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
if (!options.renderBeforeBind) {
|
|
1319
1493
|
this.subscribeToAgent();
|
|
1320
1494
|
}
|
|
1321
1495
|
await this.updateAvailableProviderCount();
|
|
@@ -1345,6 +1519,9 @@ export class InteractiveMode {
|
|
|
1345
1519
|
getRegisteredToolDefinition(toolName) {
|
|
1346
1520
|
return this.session.getToolDefinition(toolName);
|
|
1347
1521
|
}
|
|
1522
|
+
getMarkdownTransformers() {
|
|
1523
|
+
return [this.mermaidMarkdownTransformer, ...this.session.extensionRunner.getMarkdownTransformers()];
|
|
1524
|
+
}
|
|
1348
1525
|
/**
|
|
1349
1526
|
* Set up keyboard shortcuts registered by extensions.
|
|
1350
1527
|
*/
|
|
@@ -1361,6 +1538,7 @@ export class InteractiveMode {
|
|
|
1361
1538
|
sessionManager: this.sessionManager,
|
|
1362
1539
|
modelRegistry: extensionRunner.getModelRegistry(),
|
|
1363
1540
|
model: this.session.model,
|
|
1541
|
+
scopedModels: this.session.scopedModels,
|
|
1364
1542
|
thinkingLevel: this.session.thinkingLevel,
|
|
1365
1543
|
isIdle: () => this.session.isIdle,
|
|
1366
1544
|
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
@@ -1423,7 +1601,7 @@ export class InteractiveMode {
|
|
|
1423
1601
|
this.activeStatusIndicator?.dispose();
|
|
1424
1602
|
this.activeStatusIndicator = undefined;
|
|
1425
1603
|
this.statusContainer.clear();
|
|
1426
|
-
if (hadActiveStatusIndicator && this.ui.getClearOnShrink()) {
|
|
1604
|
+
if (hadActiveStatusIndicator && this.options.tuiMode === "regular" && this.ui.getClearOnShrink()) {
|
|
1427
1605
|
this.statusContainer.addChild(this.idleStatus);
|
|
1428
1606
|
}
|
|
1429
1607
|
}
|
|
@@ -1580,22 +1758,16 @@ export class InteractiveMode {
|
|
|
1580
1758
|
if (this.customFooter?.dispose) {
|
|
1581
1759
|
this.customFooter.dispose();
|
|
1582
1760
|
}
|
|
1583
|
-
|
|
1584
|
-
if (this.customFooter) {
|
|
1585
|
-
this.ui.removeChild(this.customFooter);
|
|
1586
|
-
}
|
|
1587
|
-
else {
|
|
1588
|
-
this.ui.removeChild(this.footer);
|
|
1589
|
-
}
|
|
1761
|
+
this.footerContainer.clear();
|
|
1590
1762
|
if (factory) {
|
|
1591
1763
|
// Create and add custom footer, passing the data provider
|
|
1592
1764
|
this.customFooter = factory(this.ui, theme, this.footerDataProvider);
|
|
1593
|
-
this.
|
|
1765
|
+
this.footerContainer.addChild(this.customFooter);
|
|
1594
1766
|
}
|
|
1595
1767
|
else {
|
|
1596
1768
|
// Restore built-in footer
|
|
1597
1769
|
this.customFooter = undefined;
|
|
1598
|
-
this.
|
|
1770
|
+
this.footerContainer.addChild(this.footer);
|
|
1599
1771
|
}
|
|
1600
1772
|
this.ui.requestRender();
|
|
1601
1773
|
}
|
|
@@ -1641,18 +1813,23 @@ export class InteractiveMode {
|
|
|
1641
1813
|
this.ui.requestRender();
|
|
1642
1814
|
}
|
|
1643
1815
|
addExtensionTerminalInputListener(handler) {
|
|
1644
|
-
const
|
|
1645
|
-
this.
|
|
1816
|
+
const subscription = { handler, unsubscribe: this.ui.addInputListener(handler) };
|
|
1817
|
+
this.extensionTerminalInputSubscriptions.add(subscription);
|
|
1646
1818
|
return () => {
|
|
1647
|
-
unsubscribe();
|
|
1648
|
-
this.
|
|
1819
|
+
subscription.unsubscribe();
|
|
1820
|
+
this.extensionTerminalInputSubscriptions.delete(subscription);
|
|
1649
1821
|
};
|
|
1650
1822
|
}
|
|
1651
|
-
|
|
1652
|
-
for (const
|
|
1653
|
-
unsubscribe();
|
|
1823
|
+
rebindExtensionTerminalInputListeners() {
|
|
1824
|
+
for (const subscription of this.extensionTerminalInputSubscriptions) {
|
|
1825
|
+
subscription.unsubscribe();
|
|
1826
|
+
subscription.unsubscribe = this.ui.addInputListener(subscription.handler);
|
|
1654
1827
|
}
|
|
1655
|
-
|
|
1828
|
+
}
|
|
1829
|
+
clearExtensionTerminalInputListeners() {
|
|
1830
|
+
for (const subscription of this.extensionTerminalInputSubscriptions)
|
|
1831
|
+
subscription.unsubscribe();
|
|
1832
|
+
this.extensionTerminalInputSubscriptions.clear();
|
|
1656
1833
|
}
|
|
1657
1834
|
/**
|
|
1658
1835
|
* Create the ExtensionUIContext for extensions.
|
|
@@ -1747,6 +1924,7 @@ export class InteractiveMode {
|
|
|
1747
1924
|
this.hideExtensionSelector();
|
|
1748
1925
|
resolve(undefined);
|
|
1749
1926
|
}, { tui: this.ui, timeout: opts?.timeout, onToggleToolsExpanded: () => this.toggleToolOutputExpansion() });
|
|
1927
|
+
this.disposeActiveSelector();
|
|
1750
1928
|
this.editorContainer.clear();
|
|
1751
1929
|
this.editorContainer.addChild(this.extensionSelector);
|
|
1752
1930
|
this.ui.setFocus(this.extensionSelector);
|
|
@@ -1798,6 +1976,7 @@ export class InteractiveMode {
|
|
|
1798
1976
|
this.hideExtensionInput();
|
|
1799
1977
|
resolve(undefined);
|
|
1800
1978
|
}, { tui: this.ui, timeout: opts?.timeout });
|
|
1979
|
+
this.disposeActiveSelector();
|
|
1801
1980
|
this.editorContainer.clear();
|
|
1802
1981
|
this.editorContainer.addChild(this.extensionInput);
|
|
1803
1982
|
this.ui.setFocus(this.extensionInput);
|
|
@@ -1827,6 +2006,7 @@ export class InteractiveMode {
|
|
|
1827
2006
|
this.hideExtensionEditor();
|
|
1828
2007
|
resolve(undefined);
|
|
1829
2008
|
}, undefined, this.settingsManager.getExternalEditorCommand());
|
|
2009
|
+
this.disposeActiveSelector();
|
|
1830
2010
|
this.editorContainer.clear();
|
|
1831
2011
|
this.editorContainer.addChild(this.extensionEditor);
|
|
1832
2012
|
this.ui.setFocus(this.extensionEditor);
|
|
@@ -1851,6 +2031,7 @@ export class InteractiveMode {
|
|
|
1851
2031
|
this.editorComponentFactory = factory;
|
|
1852
2032
|
// Save text from current editor before switching
|
|
1853
2033
|
const currentText = this.editor.getText();
|
|
2034
|
+
this.disposeActiveSelector();
|
|
1854
2035
|
this.editorContainer.clear();
|
|
1855
2036
|
if (factory) {
|
|
1856
2037
|
// Create the custom editor with tui, theme, and keybindings
|
|
@@ -1867,6 +2048,9 @@ export class InteractiveMode {
|
|
|
1867
2048
|
if (newEditor.setPaddingX !== undefined) {
|
|
1868
2049
|
newEditor.setPaddingX(this.defaultEditor.getPaddingX());
|
|
1869
2050
|
}
|
|
2051
|
+
if (newEditor.setAutocompleteMaxVisible !== undefined) {
|
|
2052
|
+
newEditor.setAutocompleteMaxVisible(this.defaultEditor.getAutocompleteMaxVisible());
|
|
2053
|
+
}
|
|
1870
2054
|
// Set autocomplete if supported
|
|
1871
2055
|
if (newEditor.setAutocompleteProvider && this.autocompleteProvider) {
|
|
1872
2056
|
newEditor.setAutocompleteProvider(this.autocompleteProvider);
|
|
@@ -1971,6 +2155,7 @@ export class InteractiveMode {
|
|
|
1971
2155
|
options?.onHandle?.(handle);
|
|
1972
2156
|
}
|
|
1973
2157
|
else {
|
|
2158
|
+
this.disposeActiveSelector();
|
|
1974
2159
|
this.editorContainer.clear();
|
|
1975
2160
|
this.editorContainer.addChild(component);
|
|
1976
2161
|
this.ui.setFocus(component);
|
|
@@ -2057,7 +2242,7 @@ export class InteractiveMode {
|
|
|
2057
2242
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
2058
2243
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
2059
2244
|
this.defaultEditor.onAction("app.editor.external", () => void this.handleOpenExternalEditor());
|
|
2060
|
-
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand());
|
|
2245
|
+
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand({ flashConfirmation: true }));
|
|
2061
2246
|
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
2062
2247
|
this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
|
|
2063
2248
|
this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
|
|
@@ -2077,6 +2262,22 @@ export class InteractiveMode {
|
|
|
2077
2262
|
void this.handleClipboardPaste();
|
|
2078
2263
|
};
|
|
2079
2264
|
}
|
|
2265
|
+
async handleRightClickPaste() {
|
|
2266
|
+
const target = this.renderer.getFocusedComponent();
|
|
2267
|
+
const handleInput = target?.handleInput;
|
|
2268
|
+
if (!target || !handleInput)
|
|
2269
|
+
return;
|
|
2270
|
+
try {
|
|
2271
|
+
const text = await readClipboardText();
|
|
2272
|
+
if (!text || this.renderer.getFocusedComponent() !== target)
|
|
2273
|
+
return;
|
|
2274
|
+
handleInput.call(target, `\x1b[200~${text}\x1b[201~`);
|
|
2275
|
+
this.ui.requestRender();
|
|
2276
|
+
}
|
|
2277
|
+
catch {
|
|
2278
|
+
// Silently ignore clipboard errors (may not have permission, etc.)
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2080
2281
|
async handleClipboardPaste() {
|
|
2081
2282
|
try {
|
|
2082
2283
|
const image = await readClipboardImage();
|
|
@@ -2351,17 +2552,17 @@ export class InteractiveMode {
|
|
|
2351
2552
|
this.ui.requestRender();
|
|
2352
2553
|
}
|
|
2353
2554
|
else if (event.message.role === "assistant") {
|
|
2354
|
-
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
|
|
2555
|
+
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
|
|
2355
2556
|
this.streamingMessage = event.message;
|
|
2356
2557
|
this.chatContainer.addChild(this.streamingComponent);
|
|
2357
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2558
|
+
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2358
2559
|
this.ui.requestRender();
|
|
2359
2560
|
}
|
|
2360
2561
|
break;
|
|
2361
2562
|
case "message_update":
|
|
2362
2563
|
if (this.streamingComponent && event.message.role === "assistant") {
|
|
2363
2564
|
this.streamingMessage = event.message;
|
|
2364
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2565
|
+
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2365
2566
|
for (const content of this.streamingMessage.content) {
|
|
2366
2567
|
if (content.type === "toolCall") {
|
|
2367
2568
|
if (!this.pendingTools.has(content.id)) {
|
|
@@ -2398,7 +2599,7 @@ export class InteractiveMode {
|
|
|
2398
2599
|
: "Operation aborted";
|
|
2399
2600
|
this.streamingMessage.errorMessage = errorMessage;
|
|
2400
2601
|
}
|
|
2401
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2602
|
+
this.streamingComponent.updateContent(this.streamingMessage, false);
|
|
2402
2603
|
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
2403
2604
|
if (!errorMessage) {
|
|
2404
2605
|
errorMessage = this.streamingMessage.errorMessage || "Error";
|
|
@@ -2673,12 +2874,12 @@ export class InteractiveMode {
|
|
|
2673
2874
|
// Render user message separately if present
|
|
2674
2875
|
if (skillBlock.userMessage) {
|
|
2675
2876
|
this.chatContainer.addChild(new Spacer(1));
|
|
2676
|
-
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2877
|
+
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2677
2878
|
this.chatContainer.addChild(userComponent);
|
|
2678
2879
|
}
|
|
2679
2880
|
}
|
|
2680
2881
|
else {
|
|
2681
|
-
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2882
|
+
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2682
2883
|
this.chatContainer.addChild(userComponent);
|
|
2683
2884
|
}
|
|
2684
2885
|
if (options?.populateHistory) {
|
|
@@ -2688,7 +2889,7 @@ export class InteractiveMode {
|
|
|
2688
2889
|
break;
|
|
2689
2890
|
}
|
|
2690
2891
|
case "assistant": {
|
|
2691
|
-
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
|
|
2892
|
+
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
|
|
2692
2893
|
this.chatContainer.addChild(assistantComponent);
|
|
2693
2894
|
break;
|
|
2694
2895
|
}
|
|
@@ -3122,6 +3323,8 @@ export class InteractiveMode {
|
|
|
3122
3323
|
this.setToolsExpanded(!this.toolOutputExpanded);
|
|
3123
3324
|
}
|
|
3124
3325
|
setToolsExpanded(expanded) {
|
|
3326
|
+
if (expanded === this.toolOutputExpanded)
|
|
3327
|
+
return;
|
|
3125
3328
|
this.toolOutputExpanded = expanded;
|
|
3126
3329
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
3127
3330
|
if (isExpandable(activeHeader)) {
|
|
@@ -3134,7 +3337,7 @@ export class InteractiveMode {
|
|
|
3134
3337
|
}
|
|
3135
3338
|
}
|
|
3136
3339
|
}
|
|
3137
|
-
this.
|
|
3340
|
+
this.showStatus(`Tool output: ${expanded ? "expanded" : "collapsed"}`);
|
|
3138
3341
|
}
|
|
3139
3342
|
toggleThinkingBlockVisibility() {
|
|
3140
3343
|
this.hideThinkingBlock = !this.hideThinkingBlock;
|
|
@@ -3177,7 +3380,7 @@ export class InteractiveMode {
|
|
|
3177
3380
|
}
|
|
3178
3381
|
showError(errorMessage) {
|
|
3179
3382
|
this.chatContainer.addChild(new Spacer(1));
|
|
3180
|
-
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`),
|
|
3383
|
+
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), this.outputPad, 0));
|
|
3181
3384
|
this.ui.requestRender();
|
|
3182
3385
|
}
|
|
3183
3386
|
showWarning(warningMessage) {
|
|
@@ -3387,25 +3590,43 @@ export class InteractiveMode {
|
|
|
3387
3590
|
// =========================================================================
|
|
3388
3591
|
// Selectors
|
|
3389
3592
|
// =========================================================================
|
|
3593
|
+
disposeActiveSelector() {
|
|
3594
|
+
const dispose = this.activeSelectorDispose;
|
|
3595
|
+
this.activeSelectorToken = undefined;
|
|
3596
|
+
this.activeSelectorDispose = undefined;
|
|
3597
|
+
dispose?.();
|
|
3598
|
+
}
|
|
3390
3599
|
/**
|
|
3391
3600
|
* Shows a selector component in place of the editor.
|
|
3392
3601
|
* @param create Factory that receives a `done` callback and returns the component and focus target
|
|
3393
3602
|
*/
|
|
3394
3603
|
showSelector(create) {
|
|
3604
|
+
const token = {};
|
|
3605
|
+
let dispose;
|
|
3395
3606
|
const done = () => {
|
|
3607
|
+
dispose?.();
|
|
3608
|
+
if (this.activeSelectorToken !== token)
|
|
3609
|
+
return;
|
|
3610
|
+
this.activeSelectorToken = undefined;
|
|
3611
|
+
this.activeSelectorDispose = undefined;
|
|
3396
3612
|
this.editorContainer.clear();
|
|
3397
3613
|
this.editorContainer.addChild(this.editor);
|
|
3398
3614
|
this.ui.setFocus(this.editor);
|
|
3399
3615
|
};
|
|
3400
|
-
const
|
|
3616
|
+
const created = create(done);
|
|
3617
|
+
dispose = created.dispose;
|
|
3618
|
+
this.disposeActiveSelector();
|
|
3619
|
+
this.activeSelectorToken = token;
|
|
3620
|
+
this.activeSelectorDispose = dispose;
|
|
3401
3621
|
this.editorContainer.clear();
|
|
3402
|
-
this.editorContainer.addChild(component);
|
|
3403
|
-
this.ui.setFocus(focus);
|
|
3622
|
+
this.editorContainer.addChild(created.component);
|
|
3623
|
+
this.ui.setFocus(created.focus);
|
|
3404
3624
|
this.ui.requestRender();
|
|
3405
3625
|
}
|
|
3406
3626
|
showSettingsSelector() {
|
|
3407
3627
|
this.showSelector((done) => {
|
|
3408
|
-
|
|
3628
|
+
let selector;
|
|
3629
|
+
selector = new SettingsSelectorComponent({
|
|
3409
3630
|
autoCompact: this.session.autoCompactionEnabled,
|
|
3410
3631
|
showImages: this.settingsManager.getShowImages(),
|
|
3411
3632
|
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
@@ -3422,6 +3643,7 @@ export class InteractiveMode {
|
|
|
3422
3643
|
terminalTheme: this.themeController.getTerminalTheme(),
|
|
3423
3644
|
availableThemes: getAvailableThemes(),
|
|
3424
3645
|
hideThinkingBlock: this.hideThinkingBlock,
|
|
3646
|
+
mermaidRenderingMode: this.settingsManager.getMermaidRenderingMode(),
|
|
3425
3647
|
collapseChangelog: this.settingsManager.getCollapseChangelog(),
|
|
3426
3648
|
enableInstallTelemetry: this.settingsManager.getEnableInstallTelemetry(),
|
|
3427
3649
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
@@ -3435,6 +3657,8 @@ export class InteractiveMode {
|
|
|
3435
3657
|
quietStartup: this.settingsManager.getQuietStartup(),
|
|
3436
3658
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
3437
3659
|
showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
|
|
3660
|
+
tuiMode: this.ui.mode,
|
|
3661
|
+
fullscreenScrollbar: this.settingsManager.getFullscreenScrollbar(),
|
|
3438
3662
|
warnings: this.settingsManager.getWarnings(),
|
|
3439
3663
|
}, {
|
|
3440
3664
|
onAutoCompactChange: (enabled) => {
|
|
@@ -3503,6 +3727,11 @@ export class InteractiveMode {
|
|
|
3503
3727
|
this.chatContainer.clear();
|
|
3504
3728
|
this.rebuildChatFromMessages();
|
|
3505
3729
|
},
|
|
3730
|
+
onMermaidRenderingModeChange: (mode) => {
|
|
3731
|
+
this.settingsManager.setMermaidRenderingMode(mode);
|
|
3732
|
+
this.chatContainer.invalidate();
|
|
3733
|
+
this.ui.requestRender();
|
|
3734
|
+
},
|
|
3506
3735
|
onShowCacheMissNoticesChange: (shown) => {
|
|
3507
3736
|
this.settingsManager.setShowCacheMissNotices(shown);
|
|
3508
3737
|
this.rebuildChatFromMessages();
|
|
@@ -3572,6 +3801,21 @@ export class InteractiveMode {
|
|
|
3572
3801
|
onShowTerminalProgressChange: (enabled) => {
|
|
3573
3802
|
this.settingsManager.setShowTerminalProgress(enabled);
|
|
3574
3803
|
},
|
|
3804
|
+
onTuiModeChange: (mode) => {
|
|
3805
|
+
if (!this.switchTuiMode(mode)) {
|
|
3806
|
+
selector?.getSettingsList().updateValue("tui-mode", this.ui.mode);
|
|
3807
|
+
this.showStatus("Close active overlays before changing TUI mode");
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3810
|
+
this.settingsManager.setTuiMode(mode);
|
|
3811
|
+
if (!this.activeStatusIndicator)
|
|
3812
|
+
this.statusContainer.clear();
|
|
3813
|
+
this.showStatus(`TUI mode: ${mode}`);
|
|
3814
|
+
},
|
|
3815
|
+
onFullscreenScrollbarChange: (mode) => {
|
|
3816
|
+
this.settingsManager.setFullscreenScrollbar(mode);
|
|
3817
|
+
this.applyFullscreenScrollbarSetting();
|
|
3818
|
+
},
|
|
3575
3819
|
onWarningsChange: (warnings) => {
|
|
3576
3820
|
this.settingsManager.setWarnings(warnings);
|
|
3577
3821
|
},
|
|
@@ -3606,20 +3850,37 @@ export class InteractiveMode {
|
|
|
3606
3850
|
this.showModelSelector(searchTerm);
|
|
3607
3851
|
}
|
|
3608
3852
|
async findExactModelMatch(searchTerm) {
|
|
3609
|
-
const
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
if (this.session.scopedModels.length > 0)
|
|
3614
|
-
return
|
|
3615
|
-
|
|
3853
|
+
const cachedModels = this.session.scopedModels.length > 0
|
|
3854
|
+
? this.session.scopedModels.map((scoped) => scoped.model)
|
|
3855
|
+
: [...this.session.modelRuntime.getAvailableSnapshot()];
|
|
3856
|
+
const cachedMatch = findExactModelReferenceMatch(searchTerm, cachedModels);
|
|
3857
|
+
if (cachedMatch || this.session.scopedModels.length > 0)
|
|
3858
|
+
return cachedMatch;
|
|
3859
|
+
this.showStatus("Refreshing model catalogs…");
|
|
3860
|
+
const controller = new AbortController();
|
|
3861
|
+
let timedOut = false;
|
|
3862
|
+
const timeout = setTimeout(() => {
|
|
3863
|
+
timedOut = true;
|
|
3864
|
+
controller.abort();
|
|
3865
|
+
}, 15_000);
|
|
3616
3866
|
try {
|
|
3617
|
-
await this.session.modelRuntime.refresh();
|
|
3618
|
-
|
|
3867
|
+
const result = await this.session.modelRuntime.refresh({ signal: controller.signal });
|
|
3868
|
+
if (result.aborted && timedOut) {
|
|
3869
|
+
this.showWarning("Model refresh timed out; searching cached models.");
|
|
3870
|
+
}
|
|
3871
|
+
else if (result.errors.size > 0) {
|
|
3872
|
+
this.showWarning(`Could not refresh ${[...result.errors.keys()].join(", ")}; searching cached models.`);
|
|
3873
|
+
}
|
|
3619
3874
|
}
|
|
3620
|
-
catch {
|
|
3621
|
-
|
|
3875
|
+
catch (error) {
|
|
3876
|
+
this.showWarning(timedOut
|
|
3877
|
+
? "Model refresh timed out; searching cached models."
|
|
3878
|
+
: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`);
|
|
3879
|
+
}
|
|
3880
|
+
finally {
|
|
3881
|
+
clearTimeout(timeout);
|
|
3622
3882
|
}
|
|
3883
|
+
return findExactModelReferenceMatch(searchTerm, [...this.session.modelRuntime.getAvailableSnapshot()]);
|
|
3623
3884
|
}
|
|
3624
3885
|
/** Update the footer's available provider count from the current snapshot without refreshing catalogs. */
|
|
3625
3886
|
updateAvailableProviderCount() {
|
|
@@ -3721,73 +3982,67 @@ export class InteractiveMode {
|
|
|
3721
3982
|
done();
|
|
3722
3983
|
this.ui.requestRender();
|
|
3723
3984
|
}, initialSearchInput);
|
|
3724
|
-
return { component: selector, focus: selector };
|
|
3985
|
+
return { component: selector, focus: selector, dispose: () => selector.dispose() };
|
|
3725
3986
|
});
|
|
3726
3987
|
}
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
const allModels = [...(await this.session.modelRuntime.getAvailable())];
|
|
3731
|
-
const allModelIds = new Set(allModels.map((model) => `${model.provider}/${model.id}`));
|
|
3988
|
+
showModelsSelector() {
|
|
3989
|
+
let availableModels = [...this.session.modelRuntime.getAvailableSnapshot()];
|
|
3990
|
+
let availableModelIds = new Set(availableModels.map((model) => `${model.provider}/${model.id}`));
|
|
3732
3991
|
const configuredPatterns = this.settingsManager.getEnabledModels();
|
|
3733
3992
|
const sessionScopedModels = this.session.scopedModels;
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
currentEnabledIds = configuredScope.scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3751
|
-
}
|
|
3752
|
-
for (const diagnostic of configuredScope?.diagnostics ?? []) {
|
|
3753
|
-
if (diagnostic.code !== "no-match")
|
|
3754
|
-
continue;
|
|
3755
|
-
currentEnabledIds ??= [];
|
|
3756
|
-
if (!currentEnabledIds.includes(diagnostic.pattern))
|
|
3757
|
-
currentEnabledIds.push(diagnostic.pattern);
|
|
3758
|
-
}
|
|
3759
|
-
// Helper to update session's scoped models (session-only, no persist)
|
|
3760
|
-
const updateSessionModels = async (enabledIds) => {
|
|
3993
|
+
const configuredEnabledIds = (models) => {
|
|
3994
|
+
if (!configuredPatterns?.length)
|
|
3995
|
+
return null;
|
|
3996
|
+
const resolved = resolveModelScopeFromModels(configuredPatterns, models);
|
|
3997
|
+
const ids = resolved.scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3998
|
+
for (const diagnostic of resolved.diagnostics) {
|
|
3999
|
+
if (diagnostic.code === "no-match" && !ids.includes(diagnostic.pattern))
|
|
4000
|
+
ids.push(diagnostic.pattern);
|
|
4001
|
+
}
|
|
4002
|
+
return ids;
|
|
4003
|
+
};
|
|
4004
|
+
let currentEnabledIds = sessionScopedModels.length > 0
|
|
4005
|
+
? sessionScopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`)
|
|
4006
|
+
: configuredEnabledIds(availableModels);
|
|
4007
|
+
let selectionChanged = false;
|
|
4008
|
+
const updateSessionModels = (enabledIds) => {
|
|
3761
4009
|
currentEnabledIds = enabledIds === null ? null : [...enabledIds];
|
|
3762
|
-
const hasEnabledAvailableModel = enabledIds?.some((id) =>
|
|
3763
|
-
const allAvailableModelsEnabled = enabledIds !== null && [...
|
|
4010
|
+
const hasEnabledAvailableModel = enabledIds?.some((id) => availableModelIds.has(id)) ?? false;
|
|
4011
|
+
const allAvailableModelsEnabled = enabledIds !== null && [...availableModelIds].every((id) => enabledIds.includes(id));
|
|
3764
4012
|
if (enabledIds && hasEnabledAvailableModel && !allAvailableModelsEnabled) {
|
|
3765
|
-
const newScopedModels =
|
|
3766
|
-
this.session.setScopedModels(newScopedModels.map((
|
|
3767
|
-
model:
|
|
3768
|
-
thinkingLevel:
|
|
4013
|
+
const newScopedModels = resolveModelScopeFromModels(enabledIds, availableModels).scopedModels;
|
|
4014
|
+
this.session.setScopedModels(newScopedModels.map((scoped) => ({
|
|
4015
|
+
model: scoped.model,
|
|
4016
|
+
thinkingLevel: scoped.thinkingLevel,
|
|
3769
4017
|
})));
|
|
3770
4018
|
}
|
|
3771
4019
|
else {
|
|
3772
|
-
// All enabled or none enabled = no filter
|
|
3773
4020
|
this.session.setScopedModels([]);
|
|
3774
4021
|
}
|
|
3775
|
-
|
|
4022
|
+
this.updateAvailableProviderCount();
|
|
3776
4023
|
this.ui.requestRender();
|
|
3777
4024
|
};
|
|
3778
4025
|
this.showSelector((done) => {
|
|
4026
|
+
let disposed = false;
|
|
4027
|
+
let timedOut = false;
|
|
4028
|
+
const controller = new AbortController();
|
|
4029
|
+
const timeout = setTimeout(() => {
|
|
4030
|
+
timedOut = true;
|
|
4031
|
+
controller.abort();
|
|
4032
|
+
}, 15_000);
|
|
3779
4033
|
const selector = new ScopedModelsSelectorComponent({
|
|
3780
|
-
allModels,
|
|
4034
|
+
allModels: availableModels,
|
|
3781
4035
|
enabledModelIds: currentEnabledIds,
|
|
4036
|
+
refreshStatus: "Refreshing model catalogs…",
|
|
3782
4037
|
}, {
|
|
3783
|
-
onChange:
|
|
3784
|
-
|
|
4038
|
+
onChange: (enabledIds) => {
|
|
4039
|
+
selectionChanged = true;
|
|
4040
|
+
updateSessionModels(enabledIds);
|
|
3785
4041
|
},
|
|
3786
4042
|
onPersist: (enabledIds) => {
|
|
3787
|
-
// Persist to settings
|
|
3788
4043
|
const allEnabled = enabledIds !== null &&
|
|
3789
|
-
enabledIds.length ===
|
|
3790
|
-
enabledIds.every((id) =>
|
|
4044
|
+
enabledIds.length === availableModels.length &&
|
|
4045
|
+
enabledIds.every((id) => availableModelIds.has(id));
|
|
3791
4046
|
const newPatterns = enabledIds === null || allEnabled ? undefined : enabledIds;
|
|
3792
4047
|
this.settingsManager.setEnabledModels(newPatterns ? [...newPatterns] : undefined);
|
|
3793
4048
|
this.showStatus("Model selection saved to settings");
|
|
@@ -3797,7 +4052,51 @@ export class InteractiveMode {
|
|
|
3797
4052
|
this.ui.requestRender();
|
|
3798
4053
|
},
|
|
3799
4054
|
});
|
|
3800
|
-
|
|
4055
|
+
void this.session.modelRuntime
|
|
4056
|
+
.refresh({ signal: controller.signal })
|
|
4057
|
+
.then((result) => {
|
|
4058
|
+
if (disposed)
|
|
4059
|
+
return;
|
|
4060
|
+
availableModels = [...this.session.modelRuntime.getAvailableSnapshot()];
|
|
4061
|
+
availableModelIds = new Set(availableModels.map((model) => `${model.provider}/${model.id}`));
|
|
4062
|
+
if (!selectionChanged && sessionScopedModels.length === 0) {
|
|
4063
|
+
currentEnabledIds = configuredEnabledIds(availableModels);
|
|
4064
|
+
selector.updateModels(availableModels, currentEnabledIds);
|
|
4065
|
+
}
|
|
4066
|
+
else {
|
|
4067
|
+
selector.updateModels(availableModels);
|
|
4068
|
+
}
|
|
4069
|
+
if (currentEnabledIds !== null)
|
|
4070
|
+
updateSessionModels(currentEnabledIds);
|
|
4071
|
+
if (result.aborted && timedOut) {
|
|
4072
|
+
selector.setRefreshStatus("Model refresh timed out; showing cached models.", "warning");
|
|
4073
|
+
}
|
|
4074
|
+
else if (result.errors.size > 0) {
|
|
4075
|
+
selector.setRefreshStatus(`Could not refresh ${[...result.errors.keys()].join(", ")}; showing cached models.`, "warning");
|
|
4076
|
+
}
|
|
4077
|
+
else {
|
|
4078
|
+
selector.setRefreshStatus("Model catalogs refreshed.", "success");
|
|
4079
|
+
}
|
|
4080
|
+
this.ui.requestRender();
|
|
4081
|
+
})
|
|
4082
|
+
.catch((error) => {
|
|
4083
|
+
if (disposed)
|
|
4084
|
+
return;
|
|
4085
|
+
selector.setRefreshStatus(timedOut
|
|
4086
|
+
? "Model refresh timed out; showing cached models."
|
|
4087
|
+
: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
4088
|
+
this.ui.requestRender();
|
|
4089
|
+
})
|
|
4090
|
+
.finally(() => clearTimeout(timeout));
|
|
4091
|
+
return {
|
|
4092
|
+
component: selector,
|
|
4093
|
+
focus: selector,
|
|
4094
|
+
dispose: () => {
|
|
4095
|
+
disposed = true;
|
|
4096
|
+
clearTimeout(timeout);
|
|
4097
|
+
controller.abort();
|
|
4098
|
+
},
|
|
4099
|
+
};
|
|
3801
4100
|
});
|
|
3802
4101
|
}
|
|
3803
4102
|
showUserMessageSelector() {
|
|
@@ -3859,7 +4158,7 @@ export class InteractiveMode {
|
|
|
3859
4158
|
this.showSelector((done) => {
|
|
3860
4159
|
const selector = new TreeSelectorComponent(tree, realLeafId, this.ui.terminal.rows, async (entryId) => {
|
|
3861
4160
|
// Selecting the current leaf is a no-op (already there)
|
|
3862
|
-
if (entryId ===
|
|
4161
|
+
if (entryId === this.sessionManager.getLeafId()) {
|
|
3863
4162
|
done();
|
|
3864
4163
|
this.showStatus("Already at this point");
|
|
3865
4164
|
return;
|
|
@@ -3894,6 +4193,11 @@ export class InteractiveMode {
|
|
|
3894
4193
|
break;
|
|
3895
4194
|
}
|
|
3896
4195
|
}
|
|
4196
|
+
// The user committed to navigating: stop the active response first.
|
|
4197
|
+
if (this.session.isStreaming) {
|
|
4198
|
+
this.restoreQueuedMessagesToEditor();
|
|
4199
|
+
await this.session.abort();
|
|
4200
|
+
}
|
|
3897
4201
|
// Set up escape handler and status indicator if summarizing
|
|
3898
4202
|
let showingSummaryIndicator = false;
|
|
3899
4203
|
const originalOnEscape = this.defaultEditor.onEscape;
|
|
@@ -4054,7 +4358,7 @@ export class InteractiveMode {
|
|
|
4054
4358
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
4055
4359
|
}
|
|
4056
4360
|
async getLogoutProviderOptions() {
|
|
4057
|
-
return (await this.session.modelRuntime.listCredentials())
|
|
4361
|
+
return (await this.session.modelRuntime.listCredentials({ signal: AbortSignal.timeout(15_000) }))
|
|
4058
4362
|
.map(({ providerId, type }) => ({
|
|
4059
4363
|
id: providerId,
|
|
4060
4364
|
name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
|
|
@@ -4072,7 +4376,6 @@ export class InteractiveMode {
|
|
|
4072
4376
|
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4073
4377
|
}
|
|
4074
4378
|
async handleLoginCommand(providerRef) {
|
|
4075
|
-
await this.session.modelRuntime.getAvailable();
|
|
4076
4379
|
if (!providerRef) {
|
|
4077
4380
|
this.showLoginAuthTypeSelector();
|
|
4078
4381
|
return;
|
|
@@ -4186,7 +4489,14 @@ export class InteractiveMode {
|
|
|
4186
4489
|
this.showLoginAuthTypeSelector();
|
|
4187
4490
|
return;
|
|
4188
4491
|
}
|
|
4189
|
-
|
|
4492
|
+
let providerOptions;
|
|
4493
|
+
try {
|
|
4494
|
+
providerOptions = await this.getLogoutProviderOptions();
|
|
4495
|
+
}
|
|
4496
|
+
catch (error) {
|
|
4497
|
+
this.showError(`Could not read stored credentials: ${error instanceof Error ? error.message : String(error)}`);
|
|
4498
|
+
return;
|
|
4499
|
+
}
|
|
4190
4500
|
if (providerOptions.length === 0) {
|
|
4191
4501
|
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
4192
4502
|
return;
|
|
@@ -4199,7 +4509,9 @@ export class InteractiveMode {
|
|
|
4199
4509
|
return;
|
|
4200
4510
|
}
|
|
4201
4511
|
try {
|
|
4202
|
-
await this.session.modelRuntime.logout(providerOption.id
|
|
4512
|
+
await this.session.modelRuntime.logout(providerOption.id, {
|
|
4513
|
+
signal: AbortSignal.timeout(15_000),
|
|
4514
|
+
});
|
|
4203
4515
|
await this.updateAvailableProviderCount();
|
|
4204
4516
|
const message = providerOption.authType === "oauth"
|
|
4205
4517
|
? `Logged out of ${providerOption.name}`
|
|
@@ -4207,7 +4519,10 @@ export class InteractiveMode {
|
|
|
4207
4519
|
this.showStatus(message);
|
|
4208
4520
|
}
|
|
4209
4521
|
catch (error) {
|
|
4210
|
-
|
|
4522
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4523
|
+
this.showError(error instanceof CredentialSynchronizationError
|
|
4524
|
+
? `Credentials removed for ${providerOption.name}, but local model state could not be synchronized: ${message}`
|
|
4525
|
+
: `Logout failed: ${message}`);
|
|
4211
4526
|
}
|
|
4212
4527
|
}, () => {
|
|
4213
4528
|
done();
|
|
@@ -4217,12 +4532,11 @@ export class InteractiveMode {
|
|
|
4217
4532
|
});
|
|
4218
4533
|
}
|
|
4219
4534
|
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
4220
|
-
await this.session.modelRuntime.getAvailable();
|
|
4221
4535
|
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
4222
4536
|
let selectedModel;
|
|
4223
4537
|
let selectionError;
|
|
4224
4538
|
if (isUnknownModel(previousModel)) {
|
|
4225
|
-
const availableModels =
|
|
4539
|
+
const availableModels = this.session.modelRuntime.getAvailableSnapshot();
|
|
4226
4540
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
4227
4541
|
if (!hasDefaultModelProvider(providerId)) {
|
|
4228
4542
|
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
@@ -4265,6 +4579,25 @@ export class InteractiveMode {
|
|
|
4265
4579
|
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
4266
4580
|
}
|
|
4267
4581
|
}
|
|
4582
|
+
const controller = new AbortController();
|
|
4583
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
4584
|
+
void this.session.modelRuntime
|
|
4585
|
+
.refresh({ providers: [providerId], signal: controller.signal })
|
|
4586
|
+
.then((result) => {
|
|
4587
|
+
if (result.aborted) {
|
|
4588
|
+
this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
|
|
4589
|
+
}
|
|
4590
|
+
else if (result.errors.size > 0) {
|
|
4591
|
+
this.showWarning(`${actionLabel}, but its model catalog could not be refreshed; using cached models.`);
|
|
4592
|
+
}
|
|
4593
|
+
this.updateAvailableProviderCount();
|
|
4594
|
+
this.footer.invalidate();
|
|
4595
|
+
this.ui.requestRender();
|
|
4596
|
+
})
|
|
4597
|
+
.catch((error) => {
|
|
4598
|
+
this.showWarning(`${actionLabel}, but its model catalog could not be refreshed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4599
|
+
})
|
|
4600
|
+
.finally(() => clearTimeout(timeout));
|
|
4268
4601
|
}
|
|
4269
4602
|
showAmbientAuthDialog(providerOption) {
|
|
4270
4603
|
const restoreEditor = () => {
|
|
@@ -4310,7 +4643,10 @@ export class InteractiveMode {
|
|
|
4310
4643
|
catch (error) {
|
|
4311
4644
|
restoreEditor();
|
|
4312
4645
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4313
|
-
if (
|
|
4646
|
+
if (error instanceof CredentialSynchronizationError) {
|
|
4647
|
+
this.showError(`Saved API key for ${providerName}, but local model state could not be synchronized: ${errorMsg}`);
|
|
4648
|
+
}
|
|
4649
|
+
else if (errorMsg !== "Login cancelled") {
|
|
4314
4650
|
this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
|
|
4315
4651
|
}
|
|
4316
4652
|
}
|
|
@@ -4413,7 +4749,10 @@ export class InteractiveMode {
|
|
|
4413
4749
|
catch (error) {
|
|
4414
4750
|
restoreEditor();
|
|
4415
4751
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4416
|
-
if (
|
|
4752
|
+
if (error instanceof CredentialSynchronizationError) {
|
|
4753
|
+
this.showError(`Logged in to ${providerName}, but local model state could not be synchronized: ${errorMsg}`);
|
|
4754
|
+
}
|
|
4755
|
+
else if (errorMsg !== "Login cancelled") {
|
|
4417
4756
|
this.showError(`Failed to login to ${providerName}: ${errorMsg}`);
|
|
4418
4757
|
}
|
|
4419
4758
|
}
|
|
@@ -4483,7 +4822,6 @@ export class InteractiveMode {
|
|
|
4483
4822
|
try {
|
|
4484
4823
|
await this.session.reload({ beforeSessionStart: restoreChatBeforeSessionStart });
|
|
4485
4824
|
restoreChatBeforeSessionStart();
|
|
4486
|
-
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
4487
4825
|
this.keybindings.reload();
|
|
4488
4826
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
4489
4827
|
if (isExpandable(activeHeader)) {
|
|
@@ -4491,20 +4829,7 @@ export class InteractiveMode {
|
|
|
4491
4829
|
}
|
|
4492
4830
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
4493
4831
|
await this.themeController.applyFromSettings();
|
|
4494
|
-
|
|
4495
|
-
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
4496
|
-
this.defaultEditor.setPaddingX(editorPaddingX);
|
|
4497
|
-
this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
|
|
4498
|
-
if (this.editor !== this.defaultEditor) {
|
|
4499
|
-
this.editor.setPaddingX?.(editorPaddingX);
|
|
4500
|
-
this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
|
|
4501
|
-
}
|
|
4502
|
-
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
4503
|
-
const clearOnShrink = this.settingsManager.getClearOnShrink();
|
|
4504
|
-
this.ui.setClearOnShrink(clearOnShrink);
|
|
4505
|
-
if (!clearOnShrink && !this.activeStatusIndicator) {
|
|
4506
|
-
this.statusContainer.clear();
|
|
4507
|
-
}
|
|
4832
|
+
this.applyRuntimeSettings();
|
|
4508
4833
|
this.setupAutocompleteProvider();
|
|
4509
4834
|
const runner = this.session.extensionRunner;
|
|
4510
4835
|
this.setupExtensionShortcuts(runner);
|
|
@@ -4700,7 +5025,7 @@ export class InteractiveMode {
|
|
|
4700
5025
|
}
|
|
4701
5026
|
}
|
|
4702
5027
|
}
|
|
4703
|
-
async handleCopyCommand() {
|
|
5028
|
+
async handleCopyCommand(options = {}) {
|
|
4704
5029
|
const text = this.session.getLastAssistantText();
|
|
4705
5030
|
if (!text) {
|
|
4706
5031
|
this.showError("No agent messages to copy yet.");
|
|
@@ -4708,7 +5033,12 @@ export class InteractiveMode {
|
|
|
4708
5033
|
}
|
|
4709
5034
|
try {
|
|
4710
5035
|
await copyToClipboard(text);
|
|
4711
|
-
|
|
5036
|
+
if (options.flashConfirmation && this.ui instanceof TuiAltScreen) {
|
|
5037
|
+
this.ui.flash("Copied!");
|
|
5038
|
+
}
|
|
5039
|
+
else {
|
|
5040
|
+
this.showStatus("Copied last agent message to clipboard");
|
|
5041
|
+
}
|
|
4712
5042
|
}
|
|
4713
5043
|
catch (error) {
|
|
4714
5044
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
@@ -5071,6 +5401,7 @@ export class InteractiveMode {
|
|
|
5071
5401
|
}
|
|
5072
5402
|
}
|
|
5073
5403
|
stop() {
|
|
5404
|
+
this.disposeActiveSelector();
|
|
5074
5405
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
5075
5406
|
this.ui.terminal.setProgress(false);
|
|
5076
5407
|
}
|
|
@@ -5083,7 +5414,7 @@ export class InteractiveMode {
|
|
|
5083
5414
|
this.unsubscribe();
|
|
5084
5415
|
}
|
|
5085
5416
|
if (this.isInitialized) {
|
|
5086
|
-
this.
|
|
5417
|
+
this.stopInteractiveTui();
|
|
5087
5418
|
this.isInitialized = false;
|
|
5088
5419
|
}
|
|
5089
5420
|
this.unregisterSignalHandlers();
|