You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The current extension lifecycle assumes one process = one session. registerTools(pi) receives an ExtensionAPI but no session context, so extensions that need per-session state must store it on globalThis. This works fine for single-session TUI usage, but breaks when a single process hosts multiple sessions.
Real-world scenario
We run a daemon process (headless pi + WebSocket thin client) that hosts two sessions: a conversation session for human interaction and a background session for automated task execution. When the second session's registerTools() runs, it clobbers the first session's callbacks and bridge references on globalThis.
Issues encountered
1. Callback clobber
Extensions commonly clear and re-register callbacks during init:
// Extension does this to be idempotent on /reload:constcallbacks=(globalThisasany).__myCallbacks;callbacks.splice(0);// Wipes ALL sessions' callbackscallbacks.push(myCallback);
Second session's init wipes first session's callbacks — job completion notifications, alerts, etc. are silently lost.
2. Singleton state override
Any singleton on globalThis (e.g. active session reference, bridge, shadow state) is overwritten by the second session.
3. /reload identity loss
On /reload, jiti re-imports the extension module and SDK creates a freshExtensionAPI (pi) object. Since registerTools(pi) receives a new pi and bindSessionHooks() doesn't re-run, the extension has no way to know which session is being reloaded. We tried 5 different heuristic approaches (tagging pi, counting calls, checking active bridges, etc.) — all fail because "reload of session A" and "creation of session B after A is bound" produce identical observable state from within the extension.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Problem
The current extension lifecycle assumes one process = one session.
registerTools(pi)receives anExtensionAPIbut no session context, so extensions that need per-session state must store it onglobalThis. This works fine for single-session TUI usage, but breaks when a single process hosts multiple sessions.Real-world scenario
We run a daemon process (headless pi + WebSocket thin client) that hosts two sessions: a conversation session for human interaction and a background session for automated task execution. When the second session's
registerTools()runs, it clobbers the first session's callbacks and bridge references onglobalThis.Issues encountered
1. Callback clobber
Extensions commonly clear and re-register callbacks during init:
Second session's init wipes first session's callbacks — job completion notifications, alerts, etc. are silently lost.
2. Singleton state override
Any singleton on
globalThis(e.g. active session reference, bridge, shadow state) is overwritten by the second session.3. /reload identity loss
On
/reload, jiti re-imports the extension module and SDK creates a freshExtensionAPI(pi) object. SinceregisterTools(pi)receives a new pi andbindSessionHooks()doesn't re-run, the extension has no way to know which session is being reloaded. We tried 5 different heuristic approaches (tagging pi, counting calls, checking active bridges, etc.) — all fail because "reload of session A" and "creation of session B after A is bound" produce identical observable state from within the extension.Current workarounds
TaggedCallback = { fn, sessionId }— callbacks tagged with session identity, scoped cleanupMap<sessionId, Bridge>— replaces singleton bridge__lucaSingleSessionflag — set by entry point to enable reload-specific cleanup__lucaReloadBridgeIdmarker — explicit reload signal (mechanism exists but no pre-reload hook to set it)These work but are discipline-based — every new per-session state requires manual keying by sessionId.
Proposed solution
Extend
registerTools(or add a new lifecycle) with session context:Either approach would:
globalThis.__luca*hacks/reloadtrivial (SDK reuses ctx, extension doesn't need to detect it)Backward compatibility
Option B is minimal — existing extensions that don't use
ctxcontinue to work. The parameter is simply available for extensions that need it.Happy to discuss further or contribute a PR if there's interest.
Beta Was this translation helpful? Give feedback.
All reactions