fix(plugins): a composer refreshes a stale catalog on resumed demand, shares its store watches, and is released with its last holder

Watching again, or reading once, after the catalog was invalidated or failed
while nothing watched now starts one deduplicated refresh; the observer
refetches on mount. Reconnect repair only refetches watched queries, so an
unwatched catalog stayed stale.

Each subscribe installed its own session and draft store callbacks, so N
watchers received N squared notifications, including for other agents' drafts.
One pair of subscriptions now serves all listeners, and the draft callback
ignores drafts of other agents.

A composer whose agent was removed while it was held was only released on the
next session change. The last holder letting go now checks too, and an archived
agent's idle composer is releasable.
This commit is contained in:
Mohamed Boudra
2026-11-04 08:28:12 +01:00
parent b37ab3e158
commit d4b31cedb1
4 changed files with 184 additions and 36 deletions
+7 -3
View File
@@ -681,9 +681,13 @@ implementations provide it:
`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.
`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
the subscription must go through the model's own `subscribe`. Watching again after the catalog
was invalidated or failed refetches it, and a read with no watcher starts one deduplicated
refresh and shows the cached catalog until it lands. The model keeps one pair of store
subscriptions however many components watch it. It is shared for as long as its agent is in the
session and not archived, so a command a plugin saved keeps reaching the composer a later tab
mounts; it is dropped when the agent is gone or archived and nothing watches it or has one
mounted, checked on session changes and when the last holder lets go. 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