@averyyy/pi-coding-agent 0.83.0-piclient.1 → 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 +227 -0
- package/README.md +5 -0
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +22 -3
- 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 +5 -15
- package/dist/cli/credential-print.d.ts.map +1 -1
- package/dist/cli/credential-print.js +42 -82
- package/dist/cli/credential-print.js.map +1 -1
- 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-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 +2 -11
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +61 -59
- 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 +2 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +3 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +26 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.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 +30 -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 +63 -36
- 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.map +1 -1
- package/dist/core/resource-loader.js +2 -2
- 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 +21 -7
- 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 +70 -25
- 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 +16 -4
- 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 +461 -144
- 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 +4 -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 +7 -5
- package/docs/extensions.md +30 -5
- package/docs/json.md +19 -14
- package/docs/keybindings.md +46 -14
- package/docs/models.md +24 -3
- package/docs/providers.md +9 -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/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 +2 -1
- 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 +1 -1
- package/examples/sdk/README.md +1 -1
- package/package.json +13 -6
|
@@ -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);
|
|
@@ -1288,8 +1450,12 @@ export class InteractiveMode {
|
|
|
1288
1450
|
this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
|
|
1289
1451
|
this.showStartupNoticesIfNeeded();
|
|
1290
1452
|
}
|
|
1453
|
+
applyFullscreenScrollbarSetting() {
|
|
1454
|
+
this.transcriptScrollView?.setScrollbar(this.settingsManager.getFullscreenScrollbar());
|
|
1455
|
+
}
|
|
1291
1456
|
applyRuntimeSettings() {
|
|
1292
1457
|
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
1458
|
+
this.applyFullscreenScrollbarSetting();
|
|
1293
1459
|
this.footer.setSession(this.session);
|
|
1294
1460
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
1295
1461
|
this.footerDataProvider.setCwd(this.sessionManager.getCwd());
|
|
@@ -1353,6 +1519,9 @@ export class InteractiveMode {
|
|
|
1353
1519
|
getRegisteredToolDefinition(toolName) {
|
|
1354
1520
|
return this.session.getToolDefinition(toolName);
|
|
1355
1521
|
}
|
|
1522
|
+
getMarkdownTransformers() {
|
|
1523
|
+
return [this.mermaidMarkdownTransformer, ...this.session.extensionRunner.getMarkdownTransformers()];
|
|
1524
|
+
}
|
|
1356
1525
|
/**
|
|
1357
1526
|
* Set up keyboard shortcuts registered by extensions.
|
|
1358
1527
|
*/
|
|
@@ -1432,7 +1601,7 @@ export class InteractiveMode {
|
|
|
1432
1601
|
this.activeStatusIndicator?.dispose();
|
|
1433
1602
|
this.activeStatusIndicator = undefined;
|
|
1434
1603
|
this.statusContainer.clear();
|
|
1435
|
-
if (hadActiveStatusIndicator && this.ui.getClearOnShrink()) {
|
|
1604
|
+
if (hadActiveStatusIndicator && this.options.tuiMode === "regular" && this.ui.getClearOnShrink()) {
|
|
1436
1605
|
this.statusContainer.addChild(this.idleStatus);
|
|
1437
1606
|
}
|
|
1438
1607
|
}
|
|
@@ -1589,22 +1758,16 @@ export class InteractiveMode {
|
|
|
1589
1758
|
if (this.customFooter?.dispose) {
|
|
1590
1759
|
this.customFooter.dispose();
|
|
1591
1760
|
}
|
|
1592
|
-
|
|
1593
|
-
if (this.customFooter) {
|
|
1594
|
-
this.ui.removeChild(this.customFooter);
|
|
1595
|
-
}
|
|
1596
|
-
else {
|
|
1597
|
-
this.ui.removeChild(this.footer);
|
|
1598
|
-
}
|
|
1761
|
+
this.footerContainer.clear();
|
|
1599
1762
|
if (factory) {
|
|
1600
1763
|
// Create and add custom footer, passing the data provider
|
|
1601
1764
|
this.customFooter = factory(this.ui, theme, this.footerDataProvider);
|
|
1602
|
-
this.
|
|
1765
|
+
this.footerContainer.addChild(this.customFooter);
|
|
1603
1766
|
}
|
|
1604
1767
|
else {
|
|
1605
1768
|
// Restore built-in footer
|
|
1606
1769
|
this.customFooter = undefined;
|
|
1607
|
-
this.
|
|
1770
|
+
this.footerContainer.addChild(this.footer);
|
|
1608
1771
|
}
|
|
1609
1772
|
this.ui.requestRender();
|
|
1610
1773
|
}
|
|
@@ -1650,18 +1813,23 @@ export class InteractiveMode {
|
|
|
1650
1813
|
this.ui.requestRender();
|
|
1651
1814
|
}
|
|
1652
1815
|
addExtensionTerminalInputListener(handler) {
|
|
1653
|
-
const
|
|
1654
|
-
this.
|
|
1816
|
+
const subscription = { handler, unsubscribe: this.ui.addInputListener(handler) };
|
|
1817
|
+
this.extensionTerminalInputSubscriptions.add(subscription);
|
|
1655
1818
|
return () => {
|
|
1656
|
-
unsubscribe();
|
|
1657
|
-
this.
|
|
1819
|
+
subscription.unsubscribe();
|
|
1820
|
+
this.extensionTerminalInputSubscriptions.delete(subscription);
|
|
1658
1821
|
};
|
|
1659
1822
|
}
|
|
1660
|
-
|
|
1661
|
-
for (const
|
|
1662
|
-
unsubscribe();
|
|
1823
|
+
rebindExtensionTerminalInputListeners() {
|
|
1824
|
+
for (const subscription of this.extensionTerminalInputSubscriptions) {
|
|
1825
|
+
subscription.unsubscribe();
|
|
1826
|
+
subscription.unsubscribe = this.ui.addInputListener(subscription.handler);
|
|
1663
1827
|
}
|
|
1664
|
-
|
|
1828
|
+
}
|
|
1829
|
+
clearExtensionTerminalInputListeners() {
|
|
1830
|
+
for (const subscription of this.extensionTerminalInputSubscriptions)
|
|
1831
|
+
subscription.unsubscribe();
|
|
1832
|
+
this.extensionTerminalInputSubscriptions.clear();
|
|
1665
1833
|
}
|
|
1666
1834
|
/**
|
|
1667
1835
|
* Create the ExtensionUIContext for extensions.
|
|
@@ -1756,6 +1924,7 @@ export class InteractiveMode {
|
|
|
1756
1924
|
this.hideExtensionSelector();
|
|
1757
1925
|
resolve(undefined);
|
|
1758
1926
|
}, { tui: this.ui, timeout: opts?.timeout, onToggleToolsExpanded: () => this.toggleToolOutputExpansion() });
|
|
1927
|
+
this.disposeActiveSelector();
|
|
1759
1928
|
this.editorContainer.clear();
|
|
1760
1929
|
this.editorContainer.addChild(this.extensionSelector);
|
|
1761
1930
|
this.ui.setFocus(this.extensionSelector);
|
|
@@ -1807,6 +1976,7 @@ export class InteractiveMode {
|
|
|
1807
1976
|
this.hideExtensionInput();
|
|
1808
1977
|
resolve(undefined);
|
|
1809
1978
|
}, { tui: this.ui, timeout: opts?.timeout });
|
|
1979
|
+
this.disposeActiveSelector();
|
|
1810
1980
|
this.editorContainer.clear();
|
|
1811
1981
|
this.editorContainer.addChild(this.extensionInput);
|
|
1812
1982
|
this.ui.setFocus(this.extensionInput);
|
|
@@ -1836,6 +2006,7 @@ export class InteractiveMode {
|
|
|
1836
2006
|
this.hideExtensionEditor();
|
|
1837
2007
|
resolve(undefined);
|
|
1838
2008
|
}, undefined, this.settingsManager.getExternalEditorCommand());
|
|
2009
|
+
this.disposeActiveSelector();
|
|
1839
2010
|
this.editorContainer.clear();
|
|
1840
2011
|
this.editorContainer.addChild(this.extensionEditor);
|
|
1841
2012
|
this.ui.setFocus(this.extensionEditor);
|
|
@@ -1860,6 +2031,7 @@ export class InteractiveMode {
|
|
|
1860
2031
|
this.editorComponentFactory = factory;
|
|
1861
2032
|
// Save text from current editor before switching
|
|
1862
2033
|
const currentText = this.editor.getText();
|
|
2034
|
+
this.disposeActiveSelector();
|
|
1863
2035
|
this.editorContainer.clear();
|
|
1864
2036
|
if (factory) {
|
|
1865
2037
|
// Create the custom editor with tui, theme, and keybindings
|
|
@@ -1876,6 +2048,9 @@ export class InteractiveMode {
|
|
|
1876
2048
|
if (newEditor.setPaddingX !== undefined) {
|
|
1877
2049
|
newEditor.setPaddingX(this.defaultEditor.getPaddingX());
|
|
1878
2050
|
}
|
|
2051
|
+
if (newEditor.setAutocompleteMaxVisible !== undefined) {
|
|
2052
|
+
newEditor.setAutocompleteMaxVisible(this.defaultEditor.getAutocompleteMaxVisible());
|
|
2053
|
+
}
|
|
1879
2054
|
// Set autocomplete if supported
|
|
1880
2055
|
if (newEditor.setAutocompleteProvider && this.autocompleteProvider) {
|
|
1881
2056
|
newEditor.setAutocompleteProvider(this.autocompleteProvider);
|
|
@@ -1980,6 +2155,7 @@ export class InteractiveMode {
|
|
|
1980
2155
|
options?.onHandle?.(handle);
|
|
1981
2156
|
}
|
|
1982
2157
|
else {
|
|
2158
|
+
this.disposeActiveSelector();
|
|
1983
2159
|
this.editorContainer.clear();
|
|
1984
2160
|
this.editorContainer.addChild(component);
|
|
1985
2161
|
this.ui.setFocus(component);
|
|
@@ -2066,7 +2242,7 @@ export class InteractiveMode {
|
|
|
2066
2242
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
2067
2243
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
2068
2244
|
this.defaultEditor.onAction("app.editor.external", () => void this.handleOpenExternalEditor());
|
|
2069
|
-
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand());
|
|
2245
|
+
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand({ flashConfirmation: true }));
|
|
2070
2246
|
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
2071
2247
|
this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
|
|
2072
2248
|
this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
|
|
@@ -2086,6 +2262,22 @@ export class InteractiveMode {
|
|
|
2086
2262
|
void this.handleClipboardPaste();
|
|
2087
2263
|
};
|
|
2088
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
|
+
}
|
|
2089
2281
|
async handleClipboardPaste() {
|
|
2090
2282
|
try {
|
|
2091
2283
|
const image = await readClipboardImage();
|
|
@@ -2360,17 +2552,17 @@ export class InteractiveMode {
|
|
|
2360
2552
|
this.ui.requestRender();
|
|
2361
2553
|
}
|
|
2362
2554
|
else if (event.message.role === "assistant") {
|
|
2363
|
-
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());
|
|
2364
2556
|
this.streamingMessage = event.message;
|
|
2365
2557
|
this.chatContainer.addChild(this.streamingComponent);
|
|
2366
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2558
|
+
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2367
2559
|
this.ui.requestRender();
|
|
2368
2560
|
}
|
|
2369
2561
|
break;
|
|
2370
2562
|
case "message_update":
|
|
2371
2563
|
if (this.streamingComponent && event.message.role === "assistant") {
|
|
2372
2564
|
this.streamingMessage = event.message;
|
|
2373
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2565
|
+
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2374
2566
|
for (const content of this.streamingMessage.content) {
|
|
2375
2567
|
if (content.type === "toolCall") {
|
|
2376
2568
|
if (!this.pendingTools.has(content.id)) {
|
|
@@ -2407,7 +2599,7 @@ export class InteractiveMode {
|
|
|
2407
2599
|
: "Operation aborted";
|
|
2408
2600
|
this.streamingMessage.errorMessage = errorMessage;
|
|
2409
2601
|
}
|
|
2410
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2602
|
+
this.streamingComponent.updateContent(this.streamingMessage, false);
|
|
2411
2603
|
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
2412
2604
|
if (!errorMessage) {
|
|
2413
2605
|
errorMessage = this.streamingMessage.errorMessage || "Error";
|
|
@@ -2682,12 +2874,12 @@ export class InteractiveMode {
|
|
|
2682
2874
|
// Render user message separately if present
|
|
2683
2875
|
if (skillBlock.userMessage) {
|
|
2684
2876
|
this.chatContainer.addChild(new Spacer(1));
|
|
2685
|
-
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2877
|
+
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2686
2878
|
this.chatContainer.addChild(userComponent);
|
|
2687
2879
|
}
|
|
2688
2880
|
}
|
|
2689
2881
|
else {
|
|
2690
|
-
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2882
|
+
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2691
2883
|
this.chatContainer.addChild(userComponent);
|
|
2692
2884
|
}
|
|
2693
2885
|
if (options?.populateHistory) {
|
|
@@ -2697,7 +2889,7 @@ export class InteractiveMode {
|
|
|
2697
2889
|
break;
|
|
2698
2890
|
}
|
|
2699
2891
|
case "assistant": {
|
|
2700
|
-
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());
|
|
2701
2893
|
this.chatContainer.addChild(assistantComponent);
|
|
2702
2894
|
break;
|
|
2703
2895
|
}
|
|
@@ -3131,6 +3323,8 @@ export class InteractiveMode {
|
|
|
3131
3323
|
this.setToolsExpanded(!this.toolOutputExpanded);
|
|
3132
3324
|
}
|
|
3133
3325
|
setToolsExpanded(expanded) {
|
|
3326
|
+
if (expanded === this.toolOutputExpanded)
|
|
3327
|
+
return;
|
|
3134
3328
|
this.toolOutputExpanded = expanded;
|
|
3135
3329
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
3136
3330
|
if (isExpandable(activeHeader)) {
|
|
@@ -3186,7 +3380,7 @@ export class InteractiveMode {
|
|
|
3186
3380
|
}
|
|
3187
3381
|
showError(errorMessage) {
|
|
3188
3382
|
this.chatContainer.addChild(new Spacer(1));
|
|
3189
|
-
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`),
|
|
3383
|
+
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), this.outputPad, 0));
|
|
3190
3384
|
this.ui.requestRender();
|
|
3191
3385
|
}
|
|
3192
3386
|
showWarning(warningMessage) {
|
|
@@ -3396,25 +3590,43 @@ export class InteractiveMode {
|
|
|
3396
3590
|
// =========================================================================
|
|
3397
3591
|
// Selectors
|
|
3398
3592
|
// =========================================================================
|
|
3593
|
+
disposeActiveSelector() {
|
|
3594
|
+
const dispose = this.activeSelectorDispose;
|
|
3595
|
+
this.activeSelectorToken = undefined;
|
|
3596
|
+
this.activeSelectorDispose = undefined;
|
|
3597
|
+
dispose?.();
|
|
3598
|
+
}
|
|
3399
3599
|
/**
|
|
3400
3600
|
* Shows a selector component in place of the editor.
|
|
3401
3601
|
* @param create Factory that receives a `done` callback and returns the component and focus target
|
|
3402
3602
|
*/
|
|
3403
3603
|
showSelector(create) {
|
|
3604
|
+
const token = {};
|
|
3605
|
+
let dispose;
|
|
3404
3606
|
const done = () => {
|
|
3607
|
+
dispose?.();
|
|
3608
|
+
if (this.activeSelectorToken !== token)
|
|
3609
|
+
return;
|
|
3610
|
+
this.activeSelectorToken = undefined;
|
|
3611
|
+
this.activeSelectorDispose = undefined;
|
|
3405
3612
|
this.editorContainer.clear();
|
|
3406
3613
|
this.editorContainer.addChild(this.editor);
|
|
3407
3614
|
this.ui.setFocus(this.editor);
|
|
3408
3615
|
};
|
|
3409
|
-
const
|
|
3616
|
+
const created = create(done);
|
|
3617
|
+
dispose = created.dispose;
|
|
3618
|
+
this.disposeActiveSelector();
|
|
3619
|
+
this.activeSelectorToken = token;
|
|
3620
|
+
this.activeSelectorDispose = dispose;
|
|
3410
3621
|
this.editorContainer.clear();
|
|
3411
|
-
this.editorContainer.addChild(component);
|
|
3412
|
-
this.ui.setFocus(focus);
|
|
3622
|
+
this.editorContainer.addChild(created.component);
|
|
3623
|
+
this.ui.setFocus(created.focus);
|
|
3413
3624
|
this.ui.requestRender();
|
|
3414
3625
|
}
|
|
3415
3626
|
showSettingsSelector() {
|
|
3416
3627
|
this.showSelector((done) => {
|
|
3417
|
-
|
|
3628
|
+
let selector;
|
|
3629
|
+
selector = new SettingsSelectorComponent({
|
|
3418
3630
|
autoCompact: this.session.autoCompactionEnabled,
|
|
3419
3631
|
showImages: this.settingsManager.getShowImages(),
|
|
3420
3632
|
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
@@ -3431,6 +3643,7 @@ export class InteractiveMode {
|
|
|
3431
3643
|
terminalTheme: this.themeController.getTerminalTheme(),
|
|
3432
3644
|
availableThemes: getAvailableThemes(),
|
|
3433
3645
|
hideThinkingBlock: this.hideThinkingBlock,
|
|
3646
|
+
mermaidRenderingMode: this.settingsManager.getMermaidRenderingMode(),
|
|
3434
3647
|
collapseChangelog: this.settingsManager.getCollapseChangelog(),
|
|
3435
3648
|
enableInstallTelemetry: this.settingsManager.getEnableInstallTelemetry(),
|
|
3436
3649
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
@@ -3444,6 +3657,8 @@ export class InteractiveMode {
|
|
|
3444
3657
|
quietStartup: this.settingsManager.getQuietStartup(),
|
|
3445
3658
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
3446
3659
|
showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
|
|
3660
|
+
tuiMode: this.ui.mode,
|
|
3661
|
+
fullscreenScrollbar: this.settingsManager.getFullscreenScrollbar(),
|
|
3447
3662
|
warnings: this.settingsManager.getWarnings(),
|
|
3448
3663
|
}, {
|
|
3449
3664
|
onAutoCompactChange: (enabled) => {
|
|
@@ -3512,6 +3727,11 @@ export class InteractiveMode {
|
|
|
3512
3727
|
this.chatContainer.clear();
|
|
3513
3728
|
this.rebuildChatFromMessages();
|
|
3514
3729
|
},
|
|
3730
|
+
onMermaidRenderingModeChange: (mode) => {
|
|
3731
|
+
this.settingsManager.setMermaidRenderingMode(mode);
|
|
3732
|
+
this.chatContainer.invalidate();
|
|
3733
|
+
this.ui.requestRender();
|
|
3734
|
+
},
|
|
3515
3735
|
onShowCacheMissNoticesChange: (shown) => {
|
|
3516
3736
|
this.settingsManager.setShowCacheMissNotices(shown);
|
|
3517
3737
|
this.rebuildChatFromMessages();
|
|
@@ -3581,6 +3801,21 @@ export class InteractiveMode {
|
|
|
3581
3801
|
onShowTerminalProgressChange: (enabled) => {
|
|
3582
3802
|
this.settingsManager.setShowTerminalProgress(enabled);
|
|
3583
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
|
+
},
|
|
3584
3819
|
onWarningsChange: (warnings) => {
|
|
3585
3820
|
this.settingsManager.setWarnings(warnings);
|
|
3586
3821
|
},
|
|
@@ -3615,20 +3850,37 @@ export class InteractiveMode {
|
|
|
3615
3850
|
this.showModelSelector(searchTerm);
|
|
3616
3851
|
}
|
|
3617
3852
|
async findExactModelMatch(searchTerm) {
|
|
3618
|
-
const
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
if (this.session.scopedModels.length > 0)
|
|
3623
|
-
return
|
|
3624
|
-
|
|
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);
|
|
3625
3866
|
try {
|
|
3626
|
-
await this.session.modelRuntime.refresh();
|
|
3627
|
-
|
|
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
|
+
}
|
|
3628
3874
|
}
|
|
3629
|
-
catch {
|
|
3630
|
-
|
|
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)}`);
|
|
3631
3879
|
}
|
|
3880
|
+
finally {
|
|
3881
|
+
clearTimeout(timeout);
|
|
3882
|
+
}
|
|
3883
|
+
return findExactModelReferenceMatch(searchTerm, [...this.session.modelRuntime.getAvailableSnapshot()]);
|
|
3632
3884
|
}
|
|
3633
3885
|
/** Update the footer's available provider count from the current snapshot without refreshing catalogs. */
|
|
3634
3886
|
updateAvailableProviderCount() {
|
|
@@ -3730,73 +3982,67 @@ export class InteractiveMode {
|
|
|
3730
3982
|
done();
|
|
3731
3983
|
this.ui.requestRender();
|
|
3732
3984
|
}, initialSearchInput);
|
|
3733
|
-
return { component: selector, focus: selector };
|
|
3985
|
+
return { component: selector, focus: selector, dispose: () => selector.dispose() };
|
|
3734
3986
|
});
|
|
3735
3987
|
}
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
const allModels = [...(await this.session.modelRuntime.getAvailable())];
|
|
3740
|
-
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}`));
|
|
3741
3991
|
const configuredPatterns = this.settingsManager.getEnabledModels();
|
|
3742
3992
|
const sessionScopedModels = this.session.scopedModels;
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
currentEnabledIds = configuredScope.scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3760
|
-
}
|
|
3761
|
-
for (const diagnostic of configuredScope?.diagnostics ?? []) {
|
|
3762
|
-
if (diagnostic.code !== "no-match")
|
|
3763
|
-
continue;
|
|
3764
|
-
currentEnabledIds ??= [];
|
|
3765
|
-
if (!currentEnabledIds.includes(diagnostic.pattern))
|
|
3766
|
-
currentEnabledIds.push(diagnostic.pattern);
|
|
3767
|
-
}
|
|
3768
|
-
// Helper to update session's scoped models (session-only, no persist)
|
|
3769
|
-
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) => {
|
|
3770
4009
|
currentEnabledIds = enabledIds === null ? null : [...enabledIds];
|
|
3771
|
-
const hasEnabledAvailableModel = enabledIds?.some((id) =>
|
|
3772
|
-
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));
|
|
3773
4012
|
if (enabledIds && hasEnabledAvailableModel && !allAvailableModelsEnabled) {
|
|
3774
|
-
const newScopedModels =
|
|
3775
|
-
this.session.setScopedModels(newScopedModels.map((
|
|
3776
|
-
model:
|
|
3777
|
-
thinkingLevel:
|
|
4013
|
+
const newScopedModels = resolveModelScopeFromModels(enabledIds, availableModels).scopedModels;
|
|
4014
|
+
this.session.setScopedModels(newScopedModels.map((scoped) => ({
|
|
4015
|
+
model: scoped.model,
|
|
4016
|
+
thinkingLevel: scoped.thinkingLevel,
|
|
3778
4017
|
})));
|
|
3779
4018
|
}
|
|
3780
4019
|
else {
|
|
3781
|
-
// All enabled or none enabled = no filter
|
|
3782
4020
|
this.session.setScopedModels([]);
|
|
3783
4021
|
}
|
|
3784
|
-
|
|
4022
|
+
this.updateAvailableProviderCount();
|
|
3785
4023
|
this.ui.requestRender();
|
|
3786
4024
|
};
|
|
3787
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);
|
|
3788
4033
|
const selector = new ScopedModelsSelectorComponent({
|
|
3789
|
-
allModels,
|
|
4034
|
+
allModels: availableModels,
|
|
3790
4035
|
enabledModelIds: currentEnabledIds,
|
|
4036
|
+
refreshStatus: "Refreshing model catalogs…",
|
|
3791
4037
|
}, {
|
|
3792
|
-
onChange:
|
|
3793
|
-
|
|
4038
|
+
onChange: (enabledIds) => {
|
|
4039
|
+
selectionChanged = true;
|
|
4040
|
+
updateSessionModels(enabledIds);
|
|
3794
4041
|
},
|
|
3795
4042
|
onPersist: (enabledIds) => {
|
|
3796
|
-
// Persist to settings
|
|
3797
4043
|
const allEnabled = enabledIds !== null &&
|
|
3798
|
-
enabledIds.length ===
|
|
3799
|
-
enabledIds.every((id) =>
|
|
4044
|
+
enabledIds.length === availableModels.length &&
|
|
4045
|
+
enabledIds.every((id) => availableModelIds.has(id));
|
|
3800
4046
|
const newPatterns = enabledIds === null || allEnabled ? undefined : enabledIds;
|
|
3801
4047
|
this.settingsManager.setEnabledModels(newPatterns ? [...newPatterns] : undefined);
|
|
3802
4048
|
this.showStatus("Model selection saved to settings");
|
|
@@ -3806,7 +4052,51 @@ export class InteractiveMode {
|
|
|
3806
4052
|
this.ui.requestRender();
|
|
3807
4053
|
},
|
|
3808
4054
|
});
|
|
3809
|
-
|
|
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
|
+
};
|
|
3810
4100
|
});
|
|
3811
4101
|
}
|
|
3812
4102
|
showUserMessageSelector() {
|
|
@@ -4068,7 +4358,7 @@ export class InteractiveMode {
|
|
|
4068
4358
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
4069
4359
|
}
|
|
4070
4360
|
async getLogoutProviderOptions() {
|
|
4071
|
-
return (await this.session.modelRuntime.listCredentials())
|
|
4361
|
+
return (await this.session.modelRuntime.listCredentials({ signal: AbortSignal.timeout(15_000) }))
|
|
4072
4362
|
.map(({ providerId, type }) => ({
|
|
4073
4363
|
id: providerId,
|
|
4074
4364
|
name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
|
|
@@ -4086,7 +4376,6 @@ export class InteractiveMode {
|
|
|
4086
4376
|
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4087
4377
|
}
|
|
4088
4378
|
async handleLoginCommand(providerRef) {
|
|
4089
|
-
await this.session.modelRuntime.getAvailable();
|
|
4090
4379
|
if (!providerRef) {
|
|
4091
4380
|
this.showLoginAuthTypeSelector();
|
|
4092
4381
|
return;
|
|
@@ -4200,7 +4489,14 @@ export class InteractiveMode {
|
|
|
4200
4489
|
this.showLoginAuthTypeSelector();
|
|
4201
4490
|
return;
|
|
4202
4491
|
}
|
|
4203
|
-
|
|
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
|
+
}
|
|
4204
4500
|
if (providerOptions.length === 0) {
|
|
4205
4501
|
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
4206
4502
|
return;
|
|
@@ -4213,7 +4509,9 @@ export class InteractiveMode {
|
|
|
4213
4509
|
return;
|
|
4214
4510
|
}
|
|
4215
4511
|
try {
|
|
4216
|
-
await this.session.modelRuntime.logout(providerOption.id
|
|
4512
|
+
await this.session.modelRuntime.logout(providerOption.id, {
|
|
4513
|
+
signal: AbortSignal.timeout(15_000),
|
|
4514
|
+
});
|
|
4217
4515
|
await this.updateAvailableProviderCount();
|
|
4218
4516
|
const message = providerOption.authType === "oauth"
|
|
4219
4517
|
? `Logged out of ${providerOption.name}`
|
|
@@ -4221,7 +4519,10 @@ export class InteractiveMode {
|
|
|
4221
4519
|
this.showStatus(message);
|
|
4222
4520
|
}
|
|
4223
4521
|
catch (error) {
|
|
4224
|
-
|
|
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}`);
|
|
4225
4526
|
}
|
|
4226
4527
|
}, () => {
|
|
4227
4528
|
done();
|
|
@@ -4231,12 +4532,11 @@ export class InteractiveMode {
|
|
|
4231
4532
|
});
|
|
4232
4533
|
}
|
|
4233
4534
|
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
4234
|
-
await this.session.modelRuntime.getAvailable();
|
|
4235
4535
|
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
4236
4536
|
let selectedModel;
|
|
4237
4537
|
let selectionError;
|
|
4238
4538
|
if (isUnknownModel(previousModel)) {
|
|
4239
|
-
const availableModels =
|
|
4539
|
+
const availableModels = this.session.modelRuntime.getAvailableSnapshot();
|
|
4240
4540
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
4241
4541
|
if (!hasDefaultModelProvider(providerId)) {
|
|
4242
4542
|
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
@@ -4279,6 +4579,25 @@ export class InteractiveMode {
|
|
|
4279
4579
|
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
4280
4580
|
}
|
|
4281
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));
|
|
4282
4601
|
}
|
|
4283
4602
|
showAmbientAuthDialog(providerOption) {
|
|
4284
4603
|
const restoreEditor = () => {
|
|
@@ -4324,7 +4643,10 @@ export class InteractiveMode {
|
|
|
4324
4643
|
catch (error) {
|
|
4325
4644
|
restoreEditor();
|
|
4326
4645
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4327
|
-
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") {
|
|
4328
4650
|
this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
|
|
4329
4651
|
}
|
|
4330
4652
|
}
|
|
@@ -4427,7 +4749,10 @@ export class InteractiveMode {
|
|
|
4427
4749
|
catch (error) {
|
|
4428
4750
|
restoreEditor();
|
|
4429
4751
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4430
|
-
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") {
|
|
4431
4756
|
this.showError(`Failed to login to ${providerName}: ${errorMsg}`);
|
|
4432
4757
|
}
|
|
4433
4758
|
}
|
|
@@ -4497,7 +4822,6 @@ export class InteractiveMode {
|
|
|
4497
4822
|
try {
|
|
4498
4823
|
await this.session.reload({ beforeSessionStart: restoreChatBeforeSessionStart });
|
|
4499
4824
|
restoreChatBeforeSessionStart();
|
|
4500
|
-
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
4501
4825
|
this.keybindings.reload();
|
|
4502
4826
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
4503
4827
|
if (isExpandable(activeHeader)) {
|
|
@@ -4505,20 +4829,7 @@ export class InteractiveMode {
|
|
|
4505
4829
|
}
|
|
4506
4830
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
4507
4831
|
await this.themeController.applyFromSettings();
|
|
4508
|
-
|
|
4509
|
-
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
4510
|
-
this.defaultEditor.setPaddingX(editorPaddingX);
|
|
4511
|
-
this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
|
|
4512
|
-
if (this.editor !== this.defaultEditor) {
|
|
4513
|
-
this.editor.setPaddingX?.(editorPaddingX);
|
|
4514
|
-
this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
|
|
4515
|
-
}
|
|
4516
|
-
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
4517
|
-
const clearOnShrink = this.settingsManager.getClearOnShrink();
|
|
4518
|
-
this.ui.setClearOnShrink(clearOnShrink);
|
|
4519
|
-
if (!clearOnShrink && !this.activeStatusIndicator) {
|
|
4520
|
-
this.statusContainer.clear();
|
|
4521
|
-
}
|
|
4832
|
+
this.applyRuntimeSettings();
|
|
4522
4833
|
this.setupAutocompleteProvider();
|
|
4523
4834
|
const runner = this.session.extensionRunner;
|
|
4524
4835
|
this.setupExtensionShortcuts(runner);
|
|
@@ -4714,7 +5025,7 @@ export class InteractiveMode {
|
|
|
4714
5025
|
}
|
|
4715
5026
|
}
|
|
4716
5027
|
}
|
|
4717
|
-
async handleCopyCommand() {
|
|
5028
|
+
async handleCopyCommand(options = {}) {
|
|
4718
5029
|
const text = this.session.getLastAssistantText();
|
|
4719
5030
|
if (!text) {
|
|
4720
5031
|
this.showError("No agent messages to copy yet.");
|
|
@@ -4722,7 +5033,12 @@ export class InteractiveMode {
|
|
|
4722
5033
|
}
|
|
4723
5034
|
try {
|
|
4724
5035
|
await copyToClipboard(text);
|
|
4725
|
-
|
|
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
|
+
}
|
|
4726
5042
|
}
|
|
4727
5043
|
catch (error) {
|
|
4728
5044
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
@@ -5085,6 +5401,7 @@ export class InteractiveMode {
|
|
|
5085
5401
|
}
|
|
5086
5402
|
}
|
|
5087
5403
|
stop() {
|
|
5404
|
+
this.disposeActiveSelector();
|
|
5088
5405
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
5089
5406
|
this.ui.terminal.setProgress(false);
|
|
5090
5407
|
}
|
|
@@ -5097,7 +5414,7 @@ export class InteractiveMode {
|
|
|
5097
5414
|
this.unsubscribe();
|
|
5098
5415
|
}
|
|
5099
5416
|
if (this.isInitialized) {
|
|
5100
|
-
this.
|
|
5417
|
+
this.stopInteractiveTui();
|
|
5101
5418
|
this.isInitialized = false;
|
|
5102
5419
|
}
|
|
5103
5420
|
this.unregisterSignalHandlers();
|