Detach subagents without archiving them (#1612)

* Add detach action for subagents

* Translate detached subagent connection error

* Address subagent detach review feedback

* Fix subagent integration test import

* Focus detached subagents as tabs
This commit is contained in:
Mohamed Boudra
2026-06-19 17:19:05 +08:00
committed by GitHub
parent cda66ae5f3
commit 7af92120fe
33 changed files with 1359 additions and 94 deletions
+42
View File
@@ -2762,6 +2762,48 @@ test("fetches agents via RPC with filters, sort, and pagination", async () => {
});
});
test("detaches an agent through the namespaced detach RPC", async () => {
const logger = createMockLogger();
const mock = createMockTransport();
const client = new DaemonClient({
url: "ws://test",
clientId: "clsk_unit_test",
logger,
reconnect: { enabled: false },
transportFactory: () => mock.transport,
});
clients.push(client);
const connectPromise = client.connect();
mock.triggerOpen();
await connectPromise;
const promise = client.detachAgent("child-agent");
expect(mock.sent).toHaveLength(1);
const request = parseSentFrame(mock.sent[0]);
expect(request).toMatchObject({
type: "agent.detach.request",
agentId: "child-agent",
});
expect(typeof request.requestId).toBe("string");
mock.triggerMessage(
wrapSessionMessage({
type: "agent.detach.response",
payload: {
requestId: request.requestId,
agentId: "child-agent",
accepted: true,
error: null,
},
}),
);
await expect(promise).resolves.toBeUndefined();
});
test("sends active-scoped fetch_agents_request", async () => {
const logger = createMockLogger();
const mock = createMockTransport();
+13
View File
@@ -2098,6 +2098,19 @@ export class DaemonClient {
return { archivedAt: result.archivedAt };
}
async detachAgent(agentId: string): Promise<void> {
const payload = await this.sendNamespacedCorrelatedSessionRequest<"agent.detach.response">({
message: {
type: "agent.detach.request",
agentId,
},
timeout: 10000,
});
if (!payload.accepted) {
throw new Error(payload.error ?? "detachAgent rejected");
}
}
async updateAgent(
agentId: string,
updates: { name?: string; labels?: Record<string, string> },
+4
View File
@@ -229,6 +229,7 @@ export interface PaseoAgentHandle {
refetch(requestId?: string): Promise<PaseoAgentRefetchResult | null>;
send(text: string, options?: PaseoAgentSendOptions): Promise<void>;
archive(): Promise<{ archivedAt: string }>;
detach(): Promise<void>;
subscribe(handler: (update: PaseoAgentUpdate) => void): () => void;
}
@@ -483,6 +484,9 @@ function createAgentHandleFactory(daemonClient: DaemonClient): AgentHandleFactor
}
return result;
},
detach: async () => {
await daemonClient.detachAgent(id);
},
subscribe: (handler) =>
daemonClient.on("agent_update", (message) => {
const update = message.payload;