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
{{ message }}
Repository navigation
[Bug] session:activated fan-out skips the activation-scope check that session.getActive enforces #1754
#1745 scope-checked the pull side of the session-follow feature, but the push side broadcasts to everyone:
Pull: pi.session.getActive() refuses a project-scoped plugin whose plugin is not active in the session's project — apps/desktop/electron/main/plugin-runtime.ts:4479-4488 ("A project-scoped plugin is not part of another project's conversation"); ui.revealView has the same check (:4518-4520).
Push: broadcastSessionEvent (apps/desktop/electron/main/services/plugin-services.ts:575-604) treats session:activated (in SESSION_SCOPED_EVENTS, :95) as scoped only in the sense of "skip panel windows", then fans out through plugins.broadcastEvent (plugin-runtime.ts:1997-2015 — an unconditional loop over every loaded plugin) and pluginViews.broadcast. There is no project filter, and the payload carries the other project's sessionIdandprojectPath.
So a plugin scoped to project A only needs to subscribe to session:activated to receive project B's session identity — information its pull API explicitly refuses to give it. The push/pull double standard contradicts #1745's own commit message ("keep session follow inside scope and ordered under switches").
Steps to reproduce / 复现步骤
Load a plugin whose scope is limited to project A.
Open project B and switch between its sessions.
Observe the plugin process receiving session:activated with B's sessionId/projectPath, while the same plugin's pi.session.getActive() call is rejected.
Expected behavior / 预期行为
The fan-out applies the same activation-scope check the pull side enforces: a plugin receives session:activated only for sessions its scope allows.
Actual behavior / 实际行为
The event fans out unconditionally; project scoping holds for pulls only.
App version / 应用版本
0.21.0-beta.4 (main 589e580; introduced with the #1745 series)
Operating system / 操作系统
All
Extra environment / 其他环境信息
Fix direction: run the same per-plugin scope check used by getActive/revealView when fanning out session:activated (both to plugin processes and docked plugin views).
Confirmed the asymmetry on current main (589e5803a).
Pull side is guarded — apps/desktop/electron/main/plugin-runtime.ts:4482-4488:
// A project-scoped plugin is not part of another project's conversation, so// it does not get to read which session that project has open.if(!this.services.isPluginActiveInProject?.(loaded.manifest.id)){throwapiError("PERMISSION_DENIED","plugin is not active in this project");}
Push side is not — apps/desktop/electron/main/services/plugin-services.ts:573-586:
plugins.broadcastEvent(event,[payload]);// every loaded plugin process
...
if(!SESSION_SCOPED_EVENTS.has(event)){// panels: skipped for session:activatedpluginPanels.broadcast(event,payload);}pluginViews.broadcast(event,payload);// views: no scope check either
SESSION_SCOPED_EVENTS only excludes detached ui.panel windows; it does not filter by activation scope. So a project-scoped plugin in project A receives session:activated for a session in project B — precisely the information readActiveSession refuses to give it. The dependency the fix needs is already injected (isPluginActiveInProject is a PluginServicesDependencies member, :116) and sessionProjects (:557-562) already maps session → project path, so the filter can be applied per recipient without new plumbing.
One design question worth settling while fixing it: pluginViews.broadcast follows the user's selection and a docked view belongs to the current session, so the right predicate is probably "is this plugin active in the project of the session being announced" — not the project of the view's own host window. Getting that backwards would silence the very panels the feature exists for.
Activity
muzimu217 commentedon Oct 19, 2026
Confirmed the asymmetry on current
main(589e5803a).Pull side is guarded —
apps/desktop/electron/main/plugin-runtime.ts:4482-4488:Push side is not —
apps/desktop/electron/main/services/plugin-services.ts:573-586:SESSION_SCOPED_EVENTSonly excludes detachedui.panelwindows; it does not filter by activation scope. So a project-scoped plugin in project A receivessession:activatedfor a session in project B — precisely the informationreadActiveSessionrefuses to give it. The dependency the fix needs is already injected (isPluginActiveInProjectis aPluginServicesDependenciesmember, :116) andsessionProjects(:557-562) already maps session → project path, so the filter can be applied per recipient without new plumbing.One design question worth settling while fixing it:
pluginViews.broadcastfollows the user's selection and a docked view belongs to the current session, so the right predicate is probably "is this plugin active in the project of the session being announced" — not the project of the view's own host window. Getting that backwards would silence the very panels the feature exists for.fix(plugins): require the session scope check and update the wiring c…
fix(plugins): keep session follow alive across renderer reloads and i…