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