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:
@@ -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,
|
||||||
|
|||||||
@@ -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" }),
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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" }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user