feat(FN-4545): complete Step 10 — documentation and delivery artifacts
Fusion-Task-Id: FN-4545 Fusion-Task-Lineage: 95108429-618e-4a6d-9fa9-7ac2596665a2
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Auto-recovery: contamination + message-delivery handlers. Adds ContaminationAutoRecoveryHandler (issueRetry for branch-cross-contamination, composes with FN-4499 bootstrap re-anchor and FN-4428 contamination classifier) and MessageDeliveryAutoRecoveryHandler (bounded retry-or-park for fn_send_message / fn_post_room_message inside agent-tools.ts). New ProjectSettings.autoRecovery failure class "message-delivery-failure". New run-audit event types contamination:retry-issued, contamination:irreducible-pause, message-delivery:retry-issued, message-delivery:park. Genuine destructive-ambiguity contamination still pauses; userPaused (FN-4429) is preserved; autoRecovery.mode === "off" is byte-identical to legacy behavior at every wired site.
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import { createPostRoomMessageTool, createSendMessageTool } from "../../agent-tools.js";
|
import { createPostRoomMessageTool, createSendMessageTool } from "../../agent-tools.js";
|
||||||
|
|
||||||
|
function firstText(result: { content: Array<{ type: string; text?: string }> }): string {
|
||||||
|
const first = result.content[0];
|
||||||
|
return first?.type === "text" ? (first.text ?? "") : "";
|
||||||
|
}
|
||||||
|
|
||||||
describe("reliability interaction: message delivery auto-recovery", () => {
|
describe("reliability interaction: message delivery auto-recovery", () => {
|
||||||
it("recovers transient direct-message delivery and returns success", async () => {
|
it("recovers transient direct-message delivery and returns success", async () => {
|
||||||
const sendMessage = vi
|
const sendMessage = vi
|
||||||
@@ -10,9 +15,9 @@ describe("reliability interaction: message delivery auto-recovery", () => {
|
|||||||
const messageStore = { sendMessage } as any;
|
const messageStore = { sendMessage } as any;
|
||||||
const tool = createSendMessageTool(messageStore, "agent-a", { autoRecovery: { mode: "programmatic", maxRetries: 3 } as any });
|
const tool = createSendMessageTool(messageStore, "agent-a", { autoRecovery: { mode: "programmatic", maxRetries: 3 } as any });
|
||||||
|
|
||||||
const result = await tool.execute("1", { to_id: "agent-b", content: "hello" } as any);
|
const result = await tool.execute("1", { to_id: "agent-b", content: "hello" } as any, undefined, undefined, {} as any);
|
||||||
expect(sendMessage).toHaveBeenCalledTimes(2);
|
expect(sendMessage).toHaveBeenCalledTimes(2);
|
||||||
expect(result.content[0]?.text).toContain("Message sent to agent-b");
|
expect(firstText(result as any)).toContain("Message sent to agent-b");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("recovers transient room-message delivery and returns success", async () => {
|
it("recovers transient room-message delivery and returns success", async () => {
|
||||||
@@ -26,9 +31,9 @@ describe("reliability interaction: message delivery auto-recovery", () => {
|
|||||||
} as any;
|
} as any;
|
||||||
const tool = createPostRoomMessageTool(chatStore, "agent-a", { autoRecovery: { mode: "programmatic", maxRetries: 3 } as any });
|
const tool = createPostRoomMessageTool(chatStore, "agent-a", { autoRecovery: { mode: "programmatic", maxRetries: 3 } as any });
|
||||||
|
|
||||||
const result = await tool.execute("1", { roomId: "room-1", content: "hello" } as any);
|
const result = await tool.execute("1", { roomId: "room-1", content: "hello" } as any, undefined, undefined, {} as any);
|
||||||
expect(addRoomMessage).toHaveBeenCalledTimes(2);
|
expect(addRoomMessage).toHaveBeenCalledTimes(2);
|
||||||
expect(result.content[0]?.text).toContain("Room message posted");
|
expect(firstText(result as any)).toContain("Room message posted");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("preserves ERROR contract for permanent failures", async () => {
|
it("preserves ERROR contract for permanent failures", async () => {
|
||||||
@@ -36,8 +41,8 @@ describe("reliability interaction: message delivery auto-recovery", () => {
|
|||||||
const messageStore = { sendMessage } as any;
|
const messageStore = { sendMessage } as any;
|
||||||
const tool = createSendMessageTool(messageStore, "agent-a", { autoRecovery: { mode: "programmatic", maxRetries: 3 } as any });
|
const tool = createSendMessageTool(messageStore, "agent-a", { autoRecovery: { mode: "programmatic", maxRetries: 3 } as any });
|
||||||
|
|
||||||
const result = await tool.execute("1", { to_id: "agent-b", content: "hello" } as any);
|
const result = await tool.execute("1", { to_id: "agent-b", content: "hello" } as any, undefined, undefined, {} as any);
|
||||||
expect(result.content[0]?.text).toBe("ERROR: Failed to send message: recipient not found");
|
expect(firstText(result as any)).toBe("ERROR: Failed to send message: recipient not found");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("mode off preserves first-throw ERROR contract", async () => {
|
it("mode off preserves first-throw ERROR contract", async () => {
|
||||||
@@ -47,8 +52,8 @@ describe("reliability interaction: message delivery auto-recovery", () => {
|
|||||||
const messageStore = { sendMessage } as any;
|
const messageStore = { sendMessage } as any;
|
||||||
const tool = createSendMessageTool(messageStore, "agent-a", { autoRecovery: { mode: "off", maxRetries: 3 } as any });
|
const tool = createSendMessageTool(messageStore, "agent-a", { autoRecovery: { mode: "off", maxRetries: 3 } as any });
|
||||||
|
|
||||||
const result = await tool.execute("1", { to_id: "agent-b", content: "hello" } as any);
|
const result = await tool.execute("1", { to_id: "agent-b", content: "hello" } as any, undefined, undefined, {} as any);
|
||||||
expect(sendMessage).toHaveBeenCalledTimes(1);
|
expect(sendMessage).toHaveBeenCalledTimes(1);
|
||||||
expect(result.content[0]?.text).toBe("ERROR: Failed to send message: SQLITE_BUSY");
|
expect(firstText(result as any)).toBe("ERROR: Failed to send message: SQLITE_BUSY");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user