diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index f21017d9..545c16d4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- `ctx.ui.setWorkingMessage()` extension API to customize the "Working..." message during streaming + ## [0.42.5] - 2026-01-11 ### Fixed diff --git a/packages/coding-agent/docs/extensions.md b/packages/coding-agent/docs/extensions.md index 51a2083b..00ea5259 100644 --- a/packages/coding-agent/docs/extensions.md +++ b/packages/coding-agent/docs/extensions.md @@ -1170,6 +1170,7 @@ Extensions can interact with users via `ctx.ui` methods and customize how messag - Async operations with cancel (BorderedLoader) - Settings toggles (SettingsList) - Status indicators (setStatus) +- Working message during streaming (setWorkingMessage) - Widgets above editor (setWidget) - Custom footers (setFooter) @@ -1256,6 +1257,10 @@ See [examples/extensions/timed-confirm.ts](../examples/extensions/timed-confirm. ctx.ui.setStatus("my-ext", "Processing..."); ctx.ui.setStatus("my-ext", undefined); // Clear +// Working message (shown during streaming) +ctx.ui.setWorkingMessage("Thinking deeply..."); +ctx.ui.setWorkingMessage(); // Restore default + // Widget above editor (string array or factory function) ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"]); ctx.ui.setWidget("my-widget", (tui, theme) => new Text(theme.fg("accent", "Custom"), 0, 0)); diff --git a/packages/coding-agent/src/core/extensions/runner.ts b/packages/coding-agent/src/core/extensions/runner.ts index e9ddf4c5..bd7db904 100644 --- a/packages/coding-agent/src/core/extensions/runner.ts +++ b/packages/coding-agent/src/core/extensions/runner.ts @@ -79,6 +79,7 @@ const noOpUIContext: ExtensionUIContext = { input: async () => undefined, notify: () => {}, setStatus: () => {}, + setWorkingMessage: () => {}, setWidget: () => {}, setFooter: () => {}, setHeader: () => {}, diff --git a/packages/coding-agent/src/core/extensions/types.ts b/packages/coding-agent/src/core/extensions/types.ts index 035473b0..cb57b313 100644 --- a/packages/coding-agent/src/core/extensions/types.ts +++ b/packages/coding-agent/src/core/extensions/types.ts @@ -79,6 +79,9 @@ export interface ExtensionUIContext { /** Set status text in the footer/status bar. Pass undefined to clear. */ setStatus(key: string, text: string | undefined): void; + /** Set the working/loading message shown during streaming. Call with no argument to restore default. */ + setWorkingMessage(message?: string): void; + /** Set a widget to display above the editor. Accepts string array or component factory. */ setWidget(key: string, content: string[] | undefined): void; setWidget(key: string, content: ((tui: TUI, theme: Theme) => Component & { dispose?(): void }) | undefined): void; diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 6bfa1c26..03bdd01b 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -135,6 +135,7 @@ export class InteractiveMode { private isInitialized = false; private onInputCallback?: (text: string) => void; private loadingAnimation: Loader | undefined = undefined; + private readonly defaultWorkingMessage = "Working... (esc to interrupt)"; private lastSigintTime = 0; private lastEscapeTime = 0; @@ -948,6 +949,11 @@ export class InteractiveMode { input: (title, placeholder, opts) => this.showExtensionInput(title, placeholder, opts), notify: (message, type) => this.showExtensionNotify(message, type), setStatus: (key, text) => this.setExtensionStatus(key, text), + setWorkingMessage: (message) => { + if (this.loadingAnimation) { + this.loadingAnimation.setMessage(message ?? this.defaultWorkingMessage); + } + }, setWidget: (key, content) => this.setExtensionWidget(key, content), setFooter: (factory) => this.setExtensionFooter(factory), setHeader: (factory) => this.setExtensionHeader(factory), @@ -1559,7 +1565,7 @@ export class InteractiveMode { this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), - "Working... (esc to interrupt)", + this.defaultWorkingMessage, ); this.statusContainer.addChild(this.loadingAnimation); this.ui.requestRender(); diff --git a/packages/coding-agent/src/modes/rpc/rpc-mode.ts b/packages/coding-agent/src/modes/rpc/rpc-mode.ts index a748b406..8fecbeef 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-mode.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-mode.ts @@ -150,6 +150,10 @@ export async function runRpcMode(session: AgentSession): Promise { } as RpcExtensionUIRequest); }, + setWorkingMessage(_message?: string): void { + // Working message not supported in RPC mode - requires TUI loader access + }, + setWidget(key: string, content: unknown): void { // Only support string arrays in RPC mode - factory functions are ignored if (content === undefined || Array.isArray(content)) {