feat(FN-4401): complete remaining auto-claim snapshot integration

Fusion-Task-Id: FN-4401
Fusion-Task-Lineage: c1b6c497-b22c-48d5-b1c8-299877bf09ac
This commit is contained in:
Fusion
2026-05-14 00:58:06 -07:00
committed by gsxdsm
parent b8f5a237bd
commit f860618657
16 changed files with 299 additions and 78 deletions

View File

@@ -12,15 +12,15 @@ function makeTask(overrides: Partial<Task> & Pick<Task, "id">): Task {
createdAt: overrides.createdAt ?? "2026-01-01T00:00:00.000Z",
updatedAt: overrides.updatedAt ?? "2026-01-01T00:00:00.000Z",
dependencies: overrides.dependencies ?? [],
labels: overrides.labels ?? [],
comments: overrides.comments ?? [],
activityLog: overrides.activityLog ?? [],
metadata: overrides.metadata ?? {},
steps: overrides.steps ?? [],
currentStep: overrides.currentStep ?? 0,
log: overrides.log ?? [],
assignedAgentId: overrides.assignedAgentId,
checkedOutBy: overrides.checkedOutBy,
paused: overrides.paused,
columnMovedAt: overrides.columnMovedAt,
} as Task;
} as unknown as Task;
}
describe("AutoClaimSnapshotManager", () => {

View File

@@ -896,6 +896,51 @@ describe("executeHeartbeat", () => {
expect(store.claimTaskForAgent).not.toHaveBeenCalled();
});
it("reuses one snapshot rebuild across concurrent no-task heartbeats", async () => {
const listTasks = vi.fn().mockResolvedValue([
{
id: "FN-CANDIDATE",
description: "executor reliability follow-up",
title: "Executor reliability",
prompt: "",
steps: [],
column: "todo",
dependencies: [],
log: [],
attachments: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
} as unknown as TaskDetail,
]);
mockTaskStore = createMockTaskStore({ listTasks });
const store = createStoreWithAgentForExec({ taskId: undefined, soul: "executor reliability owner" });
const monitor = new HeartbeatMonitor({ store, taskStore: mockTaskStore, rootDir: "/tmp" });
await Promise.all([
monitor.executeHeartbeat({ agentId: "agent-001", source: "timer" }),
monitor.executeHeartbeat({ agentId: "agent-001", source: "on_demand" }),
]);
expect(listTasks).toHaveBeenCalledTimes(1);
});
it("omits candidate section when autoClaimCandidatesInPrompt resolves to zero", async () => {
const store = createStoreWithAgentForExec({
taskId: undefined,
soul: "executor reliability owner",
runtimeConfig: { autoClaimRelevantTasks: true, autoClaimCandidatesInPrompt: 0 },
});
const mockSession = createMockAgentSession();
mockedCreateFnAgent.mockResolvedValue({ session: mockSession as any });
const monitor = new HeartbeatMonitor({ store, taskStore: mockTaskStore, rootDir: "/tmp" });
await monitor.executeHeartbeat({ agentId: "agent-001", source: "timer" });
const executionPrompt = mockSession.prompt.mock.calls.at(-1)?.[0] as string;
expect(executionPrompt).toContain("auto-claim relevant tasks: disabled (prompt-suppressed)");
expect(executionPrompt).not.toContain("Open unowned tasks you may auto-claim");
});
it("agent WITH instructionsText but no task creates session and completes successfully", async () => {
const store = createStoreWithAgentForExec({ taskId: undefined, instructionsText: "Monitor task board and create follow-up tasks" });
const mockSession = createMockAgentSession();

View File

@@ -0,0 +1,53 @@
import { describe, expect, it, vi } from "vitest";
import type { TaskStore } from "@fusion/core";
import { Scheduler } from "../scheduler.js";
function createStore() {
const listeners = new Map<string, ((payload: unknown) => void)[]>();
const on = vi.fn((event: string, listener: (payload: unknown) => void) => {
const existing = listeners.get(event) ?? [];
existing.push(listener);
listeners.set(event, existing);
});
const store = {
on,
off: vi.fn(),
} as unknown as TaskStore;
const emit = (event: string, payload: unknown) => {
for (const listener of listeners.get(event) ?? []) {
listener(payload);
}
};
return { store, emit };
}
describe("Scheduler auto-claim snapshot invalidation", () => {
it("invalidates on task:created and task:updated", () => {
const invalidate = vi.fn();
const { store, emit } = createStore();
new Scheduler(store, { snapshotManager: { invalidate } as any });
emit("task:created", { task: { id: "FN-1" } });
emit("task:updated", { id: "FN-1" });
expect(invalidate).toHaveBeenCalledWith("task:created");
expect(invalidate).toHaveBeenCalledWith("task:updated");
});
it("invalidates task:moved only when todo is source or destination", () => {
const invalidate = vi.fn();
const { store, emit } = createStore();
new Scheduler(store, { snapshotManager: { invalidate } as any });
emit("task:moved", { task: { id: "FN-1" }, from: "todo", to: "in-progress" });
emit("task:moved", { task: { id: "FN-2" }, from: "in-progress", to: "todo" });
emit("task:moved", { task: { id: "FN-3" }, from: "in-review", to: "done" });
expect(invalidate).toHaveBeenCalledWith("task:moved:todo->in-progress");
expect(invalidate).toHaveBeenCalledWith("task:moved:in-progress->todo");
expect(invalidate).toHaveBeenCalledTimes(2);
});
});