feat: establish tool call state machine

This commit is contained in:
2026-08-03 15:27:52 +08:00
parent 9021db6963
commit c384368f66
8 changed files with 371 additions and 0 deletions
@@ -71,4 +71,59 @@ describe("InteractionKernel", () => {
// The first id has expired from the bounded set and is accepted again.
expect(kernel.ingest({ raw: envelope({ id: "evt_status_thinking" }), source: "live" }).disposition).toBe("accepted");
});
it("projects tool calls by call_id and applies a result only after submission", () => {
const kernel = new InteractionKernel();
const call = kernel.ingest({
raw: envelope({
id: "evt_tool_call",
type: "lineup.v1.tool.call",
payload: { call_id: "call_kernel_001", tool: "choice", title: "Choose", prompt: "One", data: {} },
}),
source: "sync",
received_at: "2026-08-03T08:00:00.000Z",
});
const duplicateCallID = kernel.ingest({
raw: envelope({
id: "evt_tool_call_duplicate_id",
type: "lineup.v1.tool.call",
payload: { call_id: "call_kernel_001", tool: "choice", title: "Ignored", prompt: "Ignored", data: {} },
}),
source: "live",
});
const earlyResult = kernel.ingest({
raw: envelope({ id: "evt_tool_result_early", type: "lineup.v1.tool.result", payload: { call_id: "call_kernel_001", status: "completed", result: {} } }),
source: "live",
});
expect(call.snapshot.tool_calls).toEqual([expect.objectContaining({ call_id: "call_kernel_001", status: "pending" })]);
expect(duplicateCallID.snapshot.tool_calls).toEqual([
expect.objectContaining({ request: expect.objectContaining({ title: "Choose" }) }),
]);
expect(earlyResult.snapshot.tool_calls).toEqual([expect.objectContaining({ status: "pending" })]);
expect(kernel.submitToolCall("call_kernel_001", "2026-08-03T08:01:00.000Z")).toMatchObject({ disposition: "accepted" });
const completed = kernel.ingest({
raw: envelope({ id: "evt_tool_result_completed", type: "lineup.v1.tool.result", payload: { call_id: "call_kernel_001", status: "completed", result: { selected: "alpha" } } }),
source: "live",
});
expect(completed.snapshot.tool_calls).toEqual([
expect.objectContaining({ call_id: "call_kernel_001", status: "completed", result: { selected: "alpha" } }),
]);
});
it("keeps malformed tool payloads on the safe fallback path", () => {
const kernel = new InteractionKernel();
const invalidCall = kernel.ingest({
raw: envelope({ id: "evt_invalid_tool_call", type: "lineup.v1.tool.call", payload: { call_id: "call_bad", tool: "shell", data: {} } }),
source: "sync",
});
const invalidResult = kernel.ingest({
raw: envelope({ id: "evt_invalid_tool_result", type: "lineup.v1.tool.result", payload: { call_id: "call_bad", status: "submitted", result: {} } }),
source: "sync",
});
expect(invalidCall.items).toEqual([expect.objectContaining({ kind: "fallback", reason: "invalid_payload" })]);
expect(invalidResult.items).toEqual([expect.objectContaining({ kind: "fallback", reason: "invalid_payload" })]);
});
});