@dyyz1993/pi-coding-agent 0.70.5 → 0.74.4
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 +266 -80
- package/README.md +48 -20
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +4 -2
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/restore-sandbox-env.d.ts +13 -0
- package/dist/bun/restore-sandbox-env.d.ts.map +1 -0
- package/dist/bun/restore-sandbox-env.js +32 -0
- package/dist/bun/restore-sandbox-env.js.map +1 -0
- package/dist/cli/args.d.ts +2 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +34 -22
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +2 -1
- package/dist/cli/list-models.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +16 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +238 -66
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +10 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +14 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +2 -1
- 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 +25 -26
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +1042 -1116
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-types.d.ts +58 -0
- package/dist/core/agent-types.d.ts.map +1 -0
- package/dist/core/agent-types.js +203 -0
- package/dist/core/agent-types.js.map +1 -0
- package/dist/core/auth-guidance.d.ts +5 -0
- package/dist/core/auth-guidance.d.ts.map +1 -0
- package/dist/core/auth-guidance.js +21 -0
- package/dist/core/auth-guidance.js.map +1 -0
- package/dist/core/auth-storage.d.ts +9 -0
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +20 -1
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +9 -6
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +0 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
- package/dist/core/export-html/ansi-to-html.js +1 -1
- package/dist/core/export-html/ansi-to-html.js.map +1 -1
- package/dist/core/export-html/template.css +53 -4
- package/dist/core/export-html/template.js +84 -20
- package/dist/core/export-html/tool-renderer.d.ts +0 -6
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +15 -2
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/channel-factory.d.ts +13 -0
- package/dist/core/extensions/channel-factory.d.ts.map +1 -0
- package/dist/core/extensions/channel-factory.js +19 -0
- package/dist/core/extensions/channel-factory.js.map +1 -0
- package/dist/core/extensions/channel-registry.d.ts +28 -0
- package/dist/core/extensions/channel-registry.d.ts.map +1 -0
- package/dist/core/extensions/channel-registry.js +12 -0
- package/dist/core/extensions/channel-registry.js.map +1 -0
- package/dist/core/extensions/index.d.ts +4 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -0
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +0 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +49 -137
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +24 -20
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +128 -253
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/server-channel.d.ts +8 -8
- package/dist/core/extensions/server-channel.d.ts.map +1 -1
- package/dist/core/extensions/server-channel.js.map +1 -1
- package/dist/core/extensions/types.d.ts +88 -60
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +10 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/file-store/file-snapshot-manager.d.ts +95 -0
- package/dist/core/file-store/file-snapshot-manager.d.ts.map +1 -0
- package/dist/core/file-store/file-snapshot-manager.js +508 -0
- package/dist/core/file-store/file-snapshot-manager.js.map +1 -0
- package/dist/core/file-store/index.d.ts +5 -0
- package/dist/core/file-store/index.d.ts.map +1 -0
- package/dist/core/file-store/index.js +3 -0
- package/dist/core/file-store/index.js.map +1 -0
- package/dist/core/messages.d.ts +10 -2
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +23 -6
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry.d.ts +19 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +97 -16
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +24 -15
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.d.ts +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +61 -35
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/provider-display-names.d.ts +2 -0
- package/dist/core/provider-display-names.d.ts.map +1 -0
- package/dist/core/provider-display-names.js +32 -0
- package/dist/core/provider-display-names.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +9 -21
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +9 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +39 -18
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +27 -17
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +133 -47
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +21 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +51 -6
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +3 -8
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +4 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/bash.d.ts +0 -2
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +108 -154
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +3 -2
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +4 -3
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +50 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -0
- package/dist/core/tools/output-accumulator.js +178 -0
- package/dist/core/tools/output-accumulator.js.map +1 -0
- package/dist/core/tools/output-collector.d.ts +35 -0
- package/dist/core/tools/output-collector.d.ts.map +1 -0
- package/dist/core/tools/output-collector.js +79 -0
- package/dist/core/tools/output-collector.js.map +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +70 -13
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/spawn-managed.d.ts +18 -0
- package/dist/core/tools/spawn-managed.d.ts.map +1 -0
- package/dist/core/tools/spawn-managed.js +52 -0
- package/dist/core/tools/spawn-managed.js.map +1 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +17 -39
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +3 -3
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +3 -1
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +1 -4
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +14 -56
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +5 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +19 -4
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +1 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +18 -6
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +93 -25
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +3 -7
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +5 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +53 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +20 -4
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +423 -186
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -1
- package/dist/modes/interactive/theme/light.json +1 -1
- package/dist/modes/print-mode.d.ts +3 -0
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +62 -19
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +80 -60
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +108 -93
- 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 +106 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +115 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +238 -12
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/child-process.d.ts +1 -0
- package/dist/utils/child-process.d.ts.map +1 -1
- package/dist/utils/child-process.js +8 -0
- package/dist/utils/child-process.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +2 -2
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +84 -45
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/paths.d.ts +9 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +31 -0
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/pi-user-agent.d.ts +2 -0
- package/dist/utils/pi-user-agent.d.ts.map +1 -0
- package/dist/utils/pi-user-agent.js +5 -0
- package/dist/utils/pi-user-agent.js.map +1 -0
- package/dist/utils/structured-output.d.ts +10 -0
- package/dist/utils/structured-output.d.ts.map +1 -0
- package/dist/utils/structured-output.js +57 -0
- package/dist/utils/structured-output.js.map +1 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +6 -2
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +14 -0
- package/dist/utils/version-check.d.ts.map +1 -0
- package/dist/utils/version-check.js +77 -0
- package/dist/utils/version-check.js.map +1 -0
- package/docs/compaction.md +14 -14
- package/docs/custom-provider.md +40 -31
- package/docs/development.md +1 -1
- package/docs/docs.json +148 -0
- package/docs/extensions.md +116 -56
- package/docs/index.md +70 -0
- package/docs/json.md +4 -4
- package/docs/models.md +150 -3
- package/docs/packages.md +10 -5
- package/docs/providers.md +62 -17
- package/docs/quickstart.md +142 -0
- package/docs/rollback-architecture.md +693 -0
- package/docs/rollback-test-cases.md +412 -0
- package/docs/rpc.md +1 -1
- package/docs/sdk.md +26 -26
- package/docs/{session.md → session-format.md} +6 -6
- package/docs/sessions.md +137 -0
- package/docs/settings.md +52 -9
- package/docs/termux.md +1 -1
- package/docs/themes.md +2 -2
- package/docs/tui.md +20 -20
- package/docs/usage.md +277 -0
- package/examples/extensions/README.md +2 -4
- package/examples/extensions/border-status-editor.ts +150 -0
- package/examples/extensions/commands.ts +2 -2
- 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/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/dynamic-resources/dynamic.json +1 -1
- package/examples/extensions/git-checkpoint.ts +1 -1
- package/examples/extensions/handoff.ts +49 -11
- package/examples/extensions/plan-mode/index.ts +1 -1
- package/examples/extensions/sandbox/package-lock.json +5 -5
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/agents.ts +126 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/README.md +2 -2
- package/package.json +7 -15
- package/docs/tree.md +0 -233
- package/examples/extensions/antigravity-image-gen.ts +0 -418
|
@@ -6,27 +6,31 @@ 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 { getProviders, } from "@dyyz1993/pi-ai";
|
|
9
10
|
import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@dyyz1993/pi-tui";
|
|
10
11
|
import { spawn, spawnSync } from "child_process";
|
|
11
|
-
import { APP_NAME, getAgentDir, getAuthPath, getDebugLogPath,
|
|
12
|
+
import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
12
13
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
13
14
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
14
15
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
15
16
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
16
17
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
17
|
-
import { defaultModelPerProvider, findExactModelReferenceMatch,
|
|
18
|
+
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
18
19
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
20
|
+
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
19
21
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
20
22
|
import { SessionManager } from "../../core/session-manager.js";
|
|
21
23
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
22
|
-
import { getCwdDataDir, getGlobalDataDir, getProjectDataDir, getSessionDataDir, resolveProjectRoot, } from "../../core/storage.js";
|
|
23
24
|
import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
|
|
24
25
|
import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
|
|
25
26
|
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
26
27
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
27
28
|
import { parseGitUrl } from "../../utils/git.js";
|
|
29
|
+
import { getCwdRelativePath } from "../../utils/paths.js";
|
|
30
|
+
import { getPiUserAgent } from "../../utils/pi-user-agent.js";
|
|
28
31
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
29
32
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
33
|
+
import { checkForNewPiVersion } from "../../utils/version-check.js";
|
|
30
34
|
import { ArminComponent } from "./components/armin.js";
|
|
31
35
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
32
36
|
import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
@@ -71,7 +75,15 @@ class ExpandableText extends Text {
|
|
|
71
75
|
this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
|
-
const
|
|
78
|
+
const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
|
|
79
|
+
function isDeadTerminalError(error) {
|
|
80
|
+
if (!error || typeof error !== "object" || !("code" in error)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const code = error.code;
|
|
84
|
+
return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
|
|
85
|
+
}
|
|
86
|
+
const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage.";
|
|
75
87
|
function isAnthropicSubscriptionAuthKey(apiKey) {
|
|
76
88
|
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
|
77
89
|
}
|
|
@@ -81,6 +93,17 @@ function isUnknownModel(model) {
|
|
|
81
93
|
function hasDefaultModelProvider(providerId) {
|
|
82
94
|
return providerId in defaultModelPerProvider;
|
|
83
95
|
}
|
|
96
|
+
const BEDROCK_PROVIDER_ID = "amazon-bedrock";
|
|
97
|
+
const BUILT_IN_MODEL_PROVIDERS = new Set(getProviders());
|
|
98
|
+
export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProviderIds = BUILT_IN_MODEL_PROVIDERS) {
|
|
99
|
+
if (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (builtInProviderIds.has(providerId)) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return !oauthProviderIds.has(providerId);
|
|
106
|
+
}
|
|
84
107
|
export class InteractiveMode {
|
|
85
108
|
options;
|
|
86
109
|
runtimeHost;
|
|
@@ -90,6 +113,7 @@ export class InteractiveMode {
|
|
|
90
113
|
statusContainer;
|
|
91
114
|
defaultEditor;
|
|
92
115
|
editor;
|
|
116
|
+
editorComponentFactory;
|
|
93
117
|
autocompleteProvider;
|
|
94
118
|
autocompleteProviderWrappers = [];
|
|
95
119
|
fdPath;
|
|
@@ -103,6 +127,7 @@ export class InteractiveMode {
|
|
|
103
127
|
onInputCallback;
|
|
104
128
|
loadingAnimation = undefined;
|
|
105
129
|
workingMessage = undefined;
|
|
130
|
+
workingVisible = true;
|
|
106
131
|
workingIndicatorOptions = undefined;
|
|
107
132
|
defaultWorkingMessage = "Working...";
|
|
108
133
|
defaultHiddenThinkingLabel = "Thinking...";
|
|
@@ -120,8 +145,6 @@ export class InteractiveMode {
|
|
|
120
145
|
streamingMessage = undefined;
|
|
121
146
|
// Tool execution tracking: toolCallId -> component
|
|
122
147
|
pendingTools = new Map();
|
|
123
|
-
// Track first user message to avoid leading spacer at top of chat
|
|
124
|
-
isFirstUserMessage = true;
|
|
125
148
|
// Tool output expansion state
|
|
126
149
|
toolOutputExpanded = false;
|
|
127
150
|
// Thinking block visibility state
|
|
@@ -182,6 +205,9 @@ export class InteractiveMode {
|
|
|
182
205
|
constructor(runtimeHost, options = {}) {
|
|
183
206
|
this.options = options;
|
|
184
207
|
this.runtimeHost = runtimeHost;
|
|
208
|
+
this.runtimeHost.setBeforeSessionInvalidate(() => {
|
|
209
|
+
this.resetExtensionUI();
|
|
210
|
+
});
|
|
185
211
|
this.runtimeHost.setRebindSession(async () => {
|
|
186
212
|
await this.rebindCurrentSession();
|
|
187
213
|
});
|
|
@@ -449,10 +475,10 @@ export class InteractiveMode {
|
|
|
449
475
|
const cwdBasename = path.basename(this.sessionManager.getCwd());
|
|
450
476
|
const sessionName = this.sessionManager.getSessionName();
|
|
451
477
|
if (sessionName) {
|
|
452
|
-
this.ui.terminal.setTitle(
|
|
478
|
+
this.ui.terminal.setTitle(`${APP_TITLE} - ${sessionName} - ${cwdBasename}`);
|
|
453
479
|
}
|
|
454
480
|
else {
|
|
455
|
-
this.ui.terminal.setTitle(
|
|
481
|
+
this.ui.terminal.setTitle(`${APP_TITLE} - ${cwdBasename}`);
|
|
456
482
|
}
|
|
457
483
|
}
|
|
458
484
|
/**
|
|
@@ -462,7 +488,7 @@ export class InteractiveMode {
|
|
|
462
488
|
async run() {
|
|
463
489
|
await this.init();
|
|
464
490
|
// Start version check asynchronously
|
|
465
|
-
this.
|
|
491
|
+
checkForNewPiVersion(this.version).then((newVersion) => {
|
|
466
492
|
if (newVersion) {
|
|
467
493
|
this.showNewVersionNotification(newVersion);
|
|
468
494
|
}
|
|
@@ -525,29 +551,6 @@ export class InteractiveMode {
|
|
|
525
551
|
}
|
|
526
552
|
}
|
|
527
553
|
}
|
|
528
|
-
/**
|
|
529
|
-
* Check npm registry for a newer version.
|
|
530
|
-
*/
|
|
531
|
-
async checkForNewVersion() {
|
|
532
|
-
if (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE)
|
|
533
|
-
return undefined;
|
|
534
|
-
try {
|
|
535
|
-
const response = await fetch("https://registry.npmjs.org/@dyyz1993/pi-coding-agent/latest", {
|
|
536
|
-
signal: AbortSignal.timeout(10000),
|
|
537
|
-
});
|
|
538
|
-
if (!response.ok)
|
|
539
|
-
return undefined;
|
|
540
|
-
const data = (await response.json());
|
|
541
|
-
const latestVersion = data.version;
|
|
542
|
-
if (latestVersion && latestVersion !== this.version) {
|
|
543
|
-
return latestVersion;
|
|
544
|
-
}
|
|
545
|
-
return undefined;
|
|
546
|
-
}
|
|
547
|
-
catch {
|
|
548
|
-
return undefined;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
554
|
async checkForPackageUpdates() {
|
|
552
555
|
if (process.env.PI_OFFLINE) {
|
|
553
556
|
return [];
|
|
@@ -639,7 +642,10 @@ export class InteractiveMode {
|
|
|
639
642
|
if (!isInstallTelemetryEnabled(this.settingsManager)) {
|
|
640
643
|
return;
|
|
641
644
|
}
|
|
642
|
-
void fetch(`https://pi.dev/install?version=${encodeURIComponent(version)}`, {
|
|
645
|
+
void fetch(`https://pi.dev/api/report-install?version=${encodeURIComponent(version)}`, {
|
|
646
|
+
headers: {
|
|
647
|
+
"User-Agent": getPiUserAgent(version),
|
|
648
|
+
},
|
|
643
649
|
signal: AbortSignal.timeout(5000),
|
|
644
650
|
})
|
|
645
651
|
.then(() => undefined)
|
|
@@ -663,16 +669,17 @@ export class InteractiveMode {
|
|
|
663
669
|
}
|
|
664
670
|
return result;
|
|
665
671
|
}
|
|
672
|
+
formatExtensionDisplayPath(path) {
|
|
673
|
+
let result = this.formatDisplayPath(path);
|
|
674
|
+
result = result.replace(/\/index\.ts$/, "").replace(/\/index\.js$/, "");
|
|
675
|
+
return result;
|
|
676
|
+
}
|
|
666
677
|
formatContextPath(p) {
|
|
667
678
|
const cwd = path.resolve(this.sessionManager.getCwd());
|
|
668
679
|
const absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(cwd, p);
|
|
669
|
-
const relativePath =
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
!relativePath.startsWith(`..${path.sep}`) &&
|
|
673
|
-
!path.isAbsolute(relativePath));
|
|
674
|
-
if (isInsideCwd) {
|
|
675
|
-
return relativePath || ".";
|
|
680
|
+
const relativePath = getCwdRelativePath(absolutePath, cwd);
|
|
681
|
+
if (relativePath !== undefined) {
|
|
682
|
+
return relativePath;
|
|
676
683
|
}
|
|
677
684
|
return this.formatDisplayPath(absolutePath);
|
|
678
685
|
}
|
|
@@ -768,11 +775,18 @@ export class InteractiveMode {
|
|
|
768
775
|
}
|
|
769
776
|
getCompactExtensionLabels(extensions) {
|
|
770
777
|
const nonPackageExtensions = extensions
|
|
771
|
-
.map((extension) =>
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
segments
|
|
775
|
-
|
|
778
|
+
.map((extension) => {
|
|
779
|
+
const segments = this.getCompactDisplayPathSegments(extension.path);
|
|
780
|
+
const lastSegment = segments[segments.length - 1];
|
|
781
|
+
if (segments.length > 1 && (lastSegment === "index.ts" || lastSegment === "index.js")) {
|
|
782
|
+
segments.pop();
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
path: extension.path,
|
|
786
|
+
sourceInfo: extension.sourceInfo,
|
|
787
|
+
segments,
|
|
788
|
+
};
|
|
789
|
+
})
|
|
776
790
|
.filter((extension) => !this.isPackageSource(extension.sourceInfo));
|
|
777
791
|
return extensions.map((extension) => {
|
|
778
792
|
if (this.isPackageSource(extension.sourceInfo)) {
|
|
@@ -1011,8 +1025,8 @@ export class InteractiveMode {
|
|
|
1011
1025
|
if (extensions.length > 0) {
|
|
1012
1026
|
const groups = this.buildScopeGroups(extensions);
|
|
1013
1027
|
const extList = this.formatScopeGroups(groups, {
|
|
1014
|
-
formatPath: (item) => this.
|
|
1015
|
-
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
1028
|
+
formatPath: (item) => this.formatExtensionDisplayPath(item.path),
|
|
1029
|
+
formatPackagePath: (item) => this.formatExtensionDisplayPath(this.getShortPath(item.path, item.sourceInfo)),
|
|
1016
1030
|
});
|
|
1017
1031
|
const extensionCompactList = formatCompactList(this.getCompactExtensionLabels(extensions));
|
|
1018
1032
|
addLoadedSection("Extensions", extensionCompactList, extList, "mdHeading");
|
|
@@ -1152,7 +1166,7 @@ export class InteractiveMode {
|
|
|
1152
1166
|
this.setupAutocompleteProvider();
|
|
1153
1167
|
const extensionRunner = this.session.extensionRunner;
|
|
1154
1168
|
this.setupExtensionShortcuts(extensionRunner);
|
|
1155
|
-
this.showLoadedResources({ force: false });
|
|
1169
|
+
this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
|
|
1156
1170
|
this.showStartupNoticesIfNeeded();
|
|
1157
1171
|
}
|
|
1158
1172
|
applyRuntimeSettings() {
|
|
@@ -1172,7 +1186,6 @@ export class InteractiveMode {
|
|
|
1172
1186
|
}
|
|
1173
1187
|
}
|
|
1174
1188
|
async rebindCurrentSession() {
|
|
1175
|
-
this.resetExtensionUI();
|
|
1176
1189
|
this.unsubscribe?.();
|
|
1177
1190
|
this.unsubscribe = undefined;
|
|
1178
1191
|
this.applyRuntimeSettings();
|
|
@@ -1212,22 +1225,15 @@ export class InteractiveMode {
|
|
|
1212
1225
|
if (shortcuts.size === 0)
|
|
1213
1226
|
return;
|
|
1214
1227
|
// Create a context for shortcut handlers
|
|
1215
|
-
const createContext = (
|
|
1228
|
+
const createContext = () => ({
|
|
1216
1229
|
ui: this.createExtensionUIContext(),
|
|
1217
1230
|
hasUI: true,
|
|
1218
1231
|
cwd: this.sessionManager.getCwd(),
|
|
1219
|
-
extensionName: extName,
|
|
1220
|
-
projectRoot: resolveProjectRoot(this.sessionManager.getCwd()),
|
|
1221
|
-
sessionDataDir: getSessionDataDir(this.sessionManager.getSessionDir(), this.sessionManager.getSessionId(), extName),
|
|
1222
|
-
projectDataDir: getProjectDataDir(resolveProjectRoot(this.sessionManager.getCwd()), extName),
|
|
1223
|
-
cwdDataDir: getCwdDataDir(this.sessionManager.getCwd(), extName),
|
|
1224
|
-
globalDataDir: getGlobalDataDir(extName),
|
|
1225
1232
|
sessionManager: this.sessionManager,
|
|
1226
1233
|
modelRegistry: this.session.modelRegistry,
|
|
1227
1234
|
model: this.session.model,
|
|
1228
1235
|
isIdle: () => !this.session.isStreaming,
|
|
1229
1236
|
signal: this.session.agent.signal,
|
|
1230
|
-
sessionSignal: this.session.sessionSignal,
|
|
1231
1237
|
abort: () => this.session.abort(),
|
|
1232
1238
|
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
|
1233
1239
|
shutdown: () => {
|
|
@@ -1247,16 +1253,23 @@ export class InteractiveMode {
|
|
|
1247
1253
|
})();
|
|
1248
1254
|
},
|
|
1249
1255
|
getSystemPrompt: () => this.session.systemPrompt,
|
|
1250
|
-
|
|
1256
|
+
extensionName: "",
|
|
1257
|
+
projectRoot: this.sessionManager.getCwd(),
|
|
1258
|
+
sessionDataDir: "",
|
|
1259
|
+
projectDataDir: "",
|
|
1260
|
+
cwdDataDir: "",
|
|
1261
|
+
globalDataDir: "",
|
|
1262
|
+
sessionSignal: AbortSignal.abort(),
|
|
1263
|
+
respondUI: () => { },
|
|
1264
|
+
fileSnapshotManager: this.session.fileSnapshotManager,
|
|
1251
1265
|
});
|
|
1252
1266
|
// Set up the extension shortcut handler on the default editor
|
|
1253
1267
|
this.defaultEditor.onExtensionShortcut = (data) => {
|
|
1254
1268
|
for (const [shortcutStr, shortcut] of shortcuts) {
|
|
1255
1269
|
// Cast to KeyId - extension shortcuts use the same format
|
|
1256
1270
|
if (matchesKey(data, shortcutStr)) {
|
|
1257
|
-
const extName = extensionRunner.getExtensionNameByPath(shortcut.extensionPath) ?? "unknown";
|
|
1258
1271
|
// Run handler async, don't block input
|
|
1259
|
-
Promise.resolve(shortcut.handler(createContext(
|
|
1272
|
+
Promise.resolve(shortcut.handler(createContext())).catch((err) => {
|
|
1260
1273
|
this.showError(`Shortcut handler error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1261
1274
|
});
|
|
1262
1275
|
return true;
|
|
@@ -1272,6 +1285,33 @@ export class InteractiveMode {
|
|
|
1272
1285
|
this.footerDataProvider.setExtensionStatus(key, text);
|
|
1273
1286
|
this.ui.requestRender();
|
|
1274
1287
|
}
|
|
1288
|
+
getWorkingLoaderMessage() {
|
|
1289
|
+
return this.workingMessage ?? this.defaultWorkingMessage;
|
|
1290
|
+
}
|
|
1291
|
+
createWorkingLoader() {
|
|
1292
|
+
return new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.getWorkingLoaderMessage(), this.workingIndicatorOptions);
|
|
1293
|
+
}
|
|
1294
|
+
stopWorkingLoader() {
|
|
1295
|
+
if (this.loadingAnimation) {
|
|
1296
|
+
this.loadingAnimation.stop();
|
|
1297
|
+
this.loadingAnimation = undefined;
|
|
1298
|
+
}
|
|
1299
|
+
this.statusContainer.clear();
|
|
1300
|
+
}
|
|
1301
|
+
setWorkingVisible(visible) {
|
|
1302
|
+
this.workingVisible = visible;
|
|
1303
|
+
if (!visible) {
|
|
1304
|
+
this.stopWorkingLoader();
|
|
1305
|
+
this.ui.requestRender();
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
if (this.session.isStreaming && !this.loadingAnimation) {
|
|
1309
|
+
this.statusContainer.clear();
|
|
1310
|
+
this.loadingAnimation = this.createWorkingLoader();
|
|
1311
|
+
this.statusContainer.addChild(this.loadingAnimation);
|
|
1312
|
+
}
|
|
1313
|
+
this.ui.requestRender();
|
|
1314
|
+
}
|
|
1275
1315
|
setWorkingIndicator(options) {
|
|
1276
1316
|
this.workingIndicatorOptions = options;
|
|
1277
1317
|
this.loadingAnimation?.setIndicator(options);
|
|
@@ -1360,6 +1400,7 @@ export class InteractiveMode {
|
|
|
1360
1400
|
this.defaultEditor.onExtensionShortcut = undefined;
|
|
1361
1401
|
this.updateTerminalTitle();
|
|
1362
1402
|
this.workingMessage = undefined;
|
|
1403
|
+
this.workingVisible = true;
|
|
1363
1404
|
this.setWorkingIndicator();
|
|
1364
1405
|
if (this.loadingAnimation) {
|
|
1365
1406
|
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
|
|
@@ -1489,14 +1530,10 @@ export class InteractiveMode {
|
|
|
1489
1530
|
setWorkingMessage: (message) => {
|
|
1490
1531
|
this.workingMessage = message;
|
|
1491
1532
|
if (this.loadingAnimation) {
|
|
1492
|
-
|
|
1493
|
-
this.loadingAnimation.setMessage(message);
|
|
1494
|
-
}
|
|
1495
|
-
else {
|
|
1496
|
-
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
|
|
1497
|
-
}
|
|
1533
|
+
this.loadingAnimation.setMessage(message ?? this.defaultWorkingMessage);
|
|
1498
1534
|
}
|
|
1499
1535
|
},
|
|
1536
|
+
setWorkingVisible: (visible) => this.setWorkingVisible(visible),
|
|
1500
1537
|
setWorkingIndicator: (options) => this.setWorkingIndicator(options),
|
|
1501
1538
|
setHiddenThinkingLabel: (label) => this.setHiddenThinkingLabel(label),
|
|
1502
1539
|
setWidget: (key, content, options) => this.setExtensionWidget(key, content, options),
|
|
@@ -1513,6 +1550,7 @@ export class InteractiveMode {
|
|
|
1513
1550
|
this.setupAutocompleteProvider();
|
|
1514
1551
|
},
|
|
1515
1552
|
setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
|
|
1553
|
+
getEditorComponent: () => this.editorComponentFactory,
|
|
1516
1554
|
get theme() {
|
|
1517
1555
|
return theme;
|
|
1518
1556
|
},
|
|
@@ -1559,7 +1597,7 @@ export class InteractiveMode {
|
|
|
1559
1597
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
1560
1598
|
this.hideExtensionSelector();
|
|
1561
1599
|
resolve(undefined);
|
|
1562
|
-
}, { tui: this.ui, timeout: opts?.timeout
|
|
1600
|
+
}, { tui: this.ui, timeout: opts?.timeout });
|
|
1563
1601
|
this.editorContainer.clear();
|
|
1564
1602
|
this.editorContainer.addChild(this.extensionSelector);
|
|
1565
1603
|
this.ui.setFocus(this.extensionSelector);
|
|
@@ -1661,6 +1699,7 @@ export class InteractiveMode {
|
|
|
1661
1699
|
* Pass undefined to restore the default editor.
|
|
1662
1700
|
*/
|
|
1663
1701
|
setCustomEditorComponent(factory) {
|
|
1702
|
+
this.editorComponentFactory = factory;
|
|
1664
1703
|
// Save text from current editor before switching
|
|
1665
1704
|
const currentText = this.editor.getText();
|
|
1666
1705
|
this.editorContainer.clear();
|
|
@@ -1825,7 +1864,7 @@ export class InteractiveMode {
|
|
|
1825
1864
|
// Set up handlers on defaultEditor - they use this.editor for text access
|
|
1826
1865
|
// so they work correctly regardless of which editor is active
|
|
1827
1866
|
this.defaultEditor.onEscape = () => {
|
|
1828
|
-
if (this.
|
|
1867
|
+
if (this.session.isStreaming) {
|
|
1829
1868
|
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1830
1869
|
}
|
|
1831
1870
|
else if (this.session.isBashRunning) {
|
|
@@ -2095,7 +2134,10 @@ export class InteractiveMode {
|
|
|
2095
2134
|
this.footer.invalidate();
|
|
2096
2135
|
switch (event.type) {
|
|
2097
2136
|
case "agent_start":
|
|
2098
|
-
this.
|
|
2137
|
+
this.pendingTools.clear();
|
|
2138
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2139
|
+
this.ui.terminal.setProgress(true);
|
|
2140
|
+
}
|
|
2099
2141
|
// Restore main escape handler if retry handler is still active
|
|
2100
2142
|
// (retry success event fires later, but we need main handler now)
|
|
2101
2143
|
if (this.retryEscapeHandler) {
|
|
@@ -2110,18 +2152,26 @@ export class InteractiveMode {
|
|
|
2110
2152
|
this.retryLoader.stop();
|
|
2111
2153
|
this.retryLoader = undefined;
|
|
2112
2154
|
}
|
|
2113
|
-
|
|
2114
|
-
|
|
2155
|
+
this.stopWorkingLoader();
|
|
2156
|
+
if (this.workingVisible) {
|
|
2157
|
+
this.loadingAnimation = this.createWorkingLoader();
|
|
2158
|
+
this.statusContainer.addChild(this.loadingAnimation);
|
|
2115
2159
|
}
|
|
2116
|
-
this.statusContainer.clear();
|
|
2117
|
-
this.loadingAnimation = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.workingMessage || this.defaultWorkingMessage, this.workingIndicatorOptions);
|
|
2118
|
-
this.statusContainer.addChild(this.loadingAnimation);
|
|
2119
2160
|
this.ui.requestRender();
|
|
2120
2161
|
break;
|
|
2121
2162
|
case "queue_update":
|
|
2122
2163
|
this.updatePendingMessagesDisplay();
|
|
2123
2164
|
this.ui.requestRender();
|
|
2124
2165
|
break;
|
|
2166
|
+
case "session_info_changed":
|
|
2167
|
+
this.updateTerminalTitle();
|
|
2168
|
+
this.footer.invalidate();
|
|
2169
|
+
this.ui.requestRender();
|
|
2170
|
+
break;
|
|
2171
|
+
case "thinking_level_changed":
|
|
2172
|
+
this.footer.invalidate();
|
|
2173
|
+
this.updateEditorBorderColor();
|
|
2174
|
+
break;
|
|
2125
2175
|
case "message_start":
|
|
2126
2176
|
if (event.message.role === "custom") {
|
|
2127
2177
|
this.addMessageToChat(event.message);
|
|
@@ -2238,7 +2288,9 @@ export class InteractiveMode {
|
|
|
2238
2288
|
break;
|
|
2239
2289
|
}
|
|
2240
2290
|
case "agent_end":
|
|
2241
|
-
this.
|
|
2291
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2292
|
+
this.ui.terminal.setProgress(false);
|
|
2293
|
+
}
|
|
2242
2294
|
if (this.loadingAnimation) {
|
|
2243
2295
|
this.loadingAnimation.stop();
|
|
2244
2296
|
this.loadingAnimation = undefined;
|
|
@@ -2254,7 +2306,9 @@ export class InteractiveMode {
|
|
|
2254
2306
|
this.ui.requestRender();
|
|
2255
2307
|
break;
|
|
2256
2308
|
case "compaction_start": {
|
|
2257
|
-
this.
|
|
2309
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2310
|
+
this.ui.terminal.setProgress(true);
|
|
2311
|
+
}
|
|
2258
2312
|
// Keep editor active; submissions are queued during compaction.
|
|
2259
2313
|
this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
|
|
2260
2314
|
this.defaultEditor.onEscape = () => {
|
|
@@ -2271,7 +2325,9 @@ export class InteractiveMode {
|
|
|
2271
2325
|
break;
|
|
2272
2326
|
}
|
|
2273
2327
|
case "compaction_end": {
|
|
2274
|
-
this.
|
|
2328
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2329
|
+
this.ui.terminal.setProgress(false);
|
|
2330
|
+
}
|
|
2275
2331
|
if (this.autoCompactionEscapeHandler) {
|
|
2276
2332
|
this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
|
|
2277
2333
|
this.autoCompactionEscapeHandler = undefined;
|
|
@@ -2419,13 +2475,10 @@ export class InteractiveMode {
|
|
|
2419
2475
|
this.chatContainer.addChild(component);
|
|
2420
2476
|
break;
|
|
2421
2477
|
}
|
|
2422
|
-
case "foldSummary": {
|
|
2423
|
-
break;
|
|
2424
|
-
}
|
|
2425
2478
|
case "user": {
|
|
2426
2479
|
const textContent = this.getUserMessageText(message);
|
|
2427
2480
|
if (textContent) {
|
|
2428
|
-
if (
|
|
2481
|
+
if (this.chatContainer.children.length > 0) {
|
|
2429
2482
|
this.chatContainer.addChild(new Spacer(1));
|
|
2430
2483
|
}
|
|
2431
2484
|
const skillBlock = parseSkillBlock(textContent);
|
|
@@ -2444,7 +2497,6 @@ export class InteractiveMode {
|
|
|
2444
2497
|
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());
|
|
2445
2498
|
this.chatContainer.addChild(userComponent);
|
|
2446
2499
|
}
|
|
2447
|
-
this.isFirstUserMessage = false;
|
|
2448
2500
|
if (options?.populateHistory) {
|
|
2449
2501
|
this.editor.addToHistory?.(textContent);
|
|
2450
2502
|
}
|
|
@@ -2460,6 +2512,10 @@ export class InteractiveMode {
|
|
|
2460
2512
|
// Tool results are rendered inline with tool calls, handled separately
|
|
2461
2513
|
break;
|
|
2462
2514
|
}
|
|
2515
|
+
case "foldSummary":
|
|
2516
|
+
case "segmentSummary": {
|
|
2517
|
+
break;
|
|
2518
|
+
}
|
|
2463
2519
|
default: {
|
|
2464
2520
|
const _exhaustive = message;
|
|
2465
2521
|
}
|
|
@@ -2473,7 +2529,7 @@ export class InteractiveMode {
|
|
|
2473
2529
|
*/
|
|
2474
2530
|
renderSessionContext(sessionContext, options = {}) {
|
|
2475
2531
|
this.pendingTools.clear();
|
|
2476
|
-
|
|
2532
|
+
const renderedPendingTools = new Map();
|
|
2477
2533
|
if (options.updateFooter) {
|
|
2478
2534
|
this.footer.invalidate();
|
|
2479
2535
|
this.updateEditorBorderColor();
|
|
@@ -2506,17 +2562,17 @@ export class InteractiveMode {
|
|
|
2506
2562
|
component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
|
|
2507
2563
|
}
|
|
2508
2564
|
else {
|
|
2509
|
-
|
|
2565
|
+
renderedPendingTools.set(content.id, component);
|
|
2510
2566
|
}
|
|
2511
2567
|
}
|
|
2512
2568
|
}
|
|
2513
2569
|
}
|
|
2514
2570
|
else if (message.role === "toolResult") {
|
|
2515
2571
|
// Match tool results to pending tool components
|
|
2516
|
-
const component =
|
|
2572
|
+
const component = renderedPendingTools.get(message.toolCallId);
|
|
2517
2573
|
if (component) {
|
|
2518
2574
|
component.updateResult(message);
|
|
2519
|
-
|
|
2575
|
+
renderedPendingTools.delete(message.toolCallId);
|
|
2520
2576
|
}
|
|
2521
2577
|
}
|
|
2522
2578
|
else {
|
|
@@ -2524,7 +2580,9 @@ export class InteractiveMode {
|
|
|
2524
2580
|
this.addMessageToChat(message, options);
|
|
2525
2581
|
}
|
|
2526
2582
|
}
|
|
2527
|
-
|
|
2583
|
+
for (const [toolCallId, component] of renderedPendingTools) {
|
|
2584
|
+
this.pendingTools.set(toolCallId, component);
|
|
2585
|
+
}
|
|
2528
2586
|
this.ui.requestRender();
|
|
2529
2587
|
}
|
|
2530
2588
|
renderInitialMessages() {
|
|
@@ -2574,7 +2632,8 @@ export class InteractiveMode {
|
|
|
2574
2632
|
}
|
|
2575
2633
|
/**
|
|
2576
2634
|
* Gracefully shutdown the agent.
|
|
2577
|
-
*
|
|
2635
|
+
* Stops the TUI before emitting shutdown events so extension UI cleanup cannot
|
|
2636
|
+
* repaint the final frame while the process is exiting.
|
|
2578
2637
|
*/
|
|
2579
2638
|
isShuttingDown = false;
|
|
2580
2639
|
async shutdown() {
|
|
@@ -2582,16 +2641,21 @@ export class InteractiveMode {
|
|
|
2582
2641
|
return;
|
|
2583
2642
|
this.isShuttingDown = true;
|
|
2584
2643
|
this.unregisterSignalHandlers();
|
|
2585
|
-
await this.runtimeHost.dispose();
|
|
2586
|
-
// Wait for any pending renders to complete
|
|
2587
|
-
// requestRender() uses process.nextTick(), so we wait one tick
|
|
2588
|
-
await new Promise((resolve) => process.nextTick(resolve));
|
|
2589
2644
|
// Drain any in-flight Kitty key release events before stopping.
|
|
2590
2645
|
// This prevents escape sequences from leaking to the parent shell over slow SSH.
|
|
2591
2646
|
await this.ui.terminal.drainInput(1000);
|
|
2592
2647
|
this.stop();
|
|
2648
|
+
await this.runtimeHost.dispose();
|
|
2593
2649
|
process.exit(0);
|
|
2594
2650
|
}
|
|
2651
|
+
emergencyTerminalExit() {
|
|
2652
|
+
this.isShuttingDown = true;
|
|
2653
|
+
this.unregisterSignalHandlers();
|
|
2654
|
+
killTrackedDetachedChildren();
|
|
2655
|
+
// The terminal is gone. Do not run normal shutdown because TUI and
|
|
2656
|
+
// extension cleanup can write restore sequences and re-trigger EIO.
|
|
2657
|
+
process.exit(129);
|
|
2658
|
+
}
|
|
2595
2659
|
/**
|
|
2596
2660
|
* Check if shutdown was requested and perform shutdown if so.
|
|
2597
2661
|
*/
|
|
@@ -2608,12 +2672,25 @@ export class InteractiveMode {
|
|
|
2608
2672
|
}
|
|
2609
2673
|
for (const signal of signals) {
|
|
2610
2674
|
const handler = () => {
|
|
2675
|
+
if (signal === "SIGHUP") {
|
|
2676
|
+
this.emergencyTerminalExit();
|
|
2677
|
+
}
|
|
2611
2678
|
killTrackedDetachedChildren();
|
|
2612
2679
|
void this.shutdown();
|
|
2613
2680
|
};
|
|
2614
|
-
process.
|
|
2681
|
+
process.prependListener(signal, handler);
|
|
2615
2682
|
this.signalCleanupHandlers.push(() => process.off(signal, handler));
|
|
2616
2683
|
}
|
|
2684
|
+
const terminalErrorHandler = (error) => {
|
|
2685
|
+
if (isDeadTerminalError(error)) {
|
|
2686
|
+
this.emergencyTerminalExit();
|
|
2687
|
+
}
|
|
2688
|
+
throw error;
|
|
2689
|
+
};
|
|
2690
|
+
process.stdout.on("error", terminalErrorHandler);
|
|
2691
|
+
process.stderr.on("error", terminalErrorHandler);
|
|
2692
|
+
this.signalCleanupHandlers.push(() => process.stdout.off("error", terminalErrorHandler));
|
|
2693
|
+
this.signalCleanupHandlers.push(() => process.stderr.off("error", terminalErrorHandler));
|
|
2617
2694
|
}
|
|
2618
2695
|
unregisterSignalHandlers() {
|
|
2619
2696
|
for (const cleanup of this.signalCleanupHandlers) {
|
|
@@ -2680,6 +2757,7 @@ export class InteractiveMode {
|
|
|
2680
2757
|
}
|
|
2681
2758
|
// If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
|
|
2682
2759
|
else if (this.editor.onSubmit) {
|
|
2760
|
+
this.editor.setText("");
|
|
2683
2761
|
this.editor.onSubmit(text);
|
|
2684
2762
|
}
|
|
2685
2763
|
}
|
|
@@ -2822,9 +2900,9 @@ export class InteractiveMode {
|
|
|
2822
2900
|
this.ui.requestRender();
|
|
2823
2901
|
}
|
|
2824
2902
|
showNewVersionNotification(newVersion) {
|
|
2825
|
-
const action = theme.fg("accent",
|
|
2826
|
-
const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. `) + action;
|
|
2827
|
-
const changelogUrl = theme.fg("accent", "https://github.com/
|
|
2903
|
+
const action = theme.fg("accent", `${APP_NAME} update`);
|
|
2904
|
+
const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. Run `) + action;
|
|
2905
|
+
const changelogUrl = theme.fg("accent", "https://github.com/dyyz1993/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md");
|
|
2828
2906
|
const changelogLine = theme.fg("muted", "Changelog: ") + changelogUrl;
|
|
2829
2907
|
this.chatContainer.addChild(new Spacer(1));
|
|
2830
2908
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
@@ -3051,6 +3129,8 @@ export class InteractiveMode {
|
|
|
3051
3129
|
autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
|
|
3052
3130
|
quietStartup: this.settingsManager.getQuietStartup(),
|
|
3053
3131
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
3132
|
+
showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
|
|
3133
|
+
warnings: this.settingsManager.getWarnings(),
|
|
3054
3134
|
}, {
|
|
3055
3135
|
onAutoCompactChange: (enabled) => {
|
|
3056
3136
|
this.session.setAutoCompactionEnabled(enabled);
|
|
@@ -3160,6 +3240,12 @@ export class InteractiveMode {
|
|
|
3160
3240
|
this.settingsManager.setClearOnShrink(enabled);
|
|
3161
3241
|
this.ui.setClearOnShrink(enabled);
|
|
3162
3242
|
},
|
|
3243
|
+
onShowTerminalProgressChange: (enabled) => {
|
|
3244
|
+
this.settingsManager.setShowTerminalProgress(enabled);
|
|
3245
|
+
},
|
|
3246
|
+
onWarningsChange: (warnings) => {
|
|
3247
|
+
this.settingsManager.setWarnings(warnings);
|
|
3248
|
+
},
|
|
3163
3249
|
onCancel: () => {
|
|
3164
3250
|
done();
|
|
3165
3251
|
this.ui.requestRender();
|
|
@@ -3191,19 +3277,6 @@ export class InteractiveMode {
|
|
|
3191
3277
|
this.showModelSelector(searchTerm);
|
|
3192
3278
|
}
|
|
3193
3279
|
async findExactModelMatch(searchTerm) {
|
|
3194
|
-
const tierModels = this.settingsManager.getTierModels();
|
|
3195
|
-
const aliasTarget = resolveModelAlias(searchTerm, tierModels);
|
|
3196
|
-
if (aliasTarget) {
|
|
3197
|
-
const slashIndex = aliasTarget.indexOf("/");
|
|
3198
|
-
if (slashIndex !== -1) {
|
|
3199
|
-
const provider = aliasTarget.substring(0, slashIndex);
|
|
3200
|
-
const modelId = aliasTarget.substring(slashIndex + 1);
|
|
3201
|
-
const model = this.session.modelRegistry.find(provider, modelId);
|
|
3202
|
-
if (model && this.session.modelRegistry.hasConfiguredAuth(model)) {
|
|
3203
|
-
return model;
|
|
3204
|
-
}
|
|
3205
|
-
}
|
|
3206
|
-
}
|
|
3207
3280
|
const models = await this.getModelCandidates();
|
|
3208
3281
|
return findExactModelReferenceMatch(searchTerm, models);
|
|
3209
3282
|
}
|
|
@@ -3226,6 +3299,9 @@ export class InteractiveMode {
|
|
|
3226
3299
|
this.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);
|
|
3227
3300
|
}
|
|
3228
3301
|
async maybeWarnAboutAnthropicSubscriptionAuth(model = this.session.model) {
|
|
3302
|
+
if (this.settingsManager.getWarnings().anthropicExtraUsage === false) {
|
|
3303
|
+
return;
|
|
3304
|
+
}
|
|
3229
3305
|
if (this.anthropicSubscriptionWarningShown) {
|
|
3230
3306
|
return;
|
|
3231
3307
|
}
|
|
@@ -3555,54 +3631,259 @@ export class InteractiveMode {
|
|
|
3555
3631
|
return this.handleFatalRuntimeError("Failed to resume session", error);
|
|
3556
3632
|
}
|
|
3557
3633
|
}
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3634
|
+
getLoginProviderOptions(authType) {
|
|
3635
|
+
const authStorage = this.session.modelRegistry.authStorage;
|
|
3636
|
+
const oauthProviders = authStorage.getOAuthProviders();
|
|
3637
|
+
const oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));
|
|
3638
|
+
const options = oauthProviders.map((provider) => ({
|
|
3639
|
+
id: provider.id,
|
|
3640
|
+
name: provider.name,
|
|
3641
|
+
authType: "oauth",
|
|
3642
|
+
}));
|
|
3643
|
+
const modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));
|
|
3644
|
+
for (const providerId of modelProviders) {
|
|
3645
|
+
if (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {
|
|
3646
|
+
continue;
|
|
3647
|
+
}
|
|
3648
|
+
options.push({
|
|
3649
|
+
id: providerId,
|
|
3650
|
+
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
3651
|
+
authType: "api_key",
|
|
3652
|
+
});
|
|
3653
|
+
}
|
|
3654
|
+
const filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;
|
|
3655
|
+
return filteredOptions.sort((a, b) => a.name.localeCompare(b.name));
|
|
3656
|
+
}
|
|
3657
|
+
getLogoutProviderOptions() {
|
|
3658
|
+
const authStorage = this.session.modelRegistry.authStorage;
|
|
3659
|
+
const options = [];
|
|
3660
|
+
for (const providerId of authStorage.list()) {
|
|
3661
|
+
const credential = authStorage.get(providerId);
|
|
3662
|
+
if (!credential) {
|
|
3663
|
+
continue;
|
|
3565
3664
|
}
|
|
3665
|
+
options.push({
|
|
3666
|
+
id: providerId,
|
|
3667
|
+
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
3668
|
+
authType: credential.type,
|
|
3669
|
+
});
|
|
3670
|
+
}
|
|
3671
|
+
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
3672
|
+
}
|
|
3673
|
+
showLoginAuthTypeSelector() {
|
|
3674
|
+
const subscriptionLabel = "Use a subscription";
|
|
3675
|
+
const apiKeyLabel = "Use an API key";
|
|
3676
|
+
this.showSelector((done) => {
|
|
3677
|
+
const selector = new ExtensionSelectorComponent("Select authentication method:", [subscriptionLabel, apiKeyLabel], (option) => {
|
|
3678
|
+
done();
|
|
3679
|
+
const authType = option === subscriptionLabel ? "oauth" : "api_key";
|
|
3680
|
+
this.showLoginProviderSelector(authType);
|
|
3681
|
+
}, () => {
|
|
3682
|
+
done();
|
|
3683
|
+
this.ui.requestRender();
|
|
3684
|
+
});
|
|
3685
|
+
return { component: selector, focus: selector };
|
|
3686
|
+
});
|
|
3687
|
+
}
|
|
3688
|
+
showLoginProviderSelector(authType) {
|
|
3689
|
+
const providerOptions = this.getLoginProviderOptions(authType);
|
|
3690
|
+
if (providerOptions.length === 0) {
|
|
3691
|
+
this.showStatus(authType === "oauth" ? "No subscription providers available." : "No API key providers available.");
|
|
3692
|
+
return;
|
|
3566
3693
|
}
|
|
3567
3694
|
this.showSelector((done) => {
|
|
3568
|
-
const selector = new OAuthSelectorComponent(
|
|
3695
|
+
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
3569
3696
|
done();
|
|
3570
|
-
|
|
3571
|
-
|
|
3697
|
+
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
3698
|
+
if (!providerOption) {
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
if (providerOption.authType === "oauth") {
|
|
3702
|
+
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
3703
|
+
}
|
|
3704
|
+
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
3705
|
+
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
3706
|
+
}
|
|
3707
|
+
else {
|
|
3708
|
+
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
3709
|
+
}
|
|
3710
|
+
}, () => {
|
|
3711
|
+
done();
|
|
3712
|
+
this.showLoginAuthTypeSelector();
|
|
3713
|
+
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId));
|
|
3714
|
+
return { component: selector, focus: selector };
|
|
3715
|
+
});
|
|
3716
|
+
}
|
|
3717
|
+
async showOAuthSelector(mode) {
|
|
3718
|
+
if (mode === "login") {
|
|
3719
|
+
this.showLoginAuthTypeSelector();
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
const providerOptions = this.getLogoutProviderOptions();
|
|
3723
|
+
if (providerOptions.length === 0) {
|
|
3724
|
+
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
this.showSelector((done) => {
|
|
3728
|
+
const selector = new OAuthSelectorComponent(mode, this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
3729
|
+
done();
|
|
3730
|
+
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
3731
|
+
if (!providerOption) {
|
|
3732
|
+
return;
|
|
3733
|
+
}
|
|
3734
|
+
try {
|
|
3735
|
+
this.session.modelRegistry.authStorage.logout(providerOption.id);
|
|
3736
|
+
this.session.modelRegistry.refresh();
|
|
3737
|
+
await this.updateAvailableProviderCount();
|
|
3738
|
+
const message = providerOption.authType === "oauth"
|
|
3739
|
+
? `Logged out of ${providerOption.name}`
|
|
3740
|
+
: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;
|
|
3741
|
+
this.showStatus(message);
|
|
3742
|
+
}
|
|
3743
|
+
catch (error) {
|
|
3744
|
+
this.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3745
|
+
}
|
|
3746
|
+
}, () => {
|
|
3747
|
+
done();
|
|
3748
|
+
this.ui.requestRender();
|
|
3749
|
+
});
|
|
3750
|
+
return { component: selector, focus: selector };
|
|
3751
|
+
});
|
|
3752
|
+
}
|
|
3753
|
+
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
3754
|
+
this.session.modelRegistry.refresh();
|
|
3755
|
+
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
3756
|
+
let selectedModel;
|
|
3757
|
+
let selectionError;
|
|
3758
|
+
if (isUnknownModel(previousModel)) {
|
|
3759
|
+
const availableModels = this.session.modelRegistry.getAvailable();
|
|
3760
|
+
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
3761
|
+
if (!hasDefaultModelProvider(providerId)) {
|
|
3762
|
+
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
3763
|
+
}
|
|
3764
|
+
else if (providerModels.length === 0) {
|
|
3765
|
+
selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
|
|
3766
|
+
}
|
|
3767
|
+
else {
|
|
3768
|
+
const defaultModelId = defaultModelPerProvider[providerId];
|
|
3769
|
+
selectedModel = providerModels.find((model) => model.id === defaultModelId);
|
|
3770
|
+
if (!selectedModel) {
|
|
3771
|
+
selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
|
|
3572
3772
|
}
|
|
3573
3773
|
else {
|
|
3574
|
-
// Logout flow
|
|
3575
|
-
const providerInfo = this.session.modelRegistry.authStorage
|
|
3576
|
-
.getOAuthProviders()
|
|
3577
|
-
.find((p) => p.id === providerId);
|
|
3578
|
-
const providerName = providerInfo?.name || providerId;
|
|
3579
3774
|
try {
|
|
3580
|
-
this.session.
|
|
3581
|
-
this.session.modelRegistry.refresh();
|
|
3582
|
-
await this.updateAvailableProviderCount();
|
|
3583
|
-
this.showStatus(`Logged out of ${providerName}`);
|
|
3775
|
+
await this.session.setModel(selectedModel);
|
|
3584
3776
|
}
|
|
3585
3777
|
catch (error) {
|
|
3586
|
-
|
|
3778
|
+
selectedModel = undefined;
|
|
3779
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3780
|
+
selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
|
|
3587
3781
|
}
|
|
3588
3782
|
}
|
|
3589
|
-
}
|
|
3590
|
-
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
await this.updateAvailableProviderCount();
|
|
3786
|
+
this.footer.invalidate();
|
|
3787
|
+
this.updateEditorBorderColor();
|
|
3788
|
+
if (selectedModel) {
|
|
3789
|
+
this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
|
|
3790
|
+
void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
|
|
3791
|
+
this.checkDaxnutsEasterEgg(selectedModel);
|
|
3792
|
+
}
|
|
3793
|
+
else {
|
|
3794
|
+
this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
|
|
3795
|
+
if (selectionError) {
|
|
3796
|
+
this.showError(selectionError);
|
|
3797
|
+
}
|
|
3798
|
+
else {
|
|
3799
|
+
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
showBedrockSetupDialog(providerId, providerName) {
|
|
3804
|
+
const restoreEditor = () => {
|
|
3805
|
+
this.editorContainer.clear();
|
|
3806
|
+
this.editorContainer.addChild(this.editor);
|
|
3807
|
+
this.ui.setFocus(this.editor);
|
|
3808
|
+
this.ui.requestRender();
|
|
3809
|
+
};
|
|
3810
|
+
const dialog = new LoginDialogComponent(this.ui, providerId, () => restoreEditor(), providerName, "Amazon Bedrock setup");
|
|
3811
|
+
dialog.showInfo([
|
|
3812
|
+
theme.fg("text", "Amazon Bedrock uses AWS credentials instead of a single API key."),
|
|
3813
|
+
theme.fg("text", "Configure an AWS profile, IAM keys, bearer token, or role-based credentials."),
|
|
3814
|
+
theme.fg("muted", "See:"),
|
|
3815
|
+
theme.fg("accent", ` ${path.join(getDocsPath(), "providers.md")}`),
|
|
3816
|
+
]);
|
|
3817
|
+
this.editorContainer.clear();
|
|
3818
|
+
this.editorContainer.addChild(dialog);
|
|
3819
|
+
this.ui.setFocus(dialog);
|
|
3820
|
+
this.ui.requestRender();
|
|
3821
|
+
}
|
|
3822
|
+
async showApiKeyLoginDialog(providerId, providerName) {
|
|
3823
|
+
const previousModel = this.session.model;
|
|
3824
|
+
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
3825
|
+
// Completion handled below
|
|
3826
|
+
}, providerName);
|
|
3827
|
+
this.editorContainer.clear();
|
|
3828
|
+
this.editorContainer.addChild(dialog);
|
|
3829
|
+
this.ui.setFocus(dialog);
|
|
3830
|
+
this.ui.requestRender();
|
|
3831
|
+
const restoreEditor = () => {
|
|
3832
|
+
this.editorContainer.clear();
|
|
3833
|
+
this.editorContainer.addChild(this.editor);
|
|
3834
|
+
this.ui.setFocus(this.editor);
|
|
3835
|
+
this.ui.requestRender();
|
|
3836
|
+
};
|
|
3837
|
+
try {
|
|
3838
|
+
const apiKey = (await dialog.showPrompt("Enter API key:")).trim();
|
|
3839
|
+
if (!apiKey) {
|
|
3840
|
+
throw new Error("API key cannot be empty.");
|
|
3841
|
+
}
|
|
3842
|
+
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey });
|
|
3843
|
+
restoreEditor();
|
|
3844
|
+
await this.completeProviderAuthentication(providerId, providerName, "api_key", previousModel);
|
|
3845
|
+
}
|
|
3846
|
+
catch (error) {
|
|
3847
|
+
restoreEditor();
|
|
3848
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3849
|
+
if (errorMsg !== "Login cancelled") {
|
|
3850
|
+
this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
showOAuthLoginSelect(dialog, prompt) {
|
|
3855
|
+
return new Promise((resolve) => {
|
|
3856
|
+
const restoreDialog = () => {
|
|
3857
|
+
this.editorContainer.clear();
|
|
3858
|
+
this.editorContainer.addChild(dialog);
|
|
3859
|
+
this.ui.setFocus(dialog);
|
|
3591
3860
|
this.ui.requestRender();
|
|
3861
|
+
};
|
|
3862
|
+
const labels = prompt.options.map((option) => option.label);
|
|
3863
|
+
const selector = new ExtensionSelectorComponent(prompt.message, labels, (optionLabel) => {
|
|
3864
|
+
restoreDialog();
|
|
3865
|
+
resolve(prompt.options.find((option) => option.label === optionLabel)?.id);
|
|
3866
|
+
}, () => {
|
|
3867
|
+
restoreDialog();
|
|
3868
|
+
resolve(undefined);
|
|
3592
3869
|
});
|
|
3593
|
-
|
|
3870
|
+
this.editorContainer.clear();
|
|
3871
|
+
this.editorContainer.addChild(selector);
|
|
3872
|
+
this.ui.setFocus(selector);
|
|
3873
|
+
this.ui.requestRender();
|
|
3594
3874
|
});
|
|
3595
3875
|
}
|
|
3596
|
-
async showLoginDialog(providerId) {
|
|
3597
|
-
const providerInfo = this.session.modelRegistry.authStorage
|
|
3598
|
-
|
|
3876
|
+
async showLoginDialog(providerId, providerName) {
|
|
3877
|
+
const providerInfo = this.session.modelRegistry.authStorage
|
|
3878
|
+
.getOAuthProviders()
|
|
3879
|
+
.find((provider) => provider.id === providerId);
|
|
3599
3880
|
const previousModel = this.session.model;
|
|
3600
3881
|
// Providers that use callback servers (can paste redirect URL)
|
|
3601
3882
|
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
3602
3883
|
// Create login dialog component
|
|
3603
3884
|
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
3604
3885
|
// Completion handled below
|
|
3605
|
-
});
|
|
3886
|
+
}, providerName);
|
|
3606
3887
|
// Show dialog in editor container
|
|
3607
3888
|
this.editorContainer.clear();
|
|
3608
3889
|
this.editorContainer.addChild(dialog);
|
|
@@ -3655,58 +3936,13 @@ export class InteractiveMode {
|
|
|
3655
3936
|
onProgress: (message) => {
|
|
3656
3937
|
dialog.showProgress(message);
|
|
3657
3938
|
},
|
|
3939
|
+
onSelect: (prompt) => this.showOAuthLoginSelect(dialog, prompt),
|
|
3658
3940
|
onManualCodeInput: () => manualCodePromise,
|
|
3659
3941
|
signal: dialog.signal,
|
|
3660
3942
|
});
|
|
3661
3943
|
// Success
|
|
3662
3944
|
restoreEditor();
|
|
3663
|
-
this.
|
|
3664
|
-
let selectedModel;
|
|
3665
|
-
let selectionError;
|
|
3666
|
-
if (isUnknownModel(previousModel)) {
|
|
3667
|
-
const availableModels = this.session.modelRegistry.getAvailable();
|
|
3668
|
-
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
3669
|
-
if (!hasDefaultModelProvider(providerId)) {
|
|
3670
|
-
selectionError = `Logged in to ${providerName}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
3671
|
-
}
|
|
3672
|
-
else if (providerModels.length === 0) {
|
|
3673
|
-
selectionError = `Logged in to ${providerName}, but no models are available for that provider. Use /model to select a model.`;
|
|
3674
|
-
}
|
|
3675
|
-
else {
|
|
3676
|
-
const defaultModelId = defaultModelPerProvider[providerId];
|
|
3677
|
-
selectedModel = providerModels.find((model) => model.id === defaultModelId);
|
|
3678
|
-
if (!selectedModel) {
|
|
3679
|
-
selectionError = `Logged in to ${providerName}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
|
|
3680
|
-
}
|
|
3681
|
-
else {
|
|
3682
|
-
try {
|
|
3683
|
-
await this.session.setModel(selectedModel);
|
|
3684
|
-
}
|
|
3685
|
-
catch (error) {
|
|
3686
|
-
selectedModel = undefined;
|
|
3687
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3688
|
-
selectionError = `Logged in to ${providerName}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
|
|
3689
|
-
}
|
|
3690
|
-
}
|
|
3691
|
-
}
|
|
3692
|
-
}
|
|
3693
|
-
await this.updateAvailableProviderCount();
|
|
3694
|
-
this.footer.invalidate();
|
|
3695
|
-
this.updateEditorBorderColor();
|
|
3696
|
-
if (selectedModel) {
|
|
3697
|
-
this.showStatus(`Logged in to ${providerName}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
|
|
3698
|
-
void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
|
|
3699
|
-
this.checkDaxnutsEasterEgg(selectedModel);
|
|
3700
|
-
}
|
|
3701
|
-
else {
|
|
3702
|
-
this.showStatus(`Logged in to ${providerName}. Credentials saved to ${getAuthPath()}`);
|
|
3703
|
-
if (selectionError) {
|
|
3704
|
-
this.showError(selectionError);
|
|
3705
|
-
}
|
|
3706
|
-
else {
|
|
3707
|
-
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
3708
|
-
}
|
|
3709
|
-
}
|
|
3945
|
+
await this.completeProviderAuthentication(providerId, providerName, "oauth", previousModel);
|
|
3710
3946
|
}
|
|
3711
3947
|
catch (error) {
|
|
3712
3948
|
restoreEditor();
|
|
@@ -3996,8 +4232,7 @@ export class InteractiveMode {
|
|
|
3996
4232
|
this.ui.requestRender();
|
|
3997
4233
|
return;
|
|
3998
4234
|
}
|
|
3999
|
-
this.
|
|
4000
|
-
this.updateTerminalTitle();
|
|
4235
|
+
this.session.setSessionName(name);
|
|
4001
4236
|
this.chatContainer.addChild(new Spacer(1));
|
|
4002
4237
|
this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${name}`), 1, 0));
|
|
4003
4238
|
this.ui.requestRender();
|
|
@@ -4338,7 +4573,9 @@ export class InteractiveMode {
|
|
|
4338
4573
|
}
|
|
4339
4574
|
stop() {
|
|
4340
4575
|
this.unregisterSignalHandlers();
|
|
4341
|
-
this.
|
|
4576
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
4577
|
+
this.ui.terminal.setProgress(false);
|
|
4578
|
+
}
|
|
4342
4579
|
if (this.loadingAnimation) {
|
|
4343
4580
|
this.loadingAnimation.stop();
|
|
4344
4581
|
this.loadingAnimation = undefined;
|