mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-11 10:57:07 +00:00
Give plugins an agent's background work count and a background flag on hooks (#9362)
* feat(plugins): give plugins the agent's sidebar indicator and background flag Plugin UI re-derived agent state from status and attention fields and could not see background work, so it showed agents as done while a shell they left running was still live. Agent snapshots now carry the sidebar's own bucket as indicator/indicatorLabel, computed by the same function the sidebar uses so the two cannot drift. Lifecycle hooks and the agent.open.before request now carry `background`, so a hook can skip a hidden helper agent without a lookup. A hook cannot change it; an omitted value means unchanged, like metadata. `finished` attention still means the turn finished; background work is the indicator's concern. * fix(plugins): keep session-open hooks that omit background typechecking A hook that returns its own object instead of spreading the request has no background field; the result type now treats it as optional, like metadata, matching the runtime where an omitted value means unchanged. * refactor(plugins): give agent snapshots the background work count instead of an indicator * fix(plugins): keep legacy session-open hooks that omit background typechecking
This commit is contained in:
@@ -76,6 +76,7 @@ interface PluginHookAgent {
|
||||
provider: string;
|
||||
cwd: string;
|
||||
title: string | null;
|
||||
readonly background: boolean;
|
||||
metadata: Record<string, JsonValue>;
|
||||
}
|
||||
|
||||
@@ -96,6 +97,7 @@ type PluginTurnOutcome =
|
||||
|
||||
| Field | Shape / meaning |
|
||||
| ------------ | -------------------------------------------------------------------------------------- |
|
||||
| `background` | `true` for a [background agent](#background-agents) |
|
||||
| `turnId` | Provider-reported `string` or `null`; unique across resumed sessions |
|
||||
| `timeline` | Deprecated, always `[]`; fetch history with the SDK. |
|
||||
| `request` | SDK `AgentPermissionRequest`; `kind` is `tool`, `plan`, `question`, `mode`, or `other` |
|
||||
@@ -303,6 +305,7 @@ server.addHook("agent.open.before", async ({ request }, { paseo }) => {
|
||||
"provider": "codex",
|
||||
"cwd": "/projects/shop",
|
||||
"title": "Workspace notes",
|
||||
"background": false,
|
||||
"labels": { "notes/enabled": "true" },
|
||||
"metadata": { "linear.issue": { "id": "ENG-123" } },
|
||||
"tools": [],
|
||||
@@ -317,6 +320,7 @@ server.addHook("agent.open.before", async ({ request }, { paseo }) => {
|
||||
| ------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `workspaceId` | String or `null` |
|
||||
| `title` | Read-only string or `null`; creation title on create, stored title on resume. |
|
||||
| `background` | Read-only `true` for a [background agent](#background-agents). Fixed at creation. |
|
||||
| `labels` | Read-only string map; creation labels on create, stored labels on resume. |
|
||||
| `metadata` | Read-only JSON map; the [metadata](#metadata) stored on the agent. |
|
||||
| `tools` | Editable `AgentToolDefinition[]`, local to this plugin. See [Attach tools to agents](#attach-tools-to-agents). |
|
||||
@@ -350,7 +354,7 @@ the placeholder URL with your MCP endpoint:
|
||||
|
||||
```ts
|
||||
server.addHook("agent.open.before", async ({ request }) => {
|
||||
if (request.provider !== "codex") return;
|
||||
if (request.provider !== "codex" || request.background) return;
|
||||
|
||||
return {
|
||||
...request,
|
||||
@@ -426,6 +430,7 @@ Select agents using any of the session-open request fields:
|
||||
| Provider | `request.provider === "codex"` |
|
||||
| Workspace | `request.workspaceId === "workspace-456"` |
|
||||
| Individual agent | `request.agentId === chosenAgentId` |
|
||||
| Background agent | `request.background` |
|
||||
|
||||
`request.tools` starts empty for each plugin. Each callback sees the tools returned by earlier
|
||||
callbacks in that plugin; replace the array to add or remove tools. Returning `undefined` preserves
|
||||
@@ -623,8 +628,21 @@ server.addHook("agent.permission.after", async ({ agent, requestId, resolution }
|
||||
});
|
||||
```
|
||||
|
||||
## Background agents
|
||||
|
||||
Agent hooks exclude the daemon's private disposable helpers. Background agents and the agents in background workspaces use the same hooks.
|
||||
|
||||
A [background agent](/docs/sdk/agents#run-a-helper-as-a-background-agent) is a helper hidden from tabs, lists and attention. Every hook's `agent.background` and the `agent.open.before` request's `background` are `true` for one, so you can skip it without a lookup:
|
||||
|
||||
```ts
|
||||
server.addHook("agent.turn.after", async ({ agent }) => {
|
||||
if (agent.background) return; // A helper reports to the agent that created it.
|
||||
await notifyTeam(agent);
|
||||
});
|
||||
```
|
||||
|
||||
`background` is fixed when the agent is created and a hook cannot change it. An agent created in a background workspace without `background: true` reads `false`. Hosts older than 0.16.0 omit the field, so it reads as `undefined`.
|
||||
|
||||
## Ordering and returned values
|
||||
|
||||
```text
|
||||
|
||||
@@ -74,6 +74,7 @@ interface PluginHookAgent {
|
||||
provider: string;
|
||||
cwd: string;
|
||||
title: string | null;
|
||||
readonly background: boolean;
|
||||
metadata: Record<string, JsonValue>;
|
||||
}
|
||||
|
||||
@@ -94,6 +95,7 @@ type PluginTurnOutcome =
|
||||
|
||||
| 字段 | 结构 / 含义 |
|
||||
| ------------ | ---------------------------------------------------------------------------------------- |
|
||||
| `background` | [后台 Agent](#background-agents) 为 `true` |
|
||||
| `turnId` | Provider 报告的 `string` 或 `null`;在会话恢复后仍保持唯一 |
|
||||
| `timeline` | 已弃用,始终为 `[]`;通过 SDK 获取历史。 |
|
||||
| `request` | SDK 的 `AgentPermissionRequest`;`kind` 为 `tool`、`plan`、`question`、`mode` 或 `other` |
|
||||
@@ -299,6 +301,7 @@ server.addHook("agent.open.before", async ({ request }, { paseo }) => {
|
||||
"provider": "codex",
|
||||
"cwd": "/projects/shop",
|
||||
"title": "Workspace notes",
|
||||
"background": false,
|
||||
"labels": { "notes/enabled": "true" },
|
||||
"metadata": { "linear.issue": { "id": "ENG-123" } },
|
||||
"tools": [],
|
||||
@@ -313,6 +316,7 @@ server.addHook("agent.open.before", async ({ request }, { paseo }) => {
|
||||
| ------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| `workspaceId` | 字符串或 `null` |
|
||||
| `title` | 只读字符串或 `null`;创建时使用创建请求的标题,恢复时使用已保存的标题。 |
|
||||
| `background` | 只读;[后台 Agent](#background-agents) 为 `true`。在创建时确定。 |
|
||||
| `labels` | 只读的字符串映射;创建时使用创建请求的标签,恢复时使用已保存的标签。 |
|
||||
| `metadata` | 只读的 JSON 映射;Agent 上已保存的[元数据](#metadata)。 |
|
||||
| `tools` | 可编辑的 `AgentToolDefinition[]`,仅包含此插件的工具。参阅[为 Agent 附加工具](#attach-tools-to-agents)。 |
|
||||
@@ -344,7 +348,7 @@ server.addHook("agent.open.before", async ({ request }) => {
|
||||
|
||||
```ts
|
||||
server.addHook("agent.open.before", async ({ request }) => {
|
||||
if (request.provider !== "codex") return;
|
||||
if (request.provider !== "codex" || request.background) return;
|
||||
|
||||
return {
|
||||
...request,
|
||||
@@ -418,6 +422,7 @@ const agent = await paseo.agents.create({
|
||||
| Provider | `request.provider === "codex"` |
|
||||
| 工作区 | `request.workspaceId === "workspace-456"` |
|
||||
| 单个 Agent | `request.agentId === chosenAgentId` |
|
||||
| 后台 Agent | `request.background` |
|
||||
|
||||
每个插件的 `request.tools` 都从空数组开始。同一插件中的后续回调会看到前面回调返回的工具;
|
||||
替换数组即可添加或移除工具。返回 `undefined` 会保留请求。`purpose: "history"` 时会忽略工具。
|
||||
@@ -606,8 +611,21 @@ server.addHook("agent.permission.after", async ({ agent, requestId, resolution }
|
||||
});
|
||||
```
|
||||
|
||||
## 后台 Agent {#background-agents}
|
||||
|
||||
Agent 钩子不包括 daemon 的私有临时辅助 Agent。后台 Agent 和后台工作区中的 Agent 使用相同钩子。
|
||||
|
||||
[后台 Agent](/docs/sdk/agents#run-a-helper-as-a-background-agent) 是不出现在标签页、列表和提醒中的辅助 Agent。对后台 Agent,每个钩子的 `agent.background` 以及 `agent.open.before` 请求的 `background` 都为 `true`,无需额外查询即可跳过它:
|
||||
|
||||
```ts
|
||||
server.addHook("agent.turn.after", async ({ agent }) => {
|
||||
if (agent.background) return; // A helper reports to the agent that created it.
|
||||
await notifyTeam(agent);
|
||||
});
|
||||
```
|
||||
|
||||
`background` 在创建 Agent 时确定,钩子无法修改。在后台工作区中创建但没有传 `background: true` 的 Agent 读取到 `false`。早于 0.16.0 的主机不发送该字段,读取结果为 `undefined`。
|
||||
|
||||
## 顺序与返回值 {#ordering-and-returned-values}
|
||||
|
||||
```text
|
||||
|
||||
@@ -1572,6 +1572,8 @@ Agent snapshot fields:
|
||||
| `workspaceId` | `string` |
|
||||
| `provider` | `string` |
|
||||
| `status` | `"initializing" \| "idle" \| "running" \| "error" \| "closed"` |
|
||||
| `backgroundWork` | `{ count: number }` |
|
||||
| `background` | `boolean` |
|
||||
| `createdAt` | ISO timestamp |
|
||||
| `updatedAt` | ISO timestamp |
|
||||
| `lastActivityAt` | ISO timestamp |
|
||||
@@ -1585,6 +1587,27 @@ Agent snapshot fields:
|
||||
| `parentAgentId` | `string \| null` |
|
||||
| `labels` | `Record<string, string>` |
|
||||
|
||||
`backgroundWork.count` is the number of shells and monitors the agent left running after its turn,
|
||||
and `0` when none are live. An agent can be `idle` with `attentionReason: "finished"` while that
|
||||
work still runs, so combine the fields the way your plugin needs:
|
||||
|
||||
```tsx
|
||||
const busy = useAgent(
|
||||
agentId,
|
||||
(agent) => agent.status === "running" || agent.backgroundWork.count > 0,
|
||||
);
|
||||
if (busy === null) return null; // The agent is unavailable.
|
||||
```
|
||||
|
||||
To list or stop the work itself, use
|
||||
[`paseo.agents.ref(id).backgroundWork`](/docs/sdk/reference#agent-handle).
|
||||
|
||||
`background` is `true` for a [background agent](/docs/sdk/agents#run-a-helper-as-a-background-agent),
|
||||
a helper hidden from tabs, lists and attention. It is fixed when the agent is created. An agent in a
|
||||
background workspace reads `false` unless it was created as a background agent; read the
|
||||
workspace's own `background` for that. `backgroundWork` and `background` require Paseo 0.16.0 or
|
||||
later.
|
||||
|
||||
Paseo owns tab focus, splitting, closing, persistence, query state, the API/RPC providers, and the render error boundary. A restored tab whose plugin, panel, context, workspace, or agent is unavailable stays open with an unavailable message instead of crashing the workspace.
|
||||
|
||||
## File panels
|
||||
|
||||
@@ -1331,6 +1331,8 @@ Agent 快照字段:
|
||||
| `workspaceId` | `string` |
|
||||
| `provider` | `string` |
|
||||
| `status` | `"initializing" \| "idle" \| "running" \| "error" \| "closed"` |
|
||||
| `backgroundWork` | `{ count: number }` |
|
||||
| `background` | `boolean` |
|
||||
| `createdAt` | ISO 时间戳 |
|
||||
| `updatedAt` | ISO 时间戳 |
|
||||
| `lastActivityAt` | ISO 时间戳 |
|
||||
@@ -1344,6 +1346,20 @@ Agent 快照字段:
|
||||
| `parentAgentId` | `string \| null` |
|
||||
| `labels` | `Record<string, string>` |
|
||||
|
||||
`backgroundWork.count` 是 Agent 在回合结束后仍在运行的 shell 和监视器数量,没有时为 `0`。Agent 可能处于 `idle` 且 `attentionReason: "finished"`,同时这些工作仍在运行,因此请按插件的需要组合这些字段:
|
||||
|
||||
```tsx
|
||||
const busy = useAgent(
|
||||
agentId,
|
||||
(agent) => agent.status === "running" || agent.backgroundWork.count > 0,
|
||||
);
|
||||
if (busy === null) return null; // Agent 不可用。
|
||||
```
|
||||
|
||||
要列出或停止这些工作本身,使用 [`paseo.agents.ref(id).backgroundWork`](/docs/sdk/reference#agent-handle)。
|
||||
|
||||
[后台 Agent](/docs/sdk/agents#run-a-helper-as-a-background-agent) 的 `background` 为 `true`,它是不出现在标签页、列表和提醒中的辅助 Agent,在创建时确定。后台工作区中的 Agent 除非本身以后台 Agent 创建,否则 `background` 为 `false`;要判断工作区,读取工作区自己的 `background`。`backgroundWork` 和 `background` 需要 Paseo 0.16.0 或更高版本。
|
||||
|
||||
标签页的焦点、拆分、关闭、持久化、查询状态、API/RPC Provider 以及渲染错误边界都由 Paseo 负责。如果恢复的标签页所对应的插件、面板、上下文、工作区或 Agent 不可用,该标签页会保持打开并显示“不可用”消息,而不会让工作区崩溃。
|
||||
|
||||
## 文件面板 {#file-panels}
|
||||
|
||||
@@ -110,7 +110,8 @@ Existing workspace panels can continue passing a host-local string ID to `useWor
|
||||
Workspace snapshots include `project`, `pinnedAt`, `labels`, `background`, `branch`,
|
||||
`lastPromptAt`, `parentAgent` and `createdAt` (null on older hosts). Use `indicator` and the
|
||||
localized `indicatorLabel` to display the status Paseo shows. `status` remains the raw daemon
|
||||
status. Agent snapshots include their workspace and parent refs. Project snapshots provide
|
||||
status. Agent snapshots include their workspace and parent refs, `background`, and
|
||||
`backgroundWork.count`. Project snapshots provide
|
||||
`displayName`, `rootPath`, `kind` and `iconRevision`.
|
||||
|
||||
## List terminals
|
||||
|
||||
@@ -86,7 +86,7 @@ const selected = useWorkspace(ref, selector, equals);
|
||||
|
||||
没有选择器时,自定义相等比较函数接收快照;有选择器时接收选择结果。现有工作区面板仍可向 `useWorkspace` 和 `useAgent` 传入主机本地的字符串 ID。跨主机视图应使用引用。`useHosts()` 保留现有的 `serverId`、`label` 和连接 `status` 字段。
|
||||
|
||||
工作区快照包含 `project`、`pinnedAt`、`labels`、`background`、`branch`、`lastPromptAt`、`parentAgent` 和 `createdAt`(旧主机上为 null)。使用 `indicator` 和本地化的 `indicatorLabel` 显示 Paseo 的状态。`status` 保持 daemon 原始状态。Agent 快照包含工作区和父 Agent 引用。项目快照提供 `displayName`、`rootPath`、`kind` 和 `iconRevision`。
|
||||
工作区快照包含 `project`、`pinnedAt`、`labels`、`background`、`branch`、`lastPromptAt`、`parentAgent` 和 `createdAt`(旧主机上为 null)。使用 `indicator` 和本地化的 `indicatorLabel` 显示 Paseo 的状态。`status` 保持 daemon 原始状态。Agent 快照包含工作区和父 Agent 引用、`background` 和 `backgroundWork.count`。项目快照提供 `displayName`、`rootPath`、`kind` 和 `iconRevision`。
|
||||
|
||||
## 列出终端 {#list-terminals}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user