@kolisachint/hoocode-agent 0.4.107 → 0.4.108
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 +2 -0
- package/dist/modes/interactive/bash-execution-controller.d.ts +34 -0
- package/dist/modes/interactive/bash-execution-controller.d.ts.map +1 -0
- package/dist/modes/interactive/bash-execution-controller.js +98 -0
- package/dist/modes/interactive/bash-execution-controller.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +5 -45
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +91 -779
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/login-controller.d.ts +50 -0
- package/dist/modes/interactive/login-controller.d.ts.map +1 -0
- package/dist/modes/interactive/login-controller.js +352 -0
- package/dist/modes/interactive/login-controller.js.map +1 -0
- package/dist/modes/interactive/message-queue-controller.d.ts +49 -0
- package/dist/modes/interactive/message-queue-controller.d.ts.map +1 -0
- package/dist/modes/interactive/message-queue-controller.js +184 -0
- package/dist/modes/interactive/message-queue-controller.js.map +1 -0
- package/dist/modes/interactive/model-controller.d.ts +43 -0
- package/dist/modes/interactive/model-controller.d.ts.map +1 -0
- package/dist/modes/interactive/model-controller.js +184 -0
- package/dist/modes/interactive/model-controller.js.map +1 -0
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -7,15 +7,12 @@ import * as fs from "node:fs";
|
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { createCompactionSummaryMessage } from "@kolisachint/hoocode-agent-core";
|
|
10
|
-
import {
|
|
11
|
-
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, } from "@kolisachint/hoocode-tui";
|
|
10
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TUI, } from "@kolisachint/hoocode-tui";
|
|
12
11
|
import { spawnSync } from "child_process";
|
|
13
|
-
import { APP_NAME, APP_TITLE,
|
|
12
|
+
import { APP_NAME, APP_TITLE, VERSION } from "../../config.js";
|
|
14
13
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
15
14
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
16
15
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
17
|
-
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
18
|
-
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
19
16
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
20
17
|
import { SessionManager } from "../../core/session-manager.js";
|
|
21
18
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
@@ -26,6 +23,7 @@ import { parseGitUrl } from "../../utils/git.js";
|
|
|
26
23
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
27
24
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
28
25
|
import { checkForNewHooCodeVersion } from "../../utils/version-check.js";
|
|
26
|
+
import { BashExecutionController } from "./bash-execution-controller.js";
|
|
29
27
|
import { CommandExecutor } from "./command-executor.js";
|
|
30
28
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
31
29
|
import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
@@ -35,13 +33,8 @@ import { CountdownTimer } from "./components/countdown-timer.js";
|
|
|
35
33
|
import { CustomEditor } from "./components/custom-editor.js";
|
|
36
34
|
import { CustomMessageComponent } from "./components/custom-message.js";
|
|
37
35
|
import { DynamicBorder } from "./components/dynamic-border.js";
|
|
38
|
-
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
39
36
|
import { FooterComponent } from "./components/footer.js";
|
|
40
|
-
import {
|
|
41
|
-
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
42
|
-
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
43
|
-
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
44
|
-
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
37
|
+
import { keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
45
38
|
import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
46
39
|
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
47
40
|
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
@@ -52,11 +45,15 @@ import { UserMessageComponent } from "./components/user-message.js";
|
|
|
52
45
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
53
46
|
import { ExtensionChrome } from "./extension-chrome.js";
|
|
54
47
|
import { ExtensionDialogs } from "./extension-dialogs.js";
|
|
48
|
+
import { LoginController } from "./login-controller.js";
|
|
49
|
+
import { MessageQueueController } from "./message-queue-controller.js";
|
|
50
|
+
import { ModelController } from "./model-controller.js";
|
|
55
51
|
import { ExpandableText, formatDisplayPath, isExpandable, showLoadedResources as renderLoadedResources, } from "./resource-display.js";
|
|
56
52
|
import { checkForPackageUpdates, checkTmuxKeyboardSetup, getChangelogForDisplay } from "./startup-checks.js";
|
|
57
53
|
import { TeamFocusController } from "./team-focus.js";
|
|
58
54
|
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
59
55
|
import { VoiceController } from "./voice/voice-controller.js";
|
|
56
|
+
/** Interface for components that can be expanded/collapsed */
|
|
60
57
|
const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
|
|
61
58
|
function isDeadTerminalError(error) {
|
|
62
59
|
if (!error || typeof error !== "object" || !("code" in error)) {
|
|
@@ -65,27 +62,6 @@ function isDeadTerminalError(error) {
|
|
|
65
62
|
const code = error.code;
|
|
66
63
|
return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
|
|
67
64
|
}
|
|
68
|
-
const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth: billed per token as extra usage, not plan limits.";
|
|
69
|
-
function isAnthropicSubscriptionAuthKey(apiKey) {
|
|
70
|
-
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
|
71
|
-
}
|
|
72
|
-
function isUnknownModel(model) {
|
|
73
|
-
return !!model && model.provider === "unknown" && model.id === "unknown" && model.api === "unknown";
|
|
74
|
-
}
|
|
75
|
-
function hasDefaultModelProvider(providerId) {
|
|
76
|
-
return providerId in defaultModelPerProvider;
|
|
77
|
-
}
|
|
78
|
-
const BEDROCK_PROVIDER_ID = "amazon-bedrock";
|
|
79
|
-
const BUILT_IN_MODEL_PROVIDERS = new Set(getProviders());
|
|
80
|
-
export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProviderIds = BUILT_IN_MODEL_PROVIDERS) {
|
|
81
|
-
if (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
if (builtInProviderIds.has(providerId)) {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
return !oauthProviderIds.has(providerId);
|
|
88
|
-
}
|
|
89
65
|
export class InteractiveMode {
|
|
90
66
|
options;
|
|
91
67
|
runtimeHost;
|
|
@@ -119,7 +95,6 @@ export class InteractiveMode {
|
|
|
119
95
|
lastEscapeTime = 0;
|
|
120
96
|
changelogMarkdown = undefined;
|
|
121
97
|
startupNoticesShown = false;
|
|
122
|
-
anthropicSubscriptionWarningShown = false;
|
|
123
98
|
// Status line tracking (for mutating immediately-sequential status updates)
|
|
124
99
|
lastStatusSpacer = undefined;
|
|
125
100
|
lastStatusText = undefined;
|
|
@@ -141,10 +116,6 @@ export class InteractiveMode {
|
|
|
141
116
|
signalCleanupHandlers = [];
|
|
142
117
|
// Track if editor is in bash mode (text starts with !)
|
|
143
118
|
isBashMode = false;
|
|
144
|
-
// Track current bash execution component
|
|
145
|
-
bashComponent = undefined;
|
|
146
|
-
// Track pending bash components (shown in pending area, moved to chat on submit)
|
|
147
|
-
pendingBashComponents = [];
|
|
148
119
|
// Auto-compaction state
|
|
149
120
|
autoCompactionLoader = undefined;
|
|
150
121
|
autoCompactionEscapeHandler;
|
|
@@ -152,8 +123,6 @@ export class InteractiveMode {
|
|
|
152
123
|
retryLoader = undefined;
|
|
153
124
|
retryCountdown = undefined;
|
|
154
125
|
retryEscapeHandler;
|
|
155
|
-
// Messages queued while compaction is running
|
|
156
|
-
compactionQueuedMessages = [];
|
|
157
126
|
// Shutdown state
|
|
158
127
|
shutdownRequested = false;
|
|
159
128
|
// Extension UI state
|
|
@@ -167,6 +136,14 @@ export class InteractiveMode {
|
|
|
167
136
|
taskPanel;
|
|
168
137
|
// hooteams team client (--team): steering + attach share its single SSE stream
|
|
169
138
|
teamFocus;
|
|
139
|
+
// Bash command execution (the `!cmd` prompt mode)
|
|
140
|
+
bashExecution;
|
|
141
|
+
// Message queueing (compaction queue + pending-messages display)
|
|
142
|
+
messageQueue;
|
|
143
|
+
// Model selection (/model, /models, cycle keys)
|
|
144
|
+
modelController;
|
|
145
|
+
// Auth / login flows (/login, /logout)
|
|
146
|
+
loginController;
|
|
170
147
|
// Header container that holds the built-in or custom header
|
|
171
148
|
headerContainer;
|
|
172
149
|
// Built-in header (logo + keybinding hints + changelog)
|
|
@@ -223,9 +200,9 @@ export class InteractiveMode {
|
|
|
223
200
|
rebuildChatFromMessages: () => self.rebuildChatFromMessages(),
|
|
224
201
|
getMarkdownThemeWithSettings: () => self.getMarkdownThemeWithSettings(),
|
|
225
202
|
stopLoadingAnimation: () => self.stopLoadingAnimation(),
|
|
226
|
-
findExactModelMatch: (searchTerm) => self.findExactModelMatch(searchTerm),
|
|
227
|
-
maybeWarnAboutAnthropicSubscriptionAuth: (model) => self.maybeWarnAboutAnthropicSubscriptionAuth(model),
|
|
228
|
-
showModelSelector: (searchTerm) => self.showModelSelector(searchTerm),
|
|
203
|
+
findExactModelMatch: (searchTerm) => self.modelController.findExactModelMatch(searchTerm),
|
|
204
|
+
maybeWarnAboutAnthropicSubscriptionAuth: (model) => self.modelController.maybeWarnAboutAnthropicSubscriptionAuth(model),
|
|
205
|
+
showModelSelector: (searchTerm) => self.modelController.showModelSelector(searchTerm),
|
|
229
206
|
showExtensionConfirm: (title, message) => self.dialogs.confirm(title, message),
|
|
230
207
|
promptForMissingSessionCwd: (error) => self.promptForMissingSessionCwd(error),
|
|
231
208
|
handleFatalRuntimeError: (prefix, error) => self.handleFatalRuntimeError(prefix, error),
|
|
@@ -316,6 +293,53 @@ export class InteractiveMode {
|
|
|
316
293
|
sendEditorInput: (data) => this.editor.handleInput(data),
|
|
317
294
|
showError: (message) => this.showError(message),
|
|
318
295
|
});
|
|
296
|
+
const self = this;
|
|
297
|
+
this.bashExecution = new BashExecutionController({
|
|
298
|
+
get session() {
|
|
299
|
+
return self.session;
|
|
300
|
+
},
|
|
301
|
+
ui: this.ui,
|
|
302
|
+
pendingMessagesContainer: this.pendingMessagesContainer,
|
|
303
|
+
chatContainer: this.chatContainer,
|
|
304
|
+
showError: (message) => this.showError(message),
|
|
305
|
+
});
|
|
306
|
+
this.messageQueue = new MessageQueueController({
|
|
307
|
+
get session() {
|
|
308
|
+
return self.session;
|
|
309
|
+
},
|
|
310
|
+
getEditor: () => this.editor,
|
|
311
|
+
pendingMessagesContainer: this.pendingMessagesContainer,
|
|
312
|
+
showStatus: (message) => this.showStatus(message),
|
|
313
|
+
showError: (message) => this.showError(message),
|
|
314
|
+
});
|
|
315
|
+
this.modelController = new ModelController({
|
|
316
|
+
ui: this.ui,
|
|
317
|
+
get session() {
|
|
318
|
+
return self.session;
|
|
319
|
+
},
|
|
320
|
+
showSelector: (create) => this.showSelector(create),
|
|
321
|
+
showStatus: (message) => this.showStatus(message),
|
|
322
|
+
showError: (message) => this.showError(message),
|
|
323
|
+
showWarning: (message) => this.showWarning(message),
|
|
324
|
+
updateEditorBorderColor: () => this.updateEditorBorderColor(),
|
|
325
|
+
invalidateFooter: () => this.footer.invalidate(),
|
|
326
|
+
setAvailableProviderCount: (count) => this.footerDataProvider.setAvailableProviderCount(count),
|
|
327
|
+
});
|
|
328
|
+
this.loginController = new LoginController({
|
|
329
|
+
ui: this.ui,
|
|
330
|
+
get session() {
|
|
331
|
+
return self.session;
|
|
332
|
+
},
|
|
333
|
+
getEditor: () => this.editor,
|
|
334
|
+
editorContainer: this.editorContainer,
|
|
335
|
+
showSelector: (create) => this.showSelector(create),
|
|
336
|
+
showStatus: (message) => this.showStatus(message),
|
|
337
|
+
showError: (message) => this.showError(message),
|
|
338
|
+
updateAvailableProviderCount: () => this.modelController.updateAvailableProviderCount(),
|
|
339
|
+
updateEditorBorderColor: () => this.updateEditorBorderColor(),
|
|
340
|
+
invalidateFooter: () => this.footer.invalidate(),
|
|
341
|
+
maybeWarnAboutAnthropicSubscriptionAuth: (model) => this.modelController.maybeWarnAboutAnthropicSubscriptionAuth(model),
|
|
342
|
+
});
|
|
319
343
|
this.taskPanel.onNudge = (role) => this.teamFocus.showNudgeInput(role);
|
|
320
344
|
this.taskPanel.onAttach = (role) => this.teamFocus.showAttach(role);
|
|
321
345
|
this.taskPanel.onExitFocus = () => this.teamFocus.exitFocus();
|
|
@@ -563,7 +587,7 @@ export class InteractiveMode {
|
|
|
563
587
|
this.ui.requestRender();
|
|
564
588
|
});
|
|
565
589
|
// Initialize available provider count for footer display
|
|
566
|
-
await this.updateAvailableProviderCount();
|
|
590
|
+
await this.modelController.updateAvailableProviderCount();
|
|
567
591
|
}
|
|
568
592
|
/**
|
|
569
593
|
* Update terminal title with session name and cwd.
|
|
@@ -614,7 +638,7 @@ export class InteractiveMode {
|
|
|
614
638
|
if (modelFallbackMessage) {
|
|
615
639
|
this.showWarning(modelFallbackMessage);
|
|
616
640
|
}
|
|
617
|
-
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
641
|
+
void this.modelController.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
618
642
|
// Process initial messages
|
|
619
643
|
if (initialMessage) {
|
|
620
644
|
try {
|
|
@@ -733,7 +757,7 @@ export class InteractiveMode {
|
|
|
733
757
|
this.editor.setText(result.editorText);
|
|
734
758
|
}
|
|
735
759
|
this.showStatus("Navigated to selected point");
|
|
736
|
-
void this.flushCompactionQueue({ willRetry: false });
|
|
760
|
+
void this.messageQueue.flushCompactionQueue({ willRetry: false });
|
|
737
761
|
return { cancelled: false };
|
|
738
762
|
},
|
|
739
763
|
switchSession: async (sessionPath, options) => {
|
|
@@ -782,7 +806,7 @@ export class InteractiveMode {
|
|
|
782
806
|
this.applyRuntimeSettings();
|
|
783
807
|
await this.bindCurrentSessionExtensions();
|
|
784
808
|
this.subscribeToAgent();
|
|
785
|
-
await this.updateAvailableProviderCount();
|
|
809
|
+
await this.modelController.updateAvailableProviderCount();
|
|
786
810
|
this.updateEditorBorderColor();
|
|
787
811
|
this.updateTerminalTitle();
|
|
788
812
|
}
|
|
@@ -796,7 +820,7 @@ export class InteractiveMode {
|
|
|
796
820
|
renderCurrentSessionState() {
|
|
797
821
|
this.chatContainer.clear();
|
|
798
822
|
this.pendingMessagesContainer.clear();
|
|
799
|
-
this.
|
|
823
|
+
this.messageQueue.resetCompactionQueue();
|
|
800
824
|
this.streamingComponent = undefined;
|
|
801
825
|
this.streamingMessage = undefined;
|
|
802
826
|
this.pendingTools.clear();
|
|
@@ -1116,7 +1140,7 @@ export class InteractiveMode {
|
|
|
1116
1140
|
// so they work correctly regardless of which editor is active
|
|
1117
1141
|
this.defaultEditor.onEscape = () => {
|
|
1118
1142
|
if (this.session.isStreaming) {
|
|
1119
|
-
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1143
|
+
this.messageQueue.restoreQueuedMessagesToEditor({ abort: true });
|
|
1120
1144
|
}
|
|
1121
1145
|
else if (this.session.isBashRunning) {
|
|
1122
1146
|
this.session.abortBash();
|
|
@@ -1151,11 +1175,11 @@ export class InteractiveMode {
|
|
|
1151
1175
|
this.defaultEditor.onCtrlD = () => this.handleCtrlD();
|
|
1152
1176
|
this.defaultEditor.onAction("app.suspend", () => this.handleCtrlZ());
|
|
1153
1177
|
this.defaultEditor.onAction("app.thinking.cycle", () => this.cycleThinkingLevel());
|
|
1154
|
-
this.defaultEditor.onAction("app.model.cycleForward", () => this.cycleModel("forward"));
|
|
1155
|
-
this.defaultEditor.onAction("app.model.cycleBackward", () => this.cycleModel("backward"));
|
|
1178
|
+
this.defaultEditor.onAction("app.model.cycleForward", () => this.modelController.cycleModel("forward"));
|
|
1179
|
+
this.defaultEditor.onAction("app.model.cycleBackward", () => this.modelController.cycleModel("backward"));
|
|
1156
1180
|
// Global debug handler on TUI (works regardless of focus)
|
|
1157
1181
|
this.ui.onDebug = () => this.commandExecutor.handleDebug();
|
|
1158
|
-
this.defaultEditor.onAction("app.model.select", () => this.showModelSelector());
|
|
1182
|
+
this.defaultEditor.onAction("app.model.select", () => this.modelController.showModelSelector());
|
|
1159
1183
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
1160
1184
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
1161
1185
|
this.defaultEditor.onAction("app.tasks.cycleView", () => {
|
|
@@ -1231,7 +1255,7 @@ export class InteractiveMode {
|
|
|
1231
1255
|
"/scoped-models": {
|
|
1232
1256
|
run: async () => {
|
|
1233
1257
|
clearEditor();
|
|
1234
|
-
await this.showModelsSelector();
|
|
1258
|
+
await this.modelController.showModelsSelector();
|
|
1235
1259
|
},
|
|
1236
1260
|
},
|
|
1237
1261
|
"/model": {
|
|
@@ -1313,13 +1337,13 @@ export class InteractiveMode {
|
|
|
1313
1337
|
},
|
|
1314
1338
|
"/login": {
|
|
1315
1339
|
run: () => {
|
|
1316
|
-
this.showOAuthSelector("login");
|
|
1340
|
+
this.loginController.showOAuthSelector("login");
|
|
1317
1341
|
clearEditor();
|
|
1318
1342
|
},
|
|
1319
1343
|
},
|
|
1320
1344
|
"/logout": {
|
|
1321
1345
|
run: () => {
|
|
1322
|
-
this.showOAuthSelector("logout");
|
|
1346
|
+
this.loginController.showOAuthSelector("logout");
|
|
1323
1347
|
clearEditor();
|
|
1324
1348
|
},
|
|
1325
1349
|
},
|
|
@@ -1398,7 +1422,7 @@ export class InteractiveMode {
|
|
|
1398
1422
|
return;
|
|
1399
1423
|
}
|
|
1400
1424
|
this.editor.addToHistory?.(text);
|
|
1401
|
-
await this.handleBashCommand(command, isExcluded);
|
|
1425
|
+
await this.bashExecution.handleBashCommand(command, isExcluded);
|
|
1402
1426
|
this.isBashMode = false;
|
|
1403
1427
|
this.updateEditorBorderColor();
|
|
1404
1428
|
return;
|
|
@@ -1406,13 +1430,13 @@ export class InteractiveMode {
|
|
|
1406
1430
|
}
|
|
1407
1431
|
// Queue input during compaction (extension commands execute immediately)
|
|
1408
1432
|
if (this.session.isCompacting) {
|
|
1409
|
-
if (this.isExtensionCommand(text)) {
|
|
1433
|
+
if (this.messageQueue.isExtensionCommand(text)) {
|
|
1410
1434
|
this.editor.addToHistory?.(text);
|
|
1411
1435
|
this.editor.setText("");
|
|
1412
1436
|
await this.session.prompt(text);
|
|
1413
1437
|
}
|
|
1414
1438
|
else {
|
|
1415
|
-
this.queueCompactionMessage(text, "steer");
|
|
1439
|
+
this.messageQueue.queueCompactionMessage(text, "steer");
|
|
1416
1440
|
}
|
|
1417
1441
|
return;
|
|
1418
1442
|
}
|
|
@@ -1422,13 +1446,13 @@ export class InteractiveMode {
|
|
|
1422
1446
|
this.editor.addToHistory?.(text);
|
|
1423
1447
|
this.editor.setText("");
|
|
1424
1448
|
await this.session.prompt(text, { streamingBehavior: "steer" });
|
|
1425
|
-
this.updatePendingMessagesDisplay();
|
|
1449
|
+
this.messageQueue.updatePendingMessagesDisplay();
|
|
1426
1450
|
this.ui.requestRender();
|
|
1427
1451
|
return;
|
|
1428
1452
|
}
|
|
1429
1453
|
// Normal message submission
|
|
1430
1454
|
// First, move any pending bash components to chat
|
|
1431
|
-
this.flushPendingBashComponents();
|
|
1455
|
+
this.bashExecution.flushPendingBashComponents();
|
|
1432
1456
|
if (this.onInputCallback) {
|
|
1433
1457
|
this.onInputCallback(text);
|
|
1434
1458
|
}
|
|
@@ -1473,7 +1497,7 @@ export class InteractiveMode {
|
|
|
1473
1497
|
this.ui.requestRender();
|
|
1474
1498
|
break;
|
|
1475
1499
|
case "queue_update":
|
|
1476
|
-
this.updatePendingMessagesDisplay();
|
|
1500
|
+
this.messageQueue.updatePendingMessagesDisplay();
|
|
1477
1501
|
this.ui.requestRender();
|
|
1478
1502
|
break;
|
|
1479
1503
|
case "session_info_changed":
|
|
@@ -1499,7 +1523,7 @@ export class InteractiveMode {
|
|
|
1499
1523
|
// message can arrive while a subagent is still running.
|
|
1500
1524
|
taskStore.reset();
|
|
1501
1525
|
this.addMessageToChat(event.message);
|
|
1502
|
-
this.updatePendingMessagesDisplay();
|
|
1526
|
+
this.messageQueue.updatePendingMessagesDisplay();
|
|
1503
1527
|
this.ui.requestRender();
|
|
1504
1528
|
}
|
|
1505
1529
|
else if (event.message.role === "assistant") {
|
|
@@ -1680,7 +1704,7 @@ export class InteractiveMode {
|
|
|
1680
1704
|
this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
|
|
1681
1705
|
}
|
|
1682
1706
|
}
|
|
1683
|
-
void this.flushCompactionQueue({ willRetry: event.willRetry });
|
|
1707
|
+
void this.messageQueue.flushCompactionQueue({ willRetry: event.willRetry });
|
|
1684
1708
|
this.ui.requestRender();
|
|
1685
1709
|
break;
|
|
1686
1710
|
}
|
|
@@ -2055,13 +2079,13 @@ export class InteractiveMode {
|
|
|
2055
2079
|
return;
|
|
2056
2080
|
// Queue input during compaction (extension commands execute immediately)
|
|
2057
2081
|
if (this.session.isCompacting) {
|
|
2058
|
-
if (this.isExtensionCommand(text)) {
|
|
2082
|
+
if (this.messageQueue.isExtensionCommand(text)) {
|
|
2059
2083
|
this.editor.addToHistory?.(text);
|
|
2060
2084
|
this.editor.setText("");
|
|
2061
2085
|
await this.session.prompt(text);
|
|
2062
2086
|
}
|
|
2063
2087
|
else {
|
|
2064
|
-
this.queueCompactionMessage(text, "followUp");
|
|
2088
|
+
this.messageQueue.queueCompactionMessage(text, "followUp");
|
|
2065
2089
|
}
|
|
2066
2090
|
return;
|
|
2067
2091
|
}
|
|
@@ -2071,7 +2095,7 @@ export class InteractiveMode {
|
|
|
2071
2095
|
this.editor.addToHistory?.(text);
|
|
2072
2096
|
this.editor.setText("");
|
|
2073
2097
|
await this.session.prompt(text, { streamingBehavior: "followUp" });
|
|
2074
|
-
this.updatePendingMessagesDisplay();
|
|
2098
|
+
this.messageQueue.updatePendingMessagesDisplay();
|
|
2075
2099
|
this.ui.requestRender();
|
|
2076
2100
|
}
|
|
2077
2101
|
// If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
|
|
@@ -2081,7 +2105,7 @@ export class InteractiveMode {
|
|
|
2081
2105
|
}
|
|
2082
2106
|
}
|
|
2083
2107
|
handleDequeue() {
|
|
2084
|
-
const restored = this.restoreQueuedMessagesToEditor();
|
|
2108
|
+
const restored = this.messageQueue.restoreQueuedMessagesToEditor();
|
|
2085
2109
|
if (restored === 0) {
|
|
2086
2110
|
this.showStatus("No queued messages to restore");
|
|
2087
2111
|
}
|
|
@@ -2121,25 +2145,6 @@ export class InteractiveMode {
|
|
|
2121
2145
|
this.showStatus(`Thinking level: ${newLevel}`);
|
|
2122
2146
|
}
|
|
2123
2147
|
}
|
|
2124
|
-
async cycleModel(direction) {
|
|
2125
|
-
try {
|
|
2126
|
-
const result = await this.session.cycleModel(direction);
|
|
2127
|
-
if (result === undefined) {
|
|
2128
|
-
const msg = this.session.scopedModels.length > 0 ? "Only one model in scope" : "Only one model available";
|
|
2129
|
-
this.showStatus(msg);
|
|
2130
|
-
}
|
|
2131
|
-
else {
|
|
2132
|
-
this.footer.invalidate();
|
|
2133
|
-
this.updateEditorBorderColor();
|
|
2134
|
-
const thinkingStr = result.model.reasoning && result.thinkingLevel !== "off" ? ` (thinking: ${result.thinkingLevel})` : "";
|
|
2135
|
-
this.showStatus(`Switched to ${result.model.name || result.model.id}${thinkingStr}`);
|
|
2136
|
-
void this.maybeWarnAboutAnthropicSubscriptionAuth(result.model);
|
|
2137
|
-
}
|
|
2138
|
-
}
|
|
2139
|
-
catch (error) {
|
|
2140
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
2148
|
toggleToolOutputExpansion() {
|
|
2144
2149
|
this.setToolsExpanded(!this.toolOutputExpanded);
|
|
2145
2150
|
}
|
|
@@ -2252,170 +2257,6 @@ export class InteractiveMode {
|
|
|
2252
2257
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2253
2258
|
this.ui.requestRender();
|
|
2254
2259
|
}
|
|
2255
|
-
/**
|
|
2256
|
-
* Get all queued messages (read-only).
|
|
2257
|
-
* Combines session queue and compaction queue.
|
|
2258
|
-
*/
|
|
2259
|
-
getAllQueuedMessages() {
|
|
2260
|
-
return {
|
|
2261
|
-
steering: [
|
|
2262
|
-
...this.session.getSteeringMessages(),
|
|
2263
|
-
...this.compactionQueuedMessages.filter((msg) => msg.mode === "steer").map((msg) => msg.text),
|
|
2264
|
-
],
|
|
2265
|
-
followUp: [
|
|
2266
|
-
...this.session.getFollowUpMessages(),
|
|
2267
|
-
...this.compactionQueuedMessages.filter((msg) => msg.mode === "followUp").map((msg) => msg.text),
|
|
2268
|
-
],
|
|
2269
|
-
};
|
|
2270
|
-
}
|
|
2271
|
-
/**
|
|
2272
|
-
* Clear all queued messages and return their contents.
|
|
2273
|
-
* Clears both session queue and compaction queue.
|
|
2274
|
-
*/
|
|
2275
|
-
clearAllQueues() {
|
|
2276
|
-
const { steering, followUp } = this.session.clearQueue();
|
|
2277
|
-
const compactionSteering = this.compactionQueuedMessages
|
|
2278
|
-
.filter((msg) => msg.mode === "steer")
|
|
2279
|
-
.map((msg) => msg.text);
|
|
2280
|
-
const compactionFollowUp = this.compactionQueuedMessages
|
|
2281
|
-
.filter((msg) => msg.mode === "followUp")
|
|
2282
|
-
.map((msg) => msg.text);
|
|
2283
|
-
this.compactionQueuedMessages = [];
|
|
2284
|
-
return {
|
|
2285
|
-
steering: [...steering, ...compactionSteering],
|
|
2286
|
-
followUp: [...followUp, ...compactionFollowUp],
|
|
2287
|
-
};
|
|
2288
|
-
}
|
|
2289
|
-
updatePendingMessagesDisplay() {
|
|
2290
|
-
this.pendingMessagesContainer.clear();
|
|
2291
|
-
const { steering: steeringMessages, followUp: followUpMessages } = this.getAllQueuedMessages();
|
|
2292
|
-
if (steeringMessages.length > 0 || followUpMessages.length > 0) {
|
|
2293
|
-
this.pendingMessagesContainer.addChild(new Spacer(1));
|
|
2294
|
-
for (const message of steeringMessages) {
|
|
2295
|
-
const text = theme.fg("dim", `Steering: ${message}`);
|
|
2296
|
-
this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
|
|
2297
|
-
}
|
|
2298
|
-
for (const message of followUpMessages) {
|
|
2299
|
-
const text = theme.fg("dim", `Follow-up: ${message}`);
|
|
2300
|
-
this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
|
|
2301
|
-
}
|
|
2302
|
-
const dequeueHint = this.getAppKeyDisplay("app.message.dequeue");
|
|
2303
|
-
const hintText = theme.fg("dim", `↳ ${dequeueHint} to edit all queued messages`);
|
|
2304
|
-
this.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
|
|
2305
|
-
}
|
|
2306
|
-
}
|
|
2307
|
-
restoreQueuedMessagesToEditor(options) {
|
|
2308
|
-
const { steering, followUp } = this.clearAllQueues();
|
|
2309
|
-
const allQueued = [...steering, ...followUp];
|
|
2310
|
-
if (allQueued.length === 0) {
|
|
2311
|
-
this.updatePendingMessagesDisplay();
|
|
2312
|
-
if (options?.abort) {
|
|
2313
|
-
this.agent.abort();
|
|
2314
|
-
}
|
|
2315
|
-
return 0;
|
|
2316
|
-
}
|
|
2317
|
-
const queuedText = allQueued.join("\n\n");
|
|
2318
|
-
const currentText = options?.currentText ?? this.editor.getText();
|
|
2319
|
-
const combinedText = [queuedText, currentText].filter((t) => t.trim()).join("\n\n");
|
|
2320
|
-
this.editor.setText(combinedText);
|
|
2321
|
-
this.updatePendingMessagesDisplay();
|
|
2322
|
-
if (options?.abort) {
|
|
2323
|
-
this.agent.abort();
|
|
2324
|
-
}
|
|
2325
|
-
return allQueued.length;
|
|
2326
|
-
}
|
|
2327
|
-
queueCompactionMessage(text, mode) {
|
|
2328
|
-
this.compactionQueuedMessages.push({ text, mode });
|
|
2329
|
-
this.editor.addToHistory?.(text);
|
|
2330
|
-
this.editor.setText("");
|
|
2331
|
-
this.updatePendingMessagesDisplay();
|
|
2332
|
-
this.showStatus("Queued message for after compaction");
|
|
2333
|
-
}
|
|
2334
|
-
isExtensionCommand(text) {
|
|
2335
|
-
if (!text.startsWith("/"))
|
|
2336
|
-
return false;
|
|
2337
|
-
const extensionRunner = this.session.extensionRunner;
|
|
2338
|
-
const spaceIndex = text.indexOf(" ");
|
|
2339
|
-
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
2340
|
-
return !!extensionRunner.getCommand(commandName);
|
|
2341
|
-
}
|
|
2342
|
-
async flushCompactionQueue(options) {
|
|
2343
|
-
if (this.compactionQueuedMessages.length === 0) {
|
|
2344
|
-
return;
|
|
2345
|
-
}
|
|
2346
|
-
const queuedMessages = [...this.compactionQueuedMessages];
|
|
2347
|
-
this.compactionQueuedMessages = [];
|
|
2348
|
-
this.updatePendingMessagesDisplay();
|
|
2349
|
-
const restoreQueue = (error) => {
|
|
2350
|
-
this.session.clearQueue();
|
|
2351
|
-
this.compactionQueuedMessages = queuedMessages;
|
|
2352
|
-
this.updatePendingMessagesDisplay();
|
|
2353
|
-
this.showError(`Failed to send queued message${queuedMessages.length > 1 ? "s" : ""}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2354
|
-
};
|
|
2355
|
-
try {
|
|
2356
|
-
if (options?.willRetry) {
|
|
2357
|
-
// When retry is pending, queue messages for the retry turn
|
|
2358
|
-
for (const message of queuedMessages) {
|
|
2359
|
-
if (this.isExtensionCommand(message.text)) {
|
|
2360
|
-
await this.session.prompt(message.text);
|
|
2361
|
-
}
|
|
2362
|
-
else if (message.mode === "followUp") {
|
|
2363
|
-
await this.session.followUp(message.text);
|
|
2364
|
-
}
|
|
2365
|
-
else {
|
|
2366
|
-
await this.session.steer(message.text);
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
this.updatePendingMessagesDisplay();
|
|
2370
|
-
return;
|
|
2371
|
-
}
|
|
2372
|
-
// Find first non-extension-command message to use as prompt
|
|
2373
|
-
const firstPromptIndex = queuedMessages.findIndex((message) => !this.isExtensionCommand(message.text));
|
|
2374
|
-
if (firstPromptIndex === -1) {
|
|
2375
|
-
// All extension commands - execute them all
|
|
2376
|
-
for (const message of queuedMessages) {
|
|
2377
|
-
await this.session.prompt(message.text);
|
|
2378
|
-
}
|
|
2379
|
-
return;
|
|
2380
|
-
}
|
|
2381
|
-
// Execute any extension commands before the first prompt
|
|
2382
|
-
const preCommands = queuedMessages.slice(0, firstPromptIndex);
|
|
2383
|
-
const firstPrompt = queuedMessages[firstPromptIndex];
|
|
2384
|
-
const rest = queuedMessages.slice(firstPromptIndex + 1);
|
|
2385
|
-
for (const message of preCommands) {
|
|
2386
|
-
await this.session.prompt(message.text);
|
|
2387
|
-
}
|
|
2388
|
-
// Send first prompt (starts streaming)
|
|
2389
|
-
const promptPromise = this.session.prompt(firstPrompt.text).catch((error) => {
|
|
2390
|
-
restoreQueue(error);
|
|
2391
|
-
});
|
|
2392
|
-
// Queue remaining messages
|
|
2393
|
-
for (const message of rest) {
|
|
2394
|
-
if (this.isExtensionCommand(message.text)) {
|
|
2395
|
-
await this.session.prompt(message.text);
|
|
2396
|
-
}
|
|
2397
|
-
else if (message.mode === "followUp") {
|
|
2398
|
-
await this.session.followUp(message.text);
|
|
2399
|
-
}
|
|
2400
|
-
else {
|
|
2401
|
-
await this.session.steer(message.text);
|
|
2402
|
-
}
|
|
2403
|
-
}
|
|
2404
|
-
this.updatePendingMessagesDisplay();
|
|
2405
|
-
void promptPromise;
|
|
2406
|
-
}
|
|
2407
|
-
catch (error) {
|
|
2408
|
-
restoreQueue(error);
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
/** Move pending bash components from pending area to chat */
|
|
2412
|
-
flushPendingBashComponents() {
|
|
2413
|
-
for (const component of this.pendingBashComponents) {
|
|
2414
|
-
this.pendingMessagesContainer.removeChild(component);
|
|
2415
|
-
this.chatContainer.addChild(component);
|
|
2416
|
-
}
|
|
2417
|
-
this.pendingBashComponents = [];
|
|
2418
|
-
}
|
|
2419
2260
|
// =========================================================================
|
|
2420
2261
|
// Selectors
|
|
2421
2262
|
// =========================================================================
|
|
@@ -2586,144 +2427,6 @@ export class InteractiveMode {
|
|
|
2586
2427
|
return { component: selector, focus: selector.getSettingsList() };
|
|
2587
2428
|
});
|
|
2588
2429
|
}
|
|
2589
|
-
async findExactModelMatch(searchTerm) {
|
|
2590
|
-
const models = await this.getModelCandidates();
|
|
2591
|
-
return findExactModelReferenceMatch(searchTerm, models);
|
|
2592
|
-
}
|
|
2593
|
-
async getModelCandidates() {
|
|
2594
|
-
if (this.session.scopedModels.length > 0) {
|
|
2595
|
-
return this.session.scopedModels.map((scoped) => scoped.model);
|
|
2596
|
-
}
|
|
2597
|
-
this.session.modelRegistry.refresh();
|
|
2598
|
-
try {
|
|
2599
|
-
return await this.session.modelRegistry.getAvailable();
|
|
2600
|
-
}
|
|
2601
|
-
catch {
|
|
2602
|
-
return [];
|
|
2603
|
-
}
|
|
2604
|
-
}
|
|
2605
|
-
/** Update the footer's available provider count from current model candidates */
|
|
2606
|
-
async updateAvailableProviderCount() {
|
|
2607
|
-
const models = await this.getModelCandidates();
|
|
2608
|
-
const uniqueProviders = new Set(models.map((m) => m.provider));
|
|
2609
|
-
this.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);
|
|
2610
|
-
}
|
|
2611
|
-
async maybeWarnAboutAnthropicSubscriptionAuth(model = this.session.model) {
|
|
2612
|
-
if (this.settingsManager.getWarnings().anthropicExtraUsage === false) {
|
|
2613
|
-
return;
|
|
2614
|
-
}
|
|
2615
|
-
if (this.anthropicSubscriptionWarningShown) {
|
|
2616
|
-
return;
|
|
2617
|
-
}
|
|
2618
|
-
if (!model || model.provider !== "anthropic") {
|
|
2619
|
-
return;
|
|
2620
|
-
}
|
|
2621
|
-
const storedCredential = this.session.modelRegistry.authStorage.get("anthropic");
|
|
2622
|
-
if (storedCredential?.type === "oauth") {
|
|
2623
|
-
this.anthropicSubscriptionWarningShown = true;
|
|
2624
|
-
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
2625
|
-
return;
|
|
2626
|
-
}
|
|
2627
|
-
try {
|
|
2628
|
-
const apiKey = await this.session.modelRegistry.getApiKeyForProvider(model.provider);
|
|
2629
|
-
if (!isAnthropicSubscriptionAuthKey(apiKey)) {
|
|
2630
|
-
return;
|
|
2631
|
-
}
|
|
2632
|
-
this.anthropicSubscriptionWarningShown = true;
|
|
2633
|
-
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
2634
|
-
}
|
|
2635
|
-
catch {
|
|
2636
|
-
// Ignore auth lookup failures for warning-only checks.
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
|
-
showModelSelector(initialSearchInput) {
|
|
2640
|
-
this.showSelector((done) => {
|
|
2641
|
-
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRegistry, this.session.scopedModels, async (model) => {
|
|
2642
|
-
try {
|
|
2643
|
-
await this.session.setModel(model);
|
|
2644
|
-
this.footer.invalidate();
|
|
2645
|
-
this.updateEditorBorderColor();
|
|
2646
|
-
done();
|
|
2647
|
-
this.showStatus(`Model: ${model.id}`);
|
|
2648
|
-
void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
|
|
2649
|
-
}
|
|
2650
|
-
catch (error) {
|
|
2651
|
-
done();
|
|
2652
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
2653
|
-
}
|
|
2654
|
-
}, () => {
|
|
2655
|
-
done();
|
|
2656
|
-
this.ui.requestRender();
|
|
2657
|
-
}, initialSearchInput);
|
|
2658
|
-
return { component: selector, focus: selector };
|
|
2659
|
-
});
|
|
2660
|
-
}
|
|
2661
|
-
async showModelsSelector() {
|
|
2662
|
-
// Get all available models
|
|
2663
|
-
this.session.modelRegistry.refresh();
|
|
2664
|
-
const allModels = this.session.modelRegistry.getAvailable();
|
|
2665
|
-
if (allModels.length === 0) {
|
|
2666
|
-
this.showStatus("No models available");
|
|
2667
|
-
return;
|
|
2668
|
-
}
|
|
2669
|
-
// Check if session has scoped models (from previous session-only changes or CLI --models)
|
|
2670
|
-
const sessionScopedModels = this.session.scopedModels;
|
|
2671
|
-
const hasSessionScope = sessionScopedModels.length > 0;
|
|
2672
|
-
// Build enabled model IDs from session state or settings
|
|
2673
|
-
let currentEnabledIds = null;
|
|
2674
|
-
if (hasSessionScope) {
|
|
2675
|
-
// Use current session's scoped models
|
|
2676
|
-
currentEnabledIds = sessionScopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
2677
|
-
}
|
|
2678
|
-
else {
|
|
2679
|
-
// Fall back to settings
|
|
2680
|
-
const patterns = this.settingsManager.getEnabledModels();
|
|
2681
|
-
if (patterns !== undefined && patterns.length > 0) {
|
|
2682
|
-
const scopedModels = await resolveModelScope(patterns, this.session.modelRegistry);
|
|
2683
|
-
currentEnabledIds = scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
// Helper to update session's scoped models (session-only, no persist)
|
|
2687
|
-
const updateSessionModels = async (enabledIds) => {
|
|
2688
|
-
currentEnabledIds = enabledIds === null ? null : [...enabledIds];
|
|
2689
|
-
if (enabledIds && enabledIds.length > 0 && enabledIds.length < allModels.length) {
|
|
2690
|
-
const newScopedModels = await resolveModelScope(enabledIds, this.session.modelRegistry);
|
|
2691
|
-
this.session.setScopedModels(newScopedModels.map((sm) => ({
|
|
2692
|
-
model: sm.model,
|
|
2693
|
-
thinkingLevel: sm.thinkingLevel,
|
|
2694
|
-
})));
|
|
2695
|
-
}
|
|
2696
|
-
else {
|
|
2697
|
-
// All enabled or none enabled = no filter
|
|
2698
|
-
this.session.setScopedModels([]);
|
|
2699
|
-
}
|
|
2700
|
-
await this.updateAvailableProviderCount();
|
|
2701
|
-
this.ui.requestRender();
|
|
2702
|
-
};
|
|
2703
|
-
this.showSelector((done) => {
|
|
2704
|
-
const selector = new ScopedModelsSelectorComponent({
|
|
2705
|
-
allModels,
|
|
2706
|
-
enabledModelIds: currentEnabledIds,
|
|
2707
|
-
}, {
|
|
2708
|
-
onChange: async (enabledIds) => {
|
|
2709
|
-
await updateSessionModels(enabledIds);
|
|
2710
|
-
},
|
|
2711
|
-
onPersist: (enabledIds) => {
|
|
2712
|
-
// Persist to settings
|
|
2713
|
-
const newPatterns = enabledIds === null || enabledIds.length === allModels.length
|
|
2714
|
-
? undefined // All enabled = clear filter
|
|
2715
|
-
: enabledIds;
|
|
2716
|
-
this.settingsManager.setEnabledModels(newPatterns ? [...newPatterns] : undefined);
|
|
2717
|
-
this.showStatus("Model selection saved to settings");
|
|
2718
|
-
},
|
|
2719
|
-
onCancel: () => {
|
|
2720
|
-
done();
|
|
2721
|
-
this.ui.requestRender();
|
|
2722
|
-
},
|
|
2723
|
-
});
|
|
2724
|
-
return { component: selector, focus: selector };
|
|
2725
|
-
});
|
|
2726
|
-
}
|
|
2727
2430
|
showUserMessageSelector() {
|
|
2728
2431
|
const userMessages = this.session.getUserMessagesForForking();
|
|
2729
2432
|
if (userMessages.length === 0) {
|
|
@@ -2836,7 +2539,7 @@ export class InteractiveMode {
|
|
|
2836
2539
|
this.editor.setText(result.editorText);
|
|
2837
2540
|
}
|
|
2838
2541
|
this.showStatus("Navigated to selected point");
|
|
2839
|
-
void this.flushCompactionQueue({ willRetry: false });
|
|
2542
|
+
void this.messageQueue.flushCompactionQueue({ willRetry: false });
|
|
2840
2543
|
}
|
|
2841
2544
|
catch (error) {
|
|
2842
2545
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
@@ -2920,326 +2623,6 @@ export class InteractiveMode {
|
|
|
2920
2623
|
return this.handleFatalRuntimeError("Failed to resume session", error);
|
|
2921
2624
|
}
|
|
2922
2625
|
}
|
|
2923
|
-
getLoginProviderOptions(authType) {
|
|
2924
|
-
const authStorage = this.session.modelRegistry.authStorage;
|
|
2925
|
-
const oauthProviders = authStorage.getOAuthProviders();
|
|
2926
|
-
const oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));
|
|
2927
|
-
const options = oauthProviders.map((provider) => ({
|
|
2928
|
-
id: provider.id,
|
|
2929
|
-
name: provider.name,
|
|
2930
|
-
authType: "oauth",
|
|
2931
|
-
}));
|
|
2932
|
-
const modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));
|
|
2933
|
-
for (const providerId of modelProviders) {
|
|
2934
|
-
if (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {
|
|
2935
|
-
continue;
|
|
2936
|
-
}
|
|
2937
|
-
options.push({
|
|
2938
|
-
id: providerId,
|
|
2939
|
-
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
2940
|
-
authType: "api_key",
|
|
2941
|
-
});
|
|
2942
|
-
}
|
|
2943
|
-
const filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;
|
|
2944
|
-
return filteredOptions.sort((a, b) => a.name.localeCompare(b.name));
|
|
2945
|
-
}
|
|
2946
|
-
getLogoutProviderOptions() {
|
|
2947
|
-
const authStorage = this.session.modelRegistry.authStorage;
|
|
2948
|
-
const options = [];
|
|
2949
|
-
for (const providerId of authStorage.list()) {
|
|
2950
|
-
const credential = authStorage.get(providerId);
|
|
2951
|
-
if (!credential) {
|
|
2952
|
-
continue;
|
|
2953
|
-
}
|
|
2954
|
-
options.push({
|
|
2955
|
-
id: providerId,
|
|
2956
|
-
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
2957
|
-
authType: credential.type,
|
|
2958
|
-
});
|
|
2959
|
-
}
|
|
2960
|
-
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
2961
|
-
}
|
|
2962
|
-
showLoginAuthTypeSelector() {
|
|
2963
|
-
const subscriptionLabel = "Use a subscription";
|
|
2964
|
-
const apiKeyLabel = "Use an API key";
|
|
2965
|
-
this.showSelector((done) => {
|
|
2966
|
-
const selector = new ExtensionSelectorComponent("Select authentication method:", [subscriptionLabel, apiKeyLabel], (option) => {
|
|
2967
|
-
done();
|
|
2968
|
-
const authType = option === subscriptionLabel ? "oauth" : "api_key";
|
|
2969
|
-
this.showLoginProviderSelector(authType);
|
|
2970
|
-
}, () => {
|
|
2971
|
-
done();
|
|
2972
|
-
this.ui.requestRender();
|
|
2973
|
-
});
|
|
2974
|
-
return { component: selector, focus: selector };
|
|
2975
|
-
});
|
|
2976
|
-
}
|
|
2977
|
-
showLoginProviderSelector(authType) {
|
|
2978
|
-
const providerOptions = this.getLoginProviderOptions(authType);
|
|
2979
|
-
if (providerOptions.length === 0) {
|
|
2980
|
-
this.showStatus(authType === "oauth" ? "No subscription providers available." : "No API key providers available.");
|
|
2981
|
-
return;
|
|
2982
|
-
}
|
|
2983
|
-
this.showSelector((done) => {
|
|
2984
|
-
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
2985
|
-
done();
|
|
2986
|
-
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
2987
|
-
if (!providerOption) {
|
|
2988
|
-
return;
|
|
2989
|
-
}
|
|
2990
|
-
if (providerOption.authType === "oauth") {
|
|
2991
|
-
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
2992
|
-
}
|
|
2993
|
-
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
2994
|
-
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
2995
|
-
}
|
|
2996
|
-
else {
|
|
2997
|
-
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
2998
|
-
}
|
|
2999
|
-
}, () => {
|
|
3000
|
-
done();
|
|
3001
|
-
this.showLoginAuthTypeSelector();
|
|
3002
|
-
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId));
|
|
3003
|
-
return { component: selector, focus: selector };
|
|
3004
|
-
});
|
|
3005
|
-
}
|
|
3006
|
-
async showOAuthSelector(mode) {
|
|
3007
|
-
if (mode === "login") {
|
|
3008
|
-
this.showLoginAuthTypeSelector();
|
|
3009
|
-
return;
|
|
3010
|
-
}
|
|
3011
|
-
const providerOptions = this.getLogoutProviderOptions();
|
|
3012
|
-
if (providerOptions.length === 0) {
|
|
3013
|
-
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
3014
|
-
return;
|
|
3015
|
-
}
|
|
3016
|
-
this.showSelector((done) => {
|
|
3017
|
-
const selector = new OAuthSelectorComponent(mode, this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
3018
|
-
done();
|
|
3019
|
-
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
3020
|
-
if (!providerOption) {
|
|
3021
|
-
return;
|
|
3022
|
-
}
|
|
3023
|
-
try {
|
|
3024
|
-
this.session.modelRegistry.authStorage.logout(providerOption.id);
|
|
3025
|
-
this.session.modelRegistry.refresh();
|
|
3026
|
-
await this.updateAvailableProviderCount();
|
|
3027
|
-
const message = providerOption.authType === "oauth"
|
|
3028
|
-
? `Logged out of ${providerOption.name}`
|
|
3029
|
-
: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;
|
|
3030
|
-
this.showStatus(message);
|
|
3031
|
-
}
|
|
3032
|
-
catch (error) {
|
|
3033
|
-
this.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3034
|
-
}
|
|
3035
|
-
}, () => {
|
|
3036
|
-
done();
|
|
3037
|
-
this.ui.requestRender();
|
|
3038
|
-
});
|
|
3039
|
-
return { component: selector, focus: selector };
|
|
3040
|
-
});
|
|
3041
|
-
}
|
|
3042
|
-
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
3043
|
-
this.session.modelRegistry.refresh();
|
|
3044
|
-
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
3045
|
-
let selectedModel;
|
|
3046
|
-
let selectionError;
|
|
3047
|
-
if (isUnknownModel(previousModel)) {
|
|
3048
|
-
const availableModels = this.session.modelRegistry.getAvailable();
|
|
3049
|
-
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
3050
|
-
if (!hasDefaultModelProvider(providerId)) {
|
|
3051
|
-
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
3052
|
-
}
|
|
3053
|
-
else if (providerModels.length === 0) {
|
|
3054
|
-
selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
|
|
3055
|
-
}
|
|
3056
|
-
else {
|
|
3057
|
-
const defaultModelId = defaultModelPerProvider[providerId];
|
|
3058
|
-
selectedModel = providerModels.find((model) => model.id === defaultModelId);
|
|
3059
|
-
if (!selectedModel) {
|
|
3060
|
-
selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
|
|
3061
|
-
}
|
|
3062
|
-
else {
|
|
3063
|
-
try {
|
|
3064
|
-
await this.session.setModel(selectedModel);
|
|
3065
|
-
}
|
|
3066
|
-
catch (error) {
|
|
3067
|
-
selectedModel = undefined;
|
|
3068
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3069
|
-
selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
|
|
3070
|
-
}
|
|
3071
|
-
}
|
|
3072
|
-
}
|
|
3073
|
-
}
|
|
3074
|
-
await this.updateAvailableProviderCount();
|
|
3075
|
-
this.footer.invalidate();
|
|
3076
|
-
this.updateEditorBorderColor();
|
|
3077
|
-
if (selectedModel) {
|
|
3078
|
-
this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
|
|
3079
|
-
void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
|
|
3080
|
-
}
|
|
3081
|
-
else {
|
|
3082
|
-
this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
|
|
3083
|
-
if (selectionError) {
|
|
3084
|
-
this.showError(selectionError);
|
|
3085
|
-
}
|
|
3086
|
-
else {
|
|
3087
|
-
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
3088
|
-
}
|
|
3089
|
-
}
|
|
3090
|
-
}
|
|
3091
|
-
showBedrockSetupDialog(providerId, providerName) {
|
|
3092
|
-
const restoreEditor = () => {
|
|
3093
|
-
this.editorContainer.clear();
|
|
3094
|
-
this.editorContainer.addChild(this.editor);
|
|
3095
|
-
this.ui.setFocus(this.editor);
|
|
3096
|
-
this.ui.requestRender();
|
|
3097
|
-
};
|
|
3098
|
-
const dialog = new LoginDialogComponent(this.ui, providerId, () => restoreEditor(), providerName, "Amazon Bedrock setup");
|
|
3099
|
-
dialog.showInfo([
|
|
3100
|
-
theme.fg("text", "Amazon Bedrock uses AWS credentials instead of a single API key."),
|
|
3101
|
-
theme.fg("text", "Configure an AWS profile, IAM keys, bearer token, or role-based credentials."),
|
|
3102
|
-
theme.fg("muted", "See:"),
|
|
3103
|
-
theme.fg("accent", ` ${path.join(getDocsPath(), "providers.md")}`),
|
|
3104
|
-
]);
|
|
3105
|
-
this.editorContainer.clear();
|
|
3106
|
-
this.editorContainer.addChild(dialog);
|
|
3107
|
-
this.ui.setFocus(dialog);
|
|
3108
|
-
this.ui.requestRender();
|
|
3109
|
-
}
|
|
3110
|
-
async showApiKeyLoginDialog(providerId, providerName) {
|
|
3111
|
-
const previousModel = this.session.model;
|
|
3112
|
-
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
3113
|
-
// Completion handled below
|
|
3114
|
-
}, providerName);
|
|
3115
|
-
this.editorContainer.clear();
|
|
3116
|
-
this.editorContainer.addChild(dialog);
|
|
3117
|
-
this.ui.setFocus(dialog);
|
|
3118
|
-
this.ui.requestRender();
|
|
3119
|
-
const restoreEditor = () => {
|
|
3120
|
-
this.editorContainer.clear();
|
|
3121
|
-
this.editorContainer.addChild(this.editor);
|
|
3122
|
-
this.ui.setFocus(this.editor);
|
|
3123
|
-
this.ui.requestRender();
|
|
3124
|
-
};
|
|
3125
|
-
try {
|
|
3126
|
-
const apiKey = (await dialog.showPrompt("Enter API key:")).trim();
|
|
3127
|
-
if (!apiKey) {
|
|
3128
|
-
throw new Error("API key cannot be empty.");
|
|
3129
|
-
}
|
|
3130
|
-
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey });
|
|
3131
|
-
restoreEditor();
|
|
3132
|
-
await this.completeProviderAuthentication(providerId, providerName, "api_key", previousModel);
|
|
3133
|
-
}
|
|
3134
|
-
catch (error) {
|
|
3135
|
-
restoreEditor();
|
|
3136
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3137
|
-
if (errorMsg !== "Login cancelled") {
|
|
3138
|
-
this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
|
|
3139
|
-
}
|
|
3140
|
-
}
|
|
3141
|
-
}
|
|
3142
|
-
showOAuthLoginSelect(dialog, prompt) {
|
|
3143
|
-
return new Promise((resolve) => {
|
|
3144
|
-
const restoreDialog = () => {
|
|
3145
|
-
this.editorContainer.clear();
|
|
3146
|
-
this.editorContainer.addChild(dialog);
|
|
3147
|
-
this.ui.setFocus(dialog);
|
|
3148
|
-
this.ui.requestRender();
|
|
3149
|
-
};
|
|
3150
|
-
const labels = prompt.options.map((option) => option.label);
|
|
3151
|
-
const selector = new ExtensionSelectorComponent(prompt.message, labels, (optionLabel) => {
|
|
3152
|
-
restoreDialog();
|
|
3153
|
-
resolve(prompt.options.find((option) => option.label === optionLabel)?.id);
|
|
3154
|
-
}, () => {
|
|
3155
|
-
restoreDialog();
|
|
3156
|
-
resolve(undefined);
|
|
3157
|
-
});
|
|
3158
|
-
this.editorContainer.clear();
|
|
3159
|
-
this.editorContainer.addChild(selector);
|
|
3160
|
-
this.ui.setFocus(selector);
|
|
3161
|
-
this.ui.requestRender();
|
|
3162
|
-
});
|
|
3163
|
-
}
|
|
3164
|
-
async showLoginDialog(providerId, providerName) {
|
|
3165
|
-
const providerInfo = this.session.modelRegistry.authStorage
|
|
3166
|
-
.getOAuthProviders()
|
|
3167
|
-
.find((provider) => provider.id === providerId);
|
|
3168
|
-
const previousModel = this.session.model;
|
|
3169
|
-
// Providers that use callback servers (can paste redirect URL)
|
|
3170
|
-
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
3171
|
-
// Create login dialog component
|
|
3172
|
-
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
3173
|
-
// Completion handled below
|
|
3174
|
-
}, providerName);
|
|
3175
|
-
// Show dialog in editor container
|
|
3176
|
-
this.editorContainer.clear();
|
|
3177
|
-
this.editorContainer.addChild(dialog);
|
|
3178
|
-
this.ui.setFocus(dialog);
|
|
3179
|
-
this.ui.requestRender();
|
|
3180
|
-
// Promise for manual code input (racing with callback server)
|
|
3181
|
-
let manualCodeResolve;
|
|
3182
|
-
let manualCodeReject;
|
|
3183
|
-
const manualCodePromise = new Promise((resolve, reject) => {
|
|
3184
|
-
manualCodeResolve = resolve;
|
|
3185
|
-
manualCodeReject = reject;
|
|
3186
|
-
});
|
|
3187
|
-
// Restore editor helper
|
|
3188
|
-
const restoreEditor = () => {
|
|
3189
|
-
this.editorContainer.clear();
|
|
3190
|
-
this.editorContainer.addChild(this.editor);
|
|
3191
|
-
this.ui.setFocus(this.editor);
|
|
3192
|
-
this.ui.requestRender();
|
|
3193
|
-
};
|
|
3194
|
-
try {
|
|
3195
|
-
await this.session.modelRegistry.authStorage.login(providerId, {
|
|
3196
|
-
onAuth: (info) => {
|
|
3197
|
-
dialog.showAuth(info.url, info.instructions);
|
|
3198
|
-
if (usesCallbackServer) {
|
|
3199
|
-
// Show input for manual paste, racing with callback
|
|
3200
|
-
dialog
|
|
3201
|
-
.showManualInput("Paste redirect URL below, or complete login in browser:")
|
|
3202
|
-
.then((value) => {
|
|
3203
|
-
if (value && manualCodeResolve) {
|
|
3204
|
-
manualCodeResolve(value);
|
|
3205
|
-
manualCodeResolve = undefined;
|
|
3206
|
-
}
|
|
3207
|
-
})
|
|
3208
|
-
.catch(() => {
|
|
3209
|
-
if (manualCodeReject) {
|
|
3210
|
-
manualCodeReject(new Error("Login cancelled"));
|
|
3211
|
-
manualCodeReject = undefined;
|
|
3212
|
-
}
|
|
3213
|
-
});
|
|
3214
|
-
}
|
|
3215
|
-
else if (providerId === "github-copilot") {
|
|
3216
|
-
// GitHub Copilot polls after onAuth
|
|
3217
|
-
dialog.showWaiting("Waiting for browser authentication...");
|
|
3218
|
-
}
|
|
3219
|
-
// For Anthropic: onPrompt is called immediately after
|
|
3220
|
-
},
|
|
3221
|
-
onPrompt: async (prompt) => {
|
|
3222
|
-
return dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
3223
|
-
},
|
|
3224
|
-
onProgress: (message) => {
|
|
3225
|
-
dialog.showProgress(message);
|
|
3226
|
-
},
|
|
3227
|
-
onSelect: (prompt) => this.showOAuthLoginSelect(dialog, prompt),
|
|
3228
|
-
onManualCodeInput: () => manualCodePromise,
|
|
3229
|
-
signal: dialog.signal,
|
|
3230
|
-
});
|
|
3231
|
-
// Success
|
|
3232
|
-
restoreEditor();
|
|
3233
|
-
await this.completeProviderAuthentication(providerId, providerName, "oauth", previousModel);
|
|
3234
|
-
}
|
|
3235
|
-
catch (error) {
|
|
3236
|
-
restoreEditor();
|
|
3237
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3238
|
-
if (errorMsg !== "Login cancelled") {
|
|
3239
|
-
this.showError(`Failed to login to ${providerName}: ${errorMsg}`);
|
|
3240
|
-
}
|
|
3241
|
-
}
|
|
3242
|
-
}
|
|
3243
2626
|
// =========================================================================
|
|
3244
2627
|
// Command handlers
|
|
3245
2628
|
// =========================================================================
|
|
@@ -3316,77 +2699,6 @@ export class InteractiveMode {
|
|
|
3316
2699
|
this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3317
2700
|
}
|
|
3318
2701
|
}
|
|
3319
|
-
/**
|
|
3320
|
-
* Get capitalized display string for an app keybinding action.
|
|
3321
|
-
*/
|
|
3322
|
-
getAppKeyDisplay(action) {
|
|
3323
|
-
return keyDisplayText(action);
|
|
3324
|
-
}
|
|
3325
|
-
async handleBashCommand(command, excludeFromContext = false) {
|
|
3326
|
-
const extensionRunner = this.session.extensionRunner;
|
|
3327
|
-
// Emit user_bash event to let extensions intercept
|
|
3328
|
-
const eventResult = await extensionRunner.emitUserBash({
|
|
3329
|
-
type: "user_bash",
|
|
3330
|
-
command,
|
|
3331
|
-
excludeFromContext,
|
|
3332
|
-
cwd: this.sessionManager.getCwd(),
|
|
3333
|
-
});
|
|
3334
|
-
// If extension returned a full result, use it directly
|
|
3335
|
-
if (eventResult?.result) {
|
|
3336
|
-
const result = eventResult.result;
|
|
3337
|
-
// Create UI component for display
|
|
3338
|
-
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext);
|
|
3339
|
-
if (this.session.isStreaming) {
|
|
3340
|
-
this.pendingMessagesContainer.addChild(this.bashComponent);
|
|
3341
|
-
this.pendingBashComponents.push(this.bashComponent);
|
|
3342
|
-
}
|
|
3343
|
-
else {
|
|
3344
|
-
this.chatContainer.addChild(this.bashComponent);
|
|
3345
|
-
}
|
|
3346
|
-
// Show output and complete
|
|
3347
|
-
if (result.output) {
|
|
3348
|
-
this.bashComponent.appendOutput(result.output);
|
|
3349
|
-
}
|
|
3350
|
-
this.bashComponent.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath);
|
|
3351
|
-
// Record the result in session
|
|
3352
|
-
this.session.recordBashResult(command, result, { excludeFromContext });
|
|
3353
|
-
this.bashComponent = undefined;
|
|
3354
|
-
this.ui.requestRender();
|
|
3355
|
-
return;
|
|
3356
|
-
}
|
|
3357
|
-
// Normal execution path (possibly with custom operations)
|
|
3358
|
-
const isDeferred = this.session.isStreaming;
|
|
3359
|
-
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext);
|
|
3360
|
-
if (isDeferred) {
|
|
3361
|
-
// Show in pending area when agent is streaming
|
|
3362
|
-
this.pendingMessagesContainer.addChild(this.bashComponent);
|
|
3363
|
-
this.pendingBashComponents.push(this.bashComponent);
|
|
3364
|
-
}
|
|
3365
|
-
else {
|
|
3366
|
-
// Show in chat immediately when agent is idle
|
|
3367
|
-
this.chatContainer.addChild(this.bashComponent);
|
|
3368
|
-
}
|
|
3369
|
-
this.ui.requestRender();
|
|
3370
|
-
try {
|
|
3371
|
-
const result = await this.session.executeBash(command, (chunk) => {
|
|
3372
|
-
if (this.bashComponent) {
|
|
3373
|
-
this.bashComponent.appendOutput(chunk);
|
|
3374
|
-
this.ui.requestRender();
|
|
3375
|
-
}
|
|
3376
|
-
}, { excludeFromContext, operations: eventResult?.operations });
|
|
3377
|
-
if (this.bashComponent) {
|
|
3378
|
-
this.bashComponent.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath);
|
|
3379
|
-
}
|
|
3380
|
-
}
|
|
3381
|
-
catch (error) {
|
|
3382
|
-
if (this.bashComponent) {
|
|
3383
|
-
this.bashComponent.setComplete(undefined, false);
|
|
3384
|
-
}
|
|
3385
|
-
this.showError(`Bash command failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3386
|
-
}
|
|
3387
|
-
this.bashComponent = undefined;
|
|
3388
|
-
this.ui.requestRender();
|
|
3389
|
-
}
|
|
3390
2702
|
async handleCompactCommand(customInstructions) {
|
|
3391
2703
|
// The session is the single source of truth for whether compaction is
|
|
3392
2704
|
// possible (e.g. "Already compacted", "Nothing to compact (session too
|