diff --git a/packages/eve/src/channel/types.ts b/packages/eve/src/channel/types.ts index f8c3e89e6..4a3758674 100644 --- a/packages/eve/src/channel/types.ts +++ b/packages/eve/src/channel/types.ts @@ -187,10 +187,16 @@ export interface SubagentInputRequestHookPayload { readonly subagentName: string; } -/** Authorization lifecycle event forwarded from a delegated child. */ +/** Responder-specific lifecycle event forwarded from a delegated child. */ export type SubagentAuthorizationEvent = Extract< HandleMessageStreamEvent, - { type: "authorization.required" | "authorization.completed" } + { + type: + | "approval.candidate" + | "approval.settled" + | "authorization.required" + | "authorization.completed"; + } >; /** diff --git a/packages/eve/src/execution/subagent-adapter.ts b/packages/eve/src/execution/subagent-adapter.ts index 7661a356c..e90620da2 100644 --- a/packages/eve/src/execution/subagent-adapter.ts +++ b/packages/eve/src/execution/subagent-adapter.ts @@ -73,6 +73,12 @@ export function isSubagentAdapterState(value: unknown): value is SubagentAdapter */ export const SUBAGENT_ADAPTER: ChannelAdapter = { kind: SUBAGENT_ADAPTER_KIND, + async "approval.candidate"(data, ctx) { + await forwardSubagentAuthorizationEvent({ data, type: "approval.candidate" }, ctx); + }, + async "approval.settled"(data, ctx) { + await forwardSubagentAuthorizationEvent({ data, type: "approval.settled" }, ctx); + }, async "authorization.required"(data, ctx) { await forwardSubagentAuthorizationEvent({ data, type: "authorization.required" }, ctx); }, diff --git a/packages/eve/src/execution/subagent-auth-proxy.integration.test.ts b/packages/eve/src/execution/subagent-auth-proxy.integration.test.ts index de3805ba3..f806e5101 100644 --- a/packages/eve/src/execution/subagent-auth-proxy.integration.test.ts +++ b/packages/eve/src/execution/subagent-auth-proxy.integration.test.ts @@ -136,6 +136,51 @@ function decodeEvent(chunk: Uint8Array): TimedHandleMessageStreamEvent { } describe("subagent authorization proxy", () => { + it("preserves approval candidate and settlement events", async () => { + const parentSessionId = "parent-approval-session"; + const session = createSession(parentSessionId); + const { ctx } = buildContext({ adapter: authorizationAdapter, sessionId: parentSessionId }); + const chunks: Uint8Array[] = []; + const parentWritable = createCapturingWritable(chunks); + const candidateEvent: SubagentAuthorizationEvent = { + data: { + candidateId: "candidate-1", + outcome: "pending", + requestId: "approval-1", + sequence: 0, + stepIndex: 1, + turnId: "child-turn", + }, + type: "approval.candidate", + }; + const settledEvent: SubagentAuthorizationEvent = { + data: { + outcome: "approved", + requestId: "approval-1", + responderPrincipalId: "slack:T1:U1", + sequence: 0, + stepIndex: 2, + turnId: "child-turn", + }, + type: "approval.settled", + }; + + await emitProxiedSubagentEvent({ + ctx, + durableSession: projectToDurableSession(session), + hookPayload: authorizationPayload(candidateEvent), + parentWritable, + }); + await emitProxiedSubagentEvent({ + ctx, + durableSession: projectToDurableSession(session), + hookPayload: authorizationPayload(settledEvent), + parentWritable, + }); + + expect(decodeEvent(chunks[0]!)).toMatchObject(candidateEvent); + expect(decodeEvent(chunks[1]!)).toMatchObject(settledEvent); + }); it("preserves events and parent adapter state across required/completed steps", async () => { const parentSessionId = "parent-session"; const session = createSession(parentSessionId);