mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 00:03:00 +00:00
feat(coding-agent): add ctx.ui.setEditorComponent() extension API
- Add setEditorComponent() to ctx.ui for custom editor components - Add CustomEditor base class for extensions (handles app keybindings) - Add keybindings parameter to ctx.ui.custom() factory (breaking change) - Add modal-editor.ts example (vim-like modes) - Add rainbow-editor.ts example (animated text highlighting) - Update docs: extensions.md, tui.md Pattern 7 - Clean up terminal on TUI render errors
This commit is contained in:
parent
10e651f99b
commit
09471ebc7d
27 changed files with 578 additions and 63 deletions
|
|
@ -11,6 +11,8 @@ export type {
|
|||
// Re-exports
|
||||
AgentToolResult,
|
||||
AgentToolUpdateCallback,
|
||||
// App keybindings (for custom editors)
|
||||
AppAction,
|
||||
AppendEntryHandler,
|
||||
BashToolResultEvent,
|
||||
BeforeAgentStartEvent,
|
||||
|
|
@ -42,6 +44,7 @@ export type {
|
|||
GetAllToolsHandler,
|
||||
GetThinkingLevelHandler,
|
||||
GrepToolResultEvent,
|
||||
KeybindingsManager,
|
||||
LoadExtensionsResult,
|
||||
// Loaded Extension
|
||||
LoadedExtension,
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ function createNoOpUIContext(): ExtensionUIContext {
|
|||
setEditorText: () => {},
|
||||
getEditorText: () => "",
|
||||
editor: async () => undefined,
|
||||
setEditorComponent: () => {},
|
||||
get theme() {
|
||||
return theme;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ const noOpUIContext: ExtensionUIContext = {
|
|||
setEditorText: () => {},
|
||||
getEditorText: () => "",
|
||||
editor: async () => undefined,
|
||||
setEditorComponent: () => {},
|
||||
get theme() {
|
||||
return theme;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,12 +15,13 @@ import type {
|
|||
ThinkingLevel,
|
||||
} from "@mariozechner/pi-agent-core";
|
||||
import type { ImageContent, Model, TextContent, ToolResultMessage } from "@mariozechner/pi-ai";
|
||||
import type { Component, KeyId, TUI } from "@mariozechner/pi-tui";
|
||||
import type { Component, EditorComponent, EditorTheme, KeyId, TUI } from "@mariozechner/pi-tui";
|
||||
import type { Static, TSchema } from "@sinclair/typebox";
|
||||
import type { Theme } from "../../modes/interactive/theme/theme.js";
|
||||
import type { CompactionPreparation, CompactionResult } from "../compaction/index.js";
|
||||
import type { EventBus } from "../event-bus.js";
|
||||
import type { ExecOptions, ExecResult } from "../exec.js";
|
||||
import type { AppAction, KeybindingsManager } from "../keybindings.js";
|
||||
import type { CustomMessage } from "../messages.js";
|
||||
import type { ModelRegistry } from "../model-registry.js";
|
||||
import type {
|
||||
|
|
@ -41,6 +42,7 @@ import type {
|
|||
|
||||
export type { ExecOptions, ExecResult } from "../exec.js";
|
||||
export type { AgentToolResult, AgentToolUpdateCallback };
|
||||
export type { AppAction, KeybindingsManager } from "../keybindings.js";
|
||||
|
||||
// ============================================================================
|
||||
// UI Context
|
||||
|
|
@ -92,6 +94,7 @@ export interface ExtensionUIContext {
|
|||
factory: (
|
||||
tui: TUI,
|
||||
theme: Theme,
|
||||
keybindings: KeybindingsManager,
|
||||
done: (result: T) => void,
|
||||
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
||||
): Promise<T>;
|
||||
|
|
@ -105,6 +108,43 @@ export interface ExtensionUIContext {
|
|||
/** Show a multi-line editor for text editing. */
|
||||
editor(title: string, prefill?: string): Promise<string | undefined>;
|
||||
|
||||
/**
|
||||
* Set a custom editor component via factory function.
|
||||
* Pass undefined to restore the default editor.
|
||||
*
|
||||
* The factory receives:
|
||||
* - `theme`: EditorTheme for styling borders and autocomplete
|
||||
* - `keybindings`: KeybindingsManager for app-level keybindings
|
||||
*
|
||||
* For full app keybinding support (escape, ctrl+d, model switching, etc.),
|
||||
* extend `CustomEditor` from `@mariozechner/pi-coding-agent` and call
|
||||
* `super.handleInput(data)` for keys you don't handle.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { CustomEditor } from "@mariozechner/pi-coding-agent";
|
||||
*
|
||||
* class VimEditor extends CustomEditor {
|
||||
* private mode: "normal" | "insert" = "insert";
|
||||
*
|
||||
* handleInput(data: string): void {
|
||||
* if (this.mode === "normal") {
|
||||
* // Handle vim normal mode keys...
|
||||
* if (data === "i") { this.mode = "insert"; return; }
|
||||
* }
|
||||
* super.handleInput(data); // App keybindings + text editing
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ctx.ui.setEditorComponent((tui, theme, keybindings) =>
|
||||
* new VimEditor(tui, theme, keybindings)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
setEditorComponent(
|
||||
factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => EditorComponent) | undefined,
|
||||
): void;
|
||||
|
||||
/** Get the current theme for styling. */
|
||||
readonly theme: Theme;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -531,6 +531,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
setEditorText: () => {},
|
||||
getEditorText: () => "",
|
||||
editor: async () => undefined,
|
||||
setEditorComponent: () => {},
|
||||
get theme() {
|
||||
return {} as any;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export type {
|
|||
AgentStartEvent,
|
||||
AgentToolResult,
|
||||
AgentToolUpdateCallback,
|
||||
AppAction,
|
||||
BeforeAgentStartEvent,
|
||||
ContextEvent,
|
||||
ExecOptions,
|
||||
|
|
@ -55,6 +56,7 @@ export type {
|
|||
ExtensionShortcut,
|
||||
ExtensionUIContext,
|
||||
ExtensionUIDialogOptions,
|
||||
KeybindingsManager,
|
||||
LoadExtensionsResult,
|
||||
LoadedExtension,
|
||||
MessageRenderer,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type { AppAction, KeybindingsManager } from "../../../core/keybindings.js
|
|||
*/
|
||||
export class CustomEditor extends Editor {
|
||||
private keybindings: KeybindingsManager;
|
||||
private actionHandlers: Map<AppAction, () => void> = new Map();
|
||||
public actionHandlers: Map<AppAction, () => void> = new Map();
|
||||
|
||||
// Special handlers that can be dynamically replaced
|
||||
public onEscape?: () => void;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import * as os from "node:os";
|
|||
import * as path from "node:path";
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import { type AssistantMessage, getOAuthProviders, type Message, type OAuthProvider } from "@mariozechner/pi-ai";
|
||||
import type { KeyId, SlashCommand } from "@mariozechner/pi-tui";
|
||||
import type { EditorComponent, EditorTheme, KeyId, SlashCommand } from "@mariozechner/pi-tui";
|
||||
import {
|
||||
CombinedAutocompleteProvider,
|
||||
type Component,
|
||||
|
|
@ -96,7 +96,9 @@ export class InteractiveMode {
|
|||
private chatContainer: Container;
|
||||
private pendingMessagesContainer: Container;
|
||||
private statusContainer: Container;
|
||||
private editor: CustomEditor;
|
||||
private defaultEditor: CustomEditor;
|
||||
private editor: EditorComponent;
|
||||
private autocompleteProvider: CombinedAutocompleteProvider | undefined;
|
||||
private editorContainer: Container;
|
||||
private footer: FooterComponent;
|
||||
private keybindings: KeybindingsManager;
|
||||
|
|
@ -195,9 +197,10 @@ export class InteractiveMode {
|
|||
this.statusContainer = new Container();
|
||||
this.widgetContainer = new Container();
|
||||
this.keybindings = KeybindingsManager.create();
|
||||
this.editor = new CustomEditor(getEditorTheme(), this.keybindings);
|
||||
this.defaultEditor = new CustomEditor(getEditorTheme(), this.keybindings);
|
||||
this.editor = this.defaultEditor;
|
||||
this.editorContainer = new Container();
|
||||
this.editorContainer.addChild(this.editor);
|
||||
this.editorContainer.addChild(this.editor as Component);
|
||||
this.footer = new FooterComponent(session);
|
||||
this.footer.setAutoCompactEnabled(session.autoCompactionEnabled);
|
||||
|
||||
|
|
@ -238,12 +241,12 @@ export class InteractiveMode {
|
|||
);
|
||||
|
||||
// Setup autocomplete
|
||||
const autocompleteProvider = new CombinedAutocompleteProvider(
|
||||
this.autocompleteProvider = new CombinedAutocompleteProvider(
|
||||
[...slashCommands, ...templateCommands, ...extensionCommands],
|
||||
process.cwd(),
|
||||
fdPath,
|
||||
);
|
||||
this.editor.setAutocompleteProvider(autocompleteProvider);
|
||||
this.defaultEditor.setAutocompleteProvider(this.autocompleteProvider);
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
|
|
@ -595,8 +598,8 @@ export class InteractiveMode {
|
|||
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
||||
});
|
||||
|
||||
// Set up the extension shortcut handler on the editor
|
||||
this.editor.onExtensionShortcut = (data: string) => {
|
||||
// Set up the extension shortcut handler on the default editor
|
||||
this.defaultEditor.onExtensionShortcut = (data: string) => {
|
||||
for (const [shortcutStr, shortcut] of shortcuts) {
|
||||
// Cast to KeyId - extension shortcuts use the same format
|
||||
if (matchesKey(data, shortcutStr as KeyId)) {
|
||||
|
|
@ -753,6 +756,7 @@ export class InteractiveMode {
|
|||
setEditorText: (text) => this.editor.setText(text),
|
||||
getEditorText: () => this.editor.getText(),
|
||||
editor: (title, prefill) => this.showExtensionEditor(title, prefill),
|
||||
setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
|
||||
get theme() {
|
||||
return theme;
|
||||
},
|
||||
|
|
@ -918,6 +922,65 @@ export class InteractiveMode {
|
|||
this.ui.requestRender();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom editor component from an extension.
|
||||
* Pass undefined to restore the default editor.
|
||||
*/
|
||||
private setCustomEditorComponent(
|
||||
factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => EditorComponent) | undefined,
|
||||
): void {
|
||||
// Save text from current editor before switching
|
||||
const currentText = this.editor.getText();
|
||||
|
||||
this.editorContainer.clear();
|
||||
|
||||
if (factory) {
|
||||
// Create the custom editor with tui, theme, and keybindings
|
||||
const newEditor = factory(this.ui, getEditorTheme(), this.keybindings);
|
||||
|
||||
// Wire up callbacks from the default editor
|
||||
newEditor.onSubmit = this.defaultEditor.onSubmit;
|
||||
newEditor.onChange = this.defaultEditor.onChange;
|
||||
|
||||
// Copy text from previous editor
|
||||
newEditor.setText(currentText);
|
||||
|
||||
// Copy appearance settings if supported
|
||||
if (newEditor.borderColor !== undefined) {
|
||||
newEditor.borderColor = this.defaultEditor.borderColor;
|
||||
}
|
||||
|
||||
// Set autocomplete if supported
|
||||
if (newEditor.setAutocompleteProvider && this.autocompleteProvider) {
|
||||
newEditor.setAutocompleteProvider(this.autocompleteProvider);
|
||||
}
|
||||
|
||||
// If extending CustomEditor, copy app-level handlers
|
||||
// Use duck typing since instanceof fails across jiti module boundaries
|
||||
const customEditor = newEditor as unknown as Record<string, unknown>;
|
||||
if ("actionHandlers" in customEditor && customEditor.actionHandlers instanceof Map) {
|
||||
customEditor.onEscape = this.defaultEditor.onEscape;
|
||||
customEditor.onCtrlD = this.defaultEditor.onCtrlD;
|
||||
customEditor.onPasteImage = this.defaultEditor.onPasteImage;
|
||||
customEditor.onExtensionShortcut = this.defaultEditor.onExtensionShortcut;
|
||||
// Copy action handlers (clear, suspend, model switching, etc.)
|
||||
for (const [action, handler] of this.defaultEditor.actionHandlers) {
|
||||
(customEditor.actionHandlers as Map<string, () => void>).set(action, handler);
|
||||
}
|
||||
}
|
||||
|
||||
this.editor = newEditor;
|
||||
} else {
|
||||
// Restore default editor with text from custom editor
|
||||
this.defaultEditor.setText(currentText);
|
||||
this.editor = this.defaultEditor;
|
||||
}
|
||||
|
||||
this.editorContainer.addChild(this.editor as Component);
|
||||
this.ui.setFocus(this.editor as Component);
|
||||
this.ui.requestRender();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a notification for extensions.
|
||||
*/
|
||||
|
|
@ -938,6 +1001,7 @@ export class InteractiveMode {
|
|||
factory: (
|
||||
tui: TUI,
|
||||
theme: Theme,
|
||||
keybindings: KeybindingsManager,
|
||||
done: (result: T) => void,
|
||||
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
||||
): Promise<T> {
|
||||
|
|
@ -956,7 +1020,7 @@ export class InteractiveMode {
|
|||
resolve(result);
|
||||
};
|
||||
|
||||
Promise.resolve(factory(this.ui, theme, close)).then((c) => {
|
||||
Promise.resolve(factory(this.ui, theme, this.keybindings, close)).then((c) => {
|
||||
component = c;
|
||||
this.editorContainer.clear();
|
||||
this.editorContainer.addChild(component);
|
||||
|
|
@ -992,7 +1056,9 @@ export class InteractiveMode {
|
|||
// =========================================================================
|
||||
|
||||
private setupKeyHandlers(): void {
|
||||
this.editor.onEscape = () => {
|
||||
// Set up handlers on defaultEditor - they use this.editor for text access
|
||||
// so they work correctly regardless of which editor is active
|
||||
this.defaultEditor.onEscape = () => {
|
||||
if (this.loadingAnimation) {
|
||||
// Abort and restore queued messages to editor
|
||||
const { steering, followUp } = this.session.clearQueue();
|
||||
|
|
@ -1026,22 +1092,22 @@ export class InteractiveMode {
|
|||
};
|
||||
|
||||
// Register app action handlers
|
||||
this.editor.onAction("clear", () => this.handleCtrlC());
|
||||
this.editor.onCtrlD = () => this.handleCtrlD();
|
||||
this.editor.onAction("suspend", () => this.handleCtrlZ());
|
||||
this.editor.onAction("cycleThinkingLevel", () => this.cycleThinkingLevel());
|
||||
this.editor.onAction("cycleModelForward", () => this.cycleModel("forward"));
|
||||
this.editor.onAction("cycleModelBackward", () => this.cycleModel("backward"));
|
||||
this.defaultEditor.onAction("clear", () => this.handleCtrlC());
|
||||
this.defaultEditor.onCtrlD = () => this.handleCtrlD();
|
||||
this.defaultEditor.onAction("suspend", () => this.handleCtrlZ());
|
||||
this.defaultEditor.onAction("cycleThinkingLevel", () => this.cycleThinkingLevel());
|
||||
this.defaultEditor.onAction("cycleModelForward", () => this.cycleModel("forward"));
|
||||
this.defaultEditor.onAction("cycleModelBackward", () => this.cycleModel("backward"));
|
||||
|
||||
// Global debug handler on TUI (works regardless of focus)
|
||||
this.ui.onDebug = () => this.handleDebugCommand();
|
||||
this.editor.onAction("selectModel", () => this.showModelSelector());
|
||||
this.editor.onAction("expandTools", () => this.toggleToolOutputExpansion());
|
||||
this.editor.onAction("toggleThinking", () => this.toggleThinkingBlockVisibility());
|
||||
this.editor.onAction("externalEditor", () => this.openExternalEditor());
|
||||
this.editor.onAction("followUp", () => this.handleFollowUp());
|
||||
this.defaultEditor.onAction("selectModel", () => this.showModelSelector());
|
||||
this.defaultEditor.onAction("expandTools", () => this.toggleToolOutputExpansion());
|
||||
this.defaultEditor.onAction("toggleThinking", () => this.toggleThinkingBlockVisibility());
|
||||
this.defaultEditor.onAction("externalEditor", () => this.openExternalEditor());
|
||||
this.defaultEditor.onAction("followUp", () => this.handleFollowUp());
|
||||
|
||||
this.editor.onChange = (text: string) => {
|
||||
this.defaultEditor.onChange = (text: string) => {
|
||||
const wasBashMode = this.isBashMode;
|
||||
this.isBashMode = text.trimStart().startsWith("!");
|
||||
if (wasBashMode !== this.isBashMode) {
|
||||
|
|
@ -1050,7 +1116,7 @@ export class InteractiveMode {
|
|||
};
|
||||
|
||||
// Handle clipboard image paste (triggered on Ctrl+V)
|
||||
this.editor.onPasteImage = () => {
|
||||
this.defaultEditor.onPasteImage = () => {
|
||||
this.handleClipboardImagePaste();
|
||||
};
|
||||
}
|
||||
|
|
@ -1070,7 +1136,7 @@ export class InteractiveMode {
|
|||
fs.writeFileSync(filePath, Buffer.from(image.bytes));
|
||||
|
||||
// Insert file path directly
|
||||
this.editor.insertTextAtCursor(filePath);
|
||||
this.editor.insertTextAtCursor?.(filePath);
|
||||
this.ui.requestRender();
|
||||
} catch {
|
||||
// Silently ignore clipboard errors (may not have permission, etc.)
|
||||
|
|
@ -1078,7 +1144,7 @@ export class InteractiveMode {
|
|||
}
|
||||
|
||||
private setupEditorSubmitHandler(): void {
|
||||
this.editor.onSubmit = async (text: string) => {
|
||||
this.defaultEditor.onSubmit = async (text: string) => {
|
||||
text = text.trim();
|
||||
if (!text) return;
|
||||
|
||||
|
|
@ -1185,7 +1251,7 @@ export class InteractiveMode {
|
|||
this.editor.setText(text);
|
||||
return;
|
||||
}
|
||||
this.editor.addToHistory(text);
|
||||
this.editor.addToHistory?.(text);
|
||||
await this.handleBashCommand(command, isExcluded);
|
||||
this.isBashMode = false;
|
||||
this.updateEditorBorderColor();
|
||||
|
|
@ -1196,7 +1262,7 @@ export class InteractiveMode {
|
|||
// Queue input during compaction (extension commands execute immediately)
|
||||
if (this.session.isCompacting) {
|
||||
if (this.isExtensionCommand(text)) {
|
||||
this.editor.addToHistory(text);
|
||||
this.editor.addToHistory?.(text);
|
||||
this.editor.setText("");
|
||||
await this.session.prompt(text);
|
||||
} else {
|
||||
|
|
@ -1208,7 +1274,7 @@ export class InteractiveMode {
|
|||
// If streaming, use prompt() with steer behavior
|
||||
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
||||
if (this.session.isStreaming) {
|
||||
this.editor.addToHistory(text);
|
||||
this.editor.addToHistory?.(text);
|
||||
this.editor.setText("");
|
||||
await this.session.prompt(text, { streamingBehavior: "steer" });
|
||||
this.updatePendingMessagesDisplay();
|
||||
|
|
@ -1223,7 +1289,7 @@ export class InteractiveMode {
|
|||
if (this.onInputCallback) {
|
||||
this.onInputCallback(text);
|
||||
}
|
||||
this.editor.addToHistory(text);
|
||||
this.editor.addToHistory?.(text);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -1393,8 +1459,8 @@ export class InteractiveMode {
|
|||
case "auto_compaction_start": {
|
||||
// Keep editor active; submissions are queued during compaction.
|
||||
// Set up escape to abort auto-compaction
|
||||
this.autoCompactionEscapeHandler = this.editor.onEscape;
|
||||
this.editor.onEscape = () => {
|
||||
this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
|
||||
this.defaultEditor.onEscape = () => {
|
||||
this.session.abortCompaction();
|
||||
};
|
||||
// Show compacting indicator with reason
|
||||
|
|
@ -1414,7 +1480,7 @@ export class InteractiveMode {
|
|||
case "auto_compaction_end": {
|
||||
// Restore escape handler
|
||||
if (this.autoCompactionEscapeHandler) {
|
||||
this.editor.onEscape = this.autoCompactionEscapeHandler;
|
||||
this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
|
||||
this.autoCompactionEscapeHandler = undefined;
|
||||
}
|
||||
// Stop loader
|
||||
|
|
@ -1446,8 +1512,8 @@ export class InteractiveMode {
|
|||
|
||||
case "auto_retry_start": {
|
||||
// Set up escape to abort retry
|
||||
this.retryEscapeHandler = this.editor.onEscape;
|
||||
this.editor.onEscape = () => {
|
||||
this.retryEscapeHandler = this.defaultEditor.onEscape;
|
||||
this.defaultEditor.onEscape = () => {
|
||||
this.session.abortRetry();
|
||||
};
|
||||
// Show retry indicator
|
||||
|
|
@ -1467,7 +1533,7 @@ export class InteractiveMode {
|
|||
case "auto_retry_end": {
|
||||
// Restore escape handler
|
||||
if (this.retryEscapeHandler) {
|
||||
this.editor.onEscape = this.retryEscapeHandler;
|
||||
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
||||
this.retryEscapeHandler = undefined;
|
||||
}
|
||||
// Stop loader
|
||||
|
|
@ -1565,7 +1631,7 @@ export class InteractiveMode {
|
|||
const userComponent = new UserMessageComponent(textContent);
|
||||
this.chatContainer.addChild(userComponent);
|
||||
if (options?.populateHistory) {
|
||||
this.editor.addToHistory(textContent);
|
||||
this.editor.addToHistory?.(textContent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -1734,7 +1800,7 @@ export class InteractiveMode {
|
|||
// Queue input during compaction (extension commands execute immediately)
|
||||
if (this.session.isCompacting) {
|
||||
if (this.isExtensionCommand(text)) {
|
||||
this.editor.addToHistory(text);
|
||||
this.editor.addToHistory?.(text);
|
||||
this.editor.setText("");
|
||||
await this.session.prompt(text);
|
||||
} else {
|
||||
|
|
@ -1746,7 +1812,7 @@ export class InteractiveMode {
|
|||
// Alt+Enter queues a follow-up message (waits until agent finishes)
|
||||
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
||||
if (this.session.isStreaming) {
|
||||
this.editor.addToHistory(text);
|
||||
this.editor.addToHistory?.(text);
|
||||
this.editor.setText("");
|
||||
await this.session.prompt(text, { streamingBehavior: "followUp" });
|
||||
this.updatePendingMessagesDisplay();
|
||||
|
|
@ -1833,7 +1899,7 @@ export class InteractiveMode {
|
|||
return;
|
||||
}
|
||||
|
||||
const currentText = this.editor.getExpandedText();
|
||||
const currentText = this.editor.getExpandedText?.() ?? this.editor.getText();
|
||||
const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);
|
||||
|
||||
try {
|
||||
|
|
@ -1934,7 +2000,7 @@ export class InteractiveMode {
|
|||
|
||||
private queueCompactionMessage(text: string, mode: "steer" | "followUp"): void {
|
||||
this.compactionQueuedMessages.push({ text, mode });
|
||||
this.editor.addToHistory(text);
|
||||
this.editor.addToHistory?.(text);
|
||||
this.editor.setText("");
|
||||
this.updatePendingMessagesDisplay();
|
||||
this.showStatus("Queued message for after compaction");
|
||||
|
|
@ -2253,10 +2319,10 @@ export class InteractiveMode {
|
|||
|
||||
// Set up escape handler and loader if summarizing
|
||||
let summaryLoader: Loader | undefined;
|
||||
const originalOnEscape = this.editor.onEscape;
|
||||
const originalOnEscape = this.defaultEditor.onEscape;
|
||||
|
||||
if (wantsSummary) {
|
||||
this.editor.onEscape = () => {
|
||||
this.defaultEditor.onEscape = () => {
|
||||
this.session.abortBranchSummary();
|
||||
};
|
||||
this.chatContainer.addChild(new Spacer(1));
|
||||
|
|
@ -2298,7 +2364,7 @@ export class InteractiveMode {
|
|||
summaryLoader.stop();
|
||||
this.statusContainer.clear();
|
||||
}
|
||||
this.editor.onEscape = originalOnEscape;
|
||||
this.defaultEditor.onEscape = originalOnEscape;
|
||||
}
|
||||
},
|
||||
() => {
|
||||
|
|
@ -2921,8 +2987,8 @@ export class InteractiveMode {
|
|||
this.statusContainer.clear();
|
||||
|
||||
// Set up escape handler during compaction
|
||||
const originalOnEscape = this.editor.onEscape;
|
||||
this.editor.onEscape = () => {
|
||||
const originalOnEscape = this.defaultEditor.onEscape;
|
||||
this.defaultEditor.onEscape = () => {
|
||||
this.session.abortCompaction();
|
||||
};
|
||||
|
||||
|
|
@ -2959,7 +3025,7 @@ export class InteractiveMode {
|
|||
} finally {
|
||||
compactingLoader.stop();
|
||||
this.statusContainer.clear();
|
||||
this.editor.onEscape = originalOnEscape;
|
||||
this.defaultEditor.onEscape = originalOnEscape;
|
||||
}
|
||||
void this.flushCompactionQueue({ willRetry: false });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,6 +219,10 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|||
});
|
||||
},
|
||||
|
||||
setEditorComponent(): void {
|
||||
// Custom editor components not supported in RPC mode
|
||||
},
|
||||
|
||||
get theme() {
|
||||
return theme;
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue