@oh-my-pi/pi-coding-agent 3.37.1 → 4.0.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/CHANGELOG.md +87 -0
- package/README.md +44 -3
- package/docs/extensions.md +29 -4
- package/docs/sdk.md +3 -3
- package/package.json +5 -5
- package/src/cli/args.ts +8 -0
- package/src/config.ts +5 -15
- package/src/core/agent-session.ts +193 -47
- package/src/core/auth-storage.ts +16 -3
- package/src/core/bash-executor.ts +79 -14
- package/src/core/custom-commands/types.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/export-html/index.ts +33 -1
- package/src/core/export-html/template.css +99 -0
- package/src/core/export-html/template.generated.ts +1 -1
- package/src/core/export-html/template.js +133 -8
- package/src/core/extensions/index.ts +22 -4
- package/src/core/extensions/loader.ts +152 -214
- package/src/core/extensions/runner.ts +139 -79
- package/src/core/extensions/types.ts +143 -19
- package/src/core/extensions/wrapper.ts +5 -8
- package/src/core/hooks/types.ts +1 -1
- package/src/core/index.ts +2 -1
- package/src/core/keybindings.ts +4 -1
- package/src/core/model-registry.ts +1 -1
- package/src/core/model-resolver.ts +35 -26
- package/src/core/sdk.ts +96 -76
- package/src/core/settings-manager.ts +45 -14
- package/src/core/system-prompt.ts +5 -15
- package/src/core/tools/bash.ts +115 -54
- package/src/core/tools/find.ts +86 -7
- package/src/core/tools/grep.ts +27 -6
- package/src/core/tools/index.ts +15 -6
- package/src/core/tools/ls.ts +49 -18
- package/src/core/tools/render-utils.ts +2 -1
- package/src/core/tools/task/worker.ts +35 -12
- package/src/core/tools/web-search/auth.ts +37 -32
- package/src/core/tools/web-search/providers/anthropic.ts +35 -22
- package/src/index.ts +101 -9
- package/src/main.ts +60 -20
- package/src/migrations.ts +47 -2
- package/src/modes/index.ts +2 -2
- package/src/modes/interactive/components/assistant-message.ts +25 -7
- package/src/modes/interactive/components/bash-execution.ts +5 -0
- package/src/modes/interactive/components/branch-summary-message.ts +5 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +5 -0
- package/src/modes/interactive/components/countdown-timer.ts +38 -0
- package/src/modes/interactive/components/custom-editor.ts +8 -0
- package/src/modes/interactive/components/custom-message.ts +5 -0
- package/src/modes/interactive/components/footer.ts +2 -5
- package/src/modes/interactive/components/hook-input.ts +29 -20
- package/src/modes/interactive/components/hook-selector.ts +52 -38
- package/src/modes/interactive/components/index.ts +39 -0
- package/src/modes/interactive/components/login-dialog.ts +160 -0
- package/src/modes/interactive/components/model-selector.ts +10 -2
- package/src/modes/interactive/components/session-selector.ts +5 -1
- package/src/modes/interactive/components/settings-defs.ts +9 -0
- package/src/modes/interactive/components/status-line/segments.ts +3 -3
- package/src/modes/interactive/components/tool-execution.ts +9 -16
- package/src/modes/interactive/components/tree-selector.ts +1 -6
- package/src/modes/interactive/interactive-mode.ts +466 -215
- package/src/modes/interactive/theme/theme.ts +50 -2
- package/src/modes/print-mode.ts +78 -31
- package/src/modes/rpc/rpc-mode.ts +186 -78
- package/src/modes/rpc/rpc-types.ts +10 -3
- package/src/prompts/system-prompt.md +36 -28
- package/src/utils/clipboard.ts +90 -50
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +1 -1
- package/src/utils/tools-manager.ts +2 -2
|
@@ -27,6 +27,7 @@ import { nanoid } from "nanoid";
|
|
|
27
27
|
import { getAuthPath, getDebugLogPath } from "../../config";
|
|
28
28
|
import type { AgentSession, AgentSessionEvent } from "../../core/agent-session";
|
|
29
29
|
import type { ExtensionUIContext } from "../../core/extensions/index";
|
|
30
|
+
import { KeybindingsManager } from "../../core/keybindings";
|
|
30
31
|
import { type CustomMessage, createCompactionSummaryMessage } from "../../core/messages";
|
|
31
32
|
import { getRecentSessions, type SessionContext, SessionManager } from "../../core/session-manager";
|
|
32
33
|
import { loadSlashCommands } from "../../core/slash-commands";
|
|
@@ -66,9 +67,11 @@ import { UserMessageSelectorComponent } from "./components/user-message-selector
|
|
|
66
67
|
import { WelcomeComponent } from "./components/welcome";
|
|
67
68
|
import {
|
|
68
69
|
getAvailableThemes,
|
|
70
|
+
getAvailableThemesWithPaths,
|
|
69
71
|
getEditorTheme,
|
|
70
72
|
getMarkdownTheme,
|
|
71
73
|
getSymbolTheme,
|
|
74
|
+
getThemeByName,
|
|
72
75
|
onThemeChange,
|
|
73
76
|
setSymbolPreset,
|
|
74
77
|
setTheme,
|
|
@@ -76,6 +79,20 @@ import {
|
|
|
76
79
|
theme,
|
|
77
80
|
} from "./theme/theme";
|
|
78
81
|
|
|
82
|
+
/** Options for creating an InteractiveMode instance (for future API use) */
|
|
83
|
+
export interface InteractiveModeOptions {
|
|
84
|
+
/** Providers that were migrated during startup */
|
|
85
|
+
migratedProviders?: string[];
|
|
86
|
+
/** Warning message if model fallback occurred */
|
|
87
|
+
modelFallbackMessage?: string;
|
|
88
|
+
/** Initial message to send */
|
|
89
|
+
initialMessage?: string;
|
|
90
|
+
/** Initial images to include with the message */
|
|
91
|
+
initialImages?: ImageContent[];
|
|
92
|
+
/** Additional initial messages to queue */
|
|
93
|
+
initialMessages?: string[];
|
|
94
|
+
}
|
|
95
|
+
|
|
79
96
|
/** Interface for components that can be expanded/collapsed */
|
|
80
97
|
interface Expandable {
|
|
81
98
|
setExpanded(expanded: boolean): void;
|
|
@@ -85,6 +102,11 @@ function isExpandable(obj: unknown): obj is Expandable {
|
|
|
85
102
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
86
103
|
}
|
|
87
104
|
|
|
105
|
+
type CompactionQueuedMessage = {
|
|
106
|
+
text: string;
|
|
107
|
+
mode: "steer" | "followUp";
|
|
108
|
+
};
|
|
109
|
+
|
|
88
110
|
const VOICE_PROGRESS_DELAY_MS = 15000;
|
|
89
111
|
const VOICE_PROGRESS_MIN_CHARS = 160;
|
|
90
112
|
const VOICE_PROGRESS_DELTA_CHARS = 120;
|
|
@@ -145,6 +167,9 @@ export class InteractiveMode {
|
|
|
145
167
|
// Track pending images from clipboard paste (attached to next message)
|
|
146
168
|
private pendingImages: ImageContent[] = [];
|
|
147
169
|
|
|
170
|
+
// Slash commands loaded from files (for compaction queue handling)
|
|
171
|
+
private fileSlashCommands = new Set<string>();
|
|
172
|
+
|
|
148
173
|
// Voice mode state
|
|
149
174
|
private voiceSupervisor: VoiceSupervisor;
|
|
150
175
|
private voiceAutoModeEnabled = false;
|
|
@@ -157,6 +182,9 @@ export class InteractiveMode {
|
|
|
157
182
|
private autoCompactionLoader: Loader | undefined = undefined;
|
|
158
183
|
private autoCompactionEscapeHandler?: () => void;
|
|
159
184
|
|
|
185
|
+
// Messages queued while compaction is running
|
|
186
|
+
private compactionQueuedMessages: CompactionQueuedMessage[] = [];
|
|
187
|
+
|
|
160
188
|
// Auto-retry state
|
|
161
189
|
private retryLoader: Loader | undefined = undefined;
|
|
162
190
|
private retryEscapeHandler?: () => void;
|
|
@@ -185,7 +213,6 @@ export class InteractiveMode {
|
|
|
185
213
|
private lspServers:
|
|
186
214
|
| Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }>
|
|
187
215
|
| undefined = undefined,
|
|
188
|
-
fdPath: string | undefined = undefined,
|
|
189
216
|
) {
|
|
190
217
|
this.session = session;
|
|
191
218
|
this.version = version;
|
|
@@ -250,6 +277,7 @@ export class InteractiveMode {
|
|
|
250
277
|
|
|
251
278
|
// Load and convert file commands to SlashCommand format
|
|
252
279
|
const fileCommands = loadSlashCommands({ cwd: process.cwd() });
|
|
280
|
+
this.fileSlashCommands = new Set(fileCommands.map((cmd) => cmd.name));
|
|
253
281
|
const fileSlashCommands: SlashCommand[] = fileCommands.map((cmd) => ({
|
|
254
282
|
name: cmd.name,
|
|
255
283
|
description: cmd.description,
|
|
@@ -271,7 +299,6 @@ export class InteractiveMode {
|
|
|
271
299
|
const autocompleteProvider = new CombinedAutocompleteProvider(
|
|
272
300
|
[...slashCommands, ...fileSlashCommands, ...hookCommands, ...customCommands],
|
|
273
301
|
process.cwd(),
|
|
274
|
-
fdPath,
|
|
275
302
|
);
|
|
276
303
|
this.editor.setAutocompleteProvider(autocompleteProvider);
|
|
277
304
|
}
|
|
@@ -383,20 +410,32 @@ export class InteractiveMode {
|
|
|
383
410
|
private async initHooksAndCustomTools(): Promise<void> {
|
|
384
411
|
// Create and set hook & tool UI context
|
|
385
412
|
const uiContext: ExtensionUIContext = {
|
|
386
|
-
select: (title, options) => this.showHookSelector(title, options),
|
|
387
|
-
confirm: (title, message) => this.showHookConfirm(title, message),
|
|
388
|
-
input: (title, placeholder) => this.showHookInput(title, placeholder),
|
|
413
|
+
select: (title, options, _dialogOptions) => this.showHookSelector(title, options),
|
|
414
|
+
confirm: (title, message, _dialogOptions) => this.showHookConfirm(title, message),
|
|
415
|
+
input: (title, placeholder, _dialogOptions) => this.showHookInput(title, placeholder),
|
|
389
416
|
notify: (message, type) => this.showHookNotify(message, type),
|
|
390
417
|
setStatus: (key, text) => this.setHookStatus(key, text),
|
|
391
418
|
setWidget: (key, content) => this.setHookWidget(key, content),
|
|
392
419
|
setTitle: (title) => setTerminalTitle(title),
|
|
393
|
-
custom: (factory) => this.showHookCustom(factory),
|
|
420
|
+
custom: (factory, _options) => this.showHookCustom(factory),
|
|
394
421
|
setEditorText: (text) => this.editor.setText(text),
|
|
395
422
|
getEditorText: () => this.editor.getText(),
|
|
396
423
|
editor: (title, prefill) => this.showHookEditor(title, prefill),
|
|
397
424
|
get theme() {
|
|
398
425
|
return theme;
|
|
399
426
|
},
|
|
427
|
+
getAllThemes: () => getAvailableThemesWithPaths().map((t) => ({ name: t.name, path: t.path })),
|
|
428
|
+
getTheme: (name) => getThemeByName(name),
|
|
429
|
+
setTheme: (themeArg) => {
|
|
430
|
+
if (typeof themeArg === "string") {
|
|
431
|
+
return setTheme(themeArg, true);
|
|
432
|
+
}
|
|
433
|
+
// Theme object passed directly - not supported in current implementation
|
|
434
|
+
return { success: false, error: "Direct theme object not supported" };
|
|
435
|
+
},
|
|
436
|
+
setFooter: () => {},
|
|
437
|
+
setHeader: () => {},
|
|
438
|
+
setEditorComponent: () => {},
|
|
400
439
|
};
|
|
401
440
|
this.setToolUIContext(uiContext, true);
|
|
402
441
|
|
|
@@ -405,102 +444,130 @@ export class InteractiveMode {
|
|
|
405
444
|
return; // No hooks loaded
|
|
406
445
|
}
|
|
407
446
|
|
|
408
|
-
extensionRunner.initialize(
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
.
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
this.
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
447
|
+
extensionRunner.initialize(
|
|
448
|
+
// ExtensionActions - for pi.* API
|
|
449
|
+
{
|
|
450
|
+
sendMessage: (message, options) => {
|
|
451
|
+
const wasStreaming = this.session.isStreaming;
|
|
452
|
+
this.session
|
|
453
|
+
.sendCustomMessage(message, options)
|
|
454
|
+
.then(() => {
|
|
455
|
+
// For non-streaming cases with display=true, update UI
|
|
456
|
+
// (streaming cases update via message_end event)
|
|
457
|
+
if (!this.isBackgrounded && !wasStreaming && message.display) {
|
|
458
|
+
this.rebuildChatFromMessages();
|
|
459
|
+
}
|
|
460
|
+
})
|
|
461
|
+
.catch((err) => {
|
|
462
|
+
this.showError(
|
|
463
|
+
`Extension sendMessage failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
464
|
+
);
|
|
465
|
+
});
|
|
466
|
+
},
|
|
467
|
+
sendUserMessage: (content, options) => {
|
|
468
|
+
this.session.sendUserMessage(content, options).catch((err) => {
|
|
469
|
+
this.showError(
|
|
470
|
+
`Extension sendUserMessage failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
471
|
+
);
|
|
423
472
|
});
|
|
473
|
+
},
|
|
474
|
+
appendEntry: (customType, data) => {
|
|
475
|
+
this.sessionManager.appendCustomEntry(customType, data);
|
|
476
|
+
},
|
|
477
|
+
getActiveTools: () => this.session.getActiveToolNames(),
|
|
478
|
+
getAllTools: () => this.session.getAllToolNames(),
|
|
479
|
+
setActiveTools: (toolNames) => this.session.setActiveToolsByName(toolNames),
|
|
480
|
+
setModel: async (model) => {
|
|
481
|
+
const key = await this.session.modelRegistry.getApiKey(model);
|
|
482
|
+
if (!key) return false;
|
|
483
|
+
await this.session.setModel(model);
|
|
484
|
+
return true;
|
|
485
|
+
},
|
|
486
|
+
getThinkingLevel: () => this.session.thinkingLevel,
|
|
487
|
+
setThinkingLevel: (level) => this.session.setThinkingLevel(level),
|
|
424
488
|
},
|
|
425
|
-
|
|
426
|
-
|
|
489
|
+
// ExtensionContextActions - for ctx.* in event handlers
|
|
490
|
+
{
|
|
491
|
+
getModel: () => this.session.model,
|
|
492
|
+
isIdle: () => !this.session.isStreaming,
|
|
493
|
+
abort: () => this.session.abort(),
|
|
494
|
+
hasPendingMessages: () => this.session.queuedMessageCount > 0,
|
|
495
|
+
shutdown: () => {
|
|
496
|
+
// Signal shutdown request (will be handled by main loop)
|
|
497
|
+
},
|
|
427
498
|
},
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
499
|
+
// ExtensionCommandContextActions - for ctx.* in command handlers
|
|
500
|
+
{
|
|
501
|
+
waitForIdle: () => this.session.agent.waitForIdle(),
|
|
502
|
+
newSession: async (options) => {
|
|
503
|
+
// Stop any loading animation
|
|
504
|
+
if (this.loadingAnimation) {
|
|
505
|
+
this.loadingAnimation.stop();
|
|
506
|
+
this.loadingAnimation = undefined;
|
|
507
|
+
}
|
|
508
|
+
this.statusContainer.clear();
|
|
438
509
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
510
|
+
// Create new session
|
|
511
|
+
const success = await this.session.newSession({ parentSession: options?.parentSession });
|
|
512
|
+
if (!success) {
|
|
513
|
+
return { cancelled: true };
|
|
514
|
+
}
|
|
444
515
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
516
|
+
// Call setup callback if provided
|
|
517
|
+
if (options?.setup) {
|
|
518
|
+
await options.setup(this.sessionManager);
|
|
519
|
+
}
|
|
449
520
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
521
|
+
// Clear UI state
|
|
522
|
+
this.chatContainer.clear();
|
|
523
|
+
this.pendingMessagesContainer.clear();
|
|
524
|
+
this.compactionQueuedMessages = [];
|
|
525
|
+
this.streamingComponent = undefined;
|
|
526
|
+
this.streamingMessage = undefined;
|
|
527
|
+
this.pendingTools.clear();
|
|
456
528
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
529
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
530
|
+
this.chatContainer.addChild(
|
|
531
|
+
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
|
|
532
|
+
);
|
|
533
|
+
this.ui.requestRender();
|
|
462
534
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
535
|
+
return { cancelled: false };
|
|
536
|
+
},
|
|
537
|
+
branch: async (entryId) => {
|
|
538
|
+
const result = await this.session.branch(entryId);
|
|
539
|
+
if (result.cancelled) {
|
|
540
|
+
return { cancelled: true };
|
|
541
|
+
}
|
|
470
542
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
543
|
+
// Update UI
|
|
544
|
+
this.chatContainer.clear();
|
|
545
|
+
this.renderInitialMessages();
|
|
546
|
+
this.editor.setText(result.selectedText);
|
|
547
|
+
this.showStatus("Branched to new session");
|
|
476
548
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
549
|
+
return { cancelled: false };
|
|
550
|
+
},
|
|
551
|
+
navigateTree: async (targetId, options) => {
|
|
552
|
+
const result = await this.session.navigateTree(targetId, { summarize: options?.summarize });
|
|
553
|
+
if (result.cancelled) {
|
|
554
|
+
return { cancelled: true };
|
|
555
|
+
}
|
|
484
556
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
557
|
+
// Update UI
|
|
558
|
+
this.chatContainer.clear();
|
|
559
|
+
this.renderInitialMessages();
|
|
560
|
+
if (result.editorText) {
|
|
561
|
+
this.editor.setText(result.editorText);
|
|
562
|
+
}
|
|
563
|
+
this.showStatus("Navigated to selected point");
|
|
492
564
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
isIdle: () => !this.session.isStreaming,
|
|
496
|
-
waitForIdle: () => this.session.agent.waitForIdle(),
|
|
497
|
-
abort: () => {
|
|
498
|
-
this.session.abort();
|
|
565
|
+
return { cancelled: false };
|
|
566
|
+
},
|
|
499
567
|
},
|
|
500
|
-
|
|
568
|
+
// ExtensionUIContext
|
|
501
569
|
uiContext,
|
|
502
|
-
|
|
503
|
-
});
|
|
570
|
+
);
|
|
504
571
|
|
|
505
572
|
// Subscribe to extension errors
|
|
506
573
|
extensionRunner.onError((error) => {
|
|
@@ -521,146 +588,171 @@ export class InteractiveMode {
|
|
|
521
588
|
this.ui.requestRender();
|
|
522
589
|
}
|
|
523
590
|
|
|
524
|
-
private initializeHookRunner(uiContext: ExtensionUIContext,
|
|
591
|
+
private initializeHookRunner(uiContext: ExtensionUIContext, _hasUI: boolean): void {
|
|
525
592
|
const extensionRunner = this.session.extensionRunner;
|
|
526
593
|
if (!extensionRunner) {
|
|
527
594
|
return;
|
|
528
595
|
}
|
|
529
596
|
|
|
530
|
-
extensionRunner.initialize(
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
.
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
this.
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
597
|
+
extensionRunner.initialize(
|
|
598
|
+
// ExtensionActions - for pi.* API
|
|
599
|
+
{
|
|
600
|
+
sendMessage: (message, options) => {
|
|
601
|
+
const wasStreaming = this.session.isStreaming;
|
|
602
|
+
this.session
|
|
603
|
+
.sendCustomMessage(message, options)
|
|
604
|
+
.then(() => {
|
|
605
|
+
// For non-streaming cases with display=true, update UI
|
|
606
|
+
// (streaming cases update via message_end event)
|
|
607
|
+
if (!this.isBackgrounded && !wasStreaming && message.display) {
|
|
608
|
+
this.rebuildChatFromMessages();
|
|
609
|
+
}
|
|
610
|
+
})
|
|
611
|
+
.catch((err: Error) => {
|
|
612
|
+
const errorText = `Extension sendMessage failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
613
|
+
if (this.isBackgrounded) {
|
|
614
|
+
console.error(errorText);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
this.showError(errorText);
|
|
618
|
+
});
|
|
619
|
+
},
|
|
620
|
+
sendUserMessage: (content, options) => {
|
|
621
|
+
this.session.sendUserMessage(content, options).catch((err) => {
|
|
622
|
+
this.showError(
|
|
623
|
+
`Extension sendUserMessage failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
624
|
+
);
|
|
550
625
|
});
|
|
626
|
+
},
|
|
627
|
+
appendEntry: (customType, data) => {
|
|
628
|
+
this.sessionManager.appendCustomEntry(customType, data);
|
|
629
|
+
},
|
|
630
|
+
getActiveTools: () => this.session.getActiveToolNames(),
|
|
631
|
+
getAllTools: () => this.session.getAllToolNames(),
|
|
632
|
+
setActiveTools: (toolNames: string[]) => this.session.setActiveToolsByName(toolNames),
|
|
633
|
+
setModel: async (model) => {
|
|
634
|
+
const key = await this.session.modelRegistry.getApiKey(model);
|
|
635
|
+
if (!key) return false;
|
|
636
|
+
await this.session.setModel(model);
|
|
637
|
+
return true;
|
|
638
|
+
},
|
|
639
|
+
getThinkingLevel: () => this.session.thinkingLevel,
|
|
640
|
+
setThinkingLevel: (level) => this.session.setThinkingLevel(level),
|
|
551
641
|
},
|
|
552
|
-
|
|
553
|
-
|
|
642
|
+
// ExtensionContextActions - for ctx.* in event handlers
|
|
643
|
+
{
|
|
644
|
+
getModel: () => this.session.model,
|
|
645
|
+
isIdle: () => !this.session.isStreaming,
|
|
646
|
+
abort: () => this.session.abort(),
|
|
647
|
+
hasPendingMessages: () => this.session.queuedMessageCount > 0,
|
|
648
|
+
shutdown: () => {
|
|
649
|
+
// Signal shutdown request (will be handled by main loop)
|
|
650
|
+
},
|
|
554
651
|
},
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
652
|
+
// ExtensionCommandContextActions - for ctx.* in command handlers
|
|
653
|
+
{
|
|
654
|
+
waitForIdle: () => this.session.agent.waitForIdle(),
|
|
655
|
+
newSession: async (options) => {
|
|
656
|
+
if (this.isBackgrounded) {
|
|
657
|
+
return { cancelled: true };
|
|
658
|
+
}
|
|
659
|
+
// Stop any loading animation
|
|
660
|
+
if (this.loadingAnimation) {
|
|
661
|
+
this.loadingAnimation.stop();
|
|
662
|
+
this.loadingAnimation = undefined;
|
|
663
|
+
}
|
|
664
|
+
this.statusContainer.clear();
|
|
568
665
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
666
|
+
// Create new session
|
|
667
|
+
const success = await this.session.newSession({ parentSession: options?.parentSession });
|
|
668
|
+
if (!success) {
|
|
669
|
+
return { cancelled: true };
|
|
670
|
+
}
|
|
574
671
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
672
|
+
// Call setup callback if provided
|
|
673
|
+
if (options?.setup) {
|
|
674
|
+
await options.setup(this.sessionManager);
|
|
675
|
+
}
|
|
579
676
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
677
|
+
// Clear UI state
|
|
678
|
+
this.chatContainer.clear();
|
|
679
|
+
this.pendingMessagesContainer.clear();
|
|
680
|
+
this.compactionQueuedMessages = [];
|
|
681
|
+
this.streamingComponent = undefined;
|
|
682
|
+
this.streamingMessage = undefined;
|
|
683
|
+
this.pendingTools.clear();
|
|
586
684
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
685
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
686
|
+
this.chatContainer.addChild(
|
|
687
|
+
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
|
|
688
|
+
);
|
|
689
|
+
this.ui.requestRender();
|
|
592
690
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
691
|
+
return { cancelled: false };
|
|
692
|
+
},
|
|
693
|
+
branch: async (entryId) => {
|
|
694
|
+
if (this.isBackgrounded) {
|
|
695
|
+
return { cancelled: true };
|
|
696
|
+
}
|
|
697
|
+
const result = await this.session.branch(entryId);
|
|
698
|
+
if (result.cancelled) {
|
|
699
|
+
return { cancelled: true };
|
|
700
|
+
}
|
|
603
701
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
702
|
+
// Update UI
|
|
703
|
+
this.chatContainer.clear();
|
|
704
|
+
this.renderInitialMessages();
|
|
705
|
+
this.editor.setText(result.selectedText);
|
|
706
|
+
this.showStatus("Branched to new session");
|
|
609
707
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
708
|
+
return { cancelled: false };
|
|
709
|
+
},
|
|
710
|
+
navigateTree: async (targetId, options) => {
|
|
711
|
+
if (this.isBackgrounded) {
|
|
712
|
+
return { cancelled: true };
|
|
713
|
+
}
|
|
714
|
+
const result = await this.session.navigateTree(targetId, { summarize: options?.summarize });
|
|
715
|
+
if (result.cancelled) {
|
|
716
|
+
return { cancelled: true };
|
|
717
|
+
}
|
|
620
718
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
719
|
+
// Update UI
|
|
720
|
+
this.chatContainer.clear();
|
|
721
|
+
this.renderInitialMessages();
|
|
722
|
+
if (result.editorText) {
|
|
723
|
+
this.editor.setText(result.editorText);
|
|
724
|
+
}
|
|
725
|
+
this.showStatus("Navigated to selected point");
|
|
628
726
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
isIdle: () => !this.session.isStreaming,
|
|
632
|
-
waitForIdle: () => this.session.agent.waitForIdle(),
|
|
633
|
-
abort: () => {
|
|
634
|
-
this.session.abort();
|
|
727
|
+
return { cancelled: false };
|
|
728
|
+
},
|
|
635
729
|
},
|
|
636
|
-
hasPendingMessages: () => this.session.queuedMessageCount > 0,
|
|
637
730
|
uiContext,
|
|
638
|
-
|
|
639
|
-
});
|
|
731
|
+
);
|
|
640
732
|
}
|
|
641
733
|
|
|
642
734
|
private createBackgroundUiContext(): ExtensionUIContext {
|
|
643
735
|
return {
|
|
644
|
-
select: async (_title: string, _options: string[]) => undefined,
|
|
645
|
-
confirm: async (_title: string, _message: string) => false,
|
|
646
|
-
input: async (_title: string, _placeholder?: string) => undefined,
|
|
736
|
+
select: async (_title: string, _options: string[], _dialogOptions) => undefined,
|
|
737
|
+
confirm: async (_title: string, _message: string, _dialogOptions) => false,
|
|
738
|
+
input: async (_title: string, _placeholder?: string, _dialogOptions?: unknown) => undefined,
|
|
647
739
|
notify: () => {},
|
|
648
740
|
setStatus: () => {},
|
|
649
741
|
setWidget: () => {},
|
|
650
742
|
setTitle: () => {},
|
|
651
|
-
custom: async
|
|
652
|
-
_factory: (
|
|
653
|
-
tui: TUI,
|
|
654
|
-
theme: Theme,
|
|
655
|
-
done: (result: T) => void,
|
|
656
|
-
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
|
657
|
-
) => undefined as T,
|
|
743
|
+
custom: async () => undefined as never,
|
|
658
744
|
setEditorText: () => {},
|
|
659
745
|
getEditorText: () => "",
|
|
660
746
|
editor: async () => undefined,
|
|
661
747
|
get theme() {
|
|
662
748
|
return theme;
|
|
663
749
|
},
|
|
750
|
+
getAllThemes: () => [],
|
|
751
|
+
getTheme: () => undefined,
|
|
752
|
+
setTheme: () => ({ success: false, error: "Background mode" }),
|
|
753
|
+
setFooter: () => {},
|
|
754
|
+
setHeader: () => {},
|
|
755
|
+
setEditorComponent: () => {},
|
|
664
756
|
};
|
|
665
757
|
}
|
|
666
758
|
|
|
@@ -692,6 +784,9 @@ export class InteractiveMode {
|
|
|
692
784
|
abort: () => {
|
|
693
785
|
this.session.abort();
|
|
694
786
|
},
|
|
787
|
+
shutdown: () => {
|
|
788
|
+
// Signal shutdown request
|
|
789
|
+
},
|
|
695
790
|
});
|
|
696
791
|
} catch (err) {
|
|
697
792
|
this.showToolError(registeredTool.definition.name, err instanceof Error ? err.message : String(err));
|
|
@@ -861,10 +956,12 @@ export class InteractiveMode {
|
|
|
861
956
|
factory: (
|
|
862
957
|
tui: TUI,
|
|
863
958
|
theme: Theme,
|
|
959
|
+
keybindings: KeybindingsManager,
|
|
864
960
|
done: (result: T) => void,
|
|
865
961
|
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
|
866
962
|
): Promise<T> {
|
|
867
963
|
const savedText = this.editor.getText();
|
|
964
|
+
const keybindings = KeybindingsManager.inMemory();
|
|
868
965
|
|
|
869
966
|
return new Promise((resolve) => {
|
|
870
967
|
let component: Component & { dispose?(): void };
|
|
@@ -879,7 +976,7 @@ export class InteractiveMode {
|
|
|
879
976
|
resolve(result);
|
|
880
977
|
};
|
|
881
978
|
|
|
882
|
-
Promise.resolve(factory(this.ui, theme, close)).then((c) => {
|
|
979
|
+
Promise.resolve(factory(this.ui, theme, keybindings, close)).then((c) => {
|
|
883
980
|
component = c;
|
|
884
981
|
this.editorContainer.clear();
|
|
885
982
|
this.editorContainer.addChild(component);
|
|
@@ -955,6 +1052,7 @@ export class InteractiveMode {
|
|
|
955
1052
|
this.editor.onCtrlG = () => this.openExternalEditor();
|
|
956
1053
|
this.editor.onQuestionMark = () => this.handleHotkeysCommand();
|
|
957
1054
|
this.editor.onCtrlV = () => this.handleImagePaste();
|
|
1055
|
+
this.editor.onAltUp = () => this.handleDequeue();
|
|
958
1056
|
|
|
959
1057
|
// Wire up extension shortcuts
|
|
960
1058
|
this.registerExtensionShortcuts();
|
|
@@ -971,6 +1069,12 @@ export class InteractiveMode {
|
|
|
971
1069
|
text = text.trim();
|
|
972
1070
|
if (!text) return;
|
|
973
1071
|
|
|
1072
|
+
// Queue follow-up messages while compaction is running
|
|
1073
|
+
if (this.session.isCompacting) {
|
|
1074
|
+
this.queueCompactionMessage(text, "followUp");
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
974
1078
|
// Alt+Enter queues a follow-up message (waits until agent finishes)
|
|
975
1079
|
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
976
1080
|
if (this.session.isStreaming) {
|
|
@@ -1078,12 +1182,7 @@ export class InteractiveMode {
|
|
|
1078
1182
|
if (text === "/compact" || text.startsWith("/compact ")) {
|
|
1079
1183
|
const customInstructions = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
|
|
1080
1184
|
this.editor.setText("");
|
|
1081
|
-
this.
|
|
1082
|
-
try {
|
|
1083
|
-
await this.handleCompactCommand(customInstructions);
|
|
1084
|
-
} finally {
|
|
1085
|
-
this.editor.disableSubmit = false;
|
|
1086
|
-
}
|
|
1185
|
+
await this.handleCompactCommand(customInstructions);
|
|
1087
1186
|
return;
|
|
1088
1187
|
}
|
|
1089
1188
|
if (text === "/background" || text === "/bg") {
|
|
@@ -1130,8 +1229,13 @@ export class InteractiveMode {
|
|
|
1130
1229
|
}
|
|
1131
1230
|
}
|
|
1132
1231
|
|
|
1133
|
-
//
|
|
1232
|
+
// Queue input during compaction
|
|
1134
1233
|
if (this.session.isCompacting) {
|
|
1234
|
+
if (this.pendingImages.length > 0) {
|
|
1235
|
+
this.showStatus("Compaction in progress. Retry after it completes to send images.");
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
this.queueCompactionMessage(text, "steer");
|
|
1135
1239
|
return;
|
|
1136
1240
|
}
|
|
1137
1241
|
|
|
@@ -1193,6 +1297,16 @@ export class InteractiveMode {
|
|
|
1193
1297
|
|
|
1194
1298
|
switch (event.type) {
|
|
1195
1299
|
case "agent_start":
|
|
1300
|
+
// Restore escape handler if retry UI is still active
|
|
1301
|
+
if (this.retryEscapeHandler) {
|
|
1302
|
+
this.editor.onEscape = this.retryEscapeHandler;
|
|
1303
|
+
this.retryEscapeHandler = undefined;
|
|
1304
|
+
}
|
|
1305
|
+
if (this.retryLoader) {
|
|
1306
|
+
this.retryLoader.stop();
|
|
1307
|
+
this.retryLoader = undefined;
|
|
1308
|
+
this.statusContainer.clear();
|
|
1309
|
+
}
|
|
1196
1310
|
if (this.loadingAnimation) {
|
|
1197
1311
|
this.loadingAnimation.stop();
|
|
1198
1312
|
}
|
|
@@ -1281,10 +1395,16 @@ export class InteractiveMode {
|
|
|
1281
1395
|
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
1282
1396
|
// Skip error handling for TTSR aborts
|
|
1283
1397
|
if (!this.session.isTtsrAbortPending) {
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1398
|
+
let errorMessage: string;
|
|
1399
|
+
if (this.streamingMessage.stopReason === "aborted") {
|
|
1400
|
+
const retryAttempt = this.session.retryAttempt;
|
|
1401
|
+
errorMessage =
|
|
1402
|
+
retryAttempt > 0
|
|
1403
|
+
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
1404
|
+
: "Operation aborted";
|
|
1405
|
+
} else {
|
|
1406
|
+
errorMessage = this.streamingMessage.errorMessage || "Error";
|
|
1407
|
+
}
|
|
1288
1408
|
for (const [, component] of this.pendingTools.entries()) {
|
|
1289
1409
|
component.updateResult({
|
|
1290
1410
|
content: [{ type: "text", text: errorMessage }],
|
|
@@ -1374,8 +1494,7 @@ export class InteractiveMode {
|
|
|
1374
1494
|
break;
|
|
1375
1495
|
|
|
1376
1496
|
case "auto_compaction_start": {
|
|
1377
|
-
//
|
|
1378
|
-
this.editor.disableSubmit = true;
|
|
1497
|
+
// Allow input during compaction; submissions are queued
|
|
1379
1498
|
// Set up escape to abort auto-compaction
|
|
1380
1499
|
this.autoCompactionEscapeHandler = this.editor.onEscape;
|
|
1381
1500
|
this.editor.onEscape = () => {
|
|
@@ -1397,8 +1516,6 @@ export class InteractiveMode {
|
|
|
1397
1516
|
}
|
|
1398
1517
|
|
|
1399
1518
|
case "auto_compaction_end": {
|
|
1400
|
-
// Re-enable submit
|
|
1401
|
-
this.editor.disableSubmit = false;
|
|
1402
1519
|
// Restore escape handler
|
|
1403
1520
|
if (this.autoCompactionEscapeHandler) {
|
|
1404
1521
|
this.editor.onEscape = this.autoCompactionEscapeHandler;
|
|
@@ -1427,6 +1544,7 @@ export class InteractiveMode {
|
|
|
1427
1544
|
this.statusLine.invalidate();
|
|
1428
1545
|
this.updateEditorTopBorder();
|
|
1429
1546
|
}
|
|
1547
|
+
await this.flushCompactionQueue({ willRetry: event.willRetry });
|
|
1430
1548
|
this.ui.requestRender();
|
|
1431
1549
|
break;
|
|
1432
1550
|
}
|
|
@@ -1648,8 +1766,16 @@ export class InteractiveMode {
|
|
|
1648
1766
|
this.chatContainer.addChild(component);
|
|
1649
1767
|
|
|
1650
1768
|
if (message.stopReason === "aborted" || message.stopReason === "error") {
|
|
1651
|
-
|
|
1652
|
-
|
|
1769
|
+
let errorMessage: string;
|
|
1770
|
+
if (message.stopReason === "aborted") {
|
|
1771
|
+
const retryAttempt = this.session.retryAttempt;
|
|
1772
|
+
errorMessage =
|
|
1773
|
+
retryAttempt > 0
|
|
1774
|
+
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
1775
|
+
: "Operation aborted";
|
|
1776
|
+
} else {
|
|
1777
|
+
errorMessage = message.errorMessage || "Error";
|
|
1778
|
+
}
|
|
1653
1779
|
component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
|
|
1654
1780
|
} else {
|
|
1655
1781
|
this.pendingTools.set(content.id, component);
|
|
@@ -1756,6 +1882,21 @@ export class InteractiveMode {
|
|
|
1756
1882
|
process.kill(0, "SIGTSTP");
|
|
1757
1883
|
}
|
|
1758
1884
|
|
|
1885
|
+
/**
|
|
1886
|
+
* Handle Alt+Up: pop the last queued message and restore it to the editor.
|
|
1887
|
+
*/
|
|
1888
|
+
private handleDequeue(): void {
|
|
1889
|
+
const message = this.session.popLastQueuedMessage();
|
|
1890
|
+
if (!message) return;
|
|
1891
|
+
|
|
1892
|
+
// Prepend to existing editor text (if any)
|
|
1893
|
+
const currentText = this.editor.getText();
|
|
1894
|
+
const newText = currentText ? `${message}\n\n${currentText}` : message;
|
|
1895
|
+
this.editor.setText(newText);
|
|
1896
|
+
this.updatePendingMessagesDisplay();
|
|
1897
|
+
this.ui.requestRender();
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1759
1900
|
private handleBackgroundCommand(): void {
|
|
1760
1901
|
if (this.isBackgrounded) {
|
|
1761
1902
|
this.showStatus("Background mode already enabled");
|
|
@@ -2164,8 +2305,18 @@ export class InteractiveMode {
|
|
|
2164
2305
|
private updatePendingMessagesDisplay(): void {
|
|
2165
2306
|
this.pendingMessagesContainer.clear();
|
|
2166
2307
|
const queuedMessages = this.session.getQueuedMessages();
|
|
2167
|
-
const steeringMessages =
|
|
2168
|
-
|
|
2308
|
+
const steeringMessages = [
|
|
2309
|
+
...queuedMessages.steering.map((message) => ({ message, label: "Steer" })),
|
|
2310
|
+
...this.compactionQueuedMessages
|
|
2311
|
+
.filter((entry) => entry.mode === "steer")
|
|
2312
|
+
.map((entry) => ({ message: entry.text, label: "Steer" })),
|
|
2313
|
+
];
|
|
2314
|
+
const followUpMessages = [
|
|
2315
|
+
...queuedMessages.followUp.map((message) => ({ message, label: "Follow-up" })),
|
|
2316
|
+
...this.compactionQueuedMessages
|
|
2317
|
+
.filter((entry) => entry.mode === "followUp")
|
|
2318
|
+
.map((entry) => ({ message: entry.text, label: "Follow-up" })),
|
|
2319
|
+
];
|
|
2169
2320
|
const allMessages = [...steeringMessages, ...followUpMessages];
|
|
2170
2321
|
if (allMessages.length > 0) {
|
|
2171
2322
|
this.pendingMessagesContainer.addChild(new Spacer(1));
|
|
@@ -2176,6 +2327,102 @@ export class InteractiveMode {
|
|
|
2176
2327
|
}
|
|
2177
2328
|
}
|
|
2178
2329
|
|
|
2330
|
+
private queueCompactionMessage(text: string, mode: "steer" | "followUp"): void {
|
|
2331
|
+
this.compactionQueuedMessages.push({ text, mode });
|
|
2332
|
+
this.editor.addToHistory(text);
|
|
2333
|
+
this.editor.setText("");
|
|
2334
|
+
this.updatePendingMessagesDisplay();
|
|
2335
|
+
this.showStatus("Queued message for after compaction");
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
private isKnownSlashCommand(text: string): boolean {
|
|
2339
|
+
if (!text.startsWith("/")) return false;
|
|
2340
|
+
const spaceIndex = text.indexOf(" ");
|
|
2341
|
+
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
2342
|
+
if (!commandName) return false;
|
|
2343
|
+
|
|
2344
|
+
if (this.session.extensionRunner?.getCommand(commandName)) {
|
|
2345
|
+
return true;
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
if (this.session.customCommands.some((cmd) => cmd.command.name === commandName)) {
|
|
2349
|
+
return true;
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
return this.fileSlashCommands.has(commandName);
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
private async flushCompactionQueue(options?: { willRetry?: boolean }): Promise<void> {
|
|
2356
|
+
if (this.compactionQueuedMessages.length === 0) {
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
const queuedMessages = [...this.compactionQueuedMessages];
|
|
2361
|
+
this.compactionQueuedMessages = [];
|
|
2362
|
+
this.updatePendingMessagesDisplay();
|
|
2363
|
+
|
|
2364
|
+
const restoreQueue = (error: unknown) => {
|
|
2365
|
+
this.session.clearQueue();
|
|
2366
|
+
this.compactionQueuedMessages = queuedMessages;
|
|
2367
|
+
this.updatePendingMessagesDisplay();
|
|
2368
|
+
this.showError(
|
|
2369
|
+
`Failed to send queued message${queuedMessages.length > 1 ? "s" : ""}: ${
|
|
2370
|
+
error instanceof Error ? error.message : String(error)
|
|
2371
|
+
}`,
|
|
2372
|
+
);
|
|
2373
|
+
};
|
|
2374
|
+
|
|
2375
|
+
try {
|
|
2376
|
+
if (options?.willRetry) {
|
|
2377
|
+
for (const message of queuedMessages) {
|
|
2378
|
+
if (this.isKnownSlashCommand(message.text)) {
|
|
2379
|
+
await this.session.prompt(message.text);
|
|
2380
|
+
} else if (message.mode === "followUp") {
|
|
2381
|
+
await this.session.followUp(message.text);
|
|
2382
|
+
} else {
|
|
2383
|
+
await this.session.steer(message.text);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
this.updatePendingMessagesDisplay();
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
const firstPromptIndex = queuedMessages.findIndex((message) => !this.isKnownSlashCommand(message.text));
|
|
2391
|
+
if (firstPromptIndex === -1) {
|
|
2392
|
+
for (const message of queuedMessages) {
|
|
2393
|
+
await this.session.prompt(message.text);
|
|
2394
|
+
}
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
const preCommands = queuedMessages.slice(0, firstPromptIndex);
|
|
2399
|
+
const firstPrompt = queuedMessages[firstPromptIndex];
|
|
2400
|
+
const rest = queuedMessages.slice(firstPromptIndex + 1);
|
|
2401
|
+
|
|
2402
|
+
for (const message of preCommands) {
|
|
2403
|
+
await this.session.prompt(message.text);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
const promptPromise = this.session.prompt(firstPrompt.text).catch((error) => {
|
|
2407
|
+
restoreQueue(error);
|
|
2408
|
+
});
|
|
2409
|
+
|
|
2410
|
+
for (const message of rest) {
|
|
2411
|
+
if (this.isKnownSlashCommand(message.text)) {
|
|
2412
|
+
await this.session.prompt(message.text);
|
|
2413
|
+
} else if (message.mode === "followUp") {
|
|
2414
|
+
await this.session.followUp(message.text);
|
|
2415
|
+
} else {
|
|
2416
|
+
await this.session.steer(message.text);
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
this.updatePendingMessagesDisplay();
|
|
2420
|
+
void promptPromise;
|
|
2421
|
+
} catch (error) {
|
|
2422
|
+
restoreQueue(error);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2179
2426
|
/** Move pending bash components from pending area to chat */
|
|
2180
2427
|
private flushPendingBashComponents(): void {
|
|
2181
2428
|
for (const component of this.pendingBashComponents) {
|
|
@@ -2597,6 +2844,7 @@ export class InteractiveMode {
|
|
|
2597
2844
|
|
|
2598
2845
|
// Clear UI state
|
|
2599
2846
|
this.pendingMessagesContainer.clear();
|
|
2847
|
+
this.compactionQueuedMessages = [];
|
|
2600
2848
|
this.streamingComponent = undefined;
|
|
2601
2849
|
this.streamingMessage = undefined;
|
|
2602
2850
|
this.pendingTools.clear();
|
|
@@ -3018,6 +3266,7 @@ export class InteractiveMode {
|
|
|
3018
3266
|
| \`Ctrl+G\` | Edit message in external editor |
|
|
3019
3267
|
| \`/\` | Slash commands |
|
|
3020
3268
|
| \`!\` | Run bash command |
|
|
3269
|
+
| \`!!\` | Run bash command (excluded from context) |
|
|
3021
3270
|
`;
|
|
3022
3271
|
this.chatContainer.addChild(new Spacer(1));
|
|
3023
3272
|
this.chatContainer.addChild(new DynamicBorder());
|
|
@@ -3046,6 +3295,7 @@ export class InteractiveMode {
|
|
|
3046
3295
|
// Clear UI state
|
|
3047
3296
|
this.chatContainer.clear();
|
|
3048
3297
|
this.pendingMessagesContainer.clear();
|
|
3298
|
+
this.compactionQueuedMessages = [];
|
|
3049
3299
|
this.streamingComponent = undefined;
|
|
3050
3300
|
this.streamingMessage = undefined;
|
|
3051
3301
|
this.pendingTools.clear();
|
|
@@ -3207,6 +3457,7 @@ export class InteractiveMode {
|
|
|
3207
3457
|
this.statusContainer.clear();
|
|
3208
3458
|
this.editor.onEscape = originalOnEscape;
|
|
3209
3459
|
}
|
|
3460
|
+
await this.flushCompactionQueue({ willRetry: false });
|
|
3210
3461
|
}
|
|
3211
3462
|
|
|
3212
3463
|
stop(): void {
|