@selesai/code 0.3.9 → 0.4.0
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/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -4
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts +4 -2
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +13 -25
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/core/agent-session.d.ts +22 -5
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +130 -49
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +13 -5
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/cache-stats.d.ts +49 -0
- package/dist/core/cache-stats.d.ts.map +1 -0
- package/dist/core/cache-stats.js +101 -0
- package/dist/core/cache-stats.js.map +1 -0
- package/dist/core/compaction/compaction.d.ts +1 -2
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +56 -81
- package/dist/core/compaction/compaction.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 +10 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -3
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +38 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +41 -12
- 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 +28 -1
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +3 -6
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-registry.d.ts +4 -6
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +34 -8
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +10 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +15 -18
- package/dist/core/model-resolver.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 +68 -26
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/provider-attribution.d.ts.map +1 -1
- package/dist/core/provider-attribution.js +0 -10
- package/dist/core/provider-attribution.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -2
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +8 -7
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +8 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +21 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +104 -72
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +14 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +29 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts +1 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +3 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/timings.d.ts +4 -2
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +24 -14
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +20 -5
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +2 -2
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +12 -25
- package/dist/core/tools/read.js.map +1 -1
- package/dist/defaults/models.json +2 -3
- package/dist/extensions/caveman/index.js +16 -1
- package/dist/extensions/caveman/test/extension.test.js +7 -4
- package/dist/extensions/caveman/test/helpers.test.js +12 -1
- package/dist/extensions/context-compaction-reminder.test.ts +82 -0
- package/dist/extensions/context-compaction-reminder.ts +28 -0
- package/dist/extensions/handoff-new.test.ts +2 -13
- package/dist/extensions/handoff-new.ts +16 -25
- package/dist/extensions/package.json +1 -0
- package/dist/extensions/pi-subagents/agents/architect.md +25 -173
- package/dist/extensions/pi-subagents/agents/builder.md +16 -105
- package/dist/extensions/pi-subagents/agents/commentator.md +19 -116
- package/dist/extensions/pi-subagents/agents/explorer.md +13 -33
- package/dist/extensions/pi-subagents/agents/recapper.md +19 -11
- package/dist/extensions/pi-subagents/agents/researcher.md +10 -33
- package/dist/extensions/question/constants.ts +0 -7
- package/dist/extensions/question/index.ts +11 -108
- package/dist/extensions/question/schemas.ts +1 -26
- package/dist/extensions/question/shortcuts.ts +5 -14
- package/dist/extensions/question/tests/question-list.test.ts +8 -0
- package/dist/extensions/question/tests/ui-protocol.test.ts +1 -7
- package/dist/extensions/question/tui-adapter.ts +3 -20
- package/dist/extensions/question/types.ts +0 -6
- package/dist/extensions/question/ui-protocol.ts +2 -17
- package/dist/extensions/workflow/adapter.ts +384 -124
- package/dist/extensions/workflow/extension.ts +2 -1
- package/dist/extensions/workflow/modes/prototype.ts +6 -6
- package/dist/extensions/workflow/modes/quick.ts +6 -6
- package/dist/extensions/workflow/modes/task.ts +79 -0
- package/dist/extensions/workflow/run-state.ts +124 -0
- package/dist/extensions/workflow/state-machine.ts +4 -8
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +2 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +17 -4
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +23 -15
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts +34 -3
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +280 -25
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-entry.d.ts +19 -0
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -0
- package/dist/modes/interactive/components/custom-entry.js +52 -0
- package/dist/modes/interactive/components/custom-entry.js.map +1 -0
- package/dist/modes/interactive/components/extension-editor.d.ts +3 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +12 -3
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +4 -0
- 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/oauth-selector.d.ts +3 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +17 -6
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +25 -2
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/status-indicator.d.ts +28 -0
- package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -0
- package/dist/modes/interactive/components/status-indicator.js +60 -0
- package/dist/modes/interactive/components/status-indicator.js.map +1 -0
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +2 -1
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +6 -2
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +19 -6
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +21 -10
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +417 -196
- 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-schema.json +6 -2
- package/dist/modes/interactive/theme/theme.d.ts +3 -2
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +10 -3
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +1 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +21 -6
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +17 -3
- 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 +20 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +26 -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 +2 -2
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +71 -17
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/rpc-entry.d.ts +3 -0
- package/dist/rpc-entry.d.ts.map +1 -0
- package/dist/rpc-entry.js +10 -0
- package/dist/rpc-entry.js.map +1 -0
- package/dist/skills/workflow-creation/SKILL.md +72 -0
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +1 -1
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/image-convert.d.ts +1 -0
- package/dist/utils/image-convert.d.ts.map +1 -1
- package/dist/utils/image-convert.js +21 -15
- package/dist/utils/image-convert.js.map +1 -1
- package/dist/utils/image-process.d.ts +18 -0
- package/dist/utils/image-process.d.ts.map +1 -0
- package/dist/utils/image-process.js +83 -0
- package/dist/utils/image-process.js.map +1 -0
- package/dist/utils/mime.d.ts.map +1 -1
- package/dist/utils/mime.js +41 -0
- package/dist/utils/mime.js.map +1 -1
- package/docs/workflows.md +40 -6
- package/package.json +8 -5
|
@@ -6,12 +6,13 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { getProviders, } from "@earendil-works/pi-ai/compat";
|
|
9
|
-
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink,
|
|
9
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
|
|
10
10
|
import chalk from "chalk";
|
|
11
11
|
import { spawn, spawnSync } from "child_process";
|
|
12
12
|
import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
13
13
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
14
14
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
15
|
+
import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
|
|
15
16
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
16
17
|
import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
|
|
17
18
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
@@ -20,7 +21,7 @@ import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScop
|
|
|
20
21
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
21
22
|
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
22
23
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
23
|
-
import { SessionManager } from "../../core/session-manager.js";
|
|
24
|
+
import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
|
|
24
25
|
import { buildGitCommandPrompt, GIT_COMMAND_MODES } from "../../core/git-command.js";
|
|
25
26
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
26
27
|
import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
|
|
@@ -41,8 +42,8 @@ import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
|
41
42
|
import { BorderedLoader } from "./components/bordered-loader.js";
|
|
42
43
|
import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
|
|
43
44
|
import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
|
|
44
|
-
import { CountdownTimer } from "./components/countdown-timer.js";
|
|
45
45
|
import { CustomEditor } from "./components/custom-editor.js";
|
|
46
|
+
import { CustomEntryComponent } from "./components/custom-entry.js";
|
|
46
47
|
import { CustomMessageComponent } from "./components/custom-message.js";
|
|
47
48
|
import { DaxnutsComponent } from "./components/daxnuts.js";
|
|
48
49
|
import { DynamicBorder } from "./components/dynamic-border.js";
|
|
@@ -50,15 +51,16 @@ import { EarendilAnnouncementComponent } from "./components/earendil-announcemen
|
|
|
50
51
|
import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
51
52
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
52
53
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
53
|
-
import { FooterComponent } from "./components/footer.js";
|
|
54
|
+
import { FooterComponent, formatTokens } from "./components/footer.js";
|
|
54
55
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
55
56
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
56
57
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
57
|
-
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
58
|
+
import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
|
|
58
59
|
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
59
60
|
import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
60
61
|
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
61
62
|
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
63
|
+
import { BranchSummaryStatusIndicator, CompactionStatusIndicator, IdleStatus, RetryStatusIndicator, WorkingStatusIndicator, } from "./components/status-indicator.js";
|
|
62
64
|
import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
63
65
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
64
66
|
import { TrustSelectorComponent } from "./components/trust-selector.js";
|
|
@@ -82,6 +84,9 @@ class ExpandableText extends Text {
|
|
|
82
84
|
this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
|
|
83
85
|
}
|
|
84
86
|
}
|
|
87
|
+
function isCustomSessionEntry(item) {
|
|
88
|
+
return "type" in item && item.type === "custom";
|
|
89
|
+
}
|
|
85
90
|
const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
|
|
86
91
|
function isDeadTerminalError(error) {
|
|
87
92
|
if (!error || typeof error !== "object" || !("code" in error)) {
|
|
@@ -132,9 +137,46 @@ export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProvi
|
|
|
132
137
|
}
|
|
133
138
|
return !oauthProviderIds.has(providerId);
|
|
134
139
|
}
|
|
140
|
+
const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
|
|
141
|
+
function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
|
|
142
|
+
const filtered = fuzzyFilter(items, prefix, getSearchText);
|
|
143
|
+
if (filtered.length === 0)
|
|
144
|
+
return null;
|
|
145
|
+
return filtered.map(toAutocompleteItem);
|
|
146
|
+
}
|
|
147
|
+
function getLoginProviderCompletionOptions(providerOptions) {
|
|
148
|
+
const byId = new Map();
|
|
149
|
+
for (const provider of providerOptions) {
|
|
150
|
+
const existing = byId.get(provider.id);
|
|
151
|
+
if (existing) {
|
|
152
|
+
if (!existing.authTypes.includes(provider.authType)) {
|
|
153
|
+
existing.authTypes.push(provider.authType);
|
|
154
|
+
existing.authTypes.sort((a, b) => AUTH_TYPE_ORDER[a] - AUTH_TYPE_ORDER[b]);
|
|
155
|
+
}
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
byId.set(provider.id, {
|
|
159
|
+
id: provider.id,
|
|
160
|
+
name: provider.name,
|
|
161
|
+
authTypes: [provider.authType],
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return Array.from(byId.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
165
|
+
}
|
|
166
|
+
function getLoginProviderSearchText(provider) {
|
|
167
|
+
const authTypes = provider.authTypes
|
|
168
|
+
.map((authType) => `${authType} ${formatAuthSelectorProviderType(authType)}`)
|
|
169
|
+
.join(" ");
|
|
170
|
+
return `${provider.id} ${provider.name} ${authTypes}`;
|
|
171
|
+
}
|
|
172
|
+
function formatLoginProviderCompletionDescription(provider) {
|
|
173
|
+
const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
|
|
174
|
+
return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
|
|
175
|
+
}
|
|
135
176
|
export class InteractiveMode {
|
|
136
177
|
runtimeHost;
|
|
137
178
|
ui;
|
|
179
|
+
loadedResourcesContainer;
|
|
138
180
|
chatContainer;
|
|
139
181
|
pendingMessagesContainer;
|
|
140
182
|
statusContainer;
|
|
@@ -154,7 +196,8 @@ export class InteractiveMode {
|
|
|
154
196
|
onInputCallback;
|
|
155
197
|
pendingUserInputs = [];
|
|
156
198
|
pendingInputImages = [];
|
|
157
|
-
|
|
199
|
+
activeStatusIndicator = undefined;
|
|
200
|
+
idleStatus = new IdleStatus();
|
|
158
201
|
workingMessage = undefined;
|
|
159
202
|
workingVisible = true;
|
|
160
203
|
workingIndicatorOptions = undefined;
|
|
@@ -178,6 +221,7 @@ export class InteractiveMode {
|
|
|
178
221
|
toolOutputExpanded = false;
|
|
179
222
|
// Thinking block visibility state
|
|
180
223
|
hideThinkingBlock = false;
|
|
224
|
+
outputPad = 1;
|
|
181
225
|
// Skill commands: command name -> skill file path
|
|
182
226
|
skillCommands = new Map();
|
|
183
227
|
// Agent subscription unsubscribe function
|
|
@@ -190,11 +234,8 @@ export class InteractiveMode {
|
|
|
190
234
|
// Track pending bash components (shown in pending area, moved to chat on submit)
|
|
191
235
|
pendingBashComponents = [];
|
|
192
236
|
// Auto-compaction state
|
|
193
|
-
autoCompactionLoader = undefined;
|
|
194
237
|
autoCompactionEscapeHandler;
|
|
195
238
|
// Auto-retry state
|
|
196
|
-
retryLoader = undefined;
|
|
197
|
-
retryCountdown = undefined;
|
|
198
239
|
retryEscapeHandler;
|
|
199
240
|
// Messages queued while compaction is running
|
|
200
241
|
compactionQueuedMessages = [];
|
|
@@ -248,6 +289,7 @@ export class InteractiveMode {
|
|
|
248
289
|
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
|
|
249
290
|
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
|
250
291
|
this.headerContainer = new Container();
|
|
292
|
+
this.loadedResourcesContainer = new Container();
|
|
251
293
|
this.chatContainer = new Container();
|
|
252
294
|
this.pendingMessagesContainer = new Container();
|
|
253
295
|
this.statusContainer = new Container();
|
|
@@ -269,6 +311,7 @@ export class InteractiveMode {
|
|
|
269
311
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
270
312
|
// Load hide thinking block setting
|
|
271
313
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
314
|
+
this.outputPad = this.settingsManager.getOutputPad();
|
|
272
315
|
// Register themes from resource loader and initialize
|
|
273
316
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
274
317
|
this.themeController = new InteractiveThemeController(this.ui, this.settingsManager, (message) => this.showError(message), () => this.updateEditorBorderColor());
|
|
@@ -317,6 +360,7 @@ export class InteractiveMode {
|
|
|
317
360
|
const slashCommands = BUILTIN_SLASH_COMMANDS.map((command) => ({
|
|
318
361
|
name: command.name,
|
|
319
362
|
description: command.description,
|
|
363
|
+
...(command.argumentHint && { argumentHint: command.argumentHint }),
|
|
320
364
|
}));
|
|
321
365
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
322
366
|
if (modelCommand) {
|
|
@@ -334,11 +378,7 @@ export class InteractiveMode {
|
|
|
334
378
|
name: m.name,
|
|
335
379
|
label: `${m.provider}/${m.id}`,
|
|
336
380
|
}));
|
|
337
|
-
|
|
338
|
-
const filtered = fuzzyFilter(items, prefix, getModelSearchText);
|
|
339
|
-
if (filtered.length === 0)
|
|
340
|
-
return null;
|
|
341
|
-
return filtered.map((item) => ({
|
|
381
|
+
return createFuzzyAutocompleteItems(items, prefix, getModelSearchText, (item) => ({
|
|
342
382
|
value: item.label,
|
|
343
383
|
label: item.id,
|
|
344
384
|
description: item.provider,
|
|
@@ -355,6 +395,17 @@ export class InteractiveMode {
|
|
|
355
395
|
: null;
|
|
356
396
|
};
|
|
357
397
|
}
|
|
398
|
+
const loginCommand = slashCommands.find((command) => command.name === "login");
|
|
399
|
+
if (loginCommand) {
|
|
400
|
+
loginCommand.getArgumentCompletions = (prefix) => {
|
|
401
|
+
const providers = getLoginProviderCompletionOptions(this.getLoginProviderOptions());
|
|
402
|
+
return createFuzzyAutocompleteItems(providers, prefix, getLoginProviderSearchText, (provider) => ({
|
|
403
|
+
value: provider.id,
|
|
404
|
+
label: provider.id,
|
|
405
|
+
description: formatLoginProviderCompletionDescription(provider),
|
|
406
|
+
}));
|
|
407
|
+
};
|
|
408
|
+
}
|
|
358
409
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
359
410
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
360
411
|
name: cmd.name,
|
|
@@ -451,8 +502,10 @@ export class InteractiveMode {
|
|
|
451
502
|
: "";
|
|
452
503
|
console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
|
|
453
504
|
}
|
|
454
|
-
// Add header container as first child. Populate it after
|
|
505
|
+
// Add header container as first child. Populate it after applying theme settings.
|
|
506
|
+
// Keep loaded resources before chat so restored session messages never precede them.
|
|
455
507
|
this.ui.addChild(this.headerContainer);
|
|
508
|
+
this.ui.addChild(this.loadedResourcesContainer);
|
|
456
509
|
this.ui.addChild(this.chatContainer);
|
|
457
510
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
458
511
|
this.ui.addChild(this.statusContainer);
|
|
@@ -1002,6 +1055,8 @@ export class InteractiveMode {
|
|
|
1002
1055
|
return lines.join("\n");
|
|
1003
1056
|
}
|
|
1004
1057
|
showLoadedResources(options) {
|
|
1058
|
+
// Resource rendering is idempotent; chat clears no longer clear this separate container.
|
|
1059
|
+
this.loadedResourcesContainer.clear();
|
|
1005
1060
|
const showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
|
|
1006
1061
|
const showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;
|
|
1007
1062
|
if (!showListing && !showDiagnostics) {
|
|
@@ -1017,8 +1072,8 @@ export class InteractiveMode {
|
|
|
1017
1072
|
};
|
|
1018
1073
|
const addLoadedSection = (name, collapsedBody, expandedBody = collapsedBody, color = "mdHeading") => {
|
|
1019
1074
|
const section = new ExpandableText(() => `${sectionHeader(name, color)}\n${collapsedBody}`, () => `${sectionHeader(name, color)}\n${expandedBody}`, this.getStartupExpansionState(), 0, 0);
|
|
1020
|
-
this.
|
|
1021
|
-
this.
|
|
1075
|
+
this.loadedResourcesContainer.addChild(section);
|
|
1076
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1022
1077
|
};
|
|
1023
1078
|
const skillsResult = this.session.resourceLoader.getSkills();
|
|
1024
1079
|
const promptsResult = this.session.resourceLoader.getPrompts();
|
|
@@ -1052,7 +1107,7 @@ export class InteractiveMode {
|
|
|
1052
1107
|
if (showListing) {
|
|
1053
1108
|
const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
|
|
1054
1109
|
if (contextFiles.length > 0) {
|
|
1055
|
-
this.
|
|
1110
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1056
1111
|
const contextList = contextFiles
|
|
1057
1112
|
.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`))
|
|
1058
1113
|
.join("\n");
|
|
@@ -1115,14 +1170,14 @@ export class InteractiveMode {
|
|
|
1115
1170
|
const skillDiagnostics = skillsResult.diagnostics;
|
|
1116
1171
|
if (skillDiagnostics.length > 0) {
|
|
1117
1172
|
const warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);
|
|
1118
|
-
this.
|
|
1119
|
-
this.
|
|
1173
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
|
|
1174
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1120
1175
|
}
|
|
1121
1176
|
const promptDiagnostics = promptsResult.diagnostics;
|
|
1122
1177
|
if (promptDiagnostics.length > 0) {
|
|
1123
1178
|
const warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);
|
|
1124
|
-
this.
|
|
1125
|
-
this.
|
|
1179
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
|
|
1180
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1126
1181
|
}
|
|
1127
1182
|
const extensionDiagnostics = [];
|
|
1128
1183
|
const extensionErrors = this.session.resourceLoader.getExtensions().errors;
|
|
@@ -1138,14 +1193,14 @@ export class InteractiveMode {
|
|
|
1138
1193
|
extensionDiagnostics.push(...shortcutDiagnostics);
|
|
1139
1194
|
if (extensionDiagnostics.length > 0) {
|
|
1140
1195
|
const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
|
|
1141
|
-
this.
|
|
1142
|
-
this.
|
|
1196
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
|
|
1197
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1143
1198
|
}
|
|
1144
1199
|
const themeDiagnostics = themesResult.diagnostics;
|
|
1145
1200
|
if (themeDiagnostics.length > 0) {
|
|
1146
1201
|
const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
|
|
1147
|
-
this.
|
|
1148
|
-
this.
|
|
1202
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
|
1203
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1149
1204
|
}
|
|
1150
1205
|
}
|
|
1151
1206
|
}
|
|
@@ -1161,13 +1216,9 @@ export class InteractiveMode {
|
|
|
1161
1216
|
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1162
1217
|
},
|
|
1163
1218
|
commandContextActions: {
|
|
1164
|
-
waitForIdle: () => this.session.
|
|
1219
|
+
waitForIdle: () => this.session.waitForIdle(),
|
|
1165
1220
|
newSession: async (options) => {
|
|
1166
|
-
|
|
1167
|
-
this.loadingAnimation.stop();
|
|
1168
|
-
this.loadingAnimation = undefined;
|
|
1169
|
-
}
|
|
1170
|
-
this.statusContainer.clear();
|
|
1221
|
+
this.clearStatusIndicator();
|
|
1171
1222
|
try {
|
|
1172
1223
|
return await this.runtimeHost.newSession(options);
|
|
1173
1224
|
}
|
|
@@ -1216,7 +1267,7 @@ export class InteractiveMode {
|
|
|
1216
1267
|
},
|
|
1217
1268
|
shutdownHandler: () => {
|
|
1218
1269
|
this.shutdownRequested = true;
|
|
1219
|
-
if (
|
|
1270
|
+
if (this.session.isIdle) {
|
|
1220
1271
|
void this.shutdown();
|
|
1221
1272
|
}
|
|
1222
1273
|
},
|
|
@@ -1237,8 +1288,13 @@ export class InteractiveMode {
|
|
|
1237
1288
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
1238
1289
|
this.footerDataProvider.setCwd(this.sessionManager.getCwd());
|
|
1239
1290
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
1291
|
+
this.outputPad = this.settingsManager.getOutputPad();
|
|
1240
1292
|
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
1241
|
-
this.
|
|
1293
|
+
const clearOnShrink = this.settingsManager.getClearOnShrink();
|
|
1294
|
+
this.ui.setClearOnShrink(clearOnShrink);
|
|
1295
|
+
if (!clearOnShrink && !this.activeStatusIndicator) {
|
|
1296
|
+
this.statusContainer.clear();
|
|
1297
|
+
}
|
|
1242
1298
|
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
|
1243
1299
|
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
1244
1300
|
this.defaultEditor.setPaddingX(editorPaddingX);
|
|
@@ -1273,6 +1329,7 @@ export class InteractiveMode {
|
|
|
1273
1329
|
process.exit(1);
|
|
1274
1330
|
}
|
|
1275
1331
|
renderCurrentSessionState() {
|
|
1332
|
+
this.loadedResourcesContainer.clear();
|
|
1276
1333
|
this.chatContainer.clear();
|
|
1277
1334
|
this.pendingMessagesContainer.clear();
|
|
1278
1335
|
this.compactionQueuedMessages = [];
|
|
@@ -1303,7 +1360,7 @@ export class InteractiveMode {
|
|
|
1303
1360
|
sessionManager: this.sessionManager,
|
|
1304
1361
|
modelRegistry: this.session.modelRegistry,
|
|
1305
1362
|
model: this.session.model,
|
|
1306
|
-
isIdle: () =>
|
|
1363
|
+
isIdle: () => this.session.isIdle,
|
|
1307
1364
|
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
1308
1365
|
signal: this.session.agent.signal,
|
|
1309
1366
|
abort: () => {
|
|
@@ -1350,36 +1407,41 @@ export class InteractiveMode {
|
|
|
1350
1407
|
this.footerDataProvider.setExtensionStatus(key, text);
|
|
1351
1408
|
this.ui.requestRender();
|
|
1352
1409
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1410
|
+
showStatusIndicator(indicator) {
|
|
1411
|
+
this.activeStatusIndicator?.dispose();
|
|
1412
|
+
this.activeStatusIndicator = indicator;
|
|
1413
|
+
this.statusContainer.clear();
|
|
1414
|
+
this.statusContainer.addChild(indicator);
|
|
1358
1415
|
}
|
|
1359
|
-
|
|
1360
|
-
if (this.
|
|
1361
|
-
|
|
1362
|
-
this.loadingAnimation = undefined;
|
|
1416
|
+
clearStatusIndicator(kind) {
|
|
1417
|
+
if (kind && this.activeStatusIndicator?.kind !== kind) {
|
|
1418
|
+
return;
|
|
1363
1419
|
}
|
|
1420
|
+
const hadActiveStatusIndicator = this.activeStatusIndicator !== undefined;
|
|
1421
|
+
this.activeStatusIndicator?.dispose();
|
|
1422
|
+
this.activeStatusIndicator = undefined;
|
|
1364
1423
|
this.statusContainer.clear();
|
|
1424
|
+
if (hadActiveStatusIndicator && this.ui.getClearOnShrink()) {
|
|
1425
|
+
this.statusContainer.addChild(this.idleStatus);
|
|
1426
|
+
}
|
|
1365
1427
|
}
|
|
1366
1428
|
setWorkingVisible(visible) {
|
|
1367
1429
|
this.workingVisible = visible;
|
|
1368
1430
|
if (!visible) {
|
|
1369
|
-
this.
|
|
1431
|
+
this.clearStatusIndicator("working");
|
|
1370
1432
|
this.ui.requestRender();
|
|
1371
1433
|
return;
|
|
1372
1434
|
}
|
|
1373
|
-
if (this.session.isStreaming &&
|
|
1374
|
-
this.
|
|
1375
|
-
this.loadingAnimation = this.createWorkingLoader();
|
|
1376
|
-
this.statusContainer.addChild(this.loadingAnimation);
|
|
1435
|
+
if (this.session.isStreaming && this.activeStatusIndicator?.kind !== "working") {
|
|
1436
|
+
this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.workingIndicatorOptions));
|
|
1377
1437
|
}
|
|
1378
1438
|
this.ui.requestRender();
|
|
1379
1439
|
}
|
|
1380
1440
|
setWorkingIndicator(options) {
|
|
1381
1441
|
this.workingIndicatorOptions = options;
|
|
1382
|
-
this.
|
|
1442
|
+
if (this.activeStatusIndicator?.kind === "working") {
|
|
1443
|
+
this.activeStatusIndicator.setIndicator(options);
|
|
1444
|
+
}
|
|
1383
1445
|
this.ui.requestRender();
|
|
1384
1446
|
}
|
|
1385
1447
|
setHiddenThinkingLabel(label) {
|
|
@@ -1467,8 +1529,8 @@ export class InteractiveMode {
|
|
|
1467
1529
|
this.workingMessage = undefined;
|
|
1468
1530
|
this.workingVisible = true;
|
|
1469
1531
|
this.setWorkingIndicator();
|
|
1470
|
-
if (this.
|
|
1471
|
-
this.
|
|
1532
|
+
if (this.activeStatusIndicator?.kind === "working") {
|
|
1533
|
+
this.activeStatusIndicator.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
|
|
1472
1534
|
}
|
|
1473
1535
|
this.setHiddenThinkingLabel();
|
|
1474
1536
|
}
|
|
@@ -1608,8 +1670,8 @@ export class InteractiveMode {
|
|
|
1608
1670
|
setStatus: (key, text) => this.setExtensionStatus(key, text),
|
|
1609
1671
|
setWorkingMessage: (message) => {
|
|
1610
1672
|
this.workingMessage = message;
|
|
1611
|
-
if (this.
|
|
1612
|
-
this.
|
|
1673
|
+
if (this.activeStatusIndicator?.kind === "working") {
|
|
1674
|
+
this.activeStatusIndicator.setMessage(message ?? this.defaultWorkingMessage);
|
|
1613
1675
|
}
|
|
1614
1676
|
},
|
|
1615
1677
|
setWorkingVisible: (visible) => this.setWorkingVisible(visible),
|
|
@@ -1753,7 +1815,7 @@ export class InteractiveMode {
|
|
|
1753
1815
|
}, () => {
|
|
1754
1816
|
this.hideExtensionEditor();
|
|
1755
1817
|
resolve(undefined);
|
|
1756
|
-
});
|
|
1818
|
+
}, undefined, this.settingsManager.getExternalEditorCommand());
|
|
1757
1819
|
this.editorContainer.clear();
|
|
1758
1820
|
this.editorContainer.addChild(this.extensionEditor);
|
|
1759
1821
|
this.ui.setFocus(this.extensionEditor);
|
|
@@ -2163,9 +2225,10 @@ export class InteractiveMode {
|
|
|
2163
2225
|
this.editor.setText("");
|
|
2164
2226
|
return;
|
|
2165
2227
|
}
|
|
2166
|
-
if (text === "/login") {
|
|
2167
|
-
|
|
2228
|
+
if (text === "/login" || text.startsWith("/login ")) {
|
|
2229
|
+
const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
|
|
2168
2230
|
this.editor.setText("");
|
|
2231
|
+
await this.handleLoginCommand(providerRef);
|
|
2169
2232
|
return;
|
|
2170
2233
|
}
|
|
2171
2234
|
if (text === "/logout") {
|
|
@@ -2288,18 +2351,11 @@ export class InteractiveMode {
|
|
|
2288
2351
|
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2289
2352
|
this.retryEscapeHandler = undefined;
|
|
2290
2353
|
}
|
|
2291
|
-
if (this.retryCountdown) {
|
|
2292
|
-
this.retryCountdown.dispose();
|
|
2293
|
-
this.retryCountdown = undefined;
|
|
2294
|
-
}
|
|
2295
|
-
if (this.retryLoader) {
|
|
2296
|
-
this.retryLoader.stop();
|
|
2297
|
-
this.retryLoader = undefined;
|
|
2298
|
-
}
|
|
2299
|
-
this.stopWorkingLoader();
|
|
2300
2354
|
if (this.workingVisible) {
|
|
2301
|
-
this.
|
|
2302
|
-
|
|
2355
|
+
this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.workingIndicatorOptions));
|
|
2356
|
+
}
|
|
2357
|
+
else {
|
|
2358
|
+
this.clearStatusIndicator();
|
|
2303
2359
|
}
|
|
2304
2360
|
this.ui.requestRender();
|
|
2305
2361
|
break;
|
|
@@ -2307,6 +2363,12 @@ export class InteractiveMode {
|
|
|
2307
2363
|
this.updatePendingMessagesDisplay();
|
|
2308
2364
|
this.ui.requestRender();
|
|
2309
2365
|
break;
|
|
2366
|
+
case "entry_appended":
|
|
2367
|
+
if (event.entry.type === "custom") {
|
|
2368
|
+
this.addCustomEntryToChat(event.entry);
|
|
2369
|
+
this.ui.requestRender();
|
|
2370
|
+
}
|
|
2371
|
+
break;
|
|
2310
2372
|
case "session_info_changed":
|
|
2311
2373
|
this.updateTerminalTitle();
|
|
2312
2374
|
this.footer.invalidate();
|
|
@@ -2327,7 +2389,7 @@ export class InteractiveMode {
|
|
|
2327
2389
|
this.ui.requestRender();
|
|
2328
2390
|
}
|
|
2329
2391
|
else if (event.message.role === "assistant") {
|
|
2330
|
-
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
2392
|
+
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
|
|
2331
2393
|
this.streamingMessage = event.message;
|
|
2332
2394
|
this.chatContainer.addChild(this.streamingComponent);
|
|
2333
2395
|
this.streamingComponent.updateContent(this.streamingMessage);
|
|
@@ -2392,6 +2454,7 @@ export class InteractiveMode {
|
|
|
2392
2454
|
for (const [, component] of this.pendingTools.entries()) {
|
|
2393
2455
|
component.setArgsComplete();
|
|
2394
2456
|
}
|
|
2457
|
+
this.maybeShowCacheMissNotice(this.streamingMessage);
|
|
2395
2458
|
}
|
|
2396
2459
|
this.streamingComponent = undefined;
|
|
2397
2460
|
this.streamingMessage = undefined;
|
|
@@ -2435,20 +2498,18 @@ export class InteractiveMode {
|
|
|
2435
2498
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2436
2499
|
this.ui.terminal.setProgress(false);
|
|
2437
2500
|
}
|
|
2438
|
-
|
|
2439
|
-
this.loadingAnimation.stop();
|
|
2440
|
-
this.loadingAnimation = undefined;
|
|
2441
|
-
this.statusContainer.clear();
|
|
2442
|
-
}
|
|
2501
|
+
this.clearStatusIndicator("working");
|
|
2443
2502
|
if (this.streamingComponent) {
|
|
2444
2503
|
this.chatContainer.removeChild(this.streamingComponent);
|
|
2445
2504
|
this.streamingComponent = undefined;
|
|
2446
2505
|
this.streamingMessage = undefined;
|
|
2447
2506
|
}
|
|
2448
2507
|
this.pendingTools.clear();
|
|
2449
|
-
await this.checkShutdownRequested();
|
|
2450
2508
|
this.ui.requestRender();
|
|
2451
2509
|
break;
|
|
2510
|
+
case "agent_settled":
|
|
2511
|
+
await this.checkShutdownRequested();
|
|
2512
|
+
break;
|
|
2452
2513
|
case "compaction_start": {
|
|
2453
2514
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2454
2515
|
this.ui.terminal.setProgress(true);
|
|
@@ -2458,13 +2519,7 @@ export class InteractiveMode {
|
|
|
2458
2519
|
this.defaultEditor.onEscape = () => {
|
|
2459
2520
|
this.session.abortCompaction();
|
|
2460
2521
|
};
|
|
2461
|
-
this.
|
|
2462
|
-
const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
|
|
2463
|
-
const label = event.reason === "manual"
|
|
2464
|
-
? `Compacting context... ${cancelHint}`
|
|
2465
|
-
: `${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... ${cancelHint}`;
|
|
2466
|
-
this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
|
|
2467
|
-
this.statusContainer.addChild(this.autoCompactionLoader);
|
|
2522
|
+
this.showStatusIndicator(new CompactionStatusIndicator(this.ui, event.reason));
|
|
2468
2523
|
this.ui.requestRender();
|
|
2469
2524
|
break;
|
|
2470
2525
|
}
|
|
@@ -2476,11 +2531,7 @@ export class InteractiveMode {
|
|
|
2476
2531
|
this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
|
|
2477
2532
|
this.autoCompactionEscapeHandler = undefined;
|
|
2478
2533
|
}
|
|
2479
|
-
|
|
2480
|
-
this.autoCompactionLoader.stop();
|
|
2481
|
-
this.autoCompactionLoader = undefined;
|
|
2482
|
-
this.statusContainer.clear();
|
|
2483
|
-
}
|
|
2534
|
+
this.clearStatusIndicator("compaction");
|
|
2484
2535
|
if (event.aborted) {
|
|
2485
2536
|
if (event.reason === "manual") {
|
|
2486
2537
|
this.showError("Compaction cancelled");
|
|
@@ -2514,17 +2565,7 @@ export class InteractiveMode {
|
|
|
2514
2565
|
this.defaultEditor.onEscape = () => {
|
|
2515
2566
|
this.session.abortRetry();
|
|
2516
2567
|
};
|
|
2517
|
-
|
|
2518
|
-
this.statusContainer.clear();
|
|
2519
|
-
this.retryCountdown?.dispose();
|
|
2520
|
-
const retryMessage = (seconds) => `Retrying (${event.attempt}/${event.maxAttempts}) in ${seconds}s... (${keyText("app.interrupt")} to cancel)`;
|
|
2521
|
-
this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), retryMessage(Math.ceil(event.delayMs / 1000)));
|
|
2522
|
-
this.retryCountdown = new CountdownTimer(event.delayMs, this.ui, (seconds) => {
|
|
2523
|
-
this.retryLoader?.setMessage(retryMessage(seconds));
|
|
2524
|
-
}, () => {
|
|
2525
|
-
this.retryCountdown = undefined;
|
|
2526
|
-
});
|
|
2527
|
-
this.statusContainer.addChild(this.retryLoader);
|
|
2568
|
+
this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
|
|
2528
2569
|
this.ui.requestRender();
|
|
2529
2570
|
break;
|
|
2530
2571
|
}
|
|
@@ -2534,16 +2575,7 @@ export class InteractiveMode {
|
|
|
2534
2575
|
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2535
2576
|
this.retryEscapeHandler = undefined;
|
|
2536
2577
|
}
|
|
2537
|
-
|
|
2538
|
-
this.retryCountdown.dispose();
|
|
2539
|
-
this.retryCountdown = undefined;
|
|
2540
|
-
}
|
|
2541
|
-
// Stop loader
|
|
2542
|
-
if (this.retryLoader) {
|
|
2543
|
-
this.retryLoader.stop();
|
|
2544
|
-
this.retryLoader = undefined;
|
|
2545
|
-
this.statusContainer.clear();
|
|
2546
|
-
}
|
|
2578
|
+
this.clearStatusIndicator("retry");
|
|
2547
2579
|
// Show error only on final failure (success shows normal response)
|
|
2548
2580
|
if (!event.success) {
|
|
2549
2581
|
this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
|
|
@@ -2585,6 +2617,25 @@ export class InteractiveMode {
|
|
|
2585
2617
|
this.lastStatusText = text;
|
|
2586
2618
|
this.ui.requestRender();
|
|
2587
2619
|
}
|
|
2620
|
+
addCustomEntryToChat(entry) {
|
|
2621
|
+
const renderer = this.session.extensionRunner.getEntryRenderer(entry.customType);
|
|
2622
|
+
if (!renderer) {
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2625
|
+
const component = new CustomEntryComponent(entry, renderer);
|
|
2626
|
+
component.setExpanded(this.toolOutputExpanded);
|
|
2627
|
+
if (!component.hasContent()) {
|
|
2628
|
+
return;
|
|
2629
|
+
}
|
|
2630
|
+
if (this.streamingComponent) {
|
|
2631
|
+
const streamingIndex = this.chatContainer.children.indexOf(this.streamingComponent);
|
|
2632
|
+
if (streamingIndex >= 0) {
|
|
2633
|
+
this.chatContainer.children.splice(streamingIndex, 0, component);
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
this.chatContainer.addChild(component);
|
|
2638
|
+
}
|
|
2588
2639
|
addMessageToChat(message, options) {
|
|
2589
2640
|
switch (message.role) {
|
|
2590
2641
|
case "bashExecution": {
|
|
@@ -2634,12 +2685,12 @@ export class InteractiveMode {
|
|
|
2634
2685
|
// Render user message separately if present
|
|
2635
2686
|
if (skillBlock.userMessage) {
|
|
2636
2687
|
this.chatContainer.addChild(new Spacer(1));
|
|
2637
|
-
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings());
|
|
2688
|
+
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2638
2689
|
this.chatContainer.addChild(userComponent);
|
|
2639
2690
|
}
|
|
2640
2691
|
}
|
|
2641
2692
|
else {
|
|
2642
|
-
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());
|
|
2693
|
+
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2643
2694
|
this.chatContainer.addChild(userComponent);
|
|
2644
2695
|
}
|
|
2645
2696
|
if (options?.populateHistory) {
|
|
@@ -2649,7 +2700,7 @@ export class InteractiveMode {
|
|
|
2649
2700
|
break;
|
|
2650
2701
|
}
|
|
2651
2702
|
case "assistant": {
|
|
2652
|
-
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
2703
|
+
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
|
|
2653
2704
|
this.chatContainer.addChild(assistantComponent);
|
|
2654
2705
|
break;
|
|
2655
2706
|
}
|
|
@@ -2662,20 +2713,24 @@ export class InteractiveMode {
|
|
|
2662
2713
|
}
|
|
2663
2714
|
}
|
|
2664
2715
|
}
|
|
2665
|
-
|
|
2666
|
-
* Render session context to chat. Used for initial load and rebuild after compaction.
|
|
2667
|
-
* @param sessionContext Session context to render
|
|
2668
|
-
* @param options.updateFooter Update footer state
|
|
2669
|
-
* @param options.populateHistory Add user messages to editor history
|
|
2670
|
-
*/
|
|
2671
|
-
renderSessionContext(sessionContext, options = {}) {
|
|
2716
|
+
renderSessionItems(items, options = {}) {
|
|
2672
2717
|
this.pendingTools.clear();
|
|
2673
2718
|
const renderedPendingTools = new Map();
|
|
2719
|
+
// Cache-miss notices are not persisted; re-derive them from the full entry
|
|
2720
|
+
// list and re-inject them after the assistant messages that paid for them.
|
|
2721
|
+
const cacheMisses = this.settingsManager.getShowCacheMissNotices()
|
|
2722
|
+
? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRegistry)
|
|
2723
|
+
: new Map();
|
|
2674
2724
|
if (options.updateFooter) {
|
|
2675
2725
|
this.footer.invalidate();
|
|
2676
2726
|
this.updateEditorBorderColor();
|
|
2677
2727
|
}
|
|
2678
|
-
for (const
|
|
2728
|
+
for (const item of items) {
|
|
2729
|
+
if (isCustomSessionEntry(item)) {
|
|
2730
|
+
this.addCustomEntryToChat(item);
|
|
2731
|
+
continue;
|
|
2732
|
+
}
|
|
2733
|
+
const message = item;
|
|
2679
2734
|
// Assistant messages need special handling for tool calls
|
|
2680
2735
|
if (message.role === "assistant") {
|
|
2681
2736
|
this.addMessageToChat(message);
|
|
@@ -2707,6 +2762,11 @@ export class InteractiveMode {
|
|
|
2707
2762
|
}
|
|
2708
2763
|
}
|
|
2709
2764
|
}
|
|
2765
|
+
if (message.stopReason !== "aborted" && message.stopReason !== "error") {
|
|
2766
|
+
const miss = cacheMisses.get(message);
|
|
2767
|
+
if (miss)
|
|
2768
|
+
this.addCacheMissNotice(miss);
|
|
2769
|
+
}
|
|
2710
2770
|
}
|
|
2711
2771
|
else if (message.role === "toolResult") {
|
|
2712
2772
|
// Match tool results to pending tool components
|
|
@@ -2726,10 +2786,53 @@ export class InteractiveMode {
|
|
|
2726
2786
|
}
|
|
2727
2787
|
this.ui.requestRender();
|
|
2728
2788
|
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Render session entries to chat. Used for initial load and rebuild after compaction.
|
|
2791
|
+
* @param entries Compaction-aware session entries to render
|
|
2792
|
+
* @param options.updateFooter Update footer state
|
|
2793
|
+
* @param options.populateHistory Add user messages to editor history
|
|
2794
|
+
*/
|
|
2795
|
+
renderSessionEntries(entries, options = {}) {
|
|
2796
|
+
const items = entries.flatMap((entry) => {
|
|
2797
|
+
if (entry.type === "custom") {
|
|
2798
|
+
return [entry];
|
|
2799
|
+
}
|
|
2800
|
+
return sessionEntryToContextMessages(entry);
|
|
2801
|
+
});
|
|
2802
|
+
this.renderSessionItems(items, options);
|
|
2803
|
+
}
|
|
2804
|
+
/**
|
|
2805
|
+
* Show a transcript notice when a completed assistant message paid for a
|
|
2806
|
+
* significant cache miss. Only states observable facts: the miss itself,
|
|
2807
|
+
* a model switch, or an idle gap past the cache TTL.
|
|
2808
|
+
*/
|
|
2809
|
+
maybeShowCacheMissNotice(message) {
|
|
2810
|
+
if (!this.settingsManager.getShowCacheMissNotices())
|
|
2811
|
+
return;
|
|
2812
|
+
// Entries don't contain `message` yet: message_end fires before persistence.
|
|
2813
|
+
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.modelRegistry);
|
|
2814
|
+
if (miss)
|
|
2815
|
+
this.addCacheMissNotice(miss);
|
|
2816
|
+
}
|
|
2817
|
+
addCacheMissNotice(miss) {
|
|
2818
|
+
if (miss.missedTokens < 20_000 && miss.missedCost < 0.1)
|
|
2819
|
+
return;
|
|
2820
|
+
const cost = miss.missedCost >= 0.01 ? ` (~$${miss.missedCost.toFixed(2)})` : "";
|
|
2821
|
+
const reBilled = `${formatTokens(miss.missedTokens)} tokens re-billed${cost}`;
|
|
2822
|
+
let label = "Cache miss";
|
|
2823
|
+
if (miss.modelChanged) {
|
|
2824
|
+
label = "Cache miss after model switch";
|
|
2825
|
+
}
|
|
2826
|
+
else if (miss.idleMs >= CACHE_TTL_MS) {
|
|
2827
|
+
label = `Cache miss after ${Math.round(miss.idleMs / 60_000)}m idle`;
|
|
2828
|
+
}
|
|
2829
|
+
const text = theme.fg("warning", `${label}: ${reBilled}`);
|
|
2830
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2831
|
+
this.chatContainer.addChild(new Text(text, 1, 0));
|
|
2832
|
+
}
|
|
2729
2833
|
renderInitialMessages() {
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
this.renderSessionContext(context, {
|
|
2834
|
+
const entries = this.sessionManager.buildContextEntries();
|
|
2835
|
+
this.renderSessionEntries(entries, {
|
|
2733
2836
|
updateFooter: true,
|
|
2734
2837
|
populateHistory: true,
|
|
2735
2838
|
});
|
|
@@ -2765,8 +2868,7 @@ export class InteractiveMode {
|
|
|
2765
2868
|
}
|
|
2766
2869
|
rebuildChatFromMessages() {
|
|
2767
2870
|
this.chatContainer.clear();
|
|
2768
|
-
|
|
2769
|
-
this.renderSessionContext(context);
|
|
2871
|
+
this.renderSessionEntries(this.sessionManager.buildContextEntries());
|
|
2770
2872
|
}
|
|
2771
2873
|
// =========================================================================
|
|
2772
2874
|
// Key handlers
|
|
@@ -3039,9 +3141,11 @@ export class InteractiveMode {
|
|
|
3039
3141
|
if (isExpandable(activeHeader)) {
|
|
3040
3142
|
activeHeader.setExpanded(expanded);
|
|
3041
3143
|
}
|
|
3042
|
-
for (const
|
|
3043
|
-
|
|
3044
|
-
child
|
|
3144
|
+
for (const container of [this.loadedResourcesContainer, this.chatContainer]) {
|
|
3145
|
+
for (const child of container.children) {
|
|
3146
|
+
if (isExpandable(child)) {
|
|
3147
|
+
child.setExpanded(expanded);
|
|
3148
|
+
}
|
|
3045
3149
|
}
|
|
3046
3150
|
}
|
|
3047
3151
|
this.ui.requestRender();
|
|
@@ -3061,10 +3165,9 @@ export class InteractiveMode {
|
|
|
3061
3165
|
this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
|
|
3062
3166
|
}
|
|
3063
3167
|
async openExternalEditor() {
|
|
3064
|
-
|
|
3065
|
-
const editorCmd = process.env.VISUAL || process.env.EDITOR;
|
|
3168
|
+
const editorCmd = this.settingsManager.getExternalEditorCommand();
|
|
3066
3169
|
if (!editorCmd) {
|
|
3067
|
-
this.showWarning("No editor configured. Set
|
|
3170
|
+
this.showWarning("No editor configured. Set externalEditor in settings.json or $VISUAL/$EDITOR.");
|
|
3068
3171
|
return;
|
|
3069
3172
|
}
|
|
3070
3173
|
const currentText = this.editor.getExpandedText?.() ?? this.editor.getText();
|
|
@@ -3367,8 +3470,10 @@ export class InteractiveMode {
|
|
|
3367
3470
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
3368
3471
|
treeFilterMode: this.settingsManager.getTreeFilterMode(),
|
|
3369
3472
|
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
3473
|
+
showCacheMissNotices: this.settingsManager.getShowCacheMissNotices(),
|
|
3370
3474
|
defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
|
|
3371
3475
|
editorPaddingX: this.settingsManager.getEditorPaddingX(),
|
|
3476
|
+
outputPad: this.settingsManager.getOutputPad(),
|
|
3372
3477
|
autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
|
|
3373
3478
|
quietStartup: this.settingsManager.getQuietStartup(),
|
|
3374
3479
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
@@ -3441,6 +3546,10 @@ export class InteractiveMode {
|
|
|
3441
3546
|
this.chatContainer.clear();
|
|
3442
3547
|
this.rebuildChatFromMessages();
|
|
3443
3548
|
},
|
|
3549
|
+
onShowCacheMissNoticesChange: (shown) => {
|
|
3550
|
+
this.settingsManager.setShowCacheMissNotices(shown);
|
|
3551
|
+
this.rebuildChatFromMessages();
|
|
3552
|
+
},
|
|
3444
3553
|
onCollapseChangelogChange: (collapsed) => {
|
|
3445
3554
|
this.settingsManager.setCollapseChangelog(collapsed);
|
|
3446
3555
|
},
|
|
@@ -3470,6 +3579,23 @@ export class InteractiveMode {
|
|
|
3470
3579
|
this.editor.setPaddingX(padding);
|
|
3471
3580
|
}
|
|
3472
3581
|
},
|
|
3582
|
+
onOutputPadChange: (padding) => {
|
|
3583
|
+
this.settingsManager.setOutputPad(padding);
|
|
3584
|
+
this.outputPad = padding;
|
|
3585
|
+
if (this.streamingComponent || this.session.isStreaming) {
|
|
3586
|
+
for (const child of this.chatContainer.children) {
|
|
3587
|
+
if (child instanceof AssistantMessageComponent || child instanceof UserMessageComponent) {
|
|
3588
|
+
child.setOutputPad(padding);
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
if (this.streamingComponent) {
|
|
3592
|
+
this.streamingComponent.setOutputPad(padding);
|
|
3593
|
+
}
|
|
3594
|
+
this.ui.requestRender();
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
this.rebuildChatFromMessages();
|
|
3598
|
+
},
|
|
3473
3599
|
onAutocompleteMaxVisibleChange: (maxVisible) => {
|
|
3474
3600
|
this.settingsManager.setAutocompleteMaxVisible(maxVisible);
|
|
3475
3601
|
this.defaultEditor.setAutocompleteMaxVisible(maxVisible);
|
|
@@ -3480,6 +3606,9 @@ export class InteractiveMode {
|
|
|
3480
3606
|
onClearOnShrinkChange: (enabled) => {
|
|
3481
3607
|
this.settingsManager.setClearOnShrink(enabled);
|
|
3482
3608
|
this.ui.setClearOnShrink(enabled);
|
|
3609
|
+
if (!enabled && !this.activeStatusIndicator) {
|
|
3610
|
+
this.statusContainer.clear();
|
|
3611
|
+
}
|
|
3483
3612
|
},
|
|
3484
3613
|
onShowTerminalProgressChange: (enabled) => {
|
|
3485
3614
|
this.settingsManager.setShowTerminalProgress(enabled);
|
|
@@ -3710,19 +3839,17 @@ export class InteractiveMode {
|
|
|
3710
3839
|
const initialSelectedId = userMessages[userMessages.length - 1]?.entryId;
|
|
3711
3840
|
this.showSelector((done) => {
|
|
3712
3841
|
const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ id: m.entryId, text: m.text })), async (entryId) => {
|
|
3842
|
+
done();
|
|
3713
3843
|
try {
|
|
3714
3844
|
const result = await this.runtimeHost.fork(entryId);
|
|
3715
3845
|
if (result.cancelled) {
|
|
3716
|
-
done();
|
|
3717
3846
|
this.ui.requestRender();
|
|
3718
3847
|
return;
|
|
3719
3848
|
}
|
|
3720
3849
|
this.editor.setText(result.selectedText ?? "");
|
|
3721
|
-
done();
|
|
3722
3850
|
this.showStatus("Forked to new session");
|
|
3723
3851
|
}
|
|
3724
3852
|
catch (error) {
|
|
3725
|
-
done();
|
|
3726
3853
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
3727
3854
|
}
|
|
3728
3855
|
}, () => {
|
|
@@ -3797,16 +3924,16 @@ export class InteractiveMode {
|
|
|
3797
3924
|
break;
|
|
3798
3925
|
}
|
|
3799
3926
|
}
|
|
3800
|
-
// Set up escape handler and
|
|
3801
|
-
let
|
|
3927
|
+
// Set up escape handler and status indicator if summarizing
|
|
3928
|
+
let showingSummaryIndicator = false;
|
|
3802
3929
|
const originalOnEscape = this.defaultEditor.onEscape;
|
|
3803
3930
|
if (wantsSummary) {
|
|
3804
3931
|
this.defaultEditor.onEscape = () => {
|
|
3805
3932
|
this.session.abortBranchSummary();
|
|
3806
3933
|
};
|
|
3807
3934
|
this.chatContainer.addChild(new Spacer(1));
|
|
3808
|
-
|
|
3809
|
-
|
|
3935
|
+
this.showStatusIndicator(new BranchSummaryStatusIndicator(this.ui));
|
|
3936
|
+
showingSummaryIndicator = true;
|
|
3810
3937
|
this.ui.requestRender();
|
|
3811
3938
|
}
|
|
3812
3939
|
try {
|
|
@@ -3837,9 +3964,8 @@ export class InteractiveMode {
|
|
|
3837
3964
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
3838
3965
|
}
|
|
3839
3966
|
finally {
|
|
3840
|
-
if (
|
|
3841
|
-
|
|
3842
|
-
this.statusContainer.clear();
|
|
3967
|
+
if (showingSummaryIndicator) {
|
|
3968
|
+
this.clearStatusIndicator("branchSummary");
|
|
3843
3969
|
}
|
|
3844
3970
|
this.defaultEditor.onEscape = originalOnEscape;
|
|
3845
3971
|
}
|
|
@@ -3880,11 +4006,7 @@ export class InteractiveMode {
|
|
|
3880
4006
|
});
|
|
3881
4007
|
}
|
|
3882
4008
|
async handleResumeSession(sessionPath, options) {
|
|
3883
|
-
|
|
3884
|
-
this.loadingAnimation.stop();
|
|
3885
|
-
this.loadingAnimation = undefined;
|
|
3886
|
-
}
|
|
3887
|
-
this.statusContainer.clear();
|
|
4009
|
+
this.clearStatusIndicator();
|
|
3888
4010
|
try {
|
|
3889
4011
|
const result = await this.runtimeHost.switchSession(sessionPath, {
|
|
3890
4012
|
withSession: options?.withSession,
|
|
@@ -3956,13 +4078,82 @@ export class InteractiveMode {
|
|
|
3956
4078
|
}
|
|
3957
4079
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
3958
4080
|
}
|
|
3959
|
-
|
|
4081
|
+
findLoginProviderOptions(providerRef) {
|
|
4082
|
+
const normalizedProviderRef = providerRef.trim().toLowerCase();
|
|
4083
|
+
if (!normalizedProviderRef) {
|
|
4084
|
+
return [];
|
|
4085
|
+
}
|
|
4086
|
+
return this.getLoginProviderOptions().filter((provider) => provider.id.toLowerCase() === normalizedProviderRef ||
|
|
4087
|
+
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4088
|
+
}
|
|
4089
|
+
async handleLoginCommand(providerRef) {
|
|
4090
|
+
if (!providerRef) {
|
|
4091
|
+
this.showLoginAuthTypeSelector();
|
|
4092
|
+
return;
|
|
4093
|
+
}
|
|
4094
|
+
const providerOptions = this.findLoginProviderOptions(providerRef);
|
|
4095
|
+
if (providerOptions.length === 1) {
|
|
4096
|
+
await this.startProviderLogin(providerOptions[0]);
|
|
4097
|
+
return;
|
|
4098
|
+
}
|
|
4099
|
+
if (providerOptions.length > 1) {
|
|
4100
|
+
const providerIds = new Set(providerOptions.map((provider) => provider.id));
|
|
4101
|
+
if (providerIds.size === 1) {
|
|
4102
|
+
this.showLoginAuthTypeSelector(providerOptions);
|
|
4103
|
+
return;
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
this.showLoginProviderSelector(undefined, providerRef);
|
|
4107
|
+
}
|
|
4108
|
+
async startProviderLogin(providerOption) {
|
|
4109
|
+
if (providerOption.authType === "oauth") {
|
|
4110
|
+
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
4111
|
+
}
|
|
4112
|
+
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
4113
|
+
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
4114
|
+
}
|
|
4115
|
+
else {
|
|
4116
|
+
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
showLoginAuthTypeSelector(providerOptions) {
|
|
3960
4120
|
const subscriptionLabel = "Use a subscription";
|
|
3961
4121
|
const apiKeyLabel = "Use an API key";
|
|
4122
|
+
const availableAuthTypes = providerOptions
|
|
4123
|
+
? new Set(providerOptions.map((provider) => provider.authType))
|
|
4124
|
+
: new Set(["oauth", "api_key"]);
|
|
4125
|
+
const options = [];
|
|
4126
|
+
if (availableAuthTypes.has("oauth")) {
|
|
4127
|
+
options.push(subscriptionLabel);
|
|
4128
|
+
}
|
|
4129
|
+
if (availableAuthTypes.has("api_key")) {
|
|
4130
|
+
options.push(apiKeyLabel);
|
|
4131
|
+
}
|
|
4132
|
+
if (options.length === 0) {
|
|
4133
|
+
this.showStatus("No login methods available.");
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
4136
|
+
if (providerOptions && options.length === 1) {
|
|
4137
|
+
const providerOption = providerOptions[0];
|
|
4138
|
+
if (providerOption) {
|
|
4139
|
+
void this.startProviderLogin(providerOption);
|
|
4140
|
+
}
|
|
4141
|
+
return;
|
|
4142
|
+
}
|
|
4143
|
+
const title = providerOptions?.[0]
|
|
4144
|
+
? `Select authentication method for ${providerOptions[0].name}:`
|
|
4145
|
+
: "Select authentication method:";
|
|
3962
4146
|
this.showSelector((done) => {
|
|
3963
|
-
const selector = new ExtensionSelectorComponent(
|
|
4147
|
+
const selector = new ExtensionSelectorComponent(title, options, (option) => {
|
|
3964
4148
|
done();
|
|
3965
4149
|
const authType = option === subscriptionLabel ? "oauth" : "api_key";
|
|
4150
|
+
if (providerOptions) {
|
|
4151
|
+
const providerOption = providerOptions.find((provider) => provider.authType === authType);
|
|
4152
|
+
if (providerOption) {
|
|
4153
|
+
void this.startProviderLogin(providerOption);
|
|
4154
|
+
}
|
|
4155
|
+
return;
|
|
4156
|
+
}
|
|
3966
4157
|
this.showLoginProviderSelector(authType);
|
|
3967
4158
|
}, () => {
|
|
3968
4159
|
done();
|
|
@@ -3971,32 +4162,34 @@ export class InteractiveMode {
|
|
|
3971
4162
|
return { component: selector, focus: selector };
|
|
3972
4163
|
});
|
|
3973
4164
|
}
|
|
3974
|
-
showLoginProviderSelector(authType) {
|
|
4165
|
+
showLoginProviderSelector(authType, initialSearchInput) {
|
|
3975
4166
|
const providerOptions = this.getLoginProviderOptions(authType);
|
|
3976
4167
|
if (providerOptions.length === 0) {
|
|
3977
|
-
|
|
4168
|
+
const message = authType === "oauth"
|
|
4169
|
+
? "No subscription providers available."
|
|
4170
|
+
: authType === "api_key"
|
|
4171
|
+
? "No API key providers available."
|
|
4172
|
+
: "No login providers available.";
|
|
4173
|
+
this.showStatus(message);
|
|
3978
4174
|
return;
|
|
3979
4175
|
}
|
|
3980
4176
|
this.showSelector((done) => {
|
|
3981
|
-
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
4177
|
+
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId, selectedAuthType) => {
|
|
3982
4178
|
done();
|
|
3983
|
-
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
4179
|
+
const providerOption = providerOptions.find((provider) => provider.id === providerId && provider.authType === selectedAuthType);
|
|
3984
4180
|
if (!providerOption) {
|
|
3985
4181
|
return;
|
|
3986
4182
|
}
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
this.
|
|
4183
|
+
await this.startProviderLogin(providerOption);
|
|
4184
|
+
}, () => {
|
|
4185
|
+
done();
|
|
4186
|
+
if (authType) {
|
|
4187
|
+
this.showLoginAuthTypeSelector();
|
|
3992
4188
|
}
|
|
3993
4189
|
else {
|
|
3994
|
-
|
|
4190
|
+
this.ui.requestRender();
|
|
3995
4191
|
}
|
|
3996
|
-
}, () =>
|
|
3997
|
-
done();
|
|
3998
|
-
this.showLoginAuthTypeSelector();
|
|
3999
|
-
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId));
|
|
4192
|
+
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId), initialSearchInput);
|
|
4000
4193
|
return { component: selector, focus: selector };
|
|
4001
4194
|
});
|
|
4002
4195
|
}
|
|
@@ -4255,7 +4448,7 @@ export class InteractiveMode {
|
|
|
4255
4448
|
const borderColor = (s) => theme.fg("border", s);
|
|
4256
4449
|
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
4257
4450
|
reloadBox.addChild(new Spacer(1));
|
|
4258
|
-
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes..."), 1, 0));
|
|
4451
|
+
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes, and context files..."), 1, 0));
|
|
4259
4452
|
reloadBox.addChild(new Spacer(1));
|
|
4260
4453
|
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
4261
4454
|
const previousEditor = this.editor;
|
|
@@ -4277,6 +4470,7 @@ export class InteractiveMode {
|
|
|
4277
4470
|
return;
|
|
4278
4471
|
}
|
|
4279
4472
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
4473
|
+
this.outputPad = this.settingsManager.getOutputPad();
|
|
4280
4474
|
this.rebuildChatFromMessages();
|
|
4281
4475
|
chatRestoredBeforeSessionStart = true;
|
|
4282
4476
|
};
|
|
@@ -4300,7 +4494,11 @@ export class InteractiveMode {
|
|
|
4300
4494
|
this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
|
|
4301
4495
|
}
|
|
4302
4496
|
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
4303
|
-
this.
|
|
4497
|
+
const clearOnShrink = this.settingsManager.getClearOnShrink();
|
|
4498
|
+
this.ui.setClearOnShrink(clearOnShrink);
|
|
4499
|
+
if (!clearOnShrink && !this.activeStatusIndicator) {
|
|
4500
|
+
this.statusContainer.clear();
|
|
4501
|
+
}
|
|
4304
4502
|
this.setupAutocompleteProvider();
|
|
4305
4503
|
const runner = this.session.extensionRunner;
|
|
4306
4504
|
this.setupExtensionShortcuts(runner);
|
|
@@ -4314,8 +4512,8 @@ export class InteractiveMode {
|
|
|
4314
4512
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
4315
4513
|
}
|
|
4316
4514
|
this.showStatus(savedImplicitProjectTrust
|
|
4317
|
-
? "Reloaded keybindings, extensions, skills, prompts, themes; saved project trust"
|
|
4318
|
-
: "Reloaded keybindings, extensions, skills, prompts, themes");
|
|
4515
|
+
? "Reloaded keybindings, extensions, skills, prompts, themes, and context files; saved project trust"
|
|
4516
|
+
: "Reloaded keybindings, extensions, skills, prompts, themes, and context files");
|
|
4319
4517
|
dismissReloadBox(this.editor);
|
|
4320
4518
|
reloadBoxDismissed = true;
|
|
4321
4519
|
}
|
|
@@ -4379,11 +4577,7 @@ export class InteractiveMode {
|
|
|
4379
4577
|
return;
|
|
4380
4578
|
}
|
|
4381
4579
|
try {
|
|
4382
|
-
|
|
4383
|
-
this.loadingAnimation.stop();
|
|
4384
|
-
this.loadingAnimation = undefined;
|
|
4385
|
-
}
|
|
4386
|
-
this.statusContainer.clear();
|
|
4580
|
+
this.clearStatusIndicator();
|
|
4387
4581
|
const result = await this.runtimeHost.importFromJsonl(inputPath);
|
|
4388
4582
|
if (result.cancelled) {
|
|
4389
4583
|
this.showStatus("Import cancelled");
|
|
@@ -4540,6 +4734,26 @@ export class InteractiveMode {
|
|
|
4540
4734
|
handleSessionCommand() {
|
|
4541
4735
|
const stats = this.session.getSessionStats();
|
|
4542
4736
|
const sessionName = this.sessionManager.getSessionName();
|
|
4737
|
+
const entries = this.sessionManager.getEntries();
|
|
4738
|
+
const cacheWaste = computeCacheWaste(entries, this.session.modelRegistry);
|
|
4739
|
+
// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
|
|
4740
|
+
// resolves to a concrete responseModel), sorted by cost descending.
|
|
4741
|
+
const perModelMap = new Map();
|
|
4742
|
+
for (const entry of entries) {
|
|
4743
|
+
if (entry.type !== "message" || entry.message.role !== "assistant")
|
|
4744
|
+
continue;
|
|
4745
|
+
const message = entry.message;
|
|
4746
|
+
const usage = message.usage;
|
|
4747
|
+
const key = `${message.provider}/${message.responseModel ?? message.model}`;
|
|
4748
|
+
let bucket = perModelMap.get(key);
|
|
4749
|
+
if (!bucket) {
|
|
4750
|
+
bucket = { key, cost: 0, tokens: 0 };
|
|
4751
|
+
perModelMap.set(key, bucket);
|
|
4752
|
+
}
|
|
4753
|
+
bucket.cost += usage.cost.total;
|
|
4754
|
+
bucket.tokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
4755
|
+
}
|
|
4756
|
+
const perModel = Array.from(perModelMap.values()).sort((a, b) => b.cost - a.cost);
|
|
4543
4757
|
let info = `${theme.bold("Session Info")}\n\n`;
|
|
4544
4758
|
if (sessionName) {
|
|
4545
4759
|
info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
|
|
@@ -4547,24 +4761,42 @@ export class InteractiveMode {
|
|
|
4547
4761
|
info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
|
|
4548
4762
|
info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
|
|
4549
4763
|
info += `${theme.bold("Messages")}\n`;
|
|
4764
|
+
info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n`;
|
|
4550
4765
|
info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
|
|
4551
4766
|
info += `${theme.fg("dim", "Assistant:")} ${stats.assistantMessages}\n`;
|
|
4552
|
-
info += `${theme.fg("dim", "
|
|
4553
|
-
info += `${theme.fg("dim", "Tool Results:")} ${stats.toolResults}\n`;
|
|
4554
|
-
info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n\n`;
|
|
4767
|
+
info += `${theme.fg("dim", "Tools:")} ${stats.toolCalls} calls, ${stats.toolResults} results\n\n`;
|
|
4555
4768
|
info += `${theme.bold("Tokens")}\n`;
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
}
|
|
4561
|
-
|
|
4562
|
-
|
|
4769
|
+
// "Input" is the full prompt volume. With cache activity, split it into
|
|
4770
|
+
// cached (served from cache) vs uncached (everything else) - the only
|
|
4771
|
+
// provider-independent split. Cache writes, where reported, are a detail
|
|
4772
|
+
// of the uncached portion.
|
|
4773
|
+
const { input, cacheRead, cacheWrite } = stats.tokens;
|
|
4774
|
+
const promptTokens = input + cacheRead + cacheWrite;
|
|
4775
|
+
info += `${theme.fg("dim", "Input:")} ${promptTokens.toLocaleString()}\n`;
|
|
4776
|
+
if (promptTokens > 0 && (cacheRead > 0 || cacheWrite > 0)) {
|
|
4777
|
+
const hitRate = theme.fg("dim", `(${((cacheRead / promptTokens) * 100).toFixed(1)}%)`);
|
|
4778
|
+
info += ` ${theme.fg("dim", "Cached:")} ${cacheRead.toLocaleString()} ${hitRate}\n`;
|
|
4779
|
+
const written = cacheWrite > 0 ? ` ${theme.fg("dim", `(${cacheWrite.toLocaleString()} written to cache)`)}` : "";
|
|
4780
|
+
info += ` ${theme.fg("dim", "Uncached:")} ${(input + cacheWrite).toLocaleString()}${written}\n`;
|
|
4563
4781
|
}
|
|
4782
|
+
info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
|
|
4564
4783
|
info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
|
|
4565
|
-
if (stats.cost > 0) {
|
|
4784
|
+
if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
|
|
4566
4785
|
info += `\n${theme.bold("Cost")}\n`;
|
|
4567
|
-
info += `${theme.fg("dim", "Total:")}
|
|
4786
|
+
info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
|
|
4787
|
+
if (perModel.length > 1) {
|
|
4788
|
+
for (const entry of perModel) {
|
|
4789
|
+
info += `\n ${theme.fg("dim", `${entry.key}:`)} $${entry.cost.toFixed(3)} ${theme.fg("dim", `(${formatTokens(entry.tokens)} tokens)`)}`;
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
if (cacheWaste.missedTokens > 0) {
|
|
4793
|
+
const missLabel = cacheWaste.missCount === 1 ? "1 miss" : `${cacheWaste.missCount} misses`;
|
|
4794
|
+
const detail = `${cacheWaste.missedTokens.toLocaleString()} tokens, ${missLabel}`;
|
|
4795
|
+
info +=
|
|
4796
|
+
cacheWaste.missedCost >= 0.0001
|
|
4797
|
+
? `\n${theme.fg("dim", "Cache Re-billed:")} $${cacheWaste.missedCost.toFixed(3)} ${theme.fg("dim", `(${detail})`)}`
|
|
4798
|
+
: `\n${theme.fg("dim", "Cache Re-billed:")} ${detail}`;
|
|
4799
|
+
}
|
|
4568
4800
|
}
|
|
4569
4801
|
this.chatContainer.addChild(new Spacer(1));
|
|
4570
4802
|
this.chatContainer.addChild(new Text(info, 1, 0));
|
|
@@ -4709,11 +4941,7 @@ export class InteractiveMode {
|
|
|
4709
4941
|
this.ui.requestRender();
|
|
4710
4942
|
}
|
|
4711
4943
|
async handleClearCommand() {
|
|
4712
|
-
|
|
4713
|
-
this.loadingAnimation.stop();
|
|
4714
|
-
this.loadingAnimation = undefined;
|
|
4715
|
-
}
|
|
4716
|
-
this.statusContainer.clear();
|
|
4944
|
+
this.clearStatusIndicator();
|
|
4717
4945
|
try {
|
|
4718
4946
|
const result = await this.runtimeHost.newSession();
|
|
4719
4947
|
if (result.cancelled) {
|
|
@@ -4840,11 +5068,7 @@ export class InteractiveMode {
|
|
|
4840
5068
|
this.ui.requestRender();
|
|
4841
5069
|
}
|
|
4842
5070
|
async handleCompactCommand(customInstructions) {
|
|
4843
|
-
|
|
4844
|
-
this.loadingAnimation.stop();
|
|
4845
|
-
this.loadingAnimation = undefined;
|
|
4846
|
-
}
|
|
4847
|
-
this.statusContainer.clear();
|
|
5071
|
+
this.clearStatusIndicator();
|
|
4848
5072
|
try {
|
|
4849
5073
|
await this.session.compact(customInstructions);
|
|
4850
5074
|
}
|
|
@@ -4856,10 +5080,7 @@ export class InteractiveMode {
|
|
|
4856
5080
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
4857
5081
|
this.ui.terminal.setProgress(false);
|
|
4858
5082
|
}
|
|
4859
|
-
|
|
4860
|
-
this.loadingAnimation.stop();
|
|
4861
|
-
this.loadingAnimation = undefined;
|
|
4862
|
-
}
|
|
5083
|
+
this.clearStatusIndicator();
|
|
4863
5084
|
this.themeController.disableAutoSync();
|
|
4864
5085
|
this.clearExtensionTerminalInputListeners();
|
|
4865
5086
|
this.footer.dispose();
|