fix(engine): wire TaskStore into runtime AgentStore so heartbeat auto-claim works
The InProcessRuntime constructed its AgentStore with only `rootDir`, leaving task-claim/checkout/release operations unconfigured. As a result, the heartbeat auto-claim scan logged "TaskStore not configured for task-claim operations" whenever a relevant todo was found. Pass the runtime's TaskStore through to the AgentStore so claimTaskForAgent succeeds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
10
.changeset/heartbeat-auto-claim-taskstore.md
Normal file
10
.changeset/heartbeat-auto-claim-taskstore.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Wire `TaskStore` into the runtime's `AgentStore` so the heartbeat auto-claim
|
||||||
|
path can call `claimTaskForAgent` without warning
|
||||||
|
`TaskStore not configured for task-claim operations`. The `InProcessRuntime`
|
||||||
|
previously built its `AgentStore` with only `rootDir`, which left task-claim,
|
||||||
|
checkout, and release operations unconfigured even though the runtime had a
|
||||||
|
`TaskStore` available.
|
||||||
@@ -55,6 +55,11 @@ vi.mock("@fusion/core", async () => {
|
|||||||
self.init = vi.fn().mockResolvedValue(undefined);
|
self.init = vi.fn().mockResolvedValue(undefined);
|
||||||
self.listTasks = vi.fn().mockResolvedValue([]);
|
self.listTasks = vi.fn().mockResolvedValue([]);
|
||||||
self.getTask = mockTaskStoreGetTask;
|
self.getTask = mockTaskStoreGetTask;
|
||||||
|
// AgentStore now receives this TaskStore (passed from the runtime),
|
||||||
|
// so methods it calls during assign/claim/checkout flows must exist.
|
||||||
|
self.logEntry = vi.fn().mockResolvedValue(undefined);
|
||||||
|
self.updateTask = vi.fn().mockImplementation(async (taskId: string, patch: Record<string, unknown>) => ({ id: taskId, ...patch }));
|
||||||
|
self.moveTask = vi.fn().mockResolvedValue(undefined);
|
||||||
self.getSettings = vi.fn().mockImplementation(async () => structuredClone(mockTaskStoreSettings));
|
self.getSettings = vi.fn().mockImplementation(async () => structuredClone(mockTaskStoreSettings));
|
||||||
self.getMissionStore = vi.fn().mockReturnValue({
|
self.getMissionStore = vi.fn().mockReturnValue({
|
||||||
listMissions: vi.fn().mockReturnValue([]),
|
listMissions: vi.fn().mockReturnValue([]),
|
||||||
@@ -475,6 +480,26 @@ describe("InProcessRuntime", () => {
|
|||||||
expect(monitor).toBeDefined();
|
expect(monitor).toBeDefined();
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
|
// Regression: heartbeat auto-claim path was warning
|
||||||
|
// "TaskStore not configured for task-claim operations" because the
|
||||||
|
// runtime built its AgentStore without passing taskStore through.
|
||||||
|
it("wires AgentStore with TaskStore so claimTaskForAgent does not throw", async () => {
|
||||||
|
await runtime.start();
|
||||||
|
const store = getAgentStore(runtime);
|
||||||
|
const agent = await store.createAgent({
|
||||||
|
name: "claim-wiring",
|
||||||
|
role: "executor",
|
||||||
|
metadata: { agentKind: "task-worker" },
|
||||||
|
runtimeConfig: { enabled: false },
|
||||||
|
});
|
||||||
|
// taskStore.getTask is mocked to return null in this suite, so we
|
||||||
|
// expect the guarded "task_not_found" path rather than the
|
||||||
|
// unconfigured-taskStore throw.
|
||||||
|
mockTaskStoreGetTask.mockResolvedValueOnce(null);
|
||||||
|
const result = await store.claimTaskForAgent(agent.id, "FN-DOES-NOT-EXIST");
|
||||||
|
expect(result).toEqual({ ok: false, reason: "task_not_found" });
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
it("should return TriggerScheduler after start", async () => {
|
it("should return TriggerScheduler after start", async () => {
|
||||||
await runtime.start();
|
await runtime.start();
|
||||||
const triggerScheduler = runtime.getTriggerScheduler();
|
const triggerScheduler = runtime.getTriggerScheduler();
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ export class InProcessRuntime
|
|||||||
let agentStoreForReflection: import("@fusion/core").AgentStore | undefined;
|
let agentStoreForReflection: import("@fusion/core").AgentStore | undefined;
|
||||||
try {
|
try {
|
||||||
const { AgentStore: AgentStoreClass } = await import("@fusion/core");
|
const { AgentStore: AgentStoreClass } = await import("@fusion/core");
|
||||||
agentStoreForReflection = new AgentStoreClass({ rootDir: this.taskStore.getFusionDir() });
|
agentStoreForReflection = new AgentStoreClass({ rootDir: this.taskStore.getFusionDir(), taskStore: this.taskStore });
|
||||||
await agentStoreForReflection.init();
|
await agentStoreForReflection.init();
|
||||||
runtimeLog.log("AgentStore initialized for reflection service");
|
runtimeLog.log("AgentStore initialized for reflection service");
|
||||||
} catch (agentErr) {
|
} catch (agentErr) {
|
||||||
|
|||||||
Reference in New Issue
Block a user