fix(agent): classify attributed custom user steering

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Fatih Al-Aziz
2026-08-03 13:32:25 +07:00
parent 2416134cf0
commit e4de9bf176
2 changed files with 73 additions and 0 deletions
+3
View File
@@ -1379,6 +1379,9 @@ export class Agent {
const message = this.#steeringQueue[i];
const role = "role" in message ? message.role : undefined;
const attribution = "attribution" in message ? message.attribution : undefined;
if (attribution === "user") {
return { queued: true, source: "user" };
}
if (role !== "user") continue;
if (attribution !== "agent") {
return { queued: true, source: "user" };
+70
View File
@@ -80,6 +80,76 @@ describe("Agent", () => {
expect(skippedContent.text).not.toContain("queued user message");
});
it("classifies user-attributed custom steering as a queued user message", async () => {
const toolSchema = z.object({ value: z.string() });
const executed: string[] = [];
let agent: Agent;
const tool: AgentTool<typeof toolSchema, { value: string }> = {
name: "echo",
label: "Echo",
description: "Echo tool",
parameters: toolSchema,
concurrency: "exclusive",
async execute(_toolCallId, params) {
executed.push(params.value);
if (params.value === "first") {
agent.steer({
role: "custom",
customType: "visible-user-steer",
content: "visible custom steering",
display: true,
attribution: "user",
timestamp: Date.now(),
});
agent.steer({
role: "user",
content: "normal user steering",
timestamp: Date.now(),
});
}
return {
content: [{ type: "text", text: `ok:${params.value}` }],
details: { value: params.value },
};
},
};
const mock = createMockModel({
responses: [
{
content: [
{ type: "toolCall", id: "tool-1", name: "echo", arguments: { value: "first" } },
{ type: "toolCall", id: "tool-2", name: "echo", arguments: { value: "second" } },
],
},
{ content: ["done"] },
{ content: ["done"] },
],
});
agent = new Agent({
initialState: { model: mock.model, systemPrompt: ["Test"], tools: [tool], messages: [] },
streamFn: mock.stream,
steeringMode: "one-at-a-time",
interruptMode: "immediate",
});
const events: AgentEvent[] = [];
const unsubscribe = agent.subscribe(event => events.push(event));
await agent.prompt("start");
unsubscribe();
expect(executed).toEqual(["first"]);
const skipped = events.find(
(event): event is Extract<AgentEvent, { type: "tool_execution_end" }> =>
event.type === "tool_execution_end" && event.toolCallId === "tool-2",
);
expect(skipped).toBeDefined();
const skippedContent = skipped?.result.content[0];
expect(skippedContent?.type).toBe("text");
if (skippedContent?.type !== "text") throw new Error("skipped tool result must be text");
expect(skippedContent.text).toContain("Skipped due to queued user message");
expect(skippedContent.text).not.toContain("pending system advisory");
});
it("classifies one-at-a-time steering from the next queued mixed source", async () => {
const cases = [
{