fix(plugins): composers by reference follow the focused agent and reach retained composers

- `{ focused: true }` is the agent Command Center hands to agent items, published by its
  registration through plugins/focused-agent.ts and empty outside a workspace. The composer source
  reads that module instead of the remembered workspace selection, the navigation store and the
  layout store, which also removes the require cycles those imports added.
- A composer the app retains in a workspace the user left has no live input. `insertText` and
  `setPrompt` now store the text with `replaceDraftText`, which asks the mounted input to show it;
  before, the text reached the store but the retained input never displayed it.
- `model` and `thinking` read exactly what the picker resolves before the catalog loads (no
  models), then re-resolve when it lands, instead of reading null.
- The daemon validates a thinking option against the model the agent runs: the runtime model when
  the provider lists it, else the configured one.
- The composer sources subscribe to query-cache updates only. A query another component creates
  during its render is announced as `added`, and a subscriber that updates then made React warn
  about updating a component while rendering a different one. A catalog read no longer starts its
  load synchronously either.
This commit is contained in:
Mohamed Boudra
2026-11-04 08:10:29 +01:00
parent 0aeb1ae1f9
commit b597dfc023
21 changed files with 650 additions and 107 deletions
+10 -8
View File
@@ -2026,10 +2026,10 @@ export default function contribute(client: PluginClientContext) {
}
```
| Selector | Result |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `{ agentId }` | The composer of that agent on this host. Empty for an agent that is unknown or archived. |
| `{ focused: true }` | The composer of the agent whose tab is focused in the workspace you last viewed on this host. Empty when the focused tab is not an agent, and it follows focus as the user changes tabs. |
| Selector | Result |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `{ agentId }` | The composer of that agent on this host. Empty for an agent that is unknown or archived. |
| `{ focused: true }` | The composer of the agent the Command Center hands to agent items: the focused agent tab of the workspace on screen. Empty when the screen is not a workspace, such as a plugin screen or New workspace, or when the focused tab is not an agent. It follows focus as the user changes tabs. |
The result is always an array, empty or with one composer, so the same code reads either selector.
`useComposers` re-renders when a composer changes. A composer from `composers()` is the state at the
@@ -2038,13 +2038,15 @@ call; call `composers()` again for the next state.
The commands are those of [`useComposer()`](#composer-items), with these differences for a composer
you reach from outside:
- `insertText` inserts at the cursor while the agent's tab is open. With the tab closed it appends to
the agent's saved prompt, and the text is there when the tab opens.
- `insertText` inserts at the cursor while the agent's composer is on screen. While it is not, for
example when the user is in another workspace, it appends to the agent's saved prompt, and the text
is in the input when the user returns. `setPrompt` replaces the saved prompt the same way.
- A running agent's composer is never locked.
- `setProvider` rejects, as it does inside the agent's own composer.
- `setModel` and `setThinking` reject, with the daemon's message, for a value the provider does not
list. `model` and `thinking` read `null` until the provider's catalog has loaded, then the value
the model picker shows. Pass an id the catalog lists.
list. Pass an id the provider's catalog lists. `model` and `thinking` read what the model picker
shows at that moment: until the provider's catalog loads that is the id the agent reports or was
created with, which may be an alias, and once it loads the catalog's own id.
- Drafts and the New workspace screen are not addressable by reference. Only an item inside one
reaches its composer, through `useComposer()`.
+6 -6
View File
@@ -1704,19 +1704,19 @@ export default function contribute(client: PluginClientContext) {
}
```
| 选择器 | 结果 |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `{ agentId }` | 此主机上该 Agent 的 composer。Agent 未知或已归档时为空。 |
| `{ focused: true }` | 你在此主机上最近查看的工作区中,标签页处于聚焦状态的 Agent 的 composer。聚焦的标签页不是 Agent 时为空,并随用户切换标签页而变化。 |
| 选择器 | 结果 |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `{ agentId }` | 此主机上该 Agent 的 composer。Agent 未知或已归档时为空。 |
| `{ focused: true }` | Command Center 交给 Agent 项的那个 Agent 的 composer:屏幕上工作区中聚焦的 Agent 标签页。当前界面不是工作区(例如插件界面或新建工作区),或聚焦的标签页不是 Agent 时为空,并随用户切换标签页而变化。 |
结果始终是数组,为空或只含一个 composer,所以同一段代码可以读取任一选择器。composer 变化时 `useComposers` 会重新渲染。`composers()` 返回的 composer 是调用时的状态,要获取下一个状态请再次调用 `composers()`。
命令与 [`useComposer()`](#composer-items) 相同,从外部访问时有以下差异:
- Agent 的标签页打开时,`insertText` 在光标处插入。标签页关闭时,它会追加到该 Agent 已保存的提示词,标签页打开时文本就在那里。
- Agent 的 composer 在屏幕上时,`insertText` 在光标处插入。不在屏幕上时(例如用户在另一个工作区),它会追加到该 Agent 已保存的提示词,用户回来时文本就在输入框里。`setPrompt` 以同样的方式替换已保存的提示词。
- 运行中 Agent 的 composer 从不锁定。
- `setProvider` 会被拒绝,与在 Agent 自己的 composer 内一样。
- 对于 Provider 未列出的值,`setModel` 和 `setThinking` 会以 daemon 的消息拒绝。在 Provider 的模型目录加载完成之前,`model` 和 `thinking` 读取为 `null`,之后是模型选择器显示的值。请传入目录中列出的 id。
- 对于 Provider 未列出的值,`setModel` 和 `setThinking` 会以 daemon 的消息拒绝。请传入 Provider 目录中列出的 id。`model` 和 `thinking` 读取的是模型选择器此刻显示的值:在 Provider 的模型目录加载完成之前,是 Agent 上报或创建时使用的 id(可能是别名),加载完成后是目录中的 id。
- 草稿和新建工作区界面不能按引用访问,只有其中的项能通过 `useComposer()` 访问其 composer。
## 新建工作区项 {#new-workspace-items}