fix(plugins): useComposers holds the composer it names, and a composer lives as long as its agent

PluginComposersSource.subscribe watched the stores directly and never the
composer model, so a component using useComposers did not keep the model's
catalog observer alive and reconnect repair skipped it. subscribe now takes the
selector and holds the model it names, following focus.

Dropping a composer when it went idle left a command a plugin had saved calling
a dead model after the tab reopened. A composer is now dropped when its agent
leaves the session and nothing watches it or has one mounted.
This commit is contained in:
Mohamed Boudra
2026-11-04 08:10:29 +01:00
parent e59183f2de
commit b37ab3e158
6 changed files with 148 additions and 59 deletions
+5 -3
View File
@@ -679,9 +679,11 @@ implementations provide it:
picker shows. The model loads the catalog itself and resolves with no models until it lands, as the picker does,
so a read never differs from what the picker shows. While anything watches the model it holds a
`QueryObserver` on the agent's catalog: that is what lets reconnect repair, which refetches only
active queries, refresh a catalog nobody has a picker open for, and retry one that failed. The
model is shared while a watcher or a mounted composer holds it and dropped when the last one
lets go. The mounted
active queries, refresh a catalog nobody has a picker open for, and retry one that failed.
`PluginComposersSource.subscribe(selector, listener)` is what holds it for `useComposers`, so
the subscription must go through the model's own `subscribe`. The model is shared for as long as
its agent is in the session, so a command a plugin saved keeps reaching the composer a later tab
mounts; it is dropped once the agent is gone and nothing watches it or has one mounted. The mounted
`AgentControls` plugs its input in with `attachInput` so `insertText` lands at the cursor.
Without a live input the model writes the stored prompt with `replaceDraftText`, which also asks
a mounted input to show it. A workspace the user left stays mounted, display none, with no