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
+47
View File
@@ -2,6 +2,8 @@ import { describe, expect, test } from "vitest";
import {
FileExplorerRequestSchema,
PaseoWorktreeArchiveRequestSchema,
parseServerInfoStatusPayload,
SessionInboundMessageSchema,
SessionOutboundMessageSchema,
} from "./messages.js";
@@ -131,6 +133,51 @@ describe("workspace descriptor message compatibility", () => {
});
});
describe("agent detach RPC", () => {
test("parses the namespaced detach request", () => {
const parsed = SessionInboundMessageSchema.parse({
type: "agent.detach.request",
agentId: "child-agent",
requestId: "req-detach",
});
expect(parsed).toEqual({
type: "agent.detach.request",
agentId: "child-agent",
requestId: "req-detach",
});
});
test("parses the namespaced detach response", () => {
const parsed = SessionOutboundMessageSchema.parse({
type: "agent.detach.response",
payload: {
requestId: "req-detach",
agentId: "child-agent",
accepted: true,
error: null,
},
});
expect(parsed.type).toBe("agent.detach.response");
});
test("parses the agentDetach server feature gate", () => {
const parsed = parseServerInfoStatusPayload({
status: "server_info",
serverId: "srv-test",
features: {
agentDetach: true,
},
});
if (!parsed) {
throw new Error("Expected server info payload to parse");
}
expect(parsed.features?.agentDetach).toBe(true);
});
});
describe("file explorer request compatibility", () => {
test("acceptBinary is optional for old clients and accepted for new clients", () => {
expect(
+17
View File
@@ -1308,6 +1308,17 @@ export const SetAgentFeatureResponseMessageSchema = z.object({
payload: AgentActionResponsePayloadSchema,
});
export const AgentDetachRequestMessageSchema = z.object({
type: z.literal("agent.detach.request"),
agentId: z.string(),
requestId: z.string(),
});
export const AgentDetachResponseMessageSchema = z.object({
type: z.literal("agent.detach.response"),
payload: AgentActionResponsePayloadSchema,
});
export const AgentRewindModeSchema = z.enum(["conversation", "files", "both"]);
export const AgentRewindRequestMessageSchema = z.object({
@@ -2021,6 +2032,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
SetAgentModelRequestMessageSchema,
SetAgentThinkingRequestMessageSchema,
SetAgentFeatureRequestMessageSchema,
AgentDetachRequestMessageSchema,
AgentRewindRequestMessageSchema,
AgentPermissionResponseMessageSchema,
CheckoutStatusRequestSchema,
@@ -2280,6 +2292,8 @@ export const ServerInfoStatusPayloadSchema = z
projectRemove: z.boolean().optional(),
// COMPAT(worktreeRestore): added in v0.1.97, drop the gate when floor >= v0.1.97
worktreeRestore: z.boolean().optional(),
// COMPAT(agentDetach): added in v0.1.98, remove gate after 2026-12-19 once daemon floor >= v0.1.98.
agentDetach: z.boolean().optional(),
})
.optional(),
})
@@ -4019,6 +4033,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
SetAgentModelResponseMessageSchema,
SetAgentThinkingResponseMessageSchema,
SetAgentFeatureResponseMessageSchema,
AgentDetachResponseMessageSchema,
AgentRewindResponseMessageSchema,
UpdateAgentResponseMessageSchema,
ProjectRenameResponseSchema,
@@ -4166,6 +4181,7 @@ export type SetAgentModeResponseMessage = z.infer<typeof SetAgentModeResponseMes
export type SetAgentModelResponseMessage = z.infer<typeof SetAgentModelResponseMessageSchema>;
export type SetAgentThinkingResponseMessage = z.infer<typeof SetAgentThinkingResponseMessageSchema>;
export type SetAgentFeatureResponseMessage = z.infer<typeof SetAgentFeatureResponseMessageSchema>;
export type AgentDetachResponseMessage = z.infer<typeof AgentDetachResponseMessageSchema>;
export type AgentRewindResponseMessage = z.infer<typeof AgentRewindResponseMessageSchema>;
export type UpdateAgentResponseMessage = z.infer<typeof UpdateAgentResponseMessageSchema>;
export type ProjectRenameResponse = z.infer<typeof ProjectRenameResponseSchema>;
@@ -4299,6 +4315,7 @@ export type SetAgentModeRequestMessage = z.infer<typeof SetAgentModeRequestMessa
export type SetAgentModelRequestMessage = z.infer<typeof SetAgentModelRequestMessageSchema>;
export type SetAgentThinkingRequestMessage = z.infer<typeof SetAgentThinkingRequestMessageSchema>;
export type SetAgentFeatureRequestMessage = z.infer<typeof SetAgentFeatureRequestMessageSchema>;
export type AgentDetachRequestMessage = z.infer<typeof AgentDetachRequestMessageSchema>;
export type AgentPermissionResponseMessage = z.infer<typeof AgentPermissionResponseMessageSchema>;
export type CheckoutStatusRequest = z.infer<typeof CheckoutStatusRequestSchema>;
export type CheckoutStatusResponse = z.infer<typeof CheckoutStatusResponseSchema>;