Skip to content

Commit f68af64

Browse files
authored
fix(auto-reply): queue unsupported transcript steering (#103916)
1 parent 25c216a commit f68af64

2 files changed

Lines changed: 37 additions & 4 deletions

File tree

src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,12 @@ vi.mock("../../agents/embedded-agent-runner/runs.js", () => ({
141141
sessionId: string,
142142
prompt: string,
143143
options: unknown,
144-
) =>
145-
state.queueEmbeddedAgentMessageMock(sessionId, prompt, options)
144+
) => {
145+
const result = state.queueEmbeddedAgentMessageMock(sessionId, prompt, options);
146+
if (typeof result === "object") {
147+
return result;
148+
}
149+
return result
146150
? {
147151
queued: true,
148152
sessionId,
@@ -156,7 +160,8 @@ vi.mock("../../agents/embedded-agent-runner/runs.js", () => ({
156160
reason: "no_active_run",
157161
target: "none",
158162
gatewayHealth: "live",
159-
},
163+
};
164+
},
160165
}));
161166

162167
vi.mock("./queue.js", () => ({
@@ -352,6 +357,30 @@ describe("runReplyAgent active steering", () => {
352357
expect(state.runEmbeddedAgentMock).not.toHaveBeenCalled();
353358
expect(typing.cleanup).toHaveBeenCalledTimes(1);
354359
});
360+
361+
it("queues a follow-up when transcript-backed steering is unsupported", async () => {
362+
state.queueEmbeddedAgentMessageMock.mockReturnValueOnce({
363+
queued: false,
364+
sessionId: "session",
365+
reason: "transcript_commit_wait_unsupported",
366+
target: "none",
367+
gatewayHealth: "live",
368+
});
369+
const onTurnAdopted = vi.fn();
370+
const { run } = createMinimalRun({
371+
opts: { onTurnAdopted },
372+
isActive: true,
373+
isStreaming: true,
374+
shouldSteer: true,
375+
resolvedQueueMode: "steer",
376+
});
377+
378+
await expect(run()).resolves.toBeUndefined();
379+
380+
expect(vi.mocked(enqueueFollowupRun)).toHaveBeenCalledTimes(1);
381+
expect(state.runEmbeddedAgentMock).not.toHaveBeenCalled();
382+
expect(onTurnAdopted).not.toHaveBeenCalled();
383+
});
355384
});
356385

357386
describe("runReplyAgent heartbeat followup guard", () => {

src/auto-reply/reply/agent-runner.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,7 @@ export async function runReplyAgent(params: {
12741274
}
12751275
};
12761276

1277+
let shouldQueueAfterSteerRejection = false;
12771278
if (effectiveShouldSteer && isActive) {
12781279
const activeReplyOperation =
12791280
providedReplyOperation ?? (sessionKey ? replyRunRegistry.get(sessionKey) : undefined);
@@ -1314,14 +1315,17 @@ export async function runReplyAgent(params: {
13141315
typing.cleanup();
13151316
return undefined;
13161317
}
1318+
// The active runtime still owns the turn but cannot prove transcript adoption.
1319+
// Keep the inbound message queued so ingress can finalize after a later run.
1320+
shouldQueueAfterSteerRejection = steerOutcome.reason === "transcript_commit_wait_unsupported";
13171321
const summary = formatEmbeddedAgentQueueFailureSummary(steerOutcome);
13181322
logVerbose(`queue: active session ${steerSessionId} rejected steering injection: ${summary}`);
13191323
}
13201324

13211325
const activeRunQueueAction = resolveActiveRunQueueAction({
13221326
isActive,
13231327
isHeartbeat,
1324-
shouldFollowup: effectiveShouldFollowup,
1328+
shouldFollowup: effectiveShouldFollowup || shouldQueueAfterSteerRejection,
13251329
queueMode: activeRunQueueMode,
13261330
resetTriggered: effectiveResetTriggered,
13271331
});

0 commit comments

Comments
 (0)