From 5dbeadae051e1ff984dea92feee1d878bdab5aa3 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 26 Jan 2026 23:00:48 +0100 Subject: [PATCH] fix(coding-agent): bind extension UI context on startup --- packages/coding-agent/CHANGELOG.md | 1 + .../src/modes/interactive/interactive-mode.ts | 13 ++--- packages/coding-agent/src/modes/print-mode.ts | 55 +++++++++--------- .../coding-agent/src/modes/rpc/rpc-mode.ts | 57 +++++++++---------- 4 files changed, 60 insertions(+), 66 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index c672231a..0411e121 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -6,6 +6,7 @@ - Git extension updates now handle force-pushed remotes gracefully instead of failing ([#961](https://github.com/badlogic/pi-mono/pull/961) by [@aliou](https://github.com/aliou)) - Extension `ctx.newSession({ setup })` now properly syncs agent state and renders messages after setup callback runs ([#968](https://github.com/badlogic/pi-mono/issues/968)) +- Fixed extension UI bindings not initializing when starting with no extensions, which broke UI methods after `/reload` ## [0.50.0] - 2026-01-26 diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index b5a42c6a..b1bd9dfa 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -976,13 +976,6 @@ export class InteractiveMode { * Initialize the extension system with TUI-based UI context. */ private async initExtensions(): Promise { - const extensionRunner = this.session.extensionRunner; - if (!extensionRunner) { - this.showLoadedResources({ extensionPaths: [], force: false }); - return; - } - - // Create extension UI context const uiContext = this.createExtensionUIContext(); await this.session.bindExtensions({ uiContext, @@ -1057,6 +1050,12 @@ export class InteractiveMode { }, }); + const extensionRunner = this.session.extensionRunner; + if (!extensionRunner) { + this.showLoadedResources({ extensionPaths: [], force: false }); + return; + } + this.setupExtensionShortcuts(extensionRunner); this.showLoadedResources({ extensionPaths: extensionRunner.getExtensionPaths(), force: false }); } diff --git a/packages/coding-agent/src/modes/print-mode.ts b/packages/coding-agent/src/modes/print-mode.ts index ec411058..2a5b0c1b 100644 --- a/packages/coding-agent/src/modes/print-mode.ts +++ b/packages/coding-agent/src/modes/print-mode.ts @@ -36,37 +36,34 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti } } // Set up extensions for print mode (no UI) - const extensionRunner = session.extensionRunner; - if (extensionRunner) { - await session.bindExtensions({ - commandContextActions: { - waitForIdle: () => session.agent.waitForIdle(), - newSession: async (options) => { - const success = await session.newSession({ parentSession: options?.parentSession }); - if (success && options?.setup) { - await options.setup(session.sessionManager); - } - return { cancelled: !success }; - }, - fork: async (entryId) => { - const result = await session.fork(entryId); - return { cancelled: result.cancelled }; - }, - navigateTree: async (targetId, options) => { - const result = await session.navigateTree(targetId, { - summarize: options?.summarize, - customInstructions: options?.customInstructions, - replaceInstructions: options?.replaceInstructions, - label: options?.label, - }); - return { cancelled: result.cancelled }; - }, + await session.bindExtensions({ + commandContextActions: { + waitForIdle: () => session.agent.waitForIdle(), + newSession: async (options) => { + const success = await session.newSession({ parentSession: options?.parentSession }); + if (success && options?.setup) { + await options.setup(session.sessionManager); + } + return { cancelled: !success }; }, - onError: (err) => { - console.error(`Extension error (${err.extensionPath}): ${err.error}`); + fork: async (entryId) => { + const result = await session.fork(entryId); + return { cancelled: result.cancelled }; }, - }); - } + navigateTree: async (targetId, options) => { + const result = await session.navigateTree(targetId, { + summarize: options?.summarize, + customInstructions: options?.customInstructions, + replaceInstructions: options?.replaceInstructions, + label: options?.label, + }); + return { cancelled: result.cancelled }; + }, + }, + onError: (err) => { + console.error(`Extension error (${err.extensionPath}): ${err.error}`); + }, + }); // Always subscribe to enable session persistence via _handleAgentEvent session.subscribe((event) => { diff --git a/packages/coding-agent/src/modes/rpc/rpc-mode.ts b/packages/coding-agent/src/modes/rpc/rpc-mode.ts index fd097076..3413306d 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-mode.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-mode.ts @@ -254,39 +254,36 @@ export async function runRpcMode(session: AgentSession): Promise { }); // Set up extensions with RPC-based UI context - const extensionRunner = session.extensionRunner; - if (extensionRunner) { - await session.bindExtensions({ - uiContext: createExtensionUIContext(), - commandContextActions: { - waitForIdle: () => session.agent.waitForIdle(), - newSession: async (options) => { - // Delegate to AgentSession (handles setup + agent state sync) - const success = await session.newSession(options); - return { cancelled: !success }; - }, - fork: async (entryId) => { - const result = await session.fork(entryId); - return { cancelled: result.cancelled }; - }, - navigateTree: async (targetId, options) => { - const result = await session.navigateTree(targetId, { - summarize: options?.summarize, - customInstructions: options?.customInstructions, - replaceInstructions: options?.replaceInstructions, - label: options?.label, - }); - return { cancelled: result.cancelled }; - }, + await session.bindExtensions({ + uiContext: createExtensionUIContext(), + commandContextActions: { + waitForIdle: () => session.agent.waitForIdle(), + newSession: async (options) => { + // Delegate to AgentSession (handles setup + agent state sync) + const success = await session.newSession(options); + return { cancelled: !success }; }, - shutdownHandler: () => { - shutdownRequested = true; + fork: async (entryId) => { + const result = await session.fork(entryId); + return { cancelled: result.cancelled }; }, - onError: (err) => { - output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error }); + navigateTree: async (targetId, options) => { + const result = await session.navigateTree(targetId, { + summarize: options?.summarize, + customInstructions: options?.customInstructions, + replaceInstructions: options?.replaceInstructions, + label: options?.label, + }); + return { cancelled: result.cancelled }; }, - }); - } + }, + shutdownHandler: () => { + shutdownRequested = true; + }, + onError: (err) => { + output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error }); + }, + }); // Output all agent events as JSON session.subscribe((event) => {