fix(FN-4249): align running-agent reconciliation with taskId linkage

Fusion-Task-Id: FN-4249
Fusion-Task-Lineage: e0052fae-38e1-4d66-8240-43d26f5790bb
This commit is contained in:
Fusion
2026-05-12 22:34:30 -07:00
committed by gsxdsm
parent 53aa1e90f1
commit b536d92d9c
4 changed files with 12 additions and 12 deletions

View File

@@ -29,7 +29,7 @@ vi.mock("node:fs/promises", async (importOriginal) => {
}; };
}); });
type MutableAgent = Agent & { executionTaskId?: string | null }; type MutableAgent = Agent;
function createMockTask(overrides: Partial<Task> = {}): Task { function createMockTask(overrides: Partial<Task> = {}): Task {
return { return {
@@ -90,10 +90,10 @@ function createAgentStore(agents: MutableAgent[]): AgentStore {
if (existing.state === state) return; if (existing.state === state) return;
existing.state = state; existing.state = state;
}), }),
syncExecutionTaskLink: vi.fn(async (id: string, taskId?: string | null) => { syncExecutionTaskLink: vi.fn(async (id: string, taskId?: string) => {
const existing = byId.get(id); const existing = byId.get(id);
if (!existing) return; if (!existing) return;
existing.executionTaskId = taskId ?? null; existing.taskId = taskId;
}), }),
} as unknown as AgentStore; } as unknown as AgentStore;
} }
@@ -134,7 +134,7 @@ describe("scheduler overlap requeue agent-state invariant (FN-4249)", () => {
await workerManager.onTaskStart(queuedCandidate); await workerManager.onTaskStart(queuedCandidate);
const scheduler = new Scheduler(taskStore, { agentStore }); const scheduler = new Scheduler(taskStore, { agentStore });
(scheduler as { running: boolean }).running = true; (scheduler as any).running = true;
await scheduler.schedule(); await scheduler.schedule();
await scheduler.schedule(); await scheduler.schedule();
@@ -144,7 +144,7 @@ describe("scheduler overlap requeue agent-state invariant (FN-4249)", () => {
expect(task?.column).toBe("todo"); expect(task?.column).toBe("todo");
expect(task?.status).toBe("queued"); expect(task?.status).toBe("queued");
expect(agent.state).toBe("active"); expect(agent.state).toBe("active");
expect(agent.executionTaskId ?? null).toBeNull(); expect(agent.taskId).toBeUndefined();
expect(agentStore.updateAgentState).toHaveBeenCalledWith("agent-assigned", "active"); expect(agentStore.updateAgentState).toHaveBeenCalledWith("agent-assigned", "active");
expect(agentStore.updateAgentState).toHaveBeenCalledWith("agent-assigned", "running"); expect(agentStore.updateAgentState).toHaveBeenCalledWith("agent-assigned", "running");
expect(agentStore.updateAgentState).toHaveBeenCalledTimes(2); expect(agentStore.updateAgentState).toHaveBeenCalledTimes(2);

View File

@@ -876,13 +876,13 @@ describe("SelfHealingManager", () => {
{ {
id: "agent-recover", id: "agent-recover",
state: "running", state: "running",
executionTaskId: "FN-TODO", taskId: "FN-TODO",
updatedAt: new Date(now - 120_000).toISOString(), updatedAt: new Date(now - 120_000).toISOString(),
} as Agent, } as Agent,
{ {
id: "agent-keep", id: "agent-keep",
state: "running", state: "running",
executionTaskId: "FN-IP", taskId: "FN-IP",
updatedAt: new Date(now - 120_000).toISOString(), updatedAt: new Date(now - 120_000).toISOString(),
} as Agent, } as Agent,
]; ];
@@ -902,7 +902,7 @@ describe("SelfHealingManager", () => {
}), }),
syncExecutionTaskLink: vi.fn(async (agentId: string, taskId?: string) => { syncExecutionTaskLink: vi.fn(async (agentId: string, taskId?: string) => {
const agent = agents.find((candidate) => candidate.id === agentId); const agent = agents.find((candidate) => candidate.id === agentId);
if (agent) agent.executionTaskId = taskId; if (agent) agent.taskId = taskId;
}), }),
} as unknown as AgentStore; } as unknown as AgentStore;

View File

@@ -493,7 +493,7 @@ export class Scheduler {
if (!agentStore) return; if (!agentStore) return;
const runningAgents = await agentStore.listAgents({ state: "running", includeEphemeral: true }); const runningAgents = await agentStore.listAgents({ state: "running", includeEphemeral: true });
const linkedAgents = runningAgents.filter((agent) => agent.executionTaskId === taskId); const linkedAgents = runningAgents.filter((agent) => agent.taskId === taskId);
for (const agent of linkedAgents) { for (const agent of linkedAgents) {
await agentStore.updateAgentState(agent.id, "active"); await agentStore.updateAgentState(agent.id, "active");

View File

@@ -2368,11 +2368,11 @@ export class SelfHealingManager {
const runningAgents = await agentStore.listAgents({ state: "running", includeEphemeral: true }); const runningAgents = await agentStore.listAgents({ state: "running", includeEphemeral: true });
for (const agent of runningAgents) { for (const agent of runningAgents) {
if (isEphemeralAgent(agent) || !agent.executionTaskId) { if (isEphemeralAgent(agent) || !agent.taskId) {
continue; continue;
} }
const linkedTask = await this.store.getTask(agent.executionTaskId); const linkedTask = await this.store.getTask(agent.taskId);
if (linkedTask && (linkedTask.column === "in-progress" || linkedTask.column === "in-review" || linkedTask.column === "done" || linkedTask.column === "archived")) { if (linkedTask && (linkedTask.column === "in-progress" || linkedTask.column === "in-review" || linkedTask.column === "done" || linkedTask.column === "archived")) {
continue; continue;
} }
@@ -2388,7 +2388,7 @@ export class SelfHealingManager {
await agentStore.updateAgentState(agent.id, "active"); await agentStore.updateAgentState(agent.id, "active");
await agentStore.syncExecutionTaskLink(agent.id, undefined); await agentStore.syncExecutionTaskLink(agent.id, undefined);
recoveredAgentIds.add(agent.id); recoveredAgentIds.add(agent.id);
log.log(`Recovered running durable agent ${agent.id} on inactive task ${agent.executionTaskId}`); log.log(`Recovered running durable agent ${agent.id} on inactive task ${agent.taskId}`);
} }
return recoveredAgentIds.size; return recoveredAgentIds.size;