mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-04 15:34:39 +00:00
feat(plugins): New workspace items set the branch and workspace metadata
addNewWorkspaceItem mounts a plugin field on the New workspace screen, and useNewWorkspace() exposes host, project, isolation, base ref, branch and metadata with commands that follow the composer item rules: they act on the screen's state when they run, resolve once applied, reject while the screen is locked, and take JSON payloads. Host and project are read-only because a command that switched them would unmount the item that called it. The screen's pickable state (base selection, manual isolation, branch, metadata) moves into one plain store so pickers and plugin commands write to the same place; the screen reports derived facts to it on each render. The branch is sent as source.branchName only for a branch-off worktree, and metadata on workspace.create. Items render through ComposerControl with a new "field" surface (badge on desktop, full-width field when compact). Combobox ships here: Paseo owns the loading and error states, keeps previous results, and drops superseded searches.
This commit is contained in:
@@ -15,6 +15,7 @@ A plugin is a TypeScript project installed into one Paseo daemon. It can add
|
||||
[Command Center items](/docs/plugins/reference#command-center-items),
|
||||
[slash commands](/docs/plugins/reference#slash-commands),
|
||||
[composer items](/docs/plugins/reference#composer-items),
|
||||
[New workspace items](/docs/plugins/reference#new-workspace-items),
|
||||
[composer pills](/docs/plugins/reference#composer-pills),
|
||||
[timeline items](/docs/plugins/reference#timeline-items),
|
||||
[themes](/docs/plugins/reference#contribute-a-theme),
|
||||
|
||||
@@ -6,7 +6,7 @@ nav: 快速上手
|
||||
|
||||
# 插件快速上手 {#plugin-quickstart}
|
||||
|
||||
插件是安装到某个 Paseo daemon 中的 TypeScript 项目。它可以添加[界面和侧边栏项](/docs/plugins/reference#screens-and-sidebar-items)、[工作区面板](/docs/plugins/reference#workspace-panels)、[文件面板](/docs/plugins/reference#file-panels)、[Command Center 项](/docs/plugins/reference#command-center-items)、[斜杠命令](/docs/plugins/reference#slash-commands)、[Composer 项](/docs/plugins/reference#composer-items)、[Composer 标签](/docs/plugins/reference#composer-pills)、[时间线条目](/docs/plugins/reference#timeline-items)、[主题](/docs/plugins/reference#contribute-a-theme)、[附件来源](/docs/plugins/reference#add-a-composer-attachment-source)和 [daemon 端 RPC](/docs/plugins/reference#add-plugin-specific-backend-behavior)。它还可以[把编程 Agent 接入为 Provider](/docs/plugins/providers)。客户端贡献会在连接到该 daemon 的每个 Paseo 客户端上运行,包括移动端。
|
||||
插件是安装到某个 Paseo daemon 中的 TypeScript 项目。它可以添加[界面和侧边栏项](/docs/plugins/reference#screens-and-sidebar-items)、[工作区面板](/docs/plugins/reference#workspace-panels)、[文件面板](/docs/plugins/reference#file-panels)、[Command Center 项](/docs/plugins/reference#command-center-items)、[斜杠命令](/docs/plugins/reference#slash-commands)、[Composer 项](/docs/plugins/reference#composer-items)、[新建工作区项](/docs/plugins/reference#new-workspace-items)、[Composer 标签](/docs/plugins/reference#composer-pills)、[时间线条目](/docs/plugins/reference#timeline-items)、[主题](/docs/plugins/reference#contribute-a-theme)、[附件来源](/docs/plugins/reference#add-a-composer-attachment-source)和 [daemon 端 RPC](/docs/plugins/reference#add-plugin-specific-backend-behavior)。它还可以[把编程 Agent 接入为 Provider](/docs/plugins/providers)。客户端贡献会在连接到该 daemon 的每个 Paseo 客户端上运行,包括移动端。
|
||||
|
||||
浏览[已发布的插件](https://paseo.sh/plugins),然后在你的 daemon 上安装一个:
|
||||
|
||||
|
||||
@@ -1984,6 +1984,77 @@ 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`.
|
||||
|
||||
## New workspace items
|
||||
|
||||
`client.addNewWorkspaceItem({ id, Component })` puts a field on the New workspace screen, beside
|
||||
the project, host and branch pickers. `useNewWorkspace()` reads and changes what the screen will
|
||||
create, and `Combobox`, `Select`, `Toggle` and `Action` from `@getpaseo/plugin/client/ui` draw the
|
||||
fields. On desktop an item is a badge in the setup row; on a phone it is a full-width field.
|
||||
|
||||
```tsx
|
||||
import { useNewWorkspace, useRpc, type PluginClientContext } from "@getpaseo/plugin/client";
|
||||
import { Combobox } from "@getpaseo/plugin/client/ui";
|
||||
import { searchIssuesRpc } from "../shared/issues";
|
||||
|
||||
function IssueField() {
|
||||
const newWorkspace = useNewWorkspace();
|
||||
const searchIssues = useRpc(searchIssuesRpc);
|
||||
const issueId = newWorkspace.metadata["issues.id"];
|
||||
return (
|
||||
<Combobox
|
||||
title="Issue"
|
||||
icon="CircleDot"
|
||||
value={typeof issueId === "string" ? { id: issueId, label: issueId } : null}
|
||||
search={async ({ query }) => (await searchIssues({ query })).issues}
|
||||
onSelect={async ({ item }) => {
|
||||
await newWorkspace.setBranch(item.branch);
|
||||
await newWorkspace.setMetadata({ "issues.id": item.id });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function contribute(client: PluginClientContext) {
|
||||
client.addNewWorkspaceItem({ id: "issue", Component: IssueField });
|
||||
return () => {};
|
||||
}
|
||||
```
|
||||
|
||||
`useNewWorkspace()` returns the setup the item sits in:
|
||||
|
||||
| Field or command | Meaning |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `host`, `project` | The user's choices, `{ id, label }` and `{ id, name } \| null`. Read-only: an item cannot switch host or project. |
|
||||
| `isolation`, `setIsolation` | `"local"` or `"worktree"`. A project that cannot make worktrees reads `"local"`, and `setIsolation("worktree")` rejects. |
|
||||
| `baseRef`, `setBaseRef` | What a new worktree starts from: `{ type: "branch", name }`, `{ type: "change-request", number }`, or `null` until the default is known. `setBaseRef({ type: "branch", name })` rejects while no project is chosen. |
|
||||
| `branch`, `setBranch` | The name of the branch a new worktree is created on. `null` lets Paseo pick one. It is kept while isolation is local or the base is a change request, and it applies only to a worktree created from a branch. |
|
||||
| `metadata`, `setMetadata` | The created workspace's [metadata](/docs/plugins/hooks#metadata), not the first agent's. Top-level keys replace; `null` removes a key. Rejects on a host that does not support metadata with "Update the host to use metadata." |
|
||||
|
||||
Commands follow the [composer item](#composer-items) rules: each one acts on the screen's state
|
||||
when it runs, so `await newWorkspace.setBranch(name)` followed by `newWorkspace.setMetadata(patch)`
|
||||
changes both, and its promise resolves once the change has taken effect. Payloads are JSON values.
|
||||
`metadata` is a frozen copy. While the screen creates the workspace it is locked: your fields render
|
||||
disabled and every command rejects with "The New workspace screen is locked while the workspace is
|
||||
being created." A callback that returns a promise keeps the control disabled until it settles, and a
|
||||
rejection reaches the user as an error toast.
|
||||
|
||||
`Combobox` is a field with a searchable list whose results come from your code:
|
||||
|
||||
| Prop | Meaning |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `value` | `{ id, label, description? } \| null`, what the field shows. It need not be among the search results, so it can show a choice made earlier. |
|
||||
| `search` | `({ query }) => Promise<Item[]>`. Called with `""` when the list opens and again, debounced, as the user types. Each `Item` is `{ id, label, description? }` plus any fields you add. |
|
||||
| `onSelect` | `({ item }) => void \| Promise<void>`. `item` is the object `search` returned. |
|
||||
|
||||
Paseo shows "Searching…" while a search runs, "Search failed" with the error message when it
|
||||
rejects, and an empty state for no results. It keeps the previous results while a newer search runs
|
||||
and ignores a search that a newer one replaced, so `search` needs no cancellation of its own.
|
||||
`Combobox` takes the same `title`, `icon` and `disabled` as the other controls, and draws as a row
|
||||
with a list in the composer's overflow sheet too.
|
||||
|
||||
The [New workspace issues example](https://github.com/getpaseo/paseo/tree/main/plugin-examples/new-workspace-issues)
|
||||
searches a fixed issue list from a daemon handler and sets the branch and metadata from the choice.
|
||||
|
||||
## Composer pills
|
||||
|
||||
`client.addComposerPill({ id, workspaceId, agentId, button })` uses the same [button descriptor](#button-descriptor)
|
||||
|
||||
@@ -1665,6 +1665,63 @@ export default function contribute(client: PluginClientContext) {
|
||||
|
||||
[Composer 控件示例](https://github.com/getpaseo/paseo/tree/main/plugin-examples/composer-controls)注册了一个 `Select`、两个 `Toggle`(其中一个写入 Provider 选项)和一个 `Action`。
|
||||
|
||||
## 新建工作区项 {#new-workspace-items}
|
||||
|
||||
`client.addNewWorkspaceItem({ id, Component })` 在新建工作区界面上放置一个字段,位于项目、主机和分支选择器旁边。`useNewWorkspace()` 读取并修改界面将要创建的内容,`@getpaseo/plugin/client/ui` 中的 `Combobox`、`Select`、`Toggle` 和 `Action` 负责绘制字段。桌面端的项是设置行中的徽章;手机上是整行宽度的字段。
|
||||
|
||||
```tsx
|
||||
import { useNewWorkspace, useRpc, type PluginClientContext } from "@getpaseo/plugin/client";
|
||||
import { Combobox } from "@getpaseo/plugin/client/ui";
|
||||
import { searchIssuesRpc } from "../shared/issues";
|
||||
|
||||
function IssueField() {
|
||||
const newWorkspace = useNewWorkspace();
|
||||
const searchIssues = useRpc(searchIssuesRpc);
|
||||
const issueId = newWorkspace.metadata["issues.id"];
|
||||
return (
|
||||
<Combobox
|
||||
title="Issue"
|
||||
icon="CircleDot"
|
||||
value={typeof issueId === "string" ? { id: issueId, label: issueId } : null}
|
||||
search={async ({ query }) => (await searchIssues({ query })).issues}
|
||||
onSelect={async ({ item }) => {
|
||||
await newWorkspace.setBranch(item.branch);
|
||||
await newWorkspace.setMetadata({ "issues.id": item.id });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function contribute(client: PluginClientContext) {
|
||||
client.addNewWorkspaceItem({ id: "issue", Component: IssueField });
|
||||
return () => {};
|
||||
}
|
||||
```
|
||||
|
||||
`useNewWorkspace()` 返回该项所在的设置:
|
||||
|
||||
| 字段或命令 | 含义 |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `host`、`project` | 用户的选择,分别是 `{ id, label }` 和 `{ id, name } \| null`。只读:项不能切换主机或项目。 |
|
||||
| `isolation`、`setIsolation` | `"local"` 或 `"worktree"`。无法创建 worktree 的项目读取为 `"local"`,此时 `setIsolation("worktree")` 会被拒绝。 |
|
||||
| `baseRef`、`setBaseRef` | 新 worktree 的起点:`{ type: "branch", name }`、`{ type: "change-request", number }`,默认值未知时为 `null`。未选择项目时,`setBaseRef({ type: "branch", name })` 会被拒绝。 |
|
||||
| `branch`、`setBranch` | 新 worktree 所创建的分支名称。`null` 表示由 Paseo 选择名称。隔离为本地或起点为变更请求时会保留该值,它只对从分支创建的 worktree 生效。 |
|
||||
| `metadata`、`setMetadata` | 所创建工作区的[元数据](/docs/plugins/hooks#metadata),而不是第一个 Agent 的元数据。顶层键替换,`null` 删除该键。主机不支持元数据时以 “Update the host to use metadata.” 拒绝。 |
|
||||
|
||||
命令遵循 [Composer 项](#composer-items)的规则:每个命令都按界面运行时的状态执行,所以 `await newWorkspace.setBranch(name)` 之后再调用 `newWorkspace.setMetadata(patch)` 会同时生效,命令的 Promise 在更改生效后完成。载荷是 JSON 值。`metadata` 是冻结的副本。界面创建工作区期间会被锁定:你的字段显示为禁用,所有命令都会以 "The New workspace screen is locked while the workspace is being created." 拒绝。返回 Promise 的回调在完成之前会让控件保持禁用,被拒绝时会以错误提示向用户显示。
|
||||
|
||||
`Combobox` 是带可搜索列表的字段,结果由你的代码提供:
|
||||
|
||||
| 属性 | 含义 |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `value` | `{ id, label, description? } \| null`,字段显示的内容。它不必出现在搜索结果中,因此可以显示之前所做的选择。 |
|
||||
| `search` | `({ query }) => Promise<Item[]>`。列表打开时以 `""` 调用,之后随用户输入防抖调用。每个 `Item` 是 `{ id, label, description? }` 加上你添加的字段。 |
|
||||
| `onSelect` | `({ item }) => void \| Promise<void>`。`item` 就是 `search` 返回的对象。 |
|
||||
|
||||
搜索进行时 Paseo 显示 “Searching…”,搜索被拒绝时显示 “Search failed” 和错误信息,没有结果时显示空状态。新搜索进行期间会保留之前的结果,并忽略已被更新搜索取代的结果,所以 `search` 不需要自己处理取消。`Combobox` 接收与其他控件相同的 `title`、`icon` 和 `disabled`。
|
||||
|
||||
[新建工作区 Issue 示例](https://github.com/getpaseo/paseo/tree/main/plugin-examples/new-workspace-issues)通过守护进程处理器搜索一个固定的 Issue 列表,并根据所选项设置分支和元数据。
|
||||
|
||||
## Composer 胶囊 {#composer-pills}
|
||||
|
||||
`client.addComposerPill({ id, workspaceId, agentId, button })` 使用同样的[按钮描述符](#button-descriptor),
|
||||
|
||||
Reference in New Issue
Block a user