@earendil-works/pi-coding-agent 0.83.0 → 0.84.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 +1 -10
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +72 -70
- 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/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/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 +1 -1
- 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/interactive-mode.d.ts +36 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +454 -138
- 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/npm-shrinkwrap.json +64 -20
- 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";
|
|
@@ -17,7 +18,8 @@ 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
20
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
20
|
-
import { defaultModelPerProvider, findExactModelReferenceMatch,
|
|
21
|
+
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScopeFromModels, } from "../../core/model-resolver.js";
|
|
22
|
+
import { CredentialSynchronizationError } from "../../core/model-runtime.js";
|
|
21
23
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
22
24
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
23
25
|
import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.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) => {
|
|
@@ -1288,8 +1449,12 @@ export class InteractiveMode {
|
|
|
1288
1449
|
this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
|
|
1289
1450
|
this.showStartupNoticesIfNeeded();
|
|
1290
1451
|
}
|
|
1452
|
+
applyFullscreenScrollbarSetting() {
|
|
1453
|
+
this.transcriptScrollView?.setScrollbar(this.settingsManager.getFullscreenScrollbar());
|
|
1454
|
+
}
|
|
1291
1455
|
applyRuntimeSettings() {
|
|
1292
1456
|
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
1457
|
+
this.applyFullscreenScrollbarSetting();
|
|
1293
1458
|
this.footer.setSession(this.session);
|
|
1294
1459
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
1295
1460
|
this.footerDataProvider.setCwd(this.sessionManager.getCwd());
|
|
@@ -1353,6 +1518,9 @@ export class InteractiveMode {
|
|
|
1353
1518
|
getRegisteredToolDefinition(toolName) {
|
|
1354
1519
|
return this.session.getToolDefinition(toolName);
|
|
1355
1520
|
}
|
|
1521
|
+
getMarkdownTransformers() {
|
|
1522
|
+
return [this.mermaidMarkdownTransformer, ...this.session.extensionRunner.getMarkdownTransformers()];
|
|
1523
|
+
}
|
|
1356
1524
|
/**
|
|
1357
1525
|
* Set up keyboard shortcuts registered by extensions.
|
|
1358
1526
|
*/
|
|
@@ -1432,7 +1600,7 @@ export class InteractiveMode {
|
|
|
1432
1600
|
this.activeStatusIndicator?.dispose();
|
|
1433
1601
|
this.activeStatusIndicator = undefined;
|
|
1434
1602
|
this.statusContainer.clear();
|
|
1435
|
-
if (hadActiveStatusIndicator && this.ui.getClearOnShrink()) {
|
|
1603
|
+
if (hadActiveStatusIndicator && this.options.tuiMode === "regular" && this.ui.getClearOnShrink()) {
|
|
1436
1604
|
this.statusContainer.addChild(this.idleStatus);
|
|
1437
1605
|
}
|
|
1438
1606
|
}
|
|
@@ -1580,22 +1748,16 @@ export class InteractiveMode {
|
|
|
1580
1748
|
if (this.customFooter?.dispose) {
|
|
1581
1749
|
this.customFooter.dispose();
|
|
1582
1750
|
}
|
|
1583
|
-
|
|
1584
|
-
if (this.customFooter) {
|
|
1585
|
-
this.ui.removeChild(this.customFooter);
|
|
1586
|
-
}
|
|
1587
|
-
else {
|
|
1588
|
-
this.ui.removeChild(this.footer);
|
|
1589
|
-
}
|
|
1751
|
+
this.footerContainer.clear();
|
|
1590
1752
|
if (factory) {
|
|
1591
1753
|
// Create and add custom footer, passing the data provider
|
|
1592
1754
|
this.customFooter = factory(this.ui, theme, this.footerDataProvider);
|
|
1593
|
-
this.
|
|
1755
|
+
this.footerContainer.addChild(this.customFooter);
|
|
1594
1756
|
}
|
|
1595
1757
|
else {
|
|
1596
1758
|
// Restore built-in footer
|
|
1597
1759
|
this.customFooter = undefined;
|
|
1598
|
-
this.
|
|
1760
|
+
this.footerContainer.addChild(this.footer);
|
|
1599
1761
|
}
|
|
1600
1762
|
this.ui.requestRender();
|
|
1601
1763
|
}
|
|
@@ -1641,18 +1803,23 @@ export class InteractiveMode {
|
|
|
1641
1803
|
this.ui.requestRender();
|
|
1642
1804
|
}
|
|
1643
1805
|
addExtensionTerminalInputListener(handler) {
|
|
1644
|
-
const
|
|
1645
|
-
this.
|
|
1806
|
+
const subscription = { handler, unsubscribe: this.ui.addInputListener(handler) };
|
|
1807
|
+
this.extensionTerminalInputSubscriptions.add(subscription);
|
|
1646
1808
|
return () => {
|
|
1647
|
-
unsubscribe();
|
|
1648
|
-
this.
|
|
1809
|
+
subscription.unsubscribe();
|
|
1810
|
+
this.extensionTerminalInputSubscriptions.delete(subscription);
|
|
1649
1811
|
};
|
|
1650
1812
|
}
|
|
1651
|
-
|
|
1652
|
-
for (const
|
|
1653
|
-
unsubscribe();
|
|
1813
|
+
rebindExtensionTerminalInputListeners() {
|
|
1814
|
+
for (const subscription of this.extensionTerminalInputSubscriptions) {
|
|
1815
|
+
subscription.unsubscribe();
|
|
1816
|
+
subscription.unsubscribe = this.ui.addInputListener(subscription.handler);
|
|
1654
1817
|
}
|
|
1655
|
-
|
|
1818
|
+
}
|
|
1819
|
+
clearExtensionTerminalInputListeners() {
|
|
1820
|
+
for (const subscription of this.extensionTerminalInputSubscriptions)
|
|
1821
|
+
subscription.unsubscribe();
|
|
1822
|
+
this.extensionTerminalInputSubscriptions.clear();
|
|
1656
1823
|
}
|
|
1657
1824
|
/**
|
|
1658
1825
|
* Create the ExtensionUIContext for extensions.
|
|
@@ -1747,6 +1914,7 @@ export class InteractiveMode {
|
|
|
1747
1914
|
this.hideExtensionSelector();
|
|
1748
1915
|
resolve(undefined);
|
|
1749
1916
|
}, { tui: this.ui, timeout: opts?.timeout, onToggleToolsExpanded: () => this.toggleToolOutputExpansion() });
|
|
1917
|
+
this.disposeActiveSelector();
|
|
1750
1918
|
this.editorContainer.clear();
|
|
1751
1919
|
this.editorContainer.addChild(this.extensionSelector);
|
|
1752
1920
|
this.ui.setFocus(this.extensionSelector);
|
|
@@ -1798,6 +1966,7 @@ export class InteractiveMode {
|
|
|
1798
1966
|
this.hideExtensionInput();
|
|
1799
1967
|
resolve(undefined);
|
|
1800
1968
|
}, { tui: this.ui, timeout: opts?.timeout });
|
|
1969
|
+
this.disposeActiveSelector();
|
|
1801
1970
|
this.editorContainer.clear();
|
|
1802
1971
|
this.editorContainer.addChild(this.extensionInput);
|
|
1803
1972
|
this.ui.setFocus(this.extensionInput);
|
|
@@ -1827,6 +1996,7 @@ export class InteractiveMode {
|
|
|
1827
1996
|
this.hideExtensionEditor();
|
|
1828
1997
|
resolve(undefined);
|
|
1829
1998
|
}, undefined, this.settingsManager.getExternalEditorCommand());
|
|
1999
|
+
this.disposeActiveSelector();
|
|
1830
2000
|
this.editorContainer.clear();
|
|
1831
2001
|
this.editorContainer.addChild(this.extensionEditor);
|
|
1832
2002
|
this.ui.setFocus(this.extensionEditor);
|
|
@@ -1851,6 +2021,7 @@ export class InteractiveMode {
|
|
|
1851
2021
|
this.editorComponentFactory = factory;
|
|
1852
2022
|
// Save text from current editor before switching
|
|
1853
2023
|
const currentText = this.editor.getText();
|
|
2024
|
+
this.disposeActiveSelector();
|
|
1854
2025
|
this.editorContainer.clear();
|
|
1855
2026
|
if (factory) {
|
|
1856
2027
|
// Create the custom editor with tui, theme, and keybindings
|
|
@@ -1867,6 +2038,9 @@ export class InteractiveMode {
|
|
|
1867
2038
|
if (newEditor.setPaddingX !== undefined) {
|
|
1868
2039
|
newEditor.setPaddingX(this.defaultEditor.getPaddingX());
|
|
1869
2040
|
}
|
|
2041
|
+
if (newEditor.setAutocompleteMaxVisible !== undefined) {
|
|
2042
|
+
newEditor.setAutocompleteMaxVisible(this.defaultEditor.getAutocompleteMaxVisible());
|
|
2043
|
+
}
|
|
1870
2044
|
// Set autocomplete if supported
|
|
1871
2045
|
if (newEditor.setAutocompleteProvider && this.autocompleteProvider) {
|
|
1872
2046
|
newEditor.setAutocompleteProvider(this.autocompleteProvider);
|
|
@@ -1971,6 +2145,7 @@ export class InteractiveMode {
|
|
|
1971
2145
|
options?.onHandle?.(handle);
|
|
1972
2146
|
}
|
|
1973
2147
|
else {
|
|
2148
|
+
this.disposeActiveSelector();
|
|
1974
2149
|
this.editorContainer.clear();
|
|
1975
2150
|
this.editorContainer.addChild(component);
|
|
1976
2151
|
this.ui.setFocus(component);
|
|
@@ -2057,7 +2232,7 @@ export class InteractiveMode {
|
|
|
2057
2232
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
2058
2233
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
2059
2234
|
this.defaultEditor.onAction("app.editor.external", () => void this.handleOpenExternalEditor());
|
|
2060
|
-
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand());
|
|
2235
|
+
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand({ flashConfirmation: true }));
|
|
2061
2236
|
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
2062
2237
|
this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
|
|
2063
2238
|
this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
|
|
@@ -2077,6 +2252,22 @@ export class InteractiveMode {
|
|
|
2077
2252
|
void this.handleClipboardPaste();
|
|
2078
2253
|
};
|
|
2079
2254
|
}
|
|
2255
|
+
async handleRightClickPaste() {
|
|
2256
|
+
const target = this.renderer.getFocusedComponent();
|
|
2257
|
+
const handleInput = target?.handleInput;
|
|
2258
|
+
if (!target || !handleInput)
|
|
2259
|
+
return;
|
|
2260
|
+
try {
|
|
2261
|
+
const text = await readClipboardText();
|
|
2262
|
+
if (!text || this.renderer.getFocusedComponent() !== target)
|
|
2263
|
+
return;
|
|
2264
|
+
handleInput.call(target, `\x1b[200~${text}\x1b[201~`);
|
|
2265
|
+
this.ui.requestRender();
|
|
2266
|
+
}
|
|
2267
|
+
catch {
|
|
2268
|
+
// Silently ignore clipboard errors (may not have permission, etc.)
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2080
2271
|
async handleClipboardPaste() {
|
|
2081
2272
|
try {
|
|
2082
2273
|
const image = await readClipboardImage();
|
|
@@ -2345,17 +2536,17 @@ export class InteractiveMode {
|
|
|
2345
2536
|
this.ui.requestRender();
|
|
2346
2537
|
}
|
|
2347
2538
|
else if (event.message.role === "assistant") {
|
|
2348
|
-
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
|
|
2539
|
+
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
|
|
2349
2540
|
this.streamingMessage = event.message;
|
|
2350
2541
|
this.chatContainer.addChild(this.streamingComponent);
|
|
2351
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2542
|
+
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2352
2543
|
this.ui.requestRender();
|
|
2353
2544
|
}
|
|
2354
2545
|
break;
|
|
2355
2546
|
case "message_update":
|
|
2356
2547
|
if (this.streamingComponent && event.message.role === "assistant") {
|
|
2357
2548
|
this.streamingMessage = event.message;
|
|
2358
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2549
|
+
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2359
2550
|
for (const content of this.streamingMessage.content) {
|
|
2360
2551
|
if (content.type === "toolCall") {
|
|
2361
2552
|
if (!this.pendingTools.has(content.id)) {
|
|
@@ -2392,7 +2583,7 @@ export class InteractiveMode {
|
|
|
2392
2583
|
: "Operation aborted";
|
|
2393
2584
|
this.streamingMessage.errorMessage = errorMessage;
|
|
2394
2585
|
}
|
|
2395
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2586
|
+
this.streamingComponent.updateContent(this.streamingMessage, false);
|
|
2396
2587
|
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
2397
2588
|
if (!errorMessage) {
|
|
2398
2589
|
errorMessage = this.streamingMessage.errorMessage || "Error";
|
|
@@ -2666,12 +2857,12 @@ export class InteractiveMode {
|
|
|
2666
2857
|
// Render user message separately if present
|
|
2667
2858
|
if (skillBlock.userMessage) {
|
|
2668
2859
|
this.chatContainer.addChild(new Spacer(1));
|
|
2669
|
-
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2860
|
+
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2670
2861
|
this.chatContainer.addChild(userComponent);
|
|
2671
2862
|
}
|
|
2672
2863
|
}
|
|
2673
2864
|
else {
|
|
2674
|
-
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2865
|
+
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2675
2866
|
this.chatContainer.addChild(userComponent);
|
|
2676
2867
|
}
|
|
2677
2868
|
if (options?.populateHistory) {
|
|
@@ -2681,7 +2872,7 @@ export class InteractiveMode {
|
|
|
2681
2872
|
break;
|
|
2682
2873
|
}
|
|
2683
2874
|
case "assistant": {
|
|
2684
|
-
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
|
|
2875
|
+
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
|
|
2685
2876
|
this.chatContainer.addChild(assistantComponent);
|
|
2686
2877
|
break;
|
|
2687
2878
|
}
|
|
@@ -3115,6 +3306,8 @@ export class InteractiveMode {
|
|
|
3115
3306
|
this.setToolsExpanded(!this.toolOutputExpanded);
|
|
3116
3307
|
}
|
|
3117
3308
|
setToolsExpanded(expanded) {
|
|
3309
|
+
if (expanded === this.toolOutputExpanded)
|
|
3310
|
+
return;
|
|
3118
3311
|
this.toolOutputExpanded = expanded;
|
|
3119
3312
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
3120
3313
|
if (isExpandable(activeHeader)) {
|
|
@@ -3170,7 +3363,7 @@ export class InteractiveMode {
|
|
|
3170
3363
|
}
|
|
3171
3364
|
showError(errorMessage) {
|
|
3172
3365
|
this.chatContainer.addChild(new Spacer(1));
|
|
3173
|
-
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`),
|
|
3366
|
+
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), this.outputPad, 0));
|
|
3174
3367
|
this.ui.requestRender();
|
|
3175
3368
|
}
|
|
3176
3369
|
showWarning(warningMessage) {
|
|
@@ -3380,25 +3573,43 @@ export class InteractiveMode {
|
|
|
3380
3573
|
// =========================================================================
|
|
3381
3574
|
// Selectors
|
|
3382
3575
|
// =========================================================================
|
|
3576
|
+
disposeActiveSelector() {
|
|
3577
|
+
const dispose = this.activeSelectorDispose;
|
|
3578
|
+
this.activeSelectorToken = undefined;
|
|
3579
|
+
this.activeSelectorDispose = undefined;
|
|
3580
|
+
dispose?.();
|
|
3581
|
+
}
|
|
3383
3582
|
/**
|
|
3384
3583
|
* Shows a selector component in place of the editor.
|
|
3385
3584
|
* @param create Factory that receives a `done` callback and returns the component and focus target
|
|
3386
3585
|
*/
|
|
3387
3586
|
showSelector(create) {
|
|
3587
|
+
const token = {};
|
|
3588
|
+
let dispose;
|
|
3388
3589
|
const done = () => {
|
|
3590
|
+
dispose?.();
|
|
3591
|
+
if (this.activeSelectorToken !== token)
|
|
3592
|
+
return;
|
|
3593
|
+
this.activeSelectorToken = undefined;
|
|
3594
|
+
this.activeSelectorDispose = undefined;
|
|
3389
3595
|
this.editorContainer.clear();
|
|
3390
3596
|
this.editorContainer.addChild(this.editor);
|
|
3391
3597
|
this.ui.setFocus(this.editor);
|
|
3392
3598
|
};
|
|
3393
|
-
const
|
|
3599
|
+
const created = create(done);
|
|
3600
|
+
dispose = created.dispose;
|
|
3601
|
+
this.disposeActiveSelector();
|
|
3602
|
+
this.activeSelectorToken = token;
|
|
3603
|
+
this.activeSelectorDispose = dispose;
|
|
3394
3604
|
this.editorContainer.clear();
|
|
3395
|
-
this.editorContainer.addChild(component);
|
|
3396
|
-
this.ui.setFocus(focus);
|
|
3605
|
+
this.editorContainer.addChild(created.component);
|
|
3606
|
+
this.ui.setFocus(created.focus);
|
|
3397
3607
|
this.ui.requestRender();
|
|
3398
3608
|
}
|
|
3399
3609
|
showSettingsSelector() {
|
|
3400
3610
|
this.showSelector((done) => {
|
|
3401
|
-
|
|
3611
|
+
let selector;
|
|
3612
|
+
selector = new SettingsSelectorComponent({
|
|
3402
3613
|
autoCompact: this.session.autoCompactionEnabled,
|
|
3403
3614
|
showImages: this.settingsManager.getShowImages(),
|
|
3404
3615
|
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
@@ -3415,6 +3626,7 @@ export class InteractiveMode {
|
|
|
3415
3626
|
terminalTheme: this.themeController.getTerminalTheme(),
|
|
3416
3627
|
availableThemes: getAvailableThemes(),
|
|
3417
3628
|
hideThinkingBlock: this.hideThinkingBlock,
|
|
3629
|
+
mermaidRenderingMode: this.settingsManager.getMermaidRenderingMode(),
|
|
3418
3630
|
collapseChangelog: this.settingsManager.getCollapseChangelog(),
|
|
3419
3631
|
enableInstallTelemetry: this.settingsManager.getEnableInstallTelemetry(),
|
|
3420
3632
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
@@ -3428,6 +3640,8 @@ export class InteractiveMode {
|
|
|
3428
3640
|
quietStartup: this.settingsManager.getQuietStartup(),
|
|
3429
3641
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
3430
3642
|
showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
|
|
3643
|
+
tuiMode: this.ui.mode,
|
|
3644
|
+
fullscreenScrollbar: this.settingsManager.getFullscreenScrollbar(),
|
|
3431
3645
|
warnings: this.settingsManager.getWarnings(),
|
|
3432
3646
|
}, {
|
|
3433
3647
|
onAutoCompactChange: (enabled) => {
|
|
@@ -3496,6 +3710,11 @@ export class InteractiveMode {
|
|
|
3496
3710
|
this.chatContainer.clear();
|
|
3497
3711
|
this.rebuildChatFromMessages();
|
|
3498
3712
|
},
|
|
3713
|
+
onMermaidRenderingModeChange: (mode) => {
|
|
3714
|
+
this.settingsManager.setMermaidRenderingMode(mode);
|
|
3715
|
+
this.chatContainer.invalidate();
|
|
3716
|
+
this.ui.requestRender();
|
|
3717
|
+
},
|
|
3499
3718
|
onShowCacheMissNoticesChange: (shown) => {
|
|
3500
3719
|
this.settingsManager.setShowCacheMissNotices(shown);
|
|
3501
3720
|
this.rebuildChatFromMessages();
|
|
@@ -3565,6 +3784,21 @@ export class InteractiveMode {
|
|
|
3565
3784
|
onShowTerminalProgressChange: (enabled) => {
|
|
3566
3785
|
this.settingsManager.setShowTerminalProgress(enabled);
|
|
3567
3786
|
},
|
|
3787
|
+
onTuiModeChange: (mode) => {
|
|
3788
|
+
if (!this.switchTuiMode(mode)) {
|
|
3789
|
+
selector?.getSettingsList().updateValue("tui-mode", this.ui.mode);
|
|
3790
|
+
this.showStatus("Close active overlays before changing TUI mode");
|
|
3791
|
+
return;
|
|
3792
|
+
}
|
|
3793
|
+
this.settingsManager.setTuiMode(mode);
|
|
3794
|
+
if (!this.activeStatusIndicator)
|
|
3795
|
+
this.statusContainer.clear();
|
|
3796
|
+
this.showStatus(`TUI mode: ${mode}`);
|
|
3797
|
+
},
|
|
3798
|
+
onFullscreenScrollbarChange: (mode) => {
|
|
3799
|
+
this.settingsManager.setFullscreenScrollbar(mode);
|
|
3800
|
+
this.applyFullscreenScrollbarSetting();
|
|
3801
|
+
},
|
|
3568
3802
|
onWarningsChange: (warnings) => {
|
|
3569
3803
|
this.settingsManager.setWarnings(warnings);
|
|
3570
3804
|
},
|
|
@@ -3599,20 +3833,37 @@ export class InteractiveMode {
|
|
|
3599
3833
|
this.showModelSelector(searchTerm);
|
|
3600
3834
|
}
|
|
3601
3835
|
async findExactModelMatch(searchTerm) {
|
|
3602
|
-
const
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
if (this.session.scopedModels.length > 0)
|
|
3607
|
-
return
|
|
3608
|
-
|
|
3836
|
+
const cachedModels = this.session.scopedModels.length > 0
|
|
3837
|
+
? this.session.scopedModels.map((scoped) => scoped.model)
|
|
3838
|
+
: [...this.session.modelRuntime.getAvailableSnapshot()];
|
|
3839
|
+
const cachedMatch = findExactModelReferenceMatch(searchTerm, cachedModels);
|
|
3840
|
+
if (cachedMatch || this.session.scopedModels.length > 0)
|
|
3841
|
+
return cachedMatch;
|
|
3842
|
+
this.showStatus("Refreshing model catalogs…");
|
|
3843
|
+
const controller = new AbortController();
|
|
3844
|
+
let timedOut = false;
|
|
3845
|
+
const timeout = setTimeout(() => {
|
|
3846
|
+
timedOut = true;
|
|
3847
|
+
controller.abort();
|
|
3848
|
+
}, 15_000);
|
|
3609
3849
|
try {
|
|
3610
|
-
await this.session.modelRuntime.refresh();
|
|
3611
|
-
|
|
3850
|
+
const result = await this.session.modelRuntime.refresh({ signal: controller.signal });
|
|
3851
|
+
if (result.aborted && timedOut) {
|
|
3852
|
+
this.showWarning("Model refresh timed out; searching cached models.");
|
|
3853
|
+
}
|
|
3854
|
+
else if (result.errors.size > 0) {
|
|
3855
|
+
this.showWarning(`Could not refresh ${[...result.errors.keys()].join(", ")}; searching cached models.`);
|
|
3856
|
+
}
|
|
3612
3857
|
}
|
|
3613
|
-
catch {
|
|
3614
|
-
|
|
3858
|
+
catch (error) {
|
|
3859
|
+
this.showWarning(timedOut
|
|
3860
|
+
? "Model refresh timed out; searching cached models."
|
|
3861
|
+
: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`);
|
|
3615
3862
|
}
|
|
3863
|
+
finally {
|
|
3864
|
+
clearTimeout(timeout);
|
|
3865
|
+
}
|
|
3866
|
+
return findExactModelReferenceMatch(searchTerm, [...this.session.modelRuntime.getAvailableSnapshot()]);
|
|
3616
3867
|
}
|
|
3617
3868
|
/** Update the footer's available provider count from the current snapshot without refreshing catalogs. */
|
|
3618
3869
|
updateAvailableProviderCount() {
|
|
@@ -3714,73 +3965,67 @@ export class InteractiveMode {
|
|
|
3714
3965
|
done();
|
|
3715
3966
|
this.ui.requestRender();
|
|
3716
3967
|
}, initialSearchInput);
|
|
3717
|
-
return { component: selector, focus: selector };
|
|
3968
|
+
return { component: selector, focus: selector, dispose: () => selector.dispose() };
|
|
3718
3969
|
});
|
|
3719
3970
|
}
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
const allModels = [...(await this.session.modelRuntime.getAvailable())];
|
|
3724
|
-
const allModelIds = new Set(allModels.map((model) => `${model.provider}/${model.id}`));
|
|
3971
|
+
showModelsSelector() {
|
|
3972
|
+
let availableModels = [...this.session.modelRuntime.getAvailableSnapshot()];
|
|
3973
|
+
let availableModelIds = new Set(availableModels.map((model) => `${model.provider}/${model.id}`));
|
|
3725
3974
|
const configuredPatterns = this.settingsManager.getEnabledModels();
|
|
3726
3975
|
const sessionScopedModels = this.session.scopedModels;
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
currentEnabledIds = configuredScope.scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3744
|
-
}
|
|
3745
|
-
for (const diagnostic of configuredScope?.diagnostics ?? []) {
|
|
3746
|
-
if (diagnostic.code !== "no-match")
|
|
3747
|
-
continue;
|
|
3748
|
-
currentEnabledIds ??= [];
|
|
3749
|
-
if (!currentEnabledIds.includes(diagnostic.pattern))
|
|
3750
|
-
currentEnabledIds.push(diagnostic.pattern);
|
|
3751
|
-
}
|
|
3752
|
-
// Helper to update session's scoped models (session-only, no persist)
|
|
3753
|
-
const updateSessionModels = async (enabledIds) => {
|
|
3976
|
+
const configuredEnabledIds = (models) => {
|
|
3977
|
+
if (!configuredPatterns?.length)
|
|
3978
|
+
return null;
|
|
3979
|
+
const resolved = resolveModelScopeFromModels(configuredPatterns, models);
|
|
3980
|
+
const ids = resolved.scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3981
|
+
for (const diagnostic of resolved.diagnostics) {
|
|
3982
|
+
if (diagnostic.code === "no-match" && !ids.includes(diagnostic.pattern))
|
|
3983
|
+
ids.push(diagnostic.pattern);
|
|
3984
|
+
}
|
|
3985
|
+
return ids;
|
|
3986
|
+
};
|
|
3987
|
+
let currentEnabledIds = sessionScopedModels.length > 0
|
|
3988
|
+
? sessionScopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`)
|
|
3989
|
+
: configuredEnabledIds(availableModels);
|
|
3990
|
+
let selectionChanged = false;
|
|
3991
|
+
const updateSessionModels = (enabledIds) => {
|
|
3754
3992
|
currentEnabledIds = enabledIds === null ? null : [...enabledIds];
|
|
3755
|
-
const hasEnabledAvailableModel = enabledIds?.some((id) =>
|
|
3756
|
-
const allAvailableModelsEnabled = enabledIds !== null && [...
|
|
3993
|
+
const hasEnabledAvailableModel = enabledIds?.some((id) => availableModelIds.has(id)) ?? false;
|
|
3994
|
+
const allAvailableModelsEnabled = enabledIds !== null && [...availableModelIds].every((id) => enabledIds.includes(id));
|
|
3757
3995
|
if (enabledIds && hasEnabledAvailableModel && !allAvailableModelsEnabled) {
|
|
3758
|
-
const newScopedModels =
|
|
3759
|
-
this.session.setScopedModels(newScopedModels.map((
|
|
3760
|
-
model:
|
|
3761
|
-
thinkingLevel:
|
|
3996
|
+
const newScopedModels = resolveModelScopeFromModels(enabledIds, availableModels).scopedModels;
|
|
3997
|
+
this.session.setScopedModels(newScopedModels.map((scoped) => ({
|
|
3998
|
+
model: scoped.model,
|
|
3999
|
+
thinkingLevel: scoped.thinkingLevel,
|
|
3762
4000
|
})));
|
|
3763
4001
|
}
|
|
3764
4002
|
else {
|
|
3765
|
-
// All enabled or none enabled = no filter
|
|
3766
4003
|
this.session.setScopedModels([]);
|
|
3767
4004
|
}
|
|
3768
|
-
|
|
4005
|
+
this.updateAvailableProviderCount();
|
|
3769
4006
|
this.ui.requestRender();
|
|
3770
4007
|
};
|
|
3771
4008
|
this.showSelector((done) => {
|
|
4009
|
+
let disposed = false;
|
|
4010
|
+
let timedOut = false;
|
|
4011
|
+
const controller = new AbortController();
|
|
4012
|
+
const timeout = setTimeout(() => {
|
|
4013
|
+
timedOut = true;
|
|
4014
|
+
controller.abort();
|
|
4015
|
+
}, 15_000);
|
|
3772
4016
|
const selector = new ScopedModelsSelectorComponent({
|
|
3773
|
-
allModels,
|
|
4017
|
+
allModels: availableModels,
|
|
3774
4018
|
enabledModelIds: currentEnabledIds,
|
|
4019
|
+
refreshStatus: "Refreshing model catalogs…",
|
|
3775
4020
|
}, {
|
|
3776
|
-
onChange:
|
|
3777
|
-
|
|
4021
|
+
onChange: (enabledIds) => {
|
|
4022
|
+
selectionChanged = true;
|
|
4023
|
+
updateSessionModels(enabledIds);
|
|
3778
4024
|
},
|
|
3779
4025
|
onPersist: (enabledIds) => {
|
|
3780
|
-
// Persist to settings
|
|
3781
4026
|
const allEnabled = enabledIds !== null &&
|
|
3782
|
-
enabledIds.length ===
|
|
3783
|
-
enabledIds.every((id) =>
|
|
4027
|
+
enabledIds.length === availableModels.length &&
|
|
4028
|
+
enabledIds.every((id) => availableModelIds.has(id));
|
|
3784
4029
|
const newPatterns = enabledIds === null || allEnabled ? undefined : enabledIds;
|
|
3785
4030
|
this.settingsManager.setEnabledModels(newPatterns ? [...newPatterns] : undefined);
|
|
3786
4031
|
this.showStatus("Model selection saved to settings");
|
|
@@ -3790,7 +4035,51 @@ export class InteractiveMode {
|
|
|
3790
4035
|
this.ui.requestRender();
|
|
3791
4036
|
},
|
|
3792
4037
|
});
|
|
3793
|
-
|
|
4038
|
+
void this.session.modelRuntime
|
|
4039
|
+
.refresh({ signal: controller.signal })
|
|
4040
|
+
.then((result) => {
|
|
4041
|
+
if (disposed)
|
|
4042
|
+
return;
|
|
4043
|
+
availableModels = [...this.session.modelRuntime.getAvailableSnapshot()];
|
|
4044
|
+
availableModelIds = new Set(availableModels.map((model) => `${model.provider}/${model.id}`));
|
|
4045
|
+
if (!selectionChanged && sessionScopedModels.length === 0) {
|
|
4046
|
+
currentEnabledIds = configuredEnabledIds(availableModels);
|
|
4047
|
+
selector.updateModels(availableModels, currentEnabledIds);
|
|
4048
|
+
}
|
|
4049
|
+
else {
|
|
4050
|
+
selector.updateModels(availableModels);
|
|
4051
|
+
}
|
|
4052
|
+
if (currentEnabledIds !== null)
|
|
4053
|
+
updateSessionModels(currentEnabledIds);
|
|
4054
|
+
if (result.aborted && timedOut) {
|
|
4055
|
+
selector.setRefreshStatus("Model refresh timed out; showing cached models.", "warning");
|
|
4056
|
+
}
|
|
4057
|
+
else if (result.errors.size > 0) {
|
|
4058
|
+
selector.setRefreshStatus(`Could not refresh ${[...result.errors.keys()].join(", ")}; showing cached models.`, "warning");
|
|
4059
|
+
}
|
|
4060
|
+
else {
|
|
4061
|
+
selector.setRefreshStatus("Model catalogs refreshed.", "success");
|
|
4062
|
+
}
|
|
4063
|
+
this.ui.requestRender();
|
|
4064
|
+
})
|
|
4065
|
+
.catch((error) => {
|
|
4066
|
+
if (disposed)
|
|
4067
|
+
return;
|
|
4068
|
+
selector.setRefreshStatus(timedOut
|
|
4069
|
+
? "Model refresh timed out; showing cached models."
|
|
4070
|
+
: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
4071
|
+
this.ui.requestRender();
|
|
4072
|
+
})
|
|
4073
|
+
.finally(() => clearTimeout(timeout));
|
|
4074
|
+
return {
|
|
4075
|
+
component: selector,
|
|
4076
|
+
focus: selector,
|
|
4077
|
+
dispose: () => {
|
|
4078
|
+
disposed = true;
|
|
4079
|
+
clearTimeout(timeout);
|
|
4080
|
+
controller.abort();
|
|
4081
|
+
},
|
|
4082
|
+
};
|
|
3794
4083
|
});
|
|
3795
4084
|
}
|
|
3796
4085
|
showUserMessageSelector() {
|
|
@@ -4052,7 +4341,7 @@ export class InteractiveMode {
|
|
|
4052
4341
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
4053
4342
|
}
|
|
4054
4343
|
async getLogoutProviderOptions() {
|
|
4055
|
-
return (await this.session.modelRuntime.listCredentials())
|
|
4344
|
+
return (await this.session.modelRuntime.listCredentials({ signal: AbortSignal.timeout(15_000) }))
|
|
4056
4345
|
.map(({ providerId, type }) => ({
|
|
4057
4346
|
id: providerId,
|
|
4058
4347
|
name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
|
|
@@ -4070,7 +4359,6 @@ export class InteractiveMode {
|
|
|
4070
4359
|
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4071
4360
|
}
|
|
4072
4361
|
async handleLoginCommand(providerRef) {
|
|
4073
|
-
await this.session.modelRuntime.getAvailable();
|
|
4074
4362
|
if (!providerRef) {
|
|
4075
4363
|
this.showLoginAuthTypeSelector();
|
|
4076
4364
|
return;
|
|
@@ -4184,7 +4472,14 @@ export class InteractiveMode {
|
|
|
4184
4472
|
this.showLoginAuthTypeSelector();
|
|
4185
4473
|
return;
|
|
4186
4474
|
}
|
|
4187
|
-
|
|
4475
|
+
let providerOptions;
|
|
4476
|
+
try {
|
|
4477
|
+
providerOptions = await this.getLogoutProviderOptions();
|
|
4478
|
+
}
|
|
4479
|
+
catch (error) {
|
|
4480
|
+
this.showError(`Could not read stored credentials: ${error instanceof Error ? error.message : String(error)}`);
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4188
4483
|
if (providerOptions.length === 0) {
|
|
4189
4484
|
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
4190
4485
|
return;
|
|
@@ -4197,7 +4492,9 @@ export class InteractiveMode {
|
|
|
4197
4492
|
return;
|
|
4198
4493
|
}
|
|
4199
4494
|
try {
|
|
4200
|
-
await this.session.modelRuntime.logout(providerOption.id
|
|
4495
|
+
await this.session.modelRuntime.logout(providerOption.id, {
|
|
4496
|
+
signal: AbortSignal.timeout(15_000),
|
|
4497
|
+
});
|
|
4201
4498
|
await this.updateAvailableProviderCount();
|
|
4202
4499
|
const message = providerOption.authType === "oauth"
|
|
4203
4500
|
? `Logged out of ${providerOption.name}`
|
|
@@ -4205,7 +4502,10 @@ export class InteractiveMode {
|
|
|
4205
4502
|
this.showStatus(message);
|
|
4206
4503
|
}
|
|
4207
4504
|
catch (error) {
|
|
4208
|
-
|
|
4505
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4506
|
+
this.showError(error instanceof CredentialSynchronizationError
|
|
4507
|
+
? `Credentials removed for ${providerOption.name}, but local model state could not be synchronized: ${message}`
|
|
4508
|
+
: `Logout failed: ${message}`);
|
|
4209
4509
|
}
|
|
4210
4510
|
}, () => {
|
|
4211
4511
|
done();
|
|
@@ -4215,12 +4515,11 @@ export class InteractiveMode {
|
|
|
4215
4515
|
});
|
|
4216
4516
|
}
|
|
4217
4517
|
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
4218
|
-
await this.session.modelRuntime.getAvailable();
|
|
4219
4518
|
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
4220
4519
|
let selectedModel;
|
|
4221
4520
|
let selectionError;
|
|
4222
4521
|
if (isUnknownModel(previousModel)) {
|
|
4223
|
-
const availableModels =
|
|
4522
|
+
const availableModels = this.session.modelRuntime.getAvailableSnapshot();
|
|
4224
4523
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
4225
4524
|
if (!hasDefaultModelProvider(providerId)) {
|
|
4226
4525
|
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
@@ -4263,6 +4562,25 @@ export class InteractiveMode {
|
|
|
4263
4562
|
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
4264
4563
|
}
|
|
4265
4564
|
}
|
|
4565
|
+
const controller = new AbortController();
|
|
4566
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
4567
|
+
void this.session.modelRuntime
|
|
4568
|
+
.refresh({ providers: [providerId], signal: controller.signal })
|
|
4569
|
+
.then((result) => {
|
|
4570
|
+
if (result.aborted) {
|
|
4571
|
+
this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
|
|
4572
|
+
}
|
|
4573
|
+
else if (result.errors.size > 0) {
|
|
4574
|
+
this.showWarning(`${actionLabel}, but its model catalog could not be refreshed; using cached models.`);
|
|
4575
|
+
}
|
|
4576
|
+
this.updateAvailableProviderCount();
|
|
4577
|
+
this.footer.invalidate();
|
|
4578
|
+
this.ui.requestRender();
|
|
4579
|
+
})
|
|
4580
|
+
.catch((error) => {
|
|
4581
|
+
this.showWarning(`${actionLabel}, but its model catalog could not be refreshed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4582
|
+
})
|
|
4583
|
+
.finally(() => clearTimeout(timeout));
|
|
4266
4584
|
}
|
|
4267
4585
|
showAmbientAuthDialog(providerOption) {
|
|
4268
4586
|
const restoreEditor = () => {
|
|
@@ -4308,7 +4626,10 @@ export class InteractiveMode {
|
|
|
4308
4626
|
catch (error) {
|
|
4309
4627
|
restoreEditor();
|
|
4310
4628
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4311
|
-
if (
|
|
4629
|
+
if (error instanceof CredentialSynchronizationError) {
|
|
4630
|
+
this.showError(`Saved API key for ${providerName}, but local model state could not be synchronized: ${errorMsg}`);
|
|
4631
|
+
}
|
|
4632
|
+
else if (errorMsg !== "Login cancelled") {
|
|
4312
4633
|
this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
|
|
4313
4634
|
}
|
|
4314
4635
|
}
|
|
@@ -4411,7 +4732,10 @@ export class InteractiveMode {
|
|
|
4411
4732
|
catch (error) {
|
|
4412
4733
|
restoreEditor();
|
|
4413
4734
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4414
|
-
if (
|
|
4735
|
+
if (error instanceof CredentialSynchronizationError) {
|
|
4736
|
+
this.showError(`Logged in to ${providerName}, but local model state could not be synchronized: ${errorMsg}`);
|
|
4737
|
+
}
|
|
4738
|
+
else if (errorMsg !== "Login cancelled") {
|
|
4415
4739
|
this.showError(`Failed to login to ${providerName}: ${errorMsg}`);
|
|
4416
4740
|
}
|
|
4417
4741
|
}
|
|
@@ -4462,7 +4786,6 @@ export class InteractiveMode {
|
|
|
4462
4786
|
try {
|
|
4463
4787
|
await this.session.reload({ beforeSessionStart: restoreChatBeforeSessionStart });
|
|
4464
4788
|
restoreChatBeforeSessionStart();
|
|
4465
|
-
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
4466
4789
|
this.keybindings.reload();
|
|
4467
4790
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
4468
4791
|
if (isExpandable(activeHeader)) {
|
|
@@ -4470,20 +4793,7 @@ export class InteractiveMode {
|
|
|
4470
4793
|
}
|
|
4471
4794
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
4472
4795
|
await this.themeController.applyFromSettings();
|
|
4473
|
-
|
|
4474
|
-
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
4475
|
-
this.defaultEditor.setPaddingX(editorPaddingX);
|
|
4476
|
-
this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
|
|
4477
|
-
if (this.editor !== this.defaultEditor) {
|
|
4478
|
-
this.editor.setPaddingX?.(editorPaddingX);
|
|
4479
|
-
this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
|
|
4480
|
-
}
|
|
4481
|
-
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
4482
|
-
const clearOnShrink = this.settingsManager.getClearOnShrink();
|
|
4483
|
-
this.ui.setClearOnShrink(clearOnShrink);
|
|
4484
|
-
if (!clearOnShrink && !this.activeStatusIndicator) {
|
|
4485
|
-
this.statusContainer.clear();
|
|
4486
|
-
}
|
|
4796
|
+
this.applyRuntimeSettings();
|
|
4487
4797
|
this.setupAutocompleteProvider();
|
|
4488
4798
|
const runner = this.session.extensionRunner;
|
|
4489
4799
|
this.setupExtensionShortcuts(runner);
|
|
@@ -4679,7 +4989,7 @@ export class InteractiveMode {
|
|
|
4679
4989
|
}
|
|
4680
4990
|
}
|
|
4681
4991
|
}
|
|
4682
|
-
async handleCopyCommand() {
|
|
4992
|
+
async handleCopyCommand(options = {}) {
|
|
4683
4993
|
const text = this.session.getLastAssistantText();
|
|
4684
4994
|
if (!text) {
|
|
4685
4995
|
this.showError("No agent messages to copy yet.");
|
|
@@ -4687,7 +4997,12 @@ export class InteractiveMode {
|
|
|
4687
4997
|
}
|
|
4688
4998
|
try {
|
|
4689
4999
|
await copyToClipboard(text);
|
|
4690
|
-
|
|
5000
|
+
if (options.flashConfirmation && this.ui instanceof TuiAltScreen) {
|
|
5001
|
+
this.ui.flash("Copied!");
|
|
5002
|
+
}
|
|
5003
|
+
else {
|
|
5004
|
+
this.showStatus("Copied last agent message to clipboard");
|
|
5005
|
+
}
|
|
4691
5006
|
}
|
|
4692
5007
|
catch (error) {
|
|
4693
5008
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
@@ -5050,6 +5365,7 @@ export class InteractiveMode {
|
|
|
5050
5365
|
}
|
|
5051
5366
|
}
|
|
5052
5367
|
stop() {
|
|
5368
|
+
this.disposeActiveSelector();
|
|
5053
5369
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
5054
5370
|
this.ui.terminal.setProgress(false);
|
|
5055
5371
|
}
|
|
@@ -5062,7 +5378,7 @@ export class InteractiveMode {
|
|
|
5062
5378
|
this.unsubscribe();
|
|
5063
5379
|
}
|
|
5064
5380
|
if (this.isInitialized) {
|
|
5065
|
-
this.
|
|
5381
|
+
this.stopInteractiveTui();
|
|
5066
5382
|
this.isInitialized = false;
|
|
5067
5383
|
}
|
|
5068
5384
|
this.unregisterSignalHandlers();
|