mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-04 23:44:41 +00:00
feat(plugins): reach a running agent's composer by reference
useComposers(selector) in any plugin component and composers(selector) on every command context return the composer of a running agent, named by id or by focus, whether or not its tab is open. A running agent now has one store-backed composer model apart from any mounted tab. The mounted composer plugs its cursor-aware insert in while mounted; otherwise insertText and setPrompt write the agent's saved prompt. model and thinking resolve through the provider catalog with the picker's own function, so a read equals what the picker shows. The host reads one ComposerModel from context and no longer knows whether it is in an agent, a draft or New workspace. The daemon rejects an unlisted model or thinking option for a running agent, replacing the app-side guard.
This commit is contained in:
@@ -1982,7 +1982,71 @@ renders nothing can still leave an empty overflow sheet on a narrow composer, so
|
||||
control.
|
||||
|
||||
The [composer controls example](https://github.com/getpaseo/paseo/tree/main/plugin-examples/composer-controls)
|
||||
registers a `Select`, two `Toggle`s (one writes a provider option) and an `Action`.
|
||||
registers a `Select`, two `Toggle`s (one writes a provider option) and an `Action`, plus an
|
||||
Explorer panel and a Command Center item that reach a composer by reference.
|
||||
|
||||
### Reach a composer by reference
|
||||
|
||||
A running agent has a composer whether or not its tab is open. `useComposers(selector)` in any plugin
|
||||
component, and `composers(selector)` on every command context, return the same `PluginComposer` that
|
||||
`useComposer()` gives an item, for an agent you name or for the focused one:
|
||||
|
||||
```tsx
|
||||
import { useComposers, type PluginClientContext } from "@getpaseo/plugin/client";
|
||||
|
||||
function InsertChecklist() {
|
||||
const [focused] = useComposers({ focused: true });
|
||||
return (
|
||||
<Pressable disabled={!focused} onPress={() => focused?.insertText("- [ ] Tests\n")}>
|
||||
<Text>Insert checklist</Text>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
export default function contribute(client: PluginClientContext) {
|
||||
client.addWorkspacePanel({
|
||||
id: "tools",
|
||||
title: "Tools",
|
||||
icon: "ListChecks",
|
||||
locations: ["explorer"],
|
||||
Component: InsertChecklist,
|
||||
});
|
||||
client.addCommandCenterItem({
|
||||
id: "mark-for-review",
|
||||
title: "Mark for review",
|
||||
icon: "Flag",
|
||||
context: "agent",
|
||||
async onSelect({ agent, composers }) {
|
||||
for (const composer of composers({ agentId: agent.id })) {
|
||||
await composer.setMetadata({ "review.needsReview": true });
|
||||
}
|
||||
},
|
||||
});
|
||||
return () => {};
|
||||
}
|
||||
```
|
||||
|
||||
| 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. |
|
||||
|
||||
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
|
||||
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.
|
||||
- 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.
|
||||
- Drafts and the New workspace screen are not addressable by reference. Only an item inside one
|
||||
reaches its composer, through `useComposer()`.
|
||||
|
||||
## New workspace items
|
||||
|
||||
|
||||
@@ -1663,7 +1663,61 @@ export default function contribute(client: PluginClientContext) {
|
||||
|
||||
在 Paseo 知道渲染结果之前,composer 项就已计入可容纳的数量。不渲染任何内容的项仍可能在窄 composer 上留下空的溢出面板,所以请始终渲染一个控件。
|
||||
|
||||
[Composer 控件示例](https://github.com/getpaseo/paseo/tree/main/plugin-examples/composer-controls)注册了一个 `Select`、两个 `Toggle`(其中一个写入 Provider 选项)和一个 `Action`。
|
||||
[Composer 控件示例](https://github.com/getpaseo/paseo/tree/main/plugin-examples/composer-controls)注册了一个 `Select`、两个 `Toggle`(其中一个写入 Provider 选项)和一个 `Action`,还有一个资源管理器面板和一个 Command Center 条目,它们按引用访问 composer。
|
||||
|
||||
### 按引用访问 composer {#reach-a-composer-by-reference}
|
||||
|
||||
运行中的 Agent 无论标签页是否打开,都有自己的 composer。任意插件组件中的 `useComposers(selector)`,以及每个命令上下文中的 `composers(selector)`,都会返回与 `useComposer()` 给 composer 项的相同的 `PluginComposer`,对象可以是你指定的 Agent,也可以是当前聚焦的 Agent:
|
||||
|
||||
```tsx
|
||||
import { useComposers, type PluginClientContext } from "@getpaseo/plugin/client";
|
||||
|
||||
function InsertChecklist() {
|
||||
const [focused] = useComposers({ focused: true });
|
||||
return (
|
||||
<Pressable disabled={!focused} onPress={() => focused?.insertText("- [ ] Tests\n")}>
|
||||
<Text>Insert checklist</Text>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
export default function contribute(client: PluginClientContext) {
|
||||
client.addWorkspacePanel({
|
||||
id: "tools",
|
||||
title: "Tools",
|
||||
icon: "ListChecks",
|
||||
locations: ["explorer"],
|
||||
Component: InsertChecklist,
|
||||
});
|
||||
client.addCommandCenterItem({
|
||||
id: "mark-for-review",
|
||||
title: "Mark for review",
|
||||
icon: "Flag",
|
||||
context: "agent",
|
||||
async onSelect({ agent, composers }) {
|
||||
for (const composer of composers({ agentId: agent.id })) {
|
||||
await composer.setMetadata({ "review.needsReview": true });
|
||||
}
|
||||
},
|
||||
});
|
||||
return () => {};
|
||||
}
|
||||
```
|
||||
|
||||
| 选择器 | 结果 |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `{ agentId }` | 此主机上该 Agent 的 composer。Agent 未知或已归档时为空。 |
|
||||
| `{ focused: true }` | 你在此主机上最近查看的工作区中,标签页处于聚焦状态的 Agent 的 composer。聚焦的标签页不是 Agent 时为空,并随用户切换标签页而变化。 |
|
||||
|
||||
结果始终是数组,为空或只含一个 composer,所以同一段代码可以读取任一选择器。composer 变化时 `useComposers` 会重新渲染。`composers()` 返回的 composer 是调用时的状态,要获取下一个状态请再次调用 `composers()`。
|
||||
|
||||
命令与 [`useComposer()`](#composer-items) 相同,从外部访问时有以下差异:
|
||||
|
||||
- Agent 的标签页打开时,`insertText` 在光标处插入。标签页关闭时,它会追加到该 Agent 已保存的提示词,标签页打开时文本就在那里。
|
||||
- 运行中 Agent 的 composer 从不锁定。
|
||||
- `setProvider` 会被拒绝,与在 Agent 自己的 composer 内一样。
|
||||
- 对于 Provider 未列出的值,`setModel` 和 `setThinking` 会以 daemon 的消息拒绝。在 Provider 的模型目录加载完成之前,`model` 和 `thinking` 读取为 `null`,之后是模型选择器显示的值。请传入目录中列出的 id。
|
||||
- 草稿和新建工作区界面不能按引用访问,只有其中的项能通过 `useComposer()` 访问其 composer。
|
||||
|
||||
## 新建工作区项 {#new-workspace-items}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user