fix(coding-agent): bind extension UI context on startup

This commit is contained in:
Mario Zechner 2026-01-26 23:00:48 +01:00
parent f86e3c3103
commit 5dbeadae05
4 changed files with 60 additions and 66 deletions

View file

@ -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)) - 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)) - 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 ## [0.50.0] - 2026-01-26

View file

@ -976,13 +976,6 @@ export class InteractiveMode {
* Initialize the extension system with TUI-based UI context. * Initialize the extension system with TUI-based UI context.
*/ */
private async initExtensions(): Promise<void> { private async initExtensions(): Promise<void> {
const extensionRunner = this.session.extensionRunner;
if (!extensionRunner) {
this.showLoadedResources({ extensionPaths: [], force: false });
return;
}
// Create extension UI context
const uiContext = this.createExtensionUIContext(); const uiContext = this.createExtensionUIContext();
await this.session.bindExtensions({ await this.session.bindExtensions({
uiContext, 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.setupExtensionShortcuts(extensionRunner);
this.showLoadedResources({ extensionPaths: extensionRunner.getExtensionPaths(), force: false }); this.showLoadedResources({ extensionPaths: extensionRunner.getExtensionPaths(), force: false });
} }

View file

@ -36,37 +36,34 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
} }
} }
// Set up extensions for print mode (no UI) // Set up extensions for print mode (no UI)
const extensionRunner = session.extensionRunner; await session.bindExtensions({
if (extensionRunner) { commandContextActions: {
await session.bindExtensions({ waitForIdle: () => session.agent.waitForIdle(),
commandContextActions: { newSession: async (options) => {
waitForIdle: () => session.agent.waitForIdle(), const success = await session.newSession({ parentSession: options?.parentSession });
newSession: async (options) => { if (success && options?.setup) {
const success = await session.newSession({ parentSession: options?.parentSession }); await options.setup(session.sessionManager);
if (success && options?.setup) { }
await options.setup(session.sessionManager); return { cancelled: !success };
}
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 };
},
}, },
onError: (err) => { fork: async (entryId) => {
console.error(`Extension error (${err.extensionPath}): ${err.error}`); 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 // Always subscribe to enable session persistence via _handleAgentEvent
session.subscribe((event) => { session.subscribe((event) => {

View file

@ -254,39 +254,36 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
}); });
// Set up extensions with RPC-based UI context // Set up extensions with RPC-based UI context
const extensionRunner = session.extensionRunner; await session.bindExtensions({
if (extensionRunner) { uiContext: createExtensionUIContext(),
await session.bindExtensions({ commandContextActions: {
uiContext: createExtensionUIContext(), waitForIdle: () => session.agent.waitForIdle(),
commandContextActions: { newSession: async (options) => {
waitForIdle: () => session.agent.waitForIdle(), // Delegate to AgentSession (handles setup + agent state sync)
newSession: async (options) => { const success = await session.newSession(options);
// Delegate to AgentSession (handles setup + agent state sync) return { cancelled: !success };
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 };
},
}, },
shutdownHandler: () => { fork: async (entryId) => {
shutdownRequested = true; const result = await session.fork(entryId);
return { cancelled: result.cancelled };
}, },
onError: (err) => { navigateTree: async (targetId, options) => {
output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error }); 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 // Output all agent events as JSON
session.subscribe((event) => { session.subscribe((event) => {