* Add provider-owned subagent stream contract * OpenCode provider: detect child sessions, attach adopted children to the hosting server, synthesize external turns - Emit internal provider_child_session_detected / provider_session_deleted stream events from the global event stream (before the no-active-turn gate), including BFS hydration of existing descendants on subscribe - Track child sessions in a registry mapping child session id -> hosting server url; resumeSession attaches via acquireExisting() so adopted children share the parent's server generation and keep receiving live events - Synthesize external turns for adopted (externally driven) sessions so timeline, tool calls, and permission requests stream without a Paseo-initiated prompt - Honor launchContext.env in resumeSession (acquireDedicated parity with createSession) - Stop folding child tool parts into the parent's sub_agent action log; the parent row stays as a pointer * Render provider subagents in workspace timelines * fix provider subagent timeline reliability * fix provider subagent lifecycle edge cases * fix provider subagent event fidelity * Fix provider subagent history fidelity * Fix restored provider subagent state * Fix provider subagent visibility edges * Restore provider subagent timelines * Page provider subagent history * Forward subagent questions reliably * fix(subagents): preserve OpenCode child context * fix(subagents): harden provider timeline routing * fix(subagents): preserve provider page continuity * fix(subagents): retain live child prompts --------- Co-authored-by: Omer <639682+omercnet@users.noreply.github.com>
77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
import { SessionInboundMessageSchema, SessionOutboundMessageSchema } from "./messages.js";
|
|
|
|
describe("provider subagent protocol", () => {
|
|
test("accepts a scoped timeline request and structured live update", () => {
|
|
expect(
|
|
SessionInboundMessageSchema.parse({
|
|
type: "agent.provider_subagents.timeline.get.request",
|
|
parentAgentId: "parent-1",
|
|
subagentId: "child-1",
|
|
requestId: "request-1",
|
|
}),
|
|
).toMatchObject({ parentAgentId: "parent-1", subagentId: "child-1" });
|
|
|
|
expect(
|
|
SessionOutboundMessageSchema.parse({
|
|
type: "agent.provider_subagents.update",
|
|
payload: {
|
|
kind: "timeline",
|
|
parentAgentId: "parent-1",
|
|
subagentId: "child-1",
|
|
provider: "claude",
|
|
epoch: "epoch-1",
|
|
seq: 4,
|
|
timestamp: "2026-07-12T10:00:00.000Z",
|
|
item: { type: "assistant_message", text: "Found it." },
|
|
},
|
|
}),
|
|
).toMatchObject({
|
|
payload: {
|
|
kind: "timeline",
|
|
parentAgentId: "parent-1",
|
|
subagentId: "child-1",
|
|
seq: 4,
|
|
},
|
|
});
|
|
});
|
|
|
|
test("accepts a provider child working directory while remaining compatible when absent", () => {
|
|
const descriptor = {
|
|
id: "child-1",
|
|
parentAgentId: "parent-1",
|
|
provider: "opencode",
|
|
title: "Explore",
|
|
description: null,
|
|
status: "running",
|
|
createdAt: "2026-07-12T10:00:00.000Z",
|
|
updatedAt: "2026-07-12T10:00:00.000Z",
|
|
toolCallId: null,
|
|
};
|
|
|
|
expect(
|
|
SessionOutboundMessageSchema.parse({
|
|
type: "agent.provider_subagents.list.response",
|
|
payload: {
|
|
requestId: "request-1",
|
|
parentAgentId: "parent-1",
|
|
subagents: [{ ...descriptor, cwd: "/workspace/child" }],
|
|
error: null,
|
|
},
|
|
}),
|
|
).toMatchObject({ payload: { subagents: [{ cwd: "/workspace/child" }] } });
|
|
|
|
expect(
|
|
SessionOutboundMessageSchema.parse({
|
|
type: "agent.provider_subagents.list.response",
|
|
payload: {
|
|
requestId: "request-2",
|
|
parentAgentId: "parent-1",
|
|
subagents: [descriptor],
|
|
error: null,
|
|
},
|
|
}),
|
|
).toMatchObject({ payload: { subagents: [{ id: "child-1" }] } });
|
|
});
|
|
});
|