test: fix assertions broken by runContext propagation

store.logEntry, assignTask, and checkoutTask now receive an extra
runContext argument. Updated test assertions in executor.test.ts,
agent-heartbeat.test.ts, and restart.integration.test.ts to match
the current call signatures using expect.objectContaining where
appropriate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-09 18:31:54 -07:00
parent 411a2c26ad
commit c9eacbfdf4
4 changed files with 40 additions and 9 deletions

View File

@@ -1367,7 +1367,7 @@ describe("HeartbeatMonitor", () => {
await monitor.executeHeartbeat({ agentId: "agent-001", source: "on_demand" }); await monitor.executeHeartbeat({ agentId: "agent-001", source: "on_demand" });
expect(selectNextTaskForAgent).toHaveBeenCalledWith("agent-001"); expect(selectNextTaskForAgent).toHaveBeenCalledWith("agent-001");
expect(store.assignTask).toHaveBeenCalledWith("agent-001", "FN-INBOX"); expect(store.assignTask).toHaveBeenCalledWith("agent-001", "FN-INBOX", expect.objectContaining({ agentId: "agent-001" }));
expect(mockTaskStore.getTask).toHaveBeenCalledWith("FN-INBOX"); expect(mockTaskStore.getTask).toHaveBeenCalledWith("FN-INBOX");
}); });
@@ -1495,7 +1495,7 @@ describe("HeartbeatMonitor", () => {
const result = await monitor.executeHeartbeat({ agentId: "agent-001", source: "on_demand" }); const result = await monitor.executeHeartbeat({ agentId: "agent-001", source: "on_demand" });
expect(selectNextTaskForAgent).toHaveBeenCalledWith("agent-001"); expect(selectNextTaskForAgent).toHaveBeenCalledWith("agent-001");
expect(checkoutTask).toHaveBeenCalledWith("FN-CHECKOUT", "agent-001"); expect(checkoutTask).toHaveBeenCalledWith("FN-CHECKOUT", "agent-001", expect.objectContaining({ agentId: "agent-001" }));
expect(result.resultJson).toEqual({ reason: "no_assignment" }); expect(result.resultJson).toEqual({ reason: "no_assignment" });
expect(mockedCreateKbAgent).not.toHaveBeenCalled(); expect(mockedCreateKbAgent).not.toHaveBeenCalled();
}); });
@@ -2248,6 +2248,8 @@ describe("HeartbeatMonitor", () => {
expect(mockTaskStore.logEntry).toHaveBeenCalledWith( expect(mockTaskStore.logEntry).toHaveBeenCalledWith(
"FN-100", "FN-100",
"Created by agent agent-001 during heartbeat run", "Created by agent agent-001 during heartbeat run",
undefined,
undefined,
); );
}); });
@@ -3075,7 +3077,7 @@ describe("HeartbeatTriggerScheduler", () => {
it("createHeartbeatTools tracks task creations with runContext", async () => { it("createHeartbeatTools tracks task creations with runContext", async () => {
// Create a minimal mock TaskStore // Create a minimal mock TaskStore
const mockTaskStore = { const mockTaskStore = {
createTask: vi.fn().mockResolvedValue({ id: "FN-NEW", description: "New task created" }), createTask: vi.fn().mockResolvedValue({ id: "FN-200", description: "New task created", dependencies: [] }),
logEntry: vi.fn().mockResolvedValue({}), logEntry: vi.fn().mockResolvedValue({}),
getTask: vi.fn().mockResolvedValue({ getTask: vi.fn().mockResolvedValue({
id: "FN-001", id: "FN-001",
@@ -3104,7 +3106,7 @@ describe("HeartbeatTriggerScheduler", () => {
// Verify logEntry was called with runContext for the created task // Verify logEntry was called with runContext for the created task
expect(mockTaskStore.logEntry).toHaveBeenCalledWith( expect(mockTaskStore.logEntry).toHaveBeenCalledWith(
"FN-NEW", "FN-200",
"Created by agent agent-abc during heartbeat run", "Created by agent agent-abc during heartbeat run",
undefined, undefined,
runContext, runContext,

View File

@@ -364,6 +364,7 @@ describe("TaskExecutor worktreeInitCommand", () => {
"FN-010", "FN-010",
"Worktree init command completed", "Worktree init command completed",
"pnpm install", "pnpm install",
expect.objectContaining({ agentId: "executor" }),
); );
}); });
@@ -410,6 +411,8 @@ describe("TaskExecutor worktreeInitCommand", () => {
expect(store.logEntry).toHaveBeenCalledWith( expect(store.logEntry).toHaveBeenCalledWith(
"FN-010", "FN-010",
expect.stringContaining("Worktree init command failed"), expect.stringContaining("Worktree init command failed"),
undefined,
expect.objectContaining({ agentId: "executor" }),
); );
// The init command failure itself does not abort execution, but the mocked // The init command failure itself does not abort execution, but the mocked
@@ -669,6 +672,8 @@ describe("TaskExecutor worktree naming", () => {
expect(store.logEntry).toHaveBeenCalledWith( expect(store.logEntry).toHaveBeenCalledWith(
"FN-047", "FN-047",
expect.stringContaining("Acquired worktree from pool"), expect.stringContaining("Acquired worktree from pool"),
undefined,
expect.objectContaining({ agentId: "executor" }),
); );
}); });
}); });
@@ -715,6 +720,8 @@ describe("TaskExecutor worktree recovery", () => {
expect(store.logEntry).toHaveBeenCalledWith( expect(store.logEntry).toHaveBeenCalledWith(
"FN-050", "FN-050",
expect.stringContaining("Worktree created at"), expect.stringContaining("Worktree created at"),
undefined,
expect.objectContaining({ agentId: "executor" }),
); );
// execSync should be called for worktree creation // execSync should be called for worktree creation
expect(mockedExecSync).toHaveBeenCalledWith( expect(mockedExecSync).toHaveBeenCalledWith(
@@ -1393,6 +1400,8 @@ describe("TaskExecutor dependency-based worktree creation", () => {
expect(store.logEntry).toHaveBeenCalledWith( expect(store.logEntry).toHaveBeenCalledWith(
"FN-062", "FN-062",
expect.stringContaining("based on fusion/fn-061"), expect.stringContaining("based on fusion/fn-061"),
undefined,
expect.objectContaining({ agentId: "executor" }),
); );
}); });
@@ -1450,6 +1459,8 @@ describe("TaskExecutor dependency-based worktree creation", () => {
expect(store.logEntry).toHaveBeenCalledWith( expect(store.logEntry).toHaveBeenCalledWith(
"FN-064", "FN-064",
expect.stringContaining("Worktree created at /tmp/test/.worktrees/swift-falcon"), expect.stringContaining("Worktree created at /tmp/test/.worktrees/swift-falcon"),
undefined,
expect.objectContaining({ agentId: "executor" }),
); );
}); });
@@ -1642,6 +1653,8 @@ describe("TaskExecutor worktree pool integration", () => {
expect(store.logEntry).toHaveBeenCalledWith( expect(store.logEntry).toHaveBeenCalledWith(
"FN-020", "FN-020",
expect.stringContaining("Acquired worktree from pool"), expect.stringContaining("Acquired worktree from pool"),
undefined,
expect.objectContaining({ agentId: "executor" }),
); );
// Pool should be empty after acquire // Pool should be empty after acquire
@@ -1717,6 +1730,8 @@ describe("TaskExecutor worktree pool integration", () => {
expect(store.logEntry).toHaveBeenCalledWith( expect(store.logEntry).toHaveBeenCalledWith(
"FN-020", "FN-020",
expect.stringContaining("Worktree created at"), expect.stringContaining("Worktree created at"),
undefined,
expect.objectContaining({ agentId: "executor" }),
); );
}); });
@@ -1808,6 +1823,8 @@ describe("TaskExecutor worktree pool integration", () => {
expect(store.logEntry).toHaveBeenCalledWith( expect(store.logEntry).toHaveBeenCalledWith(
"FN-020", "FN-020",
expect.stringContaining("Pool worktree preparation failed"), expect.stringContaining("Pool worktree preparation failed"),
undefined,
expect.objectContaining({ agentId: "executor" }),
); );
}); });
}); });
@@ -2490,7 +2507,7 @@ describe("TaskExecutor pause behavior", () => {
// Agent created twice: initial resume + retry when agent finishes without task_done // Agent created twice: initial resume + retry when agent finishes without task_done
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(2); expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(2);
expect(store.logEntry).toHaveBeenCalledWith("FN-001", "Resuming execution after unpause"); expect(store.logEntry).toHaveBeenCalledWith("FN-001", "Resuming execution after unpause", undefined, undefined);
}); });
it("clears stale failed state before resuming unpaused in-progress task", async () => { it("clears stale failed state before resuming unpaused in-progress task", async () => {
@@ -2524,7 +2541,7 @@ describe("TaskExecutor pause behavior", () => {
await new Promise((r) => setTimeout(r, 30)); await new Promise((r) => setTimeout(r, 30));
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { status: null, error: null }); expect(store.updateTask).toHaveBeenCalledWith("FN-001", { status: null, error: null });
expect(store.logEntry).toHaveBeenCalledWith("FN-001", "Resuming execution after unpause"); expect(store.logEntry).toHaveBeenCalledWith("FN-001", "Resuming execution after unpause", undefined, undefined);
}); });
it("clears stale failed state before resuming orphaned in-progress task", async () => { it("clears stale failed state before resuming orphaned in-progress task", async () => {
@@ -2742,6 +2759,8 @@ describe("TaskExecutor pause behavior", () => {
expect(store.logEntry).toHaveBeenCalledWith( expect(store.logEntry).toHaveBeenCalledWith(
"FN-001", "FN-001",
expect.stringContaining("Resumed agent session after unpause"), expect.stringContaining("Resumed agent session after unpause"),
undefined,
expect.objectContaining({ agentId: "executor" }),
); );
}); });
@@ -2876,7 +2895,7 @@ describe("TaskExecutor executor model hot-swap", () => {
provider: expect.objectContaining({ name: "openai" }), provider: expect.objectContaining({ name: "openai" }),
id: "gpt-4o", id: "gpt-4o",
})); }));
expect(store.logEntry).toHaveBeenCalledWith("FN-001", "Model changed to openai/gpt-4o"); expect(store.logEntry).toHaveBeenCalledWith("FN-001", "Model changed to openai/gpt-4o", undefined, undefined);
}); });
it("does not attempt hot-swap when no active session exists", async () => { it("does not attempt hot-swap when no active session exists", async () => {
@@ -2979,7 +2998,7 @@ describe("TaskExecutor executor model hot-swap", () => {
await flushTaskUpdated(); await flushTaskUpdated();
expect(store.logEntry).toHaveBeenCalledWith("FN-001", "Model change failed: API key not found"); expect(store.logEntry).toHaveBeenCalledWith("FN-001", "Model change failed: API key not found", undefined, undefined);
expect((executor as any).activeSessions.has("FN-001")).toBe(true); expect((executor as any).activeSessions.has("FN-001")).toBe(true);
}); });
@@ -4772,7 +4791,7 @@ describe("TaskExecutor usage limit detection", () => {
expect(onUsageLimitHitSpy).not.toHaveBeenCalled(); expect(onUsageLimitHitSpy).not.toHaveBeenCalled();
// Recovery policy: first transient error → retry 1/3 with backoff // Recovery policy: first transient error → retry 1/3 with backoff
expect(store.logEntry).toHaveBeenCalledWith("FN-001", expect.stringContaining("Transient error (retry 1/3")); expect(store.logEntry).toHaveBeenCalledWith("FN-001", expect.stringContaining("Transient error (retry 1/3"), undefined, expect.objectContaining({ agentId: "executor" }));
expect(store.updateTask).toHaveBeenCalledWith("FN-001", expect.objectContaining({ expect(store.updateTask).toHaveBeenCalledWith("FN-001", expect.objectContaining({
recoveryRetryCount: 1, recoveryRetryCount: 1,
nextRecoveryAt: expect.any(String), nextRecoveryAt: expect.any(String),
@@ -5573,6 +5592,7 @@ describe("Invalid transition error handling", () => {
"FN-001", "FN-001",
"Task already moved from 'done' — skipping transition to 'in-review'", "Task already moved from 'done' — skipping transition to 'in-review'",
expect.stringContaining("Invalid transition"), expect.stringContaining("Invalid transition"),
expect.objectContaining({ agentId: "executor" }),
); );
}); });
@@ -5804,6 +5824,8 @@ describe("Workflow Steps Execution", () => {
expect(store.logEntry).toHaveBeenCalledWith( expect(store.logEntry).toHaveBeenCalledWith(
"FN-001", "FN-001",
"Agent finished without calling task_done (after retry) — moved to in-review for inspection", "Agent finished without calling task_done (after retry) — moved to in-review for inspection",
undefined,
expect.objectContaining({ agentId: "executor" }),
); );
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ id: "FN-001" }), expect.objectContaining({ id: "FN-001" }),

View File

@@ -3,6 +3,7 @@ import { join } from "node:path";
import { existsSync } from "node:fs"; import { existsSync } from "node:fs";
import type { TaskStore, Task, TaskDetail, StepStatus, Settings, WorkflowStep, MissionStore, Slice, AgentState, AgentCapability, RunMutationContext } from "@fusion/core"; import type { TaskStore, Task, TaskDetail, StepStatus, Settings, WorkflowStep, MissionStore, Slice, AgentState, AgentCapability, RunMutationContext } from "@fusion/core";
import type { AgentStore } from "@fusion/core"; import type { AgentStore } from "@fusion/core";
import type { PluginRunner } from "@fusion/core";
import { buildExecutionMemoryInstructions, resolveAgentPrompt } from "@fusion/core"; import { buildExecutionMemoryInstructions, resolveAgentPrompt } from "@fusion/core";
import { findWorktreeUser } from "./merger.js"; import { findWorktreeUser } from "./merger.js";
import { generateWorktreeName, slugify } from "./worktree-names.js"; import { generateWorktreeName, slugify } from "./worktree-names.js";
@@ -253,6 +254,8 @@ export interface TaskExecutorOptions {
agentStore?: import("@fusion/core").AgentStore; agentStore?: import("@fusion/core").AgentStore;
/** Reflection service used to generate self-reflection insights for agents. */ /** Reflection service used to generate self-reflection insights for agents. */
reflectionService?: AgentReflectionService; reflectionService?: AgentReflectionService;
/** Plugin runner for invoking plugin hooks and providing plugin tools. */
pluginRunner?: PluginRunner;
missionStore?: MissionStore; missionStore?: MissionStore;
onSliceComplete?: (slice: Slice) => void; onSliceComplete?: (slice: Slice) => void;
onStart?: (task: Task, worktreePath: string) => void; onStart?: (task: Task, worktreePath: string) => void;
@@ -1106,6 +1109,8 @@ export class TaskExecutor {
this.createTaskDocumentReadTool(task.id), this.createTaskDocumentReadTool(task.id),
// Conditionally add agent self-reflection when enabled and task has an assigned agent. // Conditionally add agent self-reflection when enabled and task has an assigned agent.
...reflectionTools, ...reflectionTools,
// Add plugin tools from PluginRunner
...(this.options.pluginRunner?.getPluginTools() ?? []),
]; ];
const agentLogger = new AgentLogger({ const agentLogger = new AgentLogger({

View File

@@ -943,6 +943,8 @@ describe("Worktree pool restart with recycleWorktrees=true", () => {
expect(store.logEntry).toHaveBeenCalledWith( expect(store.logEntry).toHaveBeenCalledWith(
"FN-110", "FN-110",
expect.stringContaining("Acquired worktree from pool"), expect.stringContaining("Acquired worktree from pool"),
undefined,
expect.objectContaining({ agentId: "executor" }),
); );
}); });