@mariozechner/pi-coding-agent 0.37.8 → 0.38.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 +84 -4
- package/README.md +10 -0
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +13 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/index.d.ts +3 -3
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +8 -6
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +94 -211
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +24 -28
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +58 -38
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +116 -27
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/wrapper.d.ts +5 -3
- package/dist/core/extensions/wrapper.d.ts.map +1 -1
- package/dist/core/extensions/wrapper.js +6 -4
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/model-resolver.d.ts +4 -2
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +8 -9
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/sdk.d.ts +3 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +19 -75
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +8 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +9 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +47 -115
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +2 -2
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
- package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -0
- package/dist/modes/interactive/components/countdown-timer.js +33 -0
- package/dist/modes/interactive/components/countdown-timer.js.map +1 -0
- package/dist/modes/interactive/components/custom-editor.d.ts +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-input.d.ts +10 -2
- package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-input.js +18 -14
- package/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +10 -2
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +18 -22
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +44 -3
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +289 -95
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts +14 -7
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +45 -21
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +101 -101
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +3 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +1 -1
- package/dist/utils/clipboard-image.js.map +1 -1
- package/docs/extensions.md +110 -9
- package/docs/sdk.md +65 -6
- package/docs/tui.md +81 -4
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/handoff.ts +1 -1
- package/examples/extensions/modal-editor.ts +85 -0
- package/examples/extensions/preset.ts +1 -1
- package/examples/extensions/qna.ts +1 -1
- package/examples/extensions/rainbow-editor.ts +95 -0
- package/examples/extensions/shutdown-command.ts +63 -0
- package/examples/extensions/snake.ts +1 -1
- package/examples/extensions/timed-confirm.ts +32 -25
- package/examples/extensions/todo.ts +1 -1
- package/examples/extensions/tools.ts +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +5 -5
|
@@ -6,18 +6,20 @@ import * as crypto from "node:crypto";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import { getOAuthProviders } from "@mariozechner/pi-ai";
|
|
9
|
+
import { getOAuthProviders, } from "@mariozechner/pi-ai";
|
|
10
10
|
import { CombinedAutocompleteProvider, Container, getEditorKeybindings, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
|
|
11
11
|
import { spawn, spawnSync } from "child_process";
|
|
12
|
-
import { APP_NAME, getAuthPath, getDebugLogPath } from "../../config.js";
|
|
12
|
+
import { APP_NAME, getAuthPath, getDebugLogPath, VERSION } from "../../config.js";
|
|
13
13
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
14
14
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
15
15
|
import { SessionManager } from "../../core/session-manager.js";
|
|
16
16
|
import { loadSkills } from "../../core/skills.js";
|
|
17
17
|
import { loadProjectContextFiles } from "../../core/system-prompt.js";
|
|
18
|
-
import {
|
|
18
|
+
import { allTools } from "../../core/tools/index.js";
|
|
19
|
+
import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
|
|
19
20
|
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
20
21
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
22
|
+
import { ensureTool } from "../../utils/tools-manager.js";
|
|
21
23
|
import { ArminComponent } from "./components/armin.js";
|
|
22
24
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
23
25
|
import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
@@ -45,13 +47,15 @@ function isExpandable(obj) {
|
|
|
45
47
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
46
48
|
}
|
|
47
49
|
export class InteractiveMode {
|
|
48
|
-
|
|
50
|
+
options;
|
|
49
51
|
session;
|
|
50
52
|
ui;
|
|
51
53
|
chatContainer;
|
|
52
54
|
pendingMessagesContainer;
|
|
53
55
|
statusContainer;
|
|
56
|
+
defaultEditor;
|
|
54
57
|
editor;
|
|
58
|
+
autocompleteProvider;
|
|
55
59
|
editorContainer;
|
|
56
60
|
footer;
|
|
57
61
|
keybindings;
|
|
@@ -90,6 +94,8 @@ export class InteractiveMode {
|
|
|
90
94
|
retryEscapeHandler;
|
|
91
95
|
// Messages queued while compaction is running
|
|
92
96
|
compactionQueuedMessages = [];
|
|
97
|
+
// Shutdown state
|
|
98
|
+
shutdownRequested = false;
|
|
93
99
|
// Extension UI state
|
|
94
100
|
extensionSelector = undefined;
|
|
95
101
|
extensionInput = undefined;
|
|
@@ -113,22 +119,26 @@ export class InteractiveMode {
|
|
|
113
119
|
get settingsManager() {
|
|
114
120
|
return this.session.settingsManager;
|
|
115
121
|
}
|
|
116
|
-
constructor(session,
|
|
117
|
-
this.
|
|
122
|
+
constructor(session, options = {}) {
|
|
123
|
+
this.options = options;
|
|
118
124
|
this.session = session;
|
|
119
|
-
this.version =
|
|
120
|
-
this.changelogMarkdown = changelogMarkdown;
|
|
125
|
+
this.version = VERSION;
|
|
121
126
|
this.ui = new TUI(new ProcessTerminal());
|
|
122
127
|
this.chatContainer = new Container();
|
|
123
128
|
this.pendingMessagesContainer = new Container();
|
|
124
129
|
this.statusContainer = new Container();
|
|
125
130
|
this.widgetContainer = new Container();
|
|
126
131
|
this.keybindings = KeybindingsManager.create();
|
|
127
|
-
this.
|
|
132
|
+
this.defaultEditor = new CustomEditor(getEditorTheme(), this.keybindings);
|
|
133
|
+
this.editor = this.defaultEditor;
|
|
128
134
|
this.editorContainer = new Container();
|
|
129
135
|
this.editorContainer.addChild(this.editor);
|
|
130
136
|
this.footer = new FooterComponent(session);
|
|
131
137
|
this.footer.setAutoCompactEnabled(session.autoCompactionEnabled);
|
|
138
|
+
// Load hide thinking block setting
|
|
139
|
+
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
140
|
+
}
|
|
141
|
+
setupAutocomplete(fdPath) {
|
|
132
142
|
// Define commands for autocomplete
|
|
133
143
|
const slashCommands = [
|
|
134
144
|
{ name: "settings", description: "Open settings menu" },
|
|
@@ -147,8 +157,6 @@ export class InteractiveMode {
|
|
|
147
157
|
{ name: "compact", description: "Manually compact the session context" },
|
|
148
158
|
{ name: "resume", description: "Resume a different session" },
|
|
149
159
|
];
|
|
150
|
-
// Load hide thinking block setting
|
|
151
|
-
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
152
160
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
153
161
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
154
162
|
name: cmd.name,
|
|
@@ -160,12 +168,17 @@ export class InteractiveMode {
|
|
|
160
168
|
description: cmd.description ?? "(extension command)",
|
|
161
169
|
}));
|
|
162
170
|
// Setup autocomplete
|
|
163
|
-
|
|
164
|
-
this.
|
|
171
|
+
this.autocompleteProvider = new CombinedAutocompleteProvider([...slashCommands, ...templateCommands, ...extensionCommands], process.cwd(), fdPath);
|
|
172
|
+
this.defaultEditor.setAutocompleteProvider(this.autocompleteProvider);
|
|
165
173
|
}
|
|
166
174
|
async init() {
|
|
167
175
|
if (this.isInitialized)
|
|
168
176
|
return;
|
|
177
|
+
// Load changelog (only show new entries, skip for resumed sessions)
|
|
178
|
+
this.changelogMarkdown = this.getChangelogForDisplay();
|
|
179
|
+
// Setup autocomplete with fd tool for file path completion
|
|
180
|
+
const fdPath = await ensureTool("fd");
|
|
181
|
+
this.setupAutocomplete(fdPath);
|
|
169
182
|
// Add header with keybindings from config
|
|
170
183
|
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
|
|
171
184
|
// Format keybinding for startup display (lowercase, compact)
|
|
@@ -293,6 +306,112 @@ export class InteractiveMode {
|
|
|
293
306
|
this.ui.requestRender();
|
|
294
307
|
});
|
|
295
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Run the interactive mode. This is the main entry point.
|
|
311
|
+
* Initializes the UI, shows warnings, processes initial messages, and starts the interactive loop.
|
|
312
|
+
*/
|
|
313
|
+
async run() {
|
|
314
|
+
await this.init();
|
|
315
|
+
// Start version check asynchronously
|
|
316
|
+
this.checkForNewVersion().then((newVersion) => {
|
|
317
|
+
if (newVersion) {
|
|
318
|
+
this.showNewVersionNotification(newVersion);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
this.renderInitialMessages();
|
|
322
|
+
// Show startup warnings
|
|
323
|
+
const { migratedProviders, modelFallbackMessage, initialMessage, initialImages, initialMessages } = this.options;
|
|
324
|
+
if (migratedProviders && migratedProviders.length > 0) {
|
|
325
|
+
this.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(", ")}`);
|
|
326
|
+
}
|
|
327
|
+
const modelsJsonError = this.session.modelRegistry.getError();
|
|
328
|
+
if (modelsJsonError) {
|
|
329
|
+
this.showError(`models.json error: ${modelsJsonError}`);
|
|
330
|
+
}
|
|
331
|
+
if (modelFallbackMessage) {
|
|
332
|
+
this.showWarning(modelFallbackMessage);
|
|
333
|
+
}
|
|
334
|
+
// Process initial messages
|
|
335
|
+
if (initialMessage) {
|
|
336
|
+
try {
|
|
337
|
+
await this.session.prompt(initialMessage, { images: initialImages });
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
341
|
+
this.showError(errorMessage);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (initialMessages) {
|
|
345
|
+
for (const message of initialMessages) {
|
|
346
|
+
try {
|
|
347
|
+
await this.session.prompt(message);
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
351
|
+
this.showError(errorMessage);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// Main interactive loop
|
|
356
|
+
while (true) {
|
|
357
|
+
const userInput = await this.getUserInput();
|
|
358
|
+
try {
|
|
359
|
+
await this.session.prompt(userInput);
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
363
|
+
this.showError(errorMessage);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Check npm registry for a newer version.
|
|
369
|
+
*/
|
|
370
|
+
async checkForNewVersion() {
|
|
371
|
+
if (process.env.PI_SKIP_VERSION_CHECK)
|
|
372
|
+
return undefined;
|
|
373
|
+
try {
|
|
374
|
+
const response = await fetch("https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest");
|
|
375
|
+
if (!response.ok)
|
|
376
|
+
return undefined;
|
|
377
|
+
const data = (await response.json());
|
|
378
|
+
const latestVersion = data.version;
|
|
379
|
+
if (latestVersion && latestVersion !== this.version) {
|
|
380
|
+
return latestVersion;
|
|
381
|
+
}
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Get changelog entries to display on startup.
|
|
390
|
+
* Only shows new entries since last seen version, skips for resumed sessions.
|
|
391
|
+
*/
|
|
392
|
+
getChangelogForDisplay() {
|
|
393
|
+
// Skip changelog for resumed/continued sessions (already have messages)
|
|
394
|
+
if (this.session.state.messages.length > 0) {
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
397
|
+
const lastVersion = this.settingsManager.getLastChangelogVersion();
|
|
398
|
+
const changelogPath = getChangelogPath();
|
|
399
|
+
const entries = parseChangelog(changelogPath);
|
|
400
|
+
if (!lastVersion) {
|
|
401
|
+
if (entries.length > 0) {
|
|
402
|
+
this.settingsManager.setLastChangelogVersion(VERSION);
|
|
403
|
+
return entries.map((e) => e.content).join("\n\n");
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
const newEntries = getNewEntries(entries, lastVersion);
|
|
408
|
+
if (newEntries.length > 0) {
|
|
409
|
+
this.settingsManager.setLastChangelogVersion(VERSION);
|
|
410
|
+
return newEntries.map((e) => e.content).join("\n\n");
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
296
415
|
// =========================================================================
|
|
297
416
|
// Extension System
|
|
298
417
|
// =========================================================================
|
|
@@ -325,22 +444,20 @@ export class InteractiveMode {
|
|
|
325
444
|
this.chatContainer.addChild(new Spacer(1));
|
|
326
445
|
}
|
|
327
446
|
}
|
|
328
|
-
// Create and set extension UI context
|
|
329
|
-
const uiContext = this.createExtensionUIContext();
|
|
330
|
-
this.setExtensionUIContext(uiContext, true);
|
|
331
447
|
const extensionRunner = this.session.extensionRunner;
|
|
332
448
|
if (!extensionRunner) {
|
|
333
449
|
return; // No extensions loaded
|
|
334
450
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
451
|
+
// Create extension UI context
|
|
452
|
+
const uiContext = this.createExtensionUIContext();
|
|
453
|
+
extensionRunner.initialize(
|
|
454
|
+
// ExtensionActions - for pi.* API
|
|
455
|
+
{
|
|
456
|
+
sendMessage: (message, options) => {
|
|
338
457
|
const wasStreaming = this.session.isStreaming;
|
|
339
458
|
this.session
|
|
340
459
|
.sendCustomMessage(message, options)
|
|
341
460
|
.then(() => {
|
|
342
|
-
// For non-streaming cases with display=true, update UI
|
|
343
|
-
// (streaming cases update via message_end event)
|
|
344
461
|
if (!wasStreaming && message.display) {
|
|
345
462
|
this.rebuildChatFromMessages();
|
|
346
463
|
}
|
|
@@ -349,34 +466,53 @@ export class InteractiveMode {
|
|
|
349
466
|
this.showError(`Extension sendMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
350
467
|
});
|
|
351
468
|
},
|
|
352
|
-
|
|
469
|
+
sendUserMessage: (content, options) => {
|
|
353
470
|
this.session.sendUserMessage(content, options).catch((err) => {
|
|
354
471
|
this.showError(`Extension sendUserMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
355
472
|
});
|
|
356
473
|
},
|
|
357
|
-
|
|
474
|
+
appendEntry: (customType, data) => {
|
|
358
475
|
this.sessionManager.appendCustomEntry(customType, data);
|
|
359
476
|
},
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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)
|
|
483
|
+
return false;
|
|
484
|
+
await this.session.setModel(model);
|
|
485
|
+
return true;
|
|
486
|
+
},
|
|
487
|
+
getThinkingLevel: () => this.session.thinkingLevel,
|
|
488
|
+
setThinkingLevel: (level) => this.session.setThinkingLevel(level),
|
|
489
|
+
},
|
|
490
|
+
// ExtensionContextActions - for ctx.* in event handlers
|
|
491
|
+
{
|
|
492
|
+
getModel: () => this.session.model,
|
|
493
|
+
isIdle: () => !this.session.isStreaming,
|
|
494
|
+
abort: () => this.session.abort(),
|
|
495
|
+
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
|
496
|
+
shutdown: () => {
|
|
497
|
+
this.shutdownRequested = true;
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
// ExtensionCommandContextActions - for ctx.* in command handlers
|
|
501
|
+
{
|
|
502
|
+
waitForIdle: () => this.session.agent.waitForIdle(),
|
|
503
|
+
newSession: async (options) => {
|
|
365
504
|
if (this.loadingAnimation) {
|
|
366
505
|
this.loadingAnimation.stop();
|
|
367
506
|
this.loadingAnimation = undefined;
|
|
368
507
|
}
|
|
369
508
|
this.statusContainer.clear();
|
|
370
|
-
// Create new session
|
|
371
509
|
const success = await this.session.newSession({ parentSession: options?.parentSession });
|
|
372
510
|
if (!success) {
|
|
373
511
|
return { cancelled: true };
|
|
374
512
|
}
|
|
375
|
-
// Call setup callback if provided
|
|
376
513
|
if (options?.setup) {
|
|
377
514
|
await options.setup(this.sessionManager);
|
|
378
515
|
}
|
|
379
|
-
// Clear UI state
|
|
380
516
|
this.chatContainer.clear();
|
|
381
517
|
this.pendingMessagesContainer.clear();
|
|
382
518
|
this.compactionQueuedMessages = [];
|
|
@@ -388,24 +524,22 @@ export class InteractiveMode {
|
|
|
388
524
|
this.ui.requestRender();
|
|
389
525
|
return { cancelled: false };
|
|
390
526
|
},
|
|
391
|
-
|
|
527
|
+
branch: async (entryId) => {
|
|
392
528
|
const result = await this.session.branch(entryId);
|
|
393
529
|
if (result.cancelled) {
|
|
394
530
|
return { cancelled: true };
|
|
395
531
|
}
|
|
396
|
-
// Update UI
|
|
397
532
|
this.chatContainer.clear();
|
|
398
533
|
this.renderInitialMessages();
|
|
399
534
|
this.editor.setText(result.selectedText);
|
|
400
535
|
this.showStatus("Branched to new session");
|
|
401
536
|
return { cancelled: false };
|
|
402
537
|
},
|
|
403
|
-
|
|
538
|
+
navigateTree: async (targetId, options) => {
|
|
404
539
|
const result = await this.session.navigateTree(targetId, { summarize: options?.summarize });
|
|
405
540
|
if (result.cancelled) {
|
|
406
541
|
return { cancelled: true };
|
|
407
542
|
}
|
|
408
|
-
// Update UI
|
|
409
543
|
this.chatContainer.clear();
|
|
410
544
|
this.renderInitialMessages();
|
|
411
545
|
if (result.editorText) {
|
|
@@ -414,24 +548,7 @@ export class InteractiveMode {
|
|
|
414
548
|
this.showStatus("Navigated to selected point");
|
|
415
549
|
return { cancelled: false };
|
|
416
550
|
},
|
|
417
|
-
|
|
418
|
-
const key = await this.session.modelRegistry.getApiKey(model);
|
|
419
|
-
if (!key)
|
|
420
|
-
return false;
|
|
421
|
-
await this.session.setModel(model);
|
|
422
|
-
return true;
|
|
423
|
-
},
|
|
424
|
-
getThinkingLevelHandler: () => this.session.thinkingLevel,
|
|
425
|
-
setThinkingLevelHandler: (level) => this.session.setThinkingLevel(level),
|
|
426
|
-
isIdle: () => !this.session.isStreaming,
|
|
427
|
-
waitForIdle: () => this.session.agent.waitForIdle(),
|
|
428
|
-
abort: () => {
|
|
429
|
-
this.session.abort();
|
|
430
|
-
},
|
|
431
|
-
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
|
432
|
-
uiContext,
|
|
433
|
-
hasUI: true,
|
|
434
|
-
});
|
|
551
|
+
}, uiContext);
|
|
435
552
|
// Subscribe to extension errors
|
|
436
553
|
extensionRunner.onError((error) => {
|
|
437
554
|
this.showExtensionError(error.extensionPath, error.error, error.stack);
|
|
@@ -445,6 +562,14 @@ export class InteractiveMode {
|
|
|
445
562
|
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded extensions:\n") + extList, 0, 0));
|
|
446
563
|
this.chatContainer.addChild(new Spacer(1));
|
|
447
564
|
}
|
|
565
|
+
// Warn about built-in tool overrides
|
|
566
|
+
const builtInToolNames = new Set(Object.keys(allTools));
|
|
567
|
+
const registeredTools = extensionRunner.getAllRegisteredTools();
|
|
568
|
+
for (const tool of registeredTools) {
|
|
569
|
+
if (builtInToolNames.has(tool.definition.name)) {
|
|
570
|
+
this.chatContainer.addChild(new Text(theme.fg("warning", `Warning: Extension "${tool.extensionPath}" overrides built-in tool "${tool.definition.name}"`), 0, 0));
|
|
571
|
+
}
|
|
572
|
+
}
|
|
448
573
|
// Emit session_start event
|
|
449
574
|
await extensionRunner.emit({
|
|
450
575
|
type: "session_start",
|
|
@@ -476,9 +601,12 @@ export class InteractiveMode {
|
|
|
476
601
|
isIdle: () => !this.session.isStreaming,
|
|
477
602
|
abort: () => this.session.abort(),
|
|
478
603
|
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
|
604
|
+
shutdown: () => {
|
|
605
|
+
this.shutdownRequested = true;
|
|
606
|
+
},
|
|
479
607
|
});
|
|
480
|
-
// Set up the extension shortcut handler on the editor
|
|
481
|
-
this.
|
|
608
|
+
// Set up the extension shortcut handler on the default editor
|
|
609
|
+
this.defaultEditor.onExtensionShortcut = (data) => {
|
|
482
610
|
for (const [shortcutStr, shortcut] of shortcuts) {
|
|
483
611
|
// Cast to KeyId - extension shortcuts use the same format
|
|
484
612
|
if (matchesKey(data, shortcutStr)) {
|
|
@@ -622,6 +750,7 @@ export class InteractiveMode {
|
|
|
622
750
|
setEditorText: (text) => this.editor.setText(text),
|
|
623
751
|
getEditorText: () => this.editor.getText(),
|
|
624
752
|
editor: (title, prefill) => this.showExtensionEditor(title, prefill),
|
|
753
|
+
setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
|
|
625
754
|
get theme() {
|
|
626
755
|
return theme;
|
|
627
756
|
},
|
|
@@ -649,7 +778,7 @@ export class InteractiveMode {
|
|
|
649
778
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
650
779
|
this.hideExtensionSelector();
|
|
651
780
|
resolve(undefined);
|
|
652
|
-
});
|
|
781
|
+
}, { tui: this.ui, timeout: opts?.timeout });
|
|
653
782
|
this.editorContainer.clear();
|
|
654
783
|
this.editorContainer.addChild(this.extensionSelector);
|
|
655
784
|
this.ui.setFocus(this.extensionSelector);
|
|
@@ -660,6 +789,7 @@ export class InteractiveMode {
|
|
|
660
789
|
* Hide the extension selector.
|
|
661
790
|
*/
|
|
662
791
|
hideExtensionSelector() {
|
|
792
|
+
this.extensionSelector?.dispose();
|
|
663
793
|
this.editorContainer.clear();
|
|
664
794
|
this.editorContainer.addChild(this.editor);
|
|
665
795
|
this.extensionSelector = undefined;
|
|
@@ -695,7 +825,7 @@ export class InteractiveMode {
|
|
|
695
825
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
696
826
|
this.hideExtensionInput();
|
|
697
827
|
resolve(undefined);
|
|
698
|
-
});
|
|
828
|
+
}, { tui: this.ui, timeout: opts?.timeout });
|
|
699
829
|
this.editorContainer.clear();
|
|
700
830
|
this.editorContainer.addChild(this.extensionInput);
|
|
701
831
|
this.ui.setFocus(this.extensionInput);
|
|
@@ -706,6 +836,7 @@ export class InteractiveMode {
|
|
|
706
836
|
* Hide the extension input.
|
|
707
837
|
*/
|
|
708
838
|
hideExtensionInput() {
|
|
839
|
+
this.extensionInput?.dispose();
|
|
709
840
|
this.editorContainer.clear();
|
|
710
841
|
this.editorContainer.addChild(this.editor);
|
|
711
842
|
this.extensionInput = undefined;
|
|
@@ -740,6 +871,54 @@ export class InteractiveMode {
|
|
|
740
871
|
this.ui.setFocus(this.editor);
|
|
741
872
|
this.ui.requestRender();
|
|
742
873
|
}
|
|
874
|
+
/**
|
|
875
|
+
* Set a custom editor component from an extension.
|
|
876
|
+
* Pass undefined to restore the default editor.
|
|
877
|
+
*/
|
|
878
|
+
setCustomEditorComponent(factory) {
|
|
879
|
+
// Save text from current editor before switching
|
|
880
|
+
const currentText = this.editor.getText();
|
|
881
|
+
this.editorContainer.clear();
|
|
882
|
+
if (factory) {
|
|
883
|
+
// Create the custom editor with tui, theme, and keybindings
|
|
884
|
+
const newEditor = factory(this.ui, getEditorTheme(), this.keybindings);
|
|
885
|
+
// Wire up callbacks from the default editor
|
|
886
|
+
newEditor.onSubmit = this.defaultEditor.onSubmit;
|
|
887
|
+
newEditor.onChange = this.defaultEditor.onChange;
|
|
888
|
+
// Copy text from previous editor
|
|
889
|
+
newEditor.setText(currentText);
|
|
890
|
+
// Copy appearance settings if supported
|
|
891
|
+
if (newEditor.borderColor !== undefined) {
|
|
892
|
+
newEditor.borderColor = this.defaultEditor.borderColor;
|
|
893
|
+
}
|
|
894
|
+
// Set autocomplete if supported
|
|
895
|
+
if (newEditor.setAutocompleteProvider && this.autocompleteProvider) {
|
|
896
|
+
newEditor.setAutocompleteProvider(this.autocompleteProvider);
|
|
897
|
+
}
|
|
898
|
+
// If extending CustomEditor, copy app-level handlers
|
|
899
|
+
// Use duck typing since instanceof fails across jiti module boundaries
|
|
900
|
+
const customEditor = newEditor;
|
|
901
|
+
if ("actionHandlers" in customEditor && customEditor.actionHandlers instanceof Map) {
|
|
902
|
+
customEditor.onEscape = this.defaultEditor.onEscape;
|
|
903
|
+
customEditor.onCtrlD = this.defaultEditor.onCtrlD;
|
|
904
|
+
customEditor.onPasteImage = this.defaultEditor.onPasteImage;
|
|
905
|
+
customEditor.onExtensionShortcut = this.defaultEditor.onExtensionShortcut;
|
|
906
|
+
// Copy action handlers (clear, suspend, model switching, etc.)
|
|
907
|
+
for (const [action, handler] of this.defaultEditor.actionHandlers) {
|
|
908
|
+
customEditor.actionHandlers.set(action, handler);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
this.editor = newEditor;
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
// Restore default editor with text from custom editor
|
|
915
|
+
this.defaultEditor.setText(currentText);
|
|
916
|
+
this.editor = this.defaultEditor;
|
|
917
|
+
}
|
|
918
|
+
this.editorContainer.addChild(this.editor);
|
|
919
|
+
this.ui.setFocus(this.editor);
|
|
920
|
+
this.ui.requestRender();
|
|
921
|
+
}
|
|
743
922
|
/**
|
|
744
923
|
* Show a notification for extensions.
|
|
745
924
|
*/
|
|
@@ -770,7 +949,7 @@ export class InteractiveMode {
|
|
|
770
949
|
this.ui.requestRender();
|
|
771
950
|
resolve(result);
|
|
772
951
|
};
|
|
773
|
-
Promise.resolve(factory(this.ui, theme, close)).then((c) => {
|
|
952
|
+
Promise.resolve(factory(this.ui, theme, this.keybindings, close)).then((c) => {
|
|
774
953
|
component = c;
|
|
775
954
|
this.editorContainer.clear();
|
|
776
955
|
this.editorContainer.addChild(component);
|
|
@@ -803,7 +982,9 @@ export class InteractiveMode {
|
|
|
803
982
|
// Key Handlers
|
|
804
983
|
// =========================================================================
|
|
805
984
|
setupKeyHandlers() {
|
|
806
|
-
this.editor
|
|
985
|
+
// Set up handlers on defaultEditor - they use this.editor for text access
|
|
986
|
+
// so they work correctly regardless of which editor is active
|
|
987
|
+
this.defaultEditor.onEscape = () => {
|
|
807
988
|
if (this.loadingAnimation) {
|
|
808
989
|
// Abort and restore queued messages to editor
|
|
809
990
|
const { steering, followUp } = this.session.clearQueue();
|
|
@@ -841,20 +1022,20 @@ export class InteractiveMode {
|
|
|
841
1022
|
}
|
|
842
1023
|
};
|
|
843
1024
|
// Register app action handlers
|
|
844
|
-
this.
|
|
845
|
-
this.
|
|
846
|
-
this.
|
|
847
|
-
this.
|
|
848
|
-
this.
|
|
849
|
-
this.
|
|
1025
|
+
this.defaultEditor.onAction("clear", () => this.handleCtrlC());
|
|
1026
|
+
this.defaultEditor.onCtrlD = () => this.handleCtrlD();
|
|
1027
|
+
this.defaultEditor.onAction("suspend", () => this.handleCtrlZ());
|
|
1028
|
+
this.defaultEditor.onAction("cycleThinkingLevel", () => this.cycleThinkingLevel());
|
|
1029
|
+
this.defaultEditor.onAction("cycleModelForward", () => this.cycleModel("forward"));
|
|
1030
|
+
this.defaultEditor.onAction("cycleModelBackward", () => this.cycleModel("backward"));
|
|
850
1031
|
// Global debug handler on TUI (works regardless of focus)
|
|
851
1032
|
this.ui.onDebug = () => this.handleDebugCommand();
|
|
852
|
-
this.
|
|
853
|
-
this.
|
|
854
|
-
this.
|
|
855
|
-
this.
|
|
856
|
-
this.
|
|
857
|
-
this.
|
|
1033
|
+
this.defaultEditor.onAction("selectModel", () => this.showModelSelector());
|
|
1034
|
+
this.defaultEditor.onAction("expandTools", () => this.toggleToolOutputExpansion());
|
|
1035
|
+
this.defaultEditor.onAction("toggleThinking", () => this.toggleThinkingBlockVisibility());
|
|
1036
|
+
this.defaultEditor.onAction("externalEditor", () => this.openExternalEditor());
|
|
1037
|
+
this.defaultEditor.onAction("followUp", () => this.handleFollowUp());
|
|
1038
|
+
this.defaultEditor.onChange = (text) => {
|
|
858
1039
|
const wasBashMode = this.isBashMode;
|
|
859
1040
|
this.isBashMode = text.trimStart().startsWith("!");
|
|
860
1041
|
if (wasBashMode !== this.isBashMode) {
|
|
@@ -862,7 +1043,7 @@ export class InteractiveMode {
|
|
|
862
1043
|
}
|
|
863
1044
|
};
|
|
864
1045
|
// Handle clipboard image paste (triggered on Ctrl+V)
|
|
865
|
-
this.
|
|
1046
|
+
this.defaultEditor.onPasteImage = () => {
|
|
866
1047
|
this.handleClipboardImagePaste();
|
|
867
1048
|
};
|
|
868
1049
|
}
|
|
@@ -879,7 +1060,7 @@ export class InteractiveMode {
|
|
|
879
1060
|
const filePath = path.join(tmpDir, fileName);
|
|
880
1061
|
fs.writeFileSync(filePath, Buffer.from(image.bytes));
|
|
881
1062
|
// Insert file path directly
|
|
882
|
-
this.editor.insertTextAtCursor(filePath);
|
|
1063
|
+
this.editor.insertTextAtCursor?.(filePath);
|
|
883
1064
|
this.ui.requestRender();
|
|
884
1065
|
}
|
|
885
1066
|
catch {
|
|
@@ -887,7 +1068,7 @@ export class InteractiveMode {
|
|
|
887
1068
|
}
|
|
888
1069
|
}
|
|
889
1070
|
setupEditorSubmitHandler() {
|
|
890
|
-
this.
|
|
1071
|
+
this.defaultEditor.onSubmit = async (text) => {
|
|
891
1072
|
text = text.trim();
|
|
892
1073
|
if (!text)
|
|
893
1074
|
return;
|
|
@@ -993,7 +1174,7 @@ export class InteractiveMode {
|
|
|
993
1174
|
this.editor.setText(text);
|
|
994
1175
|
return;
|
|
995
1176
|
}
|
|
996
|
-
this.editor.addToHistory(text);
|
|
1177
|
+
this.editor.addToHistory?.(text);
|
|
997
1178
|
await this.handleBashCommand(command, isExcluded);
|
|
998
1179
|
this.isBashMode = false;
|
|
999
1180
|
this.updateEditorBorderColor();
|
|
@@ -1003,7 +1184,7 @@ export class InteractiveMode {
|
|
|
1003
1184
|
// Queue input during compaction (extension commands execute immediately)
|
|
1004
1185
|
if (this.session.isCompacting) {
|
|
1005
1186
|
if (this.isExtensionCommand(text)) {
|
|
1006
|
-
this.editor.addToHistory(text);
|
|
1187
|
+
this.editor.addToHistory?.(text);
|
|
1007
1188
|
this.editor.setText("");
|
|
1008
1189
|
await this.session.prompt(text);
|
|
1009
1190
|
}
|
|
@@ -1015,7 +1196,7 @@ export class InteractiveMode {
|
|
|
1015
1196
|
// If streaming, use prompt() with steer behavior
|
|
1016
1197
|
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
1017
1198
|
if (this.session.isStreaming) {
|
|
1018
|
-
this.editor.addToHistory(text);
|
|
1199
|
+
this.editor.addToHistory?.(text);
|
|
1019
1200
|
this.editor.setText("");
|
|
1020
1201
|
await this.session.prompt(text, { streamingBehavior: "steer" });
|
|
1021
1202
|
this.updatePendingMessagesDisplay();
|
|
@@ -1028,7 +1209,7 @@ export class InteractiveMode {
|
|
|
1028
1209
|
if (this.onInputCallback) {
|
|
1029
1210
|
this.onInputCallback(text);
|
|
1030
1211
|
}
|
|
1031
|
-
this.editor.addToHistory(text);
|
|
1212
|
+
this.editor.addToHistory?.(text);
|
|
1032
1213
|
};
|
|
1033
1214
|
}
|
|
1034
1215
|
subscribeToAgent() {
|
|
@@ -1166,13 +1347,14 @@ export class InteractiveMode {
|
|
|
1166
1347
|
this.streamingMessage = undefined;
|
|
1167
1348
|
}
|
|
1168
1349
|
this.pendingTools.clear();
|
|
1350
|
+
await this.checkShutdownRequested();
|
|
1169
1351
|
this.ui.requestRender();
|
|
1170
1352
|
break;
|
|
1171
1353
|
case "auto_compaction_start": {
|
|
1172
1354
|
// Keep editor active; submissions are queued during compaction.
|
|
1173
1355
|
// Set up escape to abort auto-compaction
|
|
1174
|
-
this.autoCompactionEscapeHandler = this.
|
|
1175
|
-
this.
|
|
1356
|
+
this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
|
|
1357
|
+
this.defaultEditor.onEscape = () => {
|
|
1176
1358
|
this.session.abortCompaction();
|
|
1177
1359
|
};
|
|
1178
1360
|
// Show compacting indicator with reason
|
|
@@ -1186,7 +1368,7 @@ export class InteractiveMode {
|
|
|
1186
1368
|
case "auto_compaction_end": {
|
|
1187
1369
|
// Restore escape handler
|
|
1188
1370
|
if (this.autoCompactionEscapeHandler) {
|
|
1189
|
-
this.
|
|
1371
|
+
this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
|
|
1190
1372
|
this.autoCompactionEscapeHandler = undefined;
|
|
1191
1373
|
}
|
|
1192
1374
|
// Stop loader
|
|
@@ -1218,8 +1400,8 @@ export class InteractiveMode {
|
|
|
1218
1400
|
}
|
|
1219
1401
|
case "auto_retry_start": {
|
|
1220
1402
|
// Set up escape to abort retry
|
|
1221
|
-
this.retryEscapeHandler = this.
|
|
1222
|
-
this.
|
|
1403
|
+
this.retryEscapeHandler = this.defaultEditor.onEscape;
|
|
1404
|
+
this.defaultEditor.onEscape = () => {
|
|
1223
1405
|
this.session.abortRetry();
|
|
1224
1406
|
};
|
|
1225
1407
|
// Show retry indicator
|
|
@@ -1233,7 +1415,7 @@ export class InteractiveMode {
|
|
|
1233
1415
|
case "auto_retry_end": {
|
|
1234
1416
|
// Restore escape handler
|
|
1235
1417
|
if (this.retryEscapeHandler) {
|
|
1236
|
-
this.
|
|
1418
|
+
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
1237
1419
|
this.retryEscapeHandler = undefined;
|
|
1238
1420
|
}
|
|
1239
1421
|
// Stop loader
|
|
@@ -1321,7 +1503,7 @@ export class InteractiveMode {
|
|
|
1321
1503
|
const userComponent = new UserMessageComponent(textContent);
|
|
1322
1504
|
this.chatContainer.addChild(userComponent);
|
|
1323
1505
|
if (options?.populateHistory) {
|
|
1324
|
-
this.editor.addToHistory(textContent);
|
|
1506
|
+
this.editor.addToHistory?.(textContent);
|
|
1325
1507
|
}
|
|
1326
1508
|
}
|
|
1327
1509
|
break;
|
|
@@ -1437,7 +1619,11 @@ export class InteractiveMode {
|
|
|
1437
1619
|
* Gracefully shutdown the agent.
|
|
1438
1620
|
* Emits shutdown event to extensions, then exits.
|
|
1439
1621
|
*/
|
|
1622
|
+
isShuttingDown = false;
|
|
1440
1623
|
async shutdown() {
|
|
1624
|
+
if (this.isShuttingDown)
|
|
1625
|
+
return;
|
|
1626
|
+
this.isShuttingDown = true;
|
|
1441
1627
|
// Emit shutdown event to extensions
|
|
1442
1628
|
const extensionRunner = this.session.extensionRunner;
|
|
1443
1629
|
if (extensionRunner?.hasHandlers("session_shutdown")) {
|
|
@@ -1448,6 +1634,14 @@ export class InteractiveMode {
|
|
|
1448
1634
|
this.stop();
|
|
1449
1635
|
process.exit(0);
|
|
1450
1636
|
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Check if shutdown was requested and perform shutdown if so.
|
|
1639
|
+
*/
|
|
1640
|
+
async checkShutdownRequested() {
|
|
1641
|
+
if (!this.shutdownRequested)
|
|
1642
|
+
return;
|
|
1643
|
+
await this.shutdown();
|
|
1644
|
+
}
|
|
1451
1645
|
handleCtrlZ() {
|
|
1452
1646
|
// Set up handler to restore TUI when resumed
|
|
1453
1647
|
process.once("SIGCONT", () => {
|
|
@@ -1466,7 +1660,7 @@ export class InteractiveMode {
|
|
|
1466
1660
|
// Queue input during compaction (extension commands execute immediately)
|
|
1467
1661
|
if (this.session.isCompacting) {
|
|
1468
1662
|
if (this.isExtensionCommand(text)) {
|
|
1469
|
-
this.editor.addToHistory(text);
|
|
1663
|
+
this.editor.addToHistory?.(text);
|
|
1470
1664
|
this.editor.setText("");
|
|
1471
1665
|
await this.session.prompt(text);
|
|
1472
1666
|
}
|
|
@@ -1478,7 +1672,7 @@ export class InteractiveMode {
|
|
|
1478
1672
|
// Alt+Enter queues a follow-up message (waits until agent finishes)
|
|
1479
1673
|
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
1480
1674
|
if (this.session.isStreaming) {
|
|
1481
|
-
this.editor.addToHistory(text);
|
|
1675
|
+
this.editor.addToHistory?.(text);
|
|
1482
1676
|
this.editor.setText("");
|
|
1483
1677
|
await this.session.prompt(text, { streamingBehavior: "followUp" });
|
|
1484
1678
|
this.updatePendingMessagesDisplay();
|
|
@@ -1558,7 +1752,7 @@ export class InteractiveMode {
|
|
|
1558
1752
|
this.showWarning("No editor configured. Set $VISUAL or $EDITOR environment variable.");
|
|
1559
1753
|
return;
|
|
1560
1754
|
}
|
|
1561
|
-
const currentText = this.editor.getExpandedText();
|
|
1755
|
+
const currentText = this.editor.getExpandedText?.() ?? this.editor.getText();
|
|
1562
1756
|
const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);
|
|
1563
1757
|
try {
|
|
1564
1758
|
// Write current content to temp file
|
|
@@ -1642,7 +1836,7 @@ export class InteractiveMode {
|
|
|
1642
1836
|
}
|
|
1643
1837
|
queueCompactionMessage(text, mode) {
|
|
1644
1838
|
this.compactionQueuedMessages.push({ text, mode });
|
|
1645
|
-
this.editor.addToHistory(text);
|
|
1839
|
+
this.editor.addToHistory?.(text);
|
|
1646
1840
|
this.editor.setText("");
|
|
1647
1841
|
this.updatePendingMessagesDisplay();
|
|
1648
1842
|
this.showStatus("Queued message for after compaction");
|
|
@@ -1917,9 +2111,9 @@ export class InteractiveMode {
|
|
|
1917
2111
|
const wantsSummary = await this.showExtensionConfirm("Summarize branch?", "Create a summary of the branch you're leaving?");
|
|
1918
2112
|
// Set up escape handler and loader if summarizing
|
|
1919
2113
|
let summaryLoader;
|
|
1920
|
-
const originalOnEscape = this.
|
|
2114
|
+
const originalOnEscape = this.defaultEditor.onEscape;
|
|
1921
2115
|
if (wantsSummary) {
|
|
1922
|
-
this.
|
|
2116
|
+
this.defaultEditor.onEscape = () => {
|
|
1923
2117
|
this.session.abortBranchSummary();
|
|
1924
2118
|
};
|
|
1925
2119
|
this.chatContainer.addChild(new Spacer(1));
|
|
@@ -1955,7 +2149,7 @@ export class InteractiveMode {
|
|
|
1955
2149
|
summaryLoader.stop();
|
|
1956
2150
|
this.statusContainer.clear();
|
|
1957
2151
|
}
|
|
1958
|
-
this.
|
|
2152
|
+
this.defaultEditor.onEscape = originalOnEscape;
|
|
1959
2153
|
}
|
|
1960
2154
|
}, () => {
|
|
1961
2155
|
done();
|
|
@@ -2487,8 +2681,8 @@ export class InteractiveMode {
|
|
|
2487
2681
|
}
|
|
2488
2682
|
this.statusContainer.clear();
|
|
2489
2683
|
// Set up escape handler during compaction
|
|
2490
|
-
const originalOnEscape = this.
|
|
2491
|
-
this.
|
|
2684
|
+
const originalOnEscape = this.defaultEditor.onEscape;
|
|
2685
|
+
this.defaultEditor.onEscape = () => {
|
|
2492
2686
|
this.session.abortCompaction();
|
|
2493
2687
|
};
|
|
2494
2688
|
// Show compacting status
|
|
@@ -2518,7 +2712,7 @@ export class InteractiveMode {
|
|
|
2518
2712
|
finally {
|
|
2519
2713
|
compactingLoader.stop();
|
|
2520
2714
|
this.statusContainer.clear();
|
|
2521
|
-
this.
|
|
2715
|
+
this.defaultEditor.onEscape = originalOnEscape;
|
|
2522
2716
|
}
|
|
2523
2717
|
void this.flushCompactionQueue({ willRetry: false });
|
|
2524
2718
|
}
|