mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-03 23:23:21 +00:00
feat(server): add background agents
An agent can be created background in any workspace. It stays a durable, addressable agent, but default discovery, workspace status and attention leave it out. Effective background is agent.background || workspace.background. - Protocol: `create_agent_request.background` now marks the agent itself and stays the workspace intent for a workspace the request creates. Snapshots carry `background`. The daemon advertises `features.backgroundAgents`. Clients advertise the `background_agents` capability. - Daemon: the flag persists in the agent record, so restart and resume keep it. AgentDirectory owns visibility. Inclusive reads and subscriptions from sources without `background_agents` see only agents in background workspaces, because older apps would otherwise open a tab for each one. - Inheritance: omitted on agent-scoped creation, children and new workspaces take the caller agent's background. - MCP: create_agent accepts `background`. - SDK: placing a background agent in an existing workspace, or under a caller, fails fast with "Update the host to use background agents." on older hosts. - CLI: `ls -a` and `ls --background` add a BACKGROUND column.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { AgentSnapshotPayload } from "@getpaseo/protocol/messages";
|
||||
import { render } from "../../output/index.js";
|
||||
import { buildAgentLsFetchOptions, listAgents } from "./ls.js";
|
||||
|
||||
const daemonTarget = { kind: "endpoint" as const, host: "example.test:12345" };
|
||||
@@ -130,4 +131,26 @@ describe("runLsCommand", () => {
|
||||
|
||||
expect(result.data.map((item) => item.name)).toEqual(["agent 3", "agent 2", "agent 1"]);
|
||||
});
|
||||
|
||||
it("marks background agents in a BACKGROUND column only for inclusive listings", async () => {
|
||||
const client = createPagingClient();
|
||||
const fetchAgents: typeof client.fetchAgents = async (options) => {
|
||||
const page = await client.fetchAgents(options);
|
||||
return {
|
||||
...page,
|
||||
entries: page.entries.map(({ agent }) => ({
|
||||
agent: agent.title === "agent 2" ? { ...agent, background: true } : agent,
|
||||
})),
|
||||
};
|
||||
};
|
||||
const connect = async () => ({ ...client, fetchAgents });
|
||||
|
||||
const inclusive = await listAgents({ daemonTarget, all: true, global: true }, connect);
|
||||
const rows = render(inclusive, { noColor: true }).split("\n");
|
||||
expect(rows[0]).toMatch(/BACKGROUND\s*$/);
|
||||
expect(rows.slice(1).map((row) => row.trim().split(/\s+/).at(-1))).toEqual(["-", "yes", "-"]);
|
||||
|
||||
const ordinary = await listAgents({ daemonTarget, global: true }, connect);
|
||||
expect(render(ordinary, { noColor: true })).not.toContain("BACKGROUND");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ export function addLsOptions(cmd: Command): Command {
|
||||
.description("List agents. By default excludes archived agents.")
|
||||
.option("-a, --all", "Include archived and background agents")
|
||||
.option("-g, --global", "List agents across all directories")
|
||||
.option("--background", "Include background workspace agents")
|
||||
.option("--background", "Include background agents and agents in background workspaces")
|
||||
.option(
|
||||
"--label <key=value>",
|
||||
"Filter by label (can be used multiple times)",
|
||||
@@ -35,6 +35,7 @@ export interface AgentListItem {
|
||||
status: string;
|
||||
cwd: string;
|
||||
created: string;
|
||||
background: boolean;
|
||||
}
|
||||
|
||||
/** Helper to get relative time string */
|
||||
@@ -89,6 +90,15 @@ export const agentLsSchema: OutputSchema<AgentListItem> = {
|
||||
],
|
||||
};
|
||||
|
||||
/** Inclusive listings mark background agents so they read apart from visible ones. */
|
||||
const agentLsInclusiveSchema: OutputSchema<AgentListItem> = {
|
||||
...agentLsSchema,
|
||||
columns: [
|
||||
...agentLsSchema.columns,
|
||||
{ header: "BACKGROUND", field: (item) => (item.background ? "yes" : "-"), width: 10 },
|
||||
],
|
||||
};
|
||||
|
||||
/** Transform agent snapshot to AgentListItem */
|
||||
function toListItem(agent: AgentSnapshotPayload): AgentListItem {
|
||||
const model = normalizeModelId(agent.runtimeInfo?.model) ?? normalizeModelId(agent.model);
|
||||
@@ -101,6 +111,7 @@ function toListItem(agent: AgentSnapshotPayload): AgentListItem {
|
||||
status: agent.status,
|
||||
cwd: shortenPath(agent.cwd),
|
||||
created: relativeTime(agent.createdAt),
|
||||
background: agent.background === true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -259,7 +270,7 @@ export async function listAgents(
|
||||
return {
|
||||
type: "list",
|
||||
data: items,
|
||||
schema: agentLsSchema,
|
||||
schema: options.all || options.background ? agentLsInclusiveSchema : agentLsSchema,
|
||||
};
|
||||
} catch (err) {
|
||||
await client.close().catch(() => {});
|
||||
|
||||
Reference in New Issue
Block a user