fix(agent): defer steering until after tool execution

This commit is contained in:
Mario Zechner
2026-03-16 20:23:28 +01:00
parent 83378aad7e
commit 208a2cc123
2 changed files with 27 additions and 94 deletions

View File

@@ -388,7 +388,7 @@ describe("agentLoop with AgentMessage", () => {
expect(toolResultIds).toEqual(["tool-1", "tool-2"]);
});
it("should inject queued messages and skip remaining tool calls", async () => {
it("should inject queued messages after all tool calls complete", async () => {
const toolSchema = Type.Object({ value: Type.String() });
const executed: string[] = [];
const tool: AgentTool<typeof toolSchema, { value: string }> = {
@@ -423,8 +423,8 @@ describe("agentLoop with AgentMessage", () => {
convertToLlm: identityConverter,
toolExecution: "sequential",
getSteeringMessages: async () => {
// Return steering message after first tool executes
if (executed.length === 1 && !queuedDelivered) {
// Return steering message after tool execution has started.
if (executed.length >= 1 && !queuedDelivered) {
queuedDelivered = true;
return [queuedUserMessage];
}
@@ -467,29 +467,28 @@ describe("agentLoop with AgentMessage", () => {
events.push(event);
}
// Only first tool should have executed
expect(executed).toEqual(["first"]);
// Both tools should execute before steering is injected
expect(executed).toEqual(["first", "second"]);
// Second tool should be skipped
const toolEnds = events.filter(
(e): e is Extract<AgentEvent, { type: "tool_execution_end" }> => e.type === "tool_execution_end",
);
expect(toolEnds.length).toBe(2);
expect(toolEnds[0].isError).toBe(false);
expect(toolEnds[1].isError).toBe(true);
if (toolEnds[1].result.content[0]?.type === "text") {
expect(toolEnds[1].result.content[0].text).toContain("Skipped due to queued user message");
}
expect(toolEnds[1].isError).toBe(false);
// Queued message should appear in events
const queuedMessageEvent = events.find(
(e) =>
e.type === "message_start" &&
e.message.role === "user" &&
typeof e.message.content === "string" &&
e.message.content === "interrupt",
);
expect(queuedMessageEvent).toBeDefined();
// Queued message should appear in events after both tool result messages
const eventSequence = events.flatMap((event) => {
if (event.type !== "message_start") return [];
if (event.message.role === "toolResult") return [`tool:${event.message.toolCallId}`];
if (event.message.role === "user" && typeof event.message.content === "string") {
return [event.message.content];
}
return [];
});
expect(eventSequence).toContain("interrupt");
expect(eventSequence.indexOf("tool:tool-1")).toBeLessThan(eventSequence.indexOf("interrupt"));
expect(eventSequence.indexOf("tool:tool-2")).toBeLessThan(eventSequence.indexOf("interrupt"));
// Interrupt message should be in context when second LLM call is made
expect(sawInterruptInContext).toBe(true);