From e4b801590dbed9d4c939baae009b45b62e7b4201 Mon Sep 17 00:00:00 2001 From: can1357 Date: Sat, 9 May 2026 07:02:49 +0200 Subject: [PATCH] refactor(coding-agent): tightened custom editor factory typing in interactive mode - Updated ExtensionUIContext, InteractiveModeContext, and InteractiveMode to require editor factories to return CustomEditor instances. - Removed the runtime compatibility guard and warning for non-CustomEditor implementations in setEditorComponent. - Removed the test that verified rejection of non-CustomEditor factories in interactive-mode editor-component tests. --- .../src/extensibility/extensions/types.ts | 13 +++++++--- .../src/modes/interactive-mode.ts | 24 ++++--------------- packages/coding-agent/src/modes/types.ts | 4 ++-- .../interactive-mode-editor-component.test.ts | 15 +----------- 4 files changed, 17 insertions(+), 39 deletions(-) diff --git a/packages/coding-agent/src/extensibility/extensions/types.ts b/packages/coding-agent/src/extensibility/extensions/types.ts index 7dd91231f..5a9116b93 100644 --- a/packages/coding-agent/src/extensibility/extensions/types.ts +++ b/packages/coding-agent/src/extensibility/extensions/types.ts @@ -22,7 +22,7 @@ import type { } from "@oh-my-pi/pi-ai"; import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/utils/oauth/types"; import type * as piCodingAgent from "@oh-my-pi/pi-coding-agent"; -import type { AutocompleteItem, Component, EditorComponent, EditorTheme, KeyId, TUI } from "@oh-my-pi/pi-tui"; +import type { AutocompleteItem, Component, EditorTheme, KeyId, TUI } from "@oh-my-pi/pi-tui"; import type { Static, TSchema } from "@sinclair/typebox"; import type { Rule } from "../../capability/rule"; import type { KeybindingsManager } from "../../config/keybindings"; @@ -31,6 +31,7 @@ import type { EditToolDetails } from "../../edit"; import type { PythonResult } from "../../eval/py/executor"; import type { BashResult } from "../../exec/bash-executor"; import type { ExecOptions, ExecResult } from "../../exec/exec"; +import type { CustomEditor } from "../../modes/components/custom-editor"; import type { Theme } from "../../modes/theme/theme"; import type { CompactionPreparation, CompactionResult } from "../../session/compaction"; import type { CustomMessage } from "../../session/messages"; @@ -170,9 +171,15 @@ export interface ExtensionUIContext { editorOptions?: { promptStyle?: boolean }, ): Promise; - /** Set a custom editor component via factory function, or undefined to restore the default editor. */ + /** + * Set a custom editor component via factory function, or `undefined` to restore the default editor. + * + * The factory must return a {@link CustomEditor} subclass. Plain `EditorComponent`/`Editor` + * instances do not implement the action-keys, escape callbacks, and custom-key-handler surface + * required by interactive mode. + */ setEditorComponent( - factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => EditorComponent) | undefined, + factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => CustomEditor) | undefined, ): void; /** Get the current theme for styling. */ diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index 4010377bc..84d25f930 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -13,7 +13,7 @@ import { modelsAreEqual, type UsageReport, } from "@oh-my-pi/pi-ai"; -import type { Component, EditorComponent, EditorTheme, SlashCommand } from "@oh-my-pi/pi-tui"; +import type { Component, EditorTheme, SlashCommand } from "@oh-my-pi/pi-tui"; import { Container, clearRenderCache, @@ -1322,19 +1322,14 @@ export class InteractiveMode implements InteractiveModeContext { } setEditorComponent( - factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => EditorComponent) | undefined, + factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => CustomEditor) | undefined, ): void { const previousEditor = this.editor; const previousText = previousEditor.getText(); - const nextComponent = factory + const nextEditor = factory ? factory(this.ui, getEditorTheme(), this.keybindings) : new CustomEditor(getEditorTheme()); - if (!this.#isCustomEditorCompatible(nextComponent)) { - this.showWarning("Custom editor components must implement CustomEditor-compatible interactive methods."); - return; - } - const nextEditor = nextComponent; nextEditor.setUseTerminalCursor(this.ui.getShowHardwareCursor()); nextEditor.setAutocompleteMaxVisible(this.settings.get("autocompleteMaxVisible")); nextEditor.onAutocompleteCancel = () => { @@ -1365,18 +1360,7 @@ export class InteractiveMode implements InteractiveModeContext { this.updateEditorTopBorder(); this.ui.requestRender(); } - #isCustomEditorCompatible(component: EditorComponent): component is CustomEditor { - const candidate = component as CustomEditor; - return ( - typeof candidate.setUseTerminalCursor === "function" && - typeof candidate.setAutocompleteMaxVisible === "function" && - typeof candidate.setMaxHeight === "function" && - typeof candidate.setHistoryStorage === "function" && - typeof candidate.setActionKeys === "function" && - typeof candidate.clearCustomKeyHandlers === "function" && - typeof candidate.setCustomKeyHandler === "function" - ); - } + // Event handling async handleBackgroundEvent(event: AgentSessionEvent): Promise { await this.#eventController.handleBackgroundEvent(event); diff --git a/packages/coding-agent/src/modes/types.ts b/packages/coding-agent/src/modes/types.ts index 6a0d6e4bd..39c8c386d 100644 --- a/packages/coding-agent/src/modes/types.ts +++ b/packages/coding-agent/src/modes/types.ts @@ -1,6 +1,6 @@ import type { AgentMessage } from "@oh-my-pi/pi-agent-core"; import type { AssistantMessage, ImageContent, Message, UsageReport } from "@oh-my-pi/pi-ai"; -import type { Component, Container, EditorComponent, EditorTheme, Loader, Spacer, Text, TUI } from "@oh-my-pi/pi-tui"; +import type { Component, Container, EditorTheme, Loader, Spacer, Text, TUI } from "@oh-my-pi/pi-tui"; import type { KeybindingsManager } from "../config/keybindings"; import type { Settings } from "../config/settings"; import type { @@ -132,7 +132,7 @@ export interface InteractiveModeContext { initializeHookRunner(uiContext: ExtensionUIContext, hasUI: boolean): void; createBackgroundUiContext(): ExtensionUIContext; setEditorComponent( - factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => EditorComponent) | undefined, + factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => CustomEditor) | undefined, ): void; // Event handling diff --git a/packages/coding-agent/test/interactive-mode-editor-component.test.ts b/packages/coding-agent/test/interactive-mode-editor-component.test.ts index cf58b6512..fcbf84ca4 100644 --- a/packages/coding-agent/test/interactive-mode-editor-component.test.ts +++ b/packages/coding-agent/test/interactive-mode-editor-component.test.ts @@ -3,7 +3,6 @@ import * as path from "node:path"; import { Agent } from "@oh-my-pi/pi-agent-core"; import { _resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import { Editor } from "@oh-my-pi/pi-tui"; import { TempDir } from "@oh-my-pi/pi-utils"; import { ModelRegistry } from "../src/config/model-registry"; import { CustomEditor } from "../src/modes/components/custom-editor"; @@ -39,7 +38,7 @@ describe("InteractiveMode.setEditorComponent", () => { agent: new Agent({ initialState: { model, - systemPrompt: "Test", + systemPrompt: ["Test"], tools: [], messages: [], }, @@ -74,16 +73,4 @@ describe("InteractiveMode.setEditorComponent", () => { expect(mode.editor.onEscape).toBeDefined(); expect(refreshSpy).toHaveBeenCalled(); }); - - it("keeps the current editor when factory returns a non-CustomEditor", () => { - const previousEditor = mode.editor; - const warningSpy = vi.spyOn(mode, "showWarning"); - - mode.setEditorComponent((_tui, editorTheme) => new Editor(editorTheme)); - - expect(mode.editor).toBe(previousEditor); - expect(warningSpy).toHaveBeenCalledWith( - "Custom editor components must implement CustomEditor-compatible interactive methods.", - ); - }); });