From cc76e73c05bdbbb6efb285353a91980606313472 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 22 Apr 2026 22:24:32 +0200 Subject: [PATCH] fix(coding-agent): persist custom working message across loader recreation Replace transient pendingWorkingMessage with a persistent workingMessage field on InteractiveMode, matching the workingIndicatorOptions pattern. New loaders now use this.workingMessage || this.defaultWorkingMessage instead of always falling back to the default. Also add working-message-test.ts extension example. closes #3566 --- packages/coding-agent/CHANGELOG.md | 4 +++ .../extensions/working-message-test.ts | 25 +++++++++++++++++++ .../src/modes/interactive/interactive-mode.ts | 17 +++---------- 3 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 packages/coding-agent/examples/extensions/working-message-test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index f0099949f..52b4ae91d 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed `ctx.ui.setWorkingMessage()` to persist across loader recreation, matching the behavior of `ctx.ui.setWorkingIndicator()` ([#3566](https://github.com/badlogic/pi-mono/issues/3566)) + ## [0.69.0] - 2026-04-22 ### New Features diff --git a/packages/coding-agent/examples/extensions/working-message-test.ts b/packages/coding-agent/examples/extensions/working-message-test.ts new file mode 100644 index 000000000..064d39e8c --- /dev/null +++ b/packages/coding-agent/examples/extensions/working-message-test.ts @@ -0,0 +1,25 @@ +/** + * Working Message Persistence Test + * + * Sets a custom working message and indicator on session start so you can + * verify they survive across loader recreations (e.g. between agent turns). + * + * Usage: + * pi --extension examples/extensions/working-message-test.ts + * + * Then send a few messages in interactive mode. The working message should + * stay "Working... (custom)" with a brown dot indicator every time the + * loader appears, not revert to the default gray "Working...". + */ + +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; + +const CUSTOM_MESSAGE = "\x1b[38;2;155;86;63mWorking... (custom)\x1b[39m"; +const CUSTOM_INDICATOR = { frames: ["\x1b[38;2;155;86;63m●\x1b[39m"] }; + +export default function (pi: ExtensionAPI) { + pi.on("session_start", async (_event, ctx) => { + ctx.ui.setWorkingMessage(CUSTOM_MESSAGE); + ctx.ui.setWorkingIndicator(CUSTOM_INDICATOR); + }); +} diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index fae1c02ba..3bf17c9df 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -205,7 +205,7 @@ export class InteractiveMode { private isInitialized = false; private onInputCallback?: (text: string) => void; private loadingAnimation: Loader | undefined = undefined; - private pendingWorkingMessage: string | undefined = undefined; + private workingMessage: string | undefined = undefined; private workingIndicatorOptions: LoaderIndicatorOptions | undefined = undefined; private readonly defaultWorkingMessage = "Working..."; private readonly defaultHiddenThinkingLabel = "Thinking..."; @@ -1724,7 +1724,7 @@ export class InteractiveMode { this.setupAutocompleteProvider(); this.defaultEditor.onExtensionShortcut = undefined; this.updateTerminalTitle(); - this.pendingWorkingMessage = undefined; + this.workingMessage = undefined; this.setWorkingIndicator(); if (this.loadingAnimation) { this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`); @@ -1875,6 +1875,7 @@ export class InteractiveMode { onTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler), setStatus: (key, text) => this.setExtensionStatus(key, text), setWorkingMessage: (message) => { + this.workingMessage = message; if (this.loadingAnimation) { if (message) { this.loadingAnimation.setMessage(message); @@ -1883,9 +1884,6 @@ export class InteractiveMode { `${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`, ); } - } else { - // Queue message for when loadingAnimation is created (handles agent_start race) - this.pendingWorkingMessage = message; } }, setWorkingIndicator: (options) => this.setWorkingIndicator(options), @@ -2591,17 +2589,10 @@ export class InteractiveMode { this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), - this.defaultWorkingMessage, + this.workingMessage || this.defaultWorkingMessage, this.workingIndicatorOptions, ); this.statusContainer.addChild(this.loadingAnimation); - // Apply any pending working message queued before loader existed - if (this.pendingWorkingMessage !== undefined) { - if (this.pendingWorkingMessage) { - this.loadingAnimation.setMessage(this.pendingWorkingMessage); - } - this.pendingWorkingMessage = undefined; - } this.ui.requestRender(); break;