mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-09 18:04:55 +00:00
feat(plugins): let a plugin put the cursor in any agent's composer
A pill could insert text but left the cursor elsewhere, so the user had to click the composer before typing. Plugins can address any agent's composer, and an author cannot know where the user is, so focus() owns revealing the agent as well as focusing it. A running agent's composer holds one pending request and navigates to the agent, completing when an input that can take the keyboard attaches. Drafts and the New workspace composer have nothing to navigate to and must not pull the user back, so they reject when off screen.
This commit is contained in:
@@ -1953,6 +1953,7 @@ export default function contribute(client: PluginClientContext) {
|
||||
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `context` | `"agent"` for a running agent, `"draft"` for a new agent in a workspace, `"new-workspace"` for the New workspace screen. |
|
||||
| `prompt`, `setPrompt(text)`, `insertText(text)` | The prompt. `insertText` replaces the selection or inserts at the cursor. |
|
||||
| `focus(options?)` | Puts the cursor in this composer, ready to type, and resolves when it is there. `selection: { start, end }` places the cursor or selects text, as offsets into `prompt` clamped to its length; omit it to keep the cursor where it is. See [focus a composer](#focus-a-composer). |
|
||||
| `attachments`, `addAttachment`, `removeAttachment` | Items from [your own attachment sources](#add-a-composer-attachment-source). Attachments from other plugins and Paseo's own are not listed. |
|
||||
| `agent.provider`, `model`, `mode`, `thinking` | What the agent runs with, or what the draft will create. |
|
||||
| `agent.options` | The provider options in effect, defaults included. In a draft: the options the agent will be created with, including your changes. |
|
||||
@@ -1992,11 +1993,39 @@ The [composer controls example](https://github.com/getpaseo/paseo/tree/main/plug
|
||||
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.
|
||||
|
||||
### Focus a composer
|
||||
|
||||
`insertText` and `setPrompt` do not move the cursor. Call `focus()` after them so the user can keep
|
||||
typing:
|
||||
|
||||
```tsx
|
||||
composer.insertText("- [ ] Tests\n");
|
||||
await composer.focus();
|
||||
```
|
||||
|
||||
`await composer.focus({ selection: { start: 0, end: composer.prompt.length } })` selects the prompt.
|
||||
|
||||
You do not need to know where the user is. On a running agent's composer, Paseo navigates to the
|
||||
agent's tab or workspace when it is not on screen, closes a phone's agent list or Explorer panel
|
||||
that covers it, and moves focus to the agent's pane, then focuses the input. When the input is
|
||||
already on screen and focused in its pane, `focus()` only puts the cursor there. A phone opens its
|
||||
keyboard.
|
||||
|
||||
`focus()` rejects when the composer cannot take focus: the agent was archived, its composer did not
|
||||
come on screen within 15 seconds, or the input refused focus. A rejected promise returned from a
|
||||
button's `onPress` or a Command Center item's `onSelect` shows the user an error toast.
|
||||
|
||||
Drafts and the New workspace composer are only reachable from an item inside them, so `focus()` does
|
||||
not navigate to them. It works while the composer is on screen, and rejects with "The composer is
|
||||
not on screen." when the user has already moved on. A locked composer accepts focus.
|
||||
|
||||
### 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:
|
||||
component, `composers(selector)` on every command context, and `client.composers(selector)` on the
|
||||
client context return the same `PluginComposer` that `useComposer()` gives an item, for an agent you
|
||||
name or for the focused one. Use `client.composers` in a callback that receives no context, such as a
|
||||
[pill's `onPress`](#composer-pills):
|
||||
|
||||
```tsx
|
||||
import { useComposers, type PluginClientContext } from "@getpaseo/plugin/client";
|
||||
@@ -2054,6 +2083,7 @@ you reach from outside:
|
||||
rejects with the provider's message. `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.
|
||||
- `focus()` reveals the composer first, as [described above](#focus-a-composer).
|
||||
- Drafts and the New workspace screen are not addressable by reference. Only an item inside one
|
||||
reaches its composer, through `useComposer()`.
|
||||
|
||||
@@ -2158,6 +2188,31 @@ const pill = client.addComposerPill({
|
||||
});
|
||||
```
|
||||
|
||||
A pill's `onPress` receives no arguments. Reach its agent's composer with
|
||||
[`client.composers`](#reach-a-composer-by-reference):
|
||||
|
||||
```tsx
|
||||
const pill = client.addComposerPill({
|
||||
id: "hello",
|
||||
workspaceId,
|
||||
agentId,
|
||||
button: {
|
||||
title: "Add HELLO to the composer",
|
||||
icon: "Hand",
|
||||
label: "Hello",
|
||||
behavior: {
|
||||
kind: "action",
|
||||
async onPress() {
|
||||
for (const composer of client.composers({ agentId })) {
|
||||
composer.insertText("HELLO");
|
||||
await composer.focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
For pills that follow the agent directory, use an explicit [owned list subscription](/docs/sdk/events#follow-one-agents-status).
|
||||
The [local plugin example](https://github.com/getpaseo/paseo/blob/main/plugin-examples/local-plugin/client/main.tsx)
|
||||
replaces registrations on each snapshot and aborts the observation during entry cleanup, including pending bootstrap.
|
||||
|
||||
@@ -1650,6 +1650,7 @@ export default function contribute(client: PluginClientContext) {
|
||||
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `context` | `"agent"` 表示运行中的 Agent,`"draft"` 表示工作区中的新 Agent,`"new-workspace"` 表示新建工作区界面。 |
|
||||
| `prompt`、`setPrompt(text)`、`insertText(text)` | 提示词。`insertText` 会替换选区,或在光标处插入。 |
|
||||
| `focus(options?)` | 把光标放进这个 composer,准备输入,光标就位后完成。`selection: { start, end }` 放置光标或选中文本,为 `prompt` 中的偏移量,并限制在其长度内;省略时光标保持原位。参见[聚焦 composer](#focus-a-composer)。 |
|
||||
| `attachments`、`addAttachment`、`removeAttachment` | 来自[你自己的附件来源](#add-a-composer-attachment-source)的项目。其他插件和 Paseo 自身的附件不会列出。 |
|
||||
| `agent.provider`、`model`、`mode`、`thinking` | Agent 当前使用的配置,或草稿将要创建的配置。 |
|
||||
| `agent.options` | 当前生效的 Provider 选项,包含默认值。在草稿中:创建 Agent 时将使用的选项,包含你所做的修改。 |
|
||||
@@ -1669,9 +1670,26 @@ export default function contribute(client: PluginClientContext) {
|
||||
|
||||
[Composer 控件示例](https://github.com/getpaseo/paseo/tree/main/plugin-examples/composer-controls)注册了一个 `Select`、两个 `Toggle`(其中一个写入 Provider 选项)和一个 `Action`,还有一个资源管理器面板和一个 Command Center 条目,它们按引用访问 composer。
|
||||
|
||||
### 聚焦 composer {#focus-a-composer}
|
||||
|
||||
`insertText` 和 `setPrompt` 不会移动光标。在它们之后调用 `focus()`,用户就可以继续输入:
|
||||
|
||||
```tsx
|
||||
composer.insertText("- [ ] Tests\n");
|
||||
await composer.focus();
|
||||
```
|
||||
|
||||
`await composer.focus({ selection: { start: 0, end: composer.prompt.length } })` 会选中提示词。
|
||||
|
||||
你不需要知道用户在哪里。对于运行中 Agent 的 composer,当它不在屏幕上时,Paseo 会导航到该 Agent 的标签页或工作区,关闭手机上遮挡它的 Agent 列表或资源管理器面板,并把焦点移到该 Agent 的窗格,然后聚焦输入框。输入框已在屏幕上且其窗格已聚焦时,`focus()` 只放置光标。手机会弹出键盘。
|
||||
|
||||
composer 无法获得焦点时 `focus()` 会被拒绝:Agent 已归档、其 composer 未在 15 秒内出现在屏幕上,或输入框拒绝获得焦点。按钮的 `onPress` 或命令中心项的 `onSelect` 返回被拒绝的 promise 时,会向用户显示错误提示。
|
||||
|
||||
草稿和新建工作区 composer 只能从其中的项访问,所以 `focus()` 不会导航到它们。composer 在屏幕上时可用,用户已离开时会以 “The composer is not on screen.” 拒绝。锁定的 composer 接受聚焦。
|
||||
|
||||
### 按引用访问 composer {#reach-a-composer-by-reference}
|
||||
|
||||
运行中的 Agent 无论标签页是否打开,都有自己的 composer。任意插件组件中的 `useComposers(selector)`,以及每个命令上下文中的 `composers(selector)`,都会返回与 `useComposer()` 给 composer 项的相同的 `PluginComposer`,对象可以是你指定的 Agent,也可以是当前聚焦的 Agent:
|
||||
运行中的 Agent 无论标签页是否打开,都有自己的 composer。任意插件组件中的 `useComposers(selector)`、每个命令上下文中的 `composers(selector)`,以及客户端上下文中的 `client.composers(selector)`,都会返回与 `useComposer()` 给 composer 项的相同的 `PluginComposer`,对象可以是你指定的 Agent,也可以是当前聚焦的 Agent。在不接收上下文的回调中(例如[胶囊的 `onPress`](#composer-pills))使用 `client.composers`:
|
||||
|
||||
```tsx
|
||||
import { useComposers, type PluginClientContext } from "@getpaseo/plugin/client";
|
||||
@@ -1721,6 +1739,7 @@ export default function contribute(client: PluginClientContext) {
|
||||
- 运行中 Agent 的 composer 从不锁定。
|
||||
- `setProvider` 会被拒绝,与在 Agent 自己的 composer 内一样。
|
||||
- `setModel` 和 `setThinking` 不加检查地把值发给 Provider。Provider 拒绝的值会以 Provider 的消息拒绝。`model` 和 `thinking` 读取的是模型选择器此刻显示的值:在 Provider 的模型目录加载完成之前,是 Agent 上报或创建时使用的 id(可能是别名),加载完成后是目录中的 id。
|
||||
- `focus()` 会先让 composer 显示出来,如[上文所述](#focus-a-composer)。
|
||||
- 草稿和新建工作区界面不能按引用访问,只有其中的项能通过 `useComposer()` 访问其 composer。
|
||||
|
||||
## 新建工作区项 {#new-workspace-items}
|
||||
@@ -1805,6 +1824,30 @@ const pill = client.addComposerPill({
|
||||
});
|
||||
```
|
||||
|
||||
胶囊的 `onPress` 不接收参数。通过 [`client.composers`](#reach-a-composer-by-reference) 访问其 Agent 的 composer:
|
||||
|
||||
```tsx
|
||||
const pill = client.addComposerPill({
|
||||
id: "hello",
|
||||
workspaceId,
|
||||
agentId,
|
||||
button: {
|
||||
title: "Add HELLO to the composer",
|
||||
icon: "Hand",
|
||||
label: "Hello",
|
||||
behavior: {
|
||||
kind: "action",
|
||||
async onPress() {
|
||||
for (const composer of client.composers({ agentId })) {
|
||||
composer.insertText("HELLO");
|
||||
await composer.focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
对于随 Agent 目录变化的胶囊,请使用显式的[自有列表订阅](/docs/sdk/events#follow-one-agents-status)。
|
||||
[本地插件示例](https://github.com/getpaseo/paseo/blob/main/plugin-examples/local-plugin/client/main.tsx)
|
||||
会在每次快照时替换注册,并在入口清理时中止观察,包括尚未完成的引导过程。
|
||||
|
||||
Reference in New Issue
Block a user