feat(FN-4394): complete Step 4 — wire heartbeat scope resolver into runs

Fusion-Task-Id: FN-4394
Fusion-Task-Lineage: 1e7ad660-2cd2-40ba-b7b8-7ef552c6fa13
This commit is contained in:
Fusion
2026-05-14 02:55:15 -07:00
committed by gsxdsm
parent f276610bd5
commit 7d7f23c1a5
4 changed files with 100 additions and 4 deletions

View File

@@ -7,6 +7,7 @@ import {
HEARTBEAT_SYSTEM_PROMPT,
HEARTBEAT_NO_TASK_SYSTEM_PROMPT,
HEARTBEAT_PROCEDURE,
HEARTBEAT_PROCEDURE_OFF,
HEARTBEAT_NO_TASK_PROCEDURE,
getAgentSoulWords,
} from "../agent-heartbeat.js";
@@ -1146,6 +1147,72 @@ describe("executeHeartbeat", () => {
expect(savedRun?.heartbeatProcedureSource).toBe("default");
});
it("uses lite task-scoped procedure when project heartbeatScopeDiscipline is lite", async () => {
const store = createStoreWithAgentForExec({ taskId: "FN-001" });
const mockSession = createMockAgentSession();
mockedCreateFnAgent.mockResolvedValue({ session: mockSession as any });
mockTaskStore = createMockTaskStore({
getSettings: vi.fn().mockResolvedValue({ heartbeatScopeDiscipline: "lite" }),
});
const monitor = new HeartbeatMonitor({ store, taskStore: mockTaskStore, rootDir: "/tmp" });
const result = await monitor.executeHeartbeat({ agentId: "agent-001", source: "timer" });
expect(result.status).toBe("completed");
const persistedRun = vi.mocked(store.saveRun).mock.calls
.map(([arg]) => arg)
.find((arg): arg is AgentHeartbeatRun => typeof arg.executionPrompt === "string");
expect(persistedRun?.executionPrompt).toContain("Assignment review");
expect(persistedRun?.executionPrompt).toContain("Classify scope before acting");
expect(persistedRun?.executionPrompt).not.toContain("Per-tick self-check");
expect(persistedRun?.contextSnapshot?.heartbeatScopeDiscipline).toBe("lite");
});
it("uses agent runtimeConfig heartbeatScopeDiscipline over project default", async () => {
const store = createStoreWithAgentForExec({
runtimeConfig: { heartbeatScopeDiscipline: "strict" },
taskId: "FN-001",
});
const mockSession = createMockAgentSession();
mockedCreateFnAgent.mockResolvedValue({ session: mockSession as any });
mockTaskStore = createMockTaskStore({
getSettings: vi.fn().mockResolvedValue({ heartbeatScopeDiscipline: "lite" }),
});
const monitor = new HeartbeatMonitor({ store, taskStore: mockTaskStore, rootDir: "/tmp" });
const result = await monitor.executeHeartbeat({ agentId: "agent-001", source: "timer" });
expect(result.status).toBe("completed");
const persistedRun = vi.mocked(store.saveRun).mock.calls
.map(([arg]) => arg)
.find((arg): arg is AgentHeartbeatRun => typeof arg.executionPrompt === "string");
expect(persistedRun?.executionPrompt).toContain("Per-tick self-check");
expect(persistedRun?.executionPrompt).toContain("Classify the bound task");
expect(persistedRun?.executionPrompt).not.toContain("Assignment review");
expect(persistedRun?.contextSnapshot?.heartbeatScopeDiscipline).toBe("strict");
});
it("off mode omits scope-classification guidance", async () => {
const store = createStoreWithAgentForExec({ taskId: "FN-001" });
const mockSession = createMockAgentSession();
mockedCreateFnAgent.mockResolvedValue({ session: mockSession as any });
mockTaskStore = createMockTaskStore({
getSettings: vi.fn().mockResolvedValue({ heartbeatScopeDiscipline: "off" }),
});
const monitor = new HeartbeatMonitor({ store, taskStore: mockTaskStore, rootDir: "/tmp" });
const result = await monitor.executeHeartbeat({ agentId: "agent-001", source: "timer" });
expect(result.status).toBe("completed");
const executionPrompt = mockSession.prompt.mock.calls.at(-1)?.[0] as string;
expect(executionPrompt).toContain(HEARTBEAT_PROCEDURE_OFF);
expect(executionPrompt).not.toContain("Classify scope before acting");
expect(executionPrompt).not.toContain("Classify the bound task");
const savedRun = await store.getRunDetail("agent-001", result.id);
expect(savedRun?.contextSnapshot?.heartbeatScopeDiscipline).toBe("off");
});
it("task-scoped run receives HEARTBEAT_SYSTEM_PROMPT as system prompt", async () => {
const store = createStoreWithAgentForExec({ taskId: "FN-001" });
const mockSession = createMockAgentSession();
@@ -2775,6 +2842,7 @@ describe("executeHeartbeat", () => {
taskId: "FN-001",
triggeringCommentIds: ["comment-1"],
triggeringCommentType: "task",
heartbeatScopeDiscipline: "strict",
});
});

View File

@@ -22,6 +22,10 @@ describe("resolveHeartbeatScopeDisciplineMode", () => {
expect(resolveHeartbeatScopeDisciplineMode(project("invalid" as never), agent(undefined))).toBe("strict");
});
it("falls through invalid agent and invalid project modes to strict default", () => {
expect(resolveHeartbeatScopeDisciplineMode(project("invalid" as never), agent("invalid"))).toBe("strict");
});
it("defaults to strict when unset", () => {
expect(resolveHeartbeatScopeDisciplineMode(undefined, undefined)).toBe("strict");
});