Skip to content

Commit 2f3b985

Browse files
committed
fix(agent): terminalize invocation validation failures
1 parent 3b3ef36 commit 2f3b985

2 files changed

Lines changed: 67 additions & 10 deletions

File tree

packages/junior/src/chat/agent-invocations/work.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -298,16 +298,6 @@ export function createAgentInvocationWorker(options: {
298298
if (!invocation) {
299299
throw new Error(`Agent invocation is missing for ${invocationId}`);
300300
}
301-
if (invocation.childConversationId !== context.conversationId) {
302-
throw new Error(
303-
`Agent invocation ${invocationId} belongs to ${invocation.childConversationId}, not ${context.conversationId}`,
304-
);
305-
}
306-
if (context.destination) {
307-
throw new Error(
308-
`Agent invocation conversation ${context.conversationId} must not own a provider destination`,
309-
);
310-
}
311301

312302
let acknowledged = context.attempt.messages.length === 0;
313303
const acknowledge = async (): Promise<void> => {
@@ -332,6 +322,16 @@ export function createAgentInvocationWorker(options: {
332322
let sandboxRef: SandboxRef | undefined;
333323
let history: PiMessage[];
334324
try {
325+
if (invocation.childConversationId !== context.conversationId) {
326+
throw new Error(
327+
`Agent invocation ${invocationId} belongs to ${invocation.childConversationId}, not ${context.conversationId}`,
328+
);
329+
}
330+
if (context.destination) {
331+
throw new Error(
332+
`Agent invocation conversation ${context.conversationId} must not own a provider destination`,
333+
);
334+
}
335335
if (isTerminalAgentInvocation(invocation)) {
336336
await persistTerminalLifecycle(invocation);
337337
await acknowledge();

packages/junior/tests/integration/agent-invocation-work.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getAgentInvocationTurnId,
99
} from "@/chat/agent-invocations/store";
1010
import {
11+
buildAgentInvocationInboundMessage,
1112
createAgentInvocationWorker,
1213
routeAgentInvocationWork,
1314
createAndEnqueueAgentInvocation,
@@ -22,6 +23,7 @@ import { completedAgentRun } from "@/chat/runtime/agent-run-outcome";
2223
import { processConversationQueueMessage } from "@/chat/task-execution/vercel-callback";
2324
import { recoverPendingAgentInvocationMailboxAppends } from "@/chat/agent-dispatch/heartbeat";
2425
import { CONVERSATION_WORK_MAX_DELIVERY_ATTEMPTS } from "@/chat/task-execution/store";
26+
import type { ConversationWorkerContext } from "@/chat/task-execution/worker";
2527
import {
2628
getAgentTurnSessionRecord,
2729
upsertAgentTurnSessionRecord,
@@ -668,4 +670,59 @@ describe("agent invocation conversation work", () => {
668670
await fixture.close();
669671
}
670672
});
673+
674+
it("persists invariant failures on the final delivery attempt", async () => {
675+
const { fixture } = await prepareParentConversation();
676+
try {
677+
const created = await createAgentInvocation({
678+
...invocationInput,
679+
agentName: "researcher",
680+
idempotencyKey: "invalid-child-1",
681+
});
682+
const run = vi.fn();
683+
const ack = vi.fn();
684+
const worker = createAgentInvocationWorker({ agentRunner: { run } });
685+
const context = {
686+
attempt: {
687+
ack,
688+
conversationId: created.childConversationId,
689+
destination,
690+
drain: vi.fn(),
691+
isFinalAttempt: true,
692+
messages: [buildAgentInvocationInboundMessage(created)],
693+
},
694+
checkIn: vi.fn(),
695+
conversationId: created.childConversationId,
696+
destination,
697+
shouldYield: () => false,
698+
} satisfies ConversationWorkerContext;
699+
700+
await expect(worker(context, created.invocationId)).resolves.toEqual({
701+
status: "completed",
702+
});
703+
704+
expect(run).not.toHaveBeenCalled();
705+
expect(ack).toHaveBeenCalledOnce();
706+
await expect(
707+
getAgentInvocation(created.invocationId),
708+
).resolves.toMatchObject({
709+
errorMessage: expect.stringContaining(
710+
"must not own a provider destination",
711+
),
712+
status: "failed",
713+
});
714+
await expect(
715+
createAgentInvocation({
716+
...invocationInput,
717+
agentName: "researcher",
718+
idempotencyKey: "invalid-child-2",
719+
}),
720+
).resolves.toMatchObject({
721+
childConversationId: created.childConversationId,
722+
status: "pending",
723+
});
724+
} finally {
725+
await fixture.close();
726+
}
727+
});
671728
});

0 commit comments

Comments
 (0)