diff --git a/src/agents/internal-event-contract.ts b/src/agents/internal-event-contract.ts new file mode 100644 index 00000000000..38c2aa3bf57 --- /dev/null +++ b/src/agents/internal-event-contract.ts @@ -0,0 +1,14 @@ +export const AGENT_INTERNAL_EVENT_TYPE_TASK_COMPLETION = "task_completion" as const; + +export const AGENT_INTERNAL_EVENT_SOURCES = [ + "subagent", + "cron", + "video_generation", + "music_generation", +] as const; + +export const AGENT_INTERNAL_EVENT_STATUSES = ["ok", "timeout", "error", "unknown"] as const; + +export type AgentInternalEventType = typeof AGENT_INTERNAL_EVENT_TYPE_TASK_COMPLETION; +export type AgentInternalEventSource = (typeof AGENT_INTERNAL_EVENT_SOURCES)[number]; +export type AgentInternalEventStatus = (typeof AGENT_INTERNAL_EVENT_STATUSES)[number]; diff --git a/src/agents/internal-events.ts b/src/agents/internal-events.ts index d2106a67b33..7b0025f158a 100644 --- a/src/agents/internal-events.ts +++ b/src/agents/internal-events.ts @@ -1,19 +1,22 @@ +import { + AGENT_INTERNAL_EVENT_TYPE_TASK_COMPLETION, + type AgentInternalEventSource, + type AgentInternalEventStatus, +} from "./internal-event-contract.js"; import { escapeInternalRuntimeContextDelimiters, INTERNAL_RUNTIME_CONTEXT_BEGIN, INTERNAL_RUNTIME_CONTEXT_END, } from "./internal-runtime-context.js"; -export type AgentInternalEventType = "task_completion"; - export type AgentTaskCompletionInternalEvent = { - type: "task_completion"; - source: "subagent" | "cron" | "video_generation" | "music_generation"; + type: typeof AGENT_INTERNAL_EVENT_TYPE_TASK_COMPLETION; + source: AgentInternalEventSource; childSessionKey: string; childSessionId?: string; announceType: string; taskLabel: string; - status: "ok" | "timeout" | "error" | "unknown"; + status: AgentInternalEventStatus; statusLabel: string; result: string; statsLine?: string; diff --git a/src/gateway/protocol/schema/agent.ts b/src/gateway/protocol/schema/agent.ts index 57a40c3eca8..2f3b2fd6cc5 100644 --- a/src/gateway/protocol/schema/agent.ts +++ b/src/gateway/protocol/schema/agent.ts @@ -1,15 +1,20 @@ import { Type } from "@sinclair/typebox"; +import { + AGENT_INTERNAL_EVENT_SOURCES, + AGENT_INTERNAL_EVENT_STATUSES, + AGENT_INTERNAL_EVENT_TYPE_TASK_COMPLETION, +} from "../../../agents/internal-event-contract.js"; import { InputProvenanceSchema, NonEmptyString, SessionLabelString } from "./primitives.js"; export const AgentInternalEventSchema = Type.Object( { - type: Type.Literal("task_completion"), - source: Type.String({ enum: ["subagent", "cron", "video_generation"] }), + type: Type.Literal(AGENT_INTERNAL_EVENT_TYPE_TASK_COMPLETION), + source: Type.String({ enum: [...AGENT_INTERNAL_EVENT_SOURCES] }), childSessionKey: Type.String(), childSessionId: Type.Optional(Type.String()), announceType: Type.String(), taskLabel: Type.String(), - status: Type.String({ enum: ["ok", "timeout", "error", "unknown"] }), + status: Type.String({ enum: [...AGENT_INTERNAL_EVENT_STATUSES] }), statusLabel: Type.String(), result: Type.String(), statsLine: Type.Optional(Type.String()), diff --git a/src/gateway/server-methods/agent.test.ts b/src/gateway/server-methods/agent.test.ts index 87bc5ff3602..1381fb8bbb8 100644 --- a/src/gateway/server-methods/agent.test.ts +++ b/src/gateway/server-methods/agent.test.ts @@ -759,6 +759,44 @@ describe("gateway agent handler", () => { ); }); + it("accepts music generation internal events", async () => { + primeMainAgentRun(); + mocks.agentCommand.mockClear(); + const respond = vi.fn(); + + await invokeAgent( + { + message: "music generation finished", + sessionKey: "agent:main:main", + internalEvents: [ + { + type: "task_completion", + source: "music_generation", + childSessionKey: "music:task-123", + childSessionId: "task-123", + announceType: "music generation task", + taskLabel: "compose a loop", + status: "ok", + statusLabel: "completed successfully", + result: "MEDIA: https://example.test/song.mp3", + replyInstruction: "Reply in your normal assistant voice now.", + }, + ], + idempotencyKey: "music-generation-event", + }, + { reqId: "music-generation-event-1", respond }, + ); + + await waitForAssertion(() => expect(mocks.agentCommand).toHaveBeenCalled()); + expect(respond).not.toHaveBeenCalledWith( + false, + undefined, + expect.objectContaining({ + message: expect.stringContaining("invalid agent params"), + }), + ); + }); + it("only forwards workspaceDir for spawned sessions with stored workspace inheritance", async () => { primeMainAgentRun(); mockMainSessionEntry({