Show provider subagents in the subagents track (#2013)
* 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>
This commit is contained in:
@@ -555,6 +555,7 @@ test("advertises client capabilities in hello", async () => {
|
||||
protocolVersion: 1,
|
||||
capabilities: {
|
||||
custom_mode_icons: true,
|
||||
provider_subagents: true,
|
||||
reasoning_merge_enum: true,
|
||||
terminal_reflowable_snapshot: true,
|
||||
browser_host: {
|
||||
|
||||
@@ -490,6 +490,22 @@ export interface FetchAgentTimelineOptions {
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export type ProviderSubagentListPayload = Extract<
|
||||
SessionOutboundMessage,
|
||||
{ type: "agent.provider_subagents.list.response" }
|
||||
>["payload"];
|
||||
export type ProviderSubagentTimelinePayload = Extract<
|
||||
SessionOutboundMessage,
|
||||
{ type: "agent.provider_subagents.timeline.get.response" }
|
||||
>["payload"];
|
||||
export interface FetchProviderSubagentTimelineOptions {
|
||||
direction?: ProviderSubagentTimelinePayload["direction"];
|
||||
cursor?: FetchAgentTimelineCursor;
|
||||
limit?: number;
|
||||
requestId?: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
// COMPAT(daemon-client-object-options): added in v0.1.102; remove after
|
||||
// 2026-12-29 once SDK callers have migrated to object parameters.
|
||||
function normalizeFetchAgentOptions(
|
||||
@@ -2429,6 +2445,65 @@ export class DaemonClient {
|
||||
return payload;
|
||||
}
|
||||
|
||||
async listProviderSubagents(
|
||||
parentAgentId: string,
|
||||
options: { requestId?: string; timeout?: number } = {},
|
||||
): Promise<ProviderSubagentListPayload> {
|
||||
const requestId = this.createRequestId(options.requestId);
|
||||
const message = SessionInboundMessageSchema.parse({
|
||||
type: "agent.provider_subagents.list.request",
|
||||
parentAgentId,
|
||||
requestId,
|
||||
});
|
||||
const payload = await this.sendRequest({
|
||||
requestId,
|
||||
message,
|
||||
timeout: options.timeout,
|
||||
options: { skipQueue: true },
|
||||
select: (response) =>
|
||||
response.type === "agent.provider_subagents.list.response" &&
|
||||
response.payload.requestId === requestId
|
||||
? response.payload
|
||||
: null,
|
||||
});
|
||||
if (payload.error) {
|
||||
throw new Error(payload.error);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
async fetchProviderSubagentTimeline(
|
||||
parentAgentId: string,
|
||||
subagentId: string,
|
||||
options: FetchProviderSubagentTimelineOptions = {},
|
||||
): Promise<ProviderSubagentTimelinePayload> {
|
||||
const requestId = this.createRequestId(options.requestId);
|
||||
const message = SessionInboundMessageSchema.parse({
|
||||
type: "agent.provider_subagents.timeline.get.request",
|
||||
parentAgentId,
|
||||
subagentId,
|
||||
requestId,
|
||||
...(options.direction ? { direction: options.direction } : {}),
|
||||
...(options.cursor ? { cursor: options.cursor } : {}),
|
||||
...(typeof options.limit === "number" ? { limit: options.limit } : {}),
|
||||
});
|
||||
const payload = await this.sendRequest({
|
||||
requestId,
|
||||
message,
|
||||
timeout: options.timeout,
|
||||
options: { skipQueue: true },
|
||||
select: (response) =>
|
||||
response.type === "agent.provider_subagents.timeline.get.response" &&
|
||||
response.payload.requestId === requestId
|
||||
? response.payload
|
||||
: null,
|
||||
});
|
||||
if (payload.error) {
|
||||
throw new Error(payload.error);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
async buildAgentForkContext(
|
||||
agentId: string,
|
||||
options: AgentForkContextOptions = {},
|
||||
@@ -4672,6 +4747,7 @@ export class DaemonClient {
|
||||
[CLIENT_CAPS.customModeIcons]: true,
|
||||
[CLIENT_CAPS.reasoningMergeEnum]: true,
|
||||
[CLIENT_CAPS.terminalReflowableSnapshot]: true,
|
||||
[CLIENT_CAPS.providerSubagents]: true,
|
||||
...this.config.capabilities,
|
||||
},
|
||||
...(this.config.appVersion ? { appVersion: this.config.appVersion } : {}),
|
||||
|
||||
Reference in New Issue
Block a user