Investigated extension command registration and UI notification.
UnderstandBound UI contexts on startup for reload support.
BuildUpdated changelog with the fix.
EditRan tests and pushed fix to main branch.
Verifyi had you write a trivial extension in another session to .pi/extensions/test.ts (no longer there). it registered a command /test that would use ui.notify().
when i started the session, test.ts didn'T exist yet, you created it. then i did /reload. the /test command got registered, but running it did not show the ui notification. can you figure out why that is?
Let me investigate how extensions and UI notifications work in pi.
Let me look at how commands work and how ctx.ui.notify is implemented:
Let me check the exact flow by looking at how the command gets executed after reload:
actually, codex, please take over
Taking over. The likely cause is that when pi started there were no extensions, so InteractiveMode.initExtensions() returned early and never called session.bindExtensions(). That means _extensionUIContext stayed undefined. On /reload, AgentSession.reload() creates a new ExtensionRunner and calls _applyExtensionBindings, which passes the undefined UI context. ExtensionRunner.setUIContext(undefined) falls back to the no-op UI context, so ctx.ui.notify() does nothing. That matches what you saw.
If you want, I can prepare a fix. One option is to re-run initExtensions() after session.reload() or call session.bindExtensions() inside handleReloadCommand() whenever a new runner exists.
ohhhh that explains it! i was running it in ../shittycodingagent.ai/ and i think i have no user extensions loaded? yeah, i don't. howe can we fix that?
It happens when you start with zero extensions. initExtensions() returns early, so session.bindExtensions() never runs and _extensionUIContext stays undefined. On /reload, the new runner gets noOpUIContext, so ctx.ui.notify() does nothing.
Fix options:
InteractiveMode.initExtensions(), call session.bindExtensions() even when there is no runner, so the UI context is stored for future reloads.handleReloadCommand(), after session.reload(), call session.bindExtensions() with createExtensionUIContext() if a runner exists.initExtensions() after reload instead of only rebuilding shortcuts.Tell me which approach you want, or I can pick one and implement it.
i suppose we need to do the same in prc and print(json mode?
also, i would think agent-session.ts reload() would handle the rewiring of extensions and contexts. it's very weird for modes to having to do that on reload. what concise solution would make it so agent-session.ts handles all of that?
also because if we registered the ui context after session.reload(), extensions might already have been run, specifically session_start events could have been emitted, wherein extensions can do ui things, no?
Yes, same issue in RPC and print or json. They only call session.bindExtensions() when session.extensionRunner exists, so if you start with zero extensions the UI context never gets stored. On /reload, the new runner gets noOpUIContext, so ctx.ui.notify() does nothing.
AgentSession.reload() already rebinds and emits session_start if bindings are present. The missing piece is that the bindings never get stored when there were no extensions on startup. The concise fix is to call session.bindExtensions() unconditionally in each mode during initialization, even when there is no extension runner. That stores the UI context and command actions up front. Then reload reuses them and session_start runs with the real UI context.
This also answers your last point. If we only bind after reload(), session_start can fire before the UI is set. So the binding must be stored before reload runs.
If you want me to implement it, I will update:
packages/coding-agent/src/modes/interactive/interactive-mode.tspackages/coding-agent/src/modes/rpc/rpc-mode.tspackages/coding-agent/src/modes/print-mode.tsTell me to proceed.
please implement concisely
Summary:
packages/coding-agent/src/modes/interactive/interactive-mode.ts to bind extension UI context before checking for a runner, so reload has the context even when starting with zero extensions.packages/coding-agent/src/modes/rpc/rpc-mode.ts and packages/coding-agent/src/modes/print-mode.ts to bind extensions unconditionally for the same reason.Tests:
npm run checklgtm, commit and push, add a changelog entry
Summary:
packages/coding-agent/CHANGELOG.md.Tests:
npm run checkPushed to main with commit 5dbeadae.