From 4d47712669000e6511c9274c5cc291d1f5752c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 26 Mar 2026 19:00:51 -0400 Subject: [PATCH] fix(04-01): resolve type errors and update integration tests for wakeup API - Fix SQLInputValue type in listWakeupRequests params - Fix leftover pendingWakes reference in worker-service.ts - Fix RunStatus 'completed' -> 'succeeded' in atomic-checkout test - Update autonomous-chain.spec.ts to use new wakeup lifecycle API --- src/main/database.ts | 2 +- src/main/worker-service.ts | 2 +- tests/integration/autonomous-chain.spec.ts | 88 ++++++++++++---------- tests/unit/atomic-checkout.test.ts | 2 +- 4 files changed, 53 insertions(+), 41 deletions(-) diff --git a/src/main/database.ts b/src/main/database.ts index 29a358f5..c2e805df 100644 --- a/src/main/database.ts +++ b/src/main/database.ts @@ -2091,7 +2091,7 @@ export class AppDatabase { claimedAt: string | null; finishedAt: string | null; createdAt: string; updatedAt: string; }> { const conditions = ["company_id = ?"]; - const params: unknown[] = [companyId]; + const params: SQLInputValue[] = [companyId]; if (filters?.status) { conditions.push("status = ?"); params.push(filters.status); } if (filters?.agentId) { conditions.push("agent_id = ?"); params.push(filters.agentId); } const limit = filters?.limit ?? 50; diff --git a/src/main/worker-service.ts b/src/main/worker-service.ts index 53ff62c4..d4cbb8d7 100644 --- a/src/main/worker-service.ts +++ b/src/main/worker-service.ts @@ -411,7 +411,7 @@ export function createWorkerService({ } // Self-wake with circuit breaker to prevent infinite loops - if (pendingWakes.length === 0 && !isQuitting() && !destroyed) { + if (queuedWakes.length === 0 && !isQuitting() && !destroyed) { try { const actionableTasks = db.listAgentTasks(run.agentId, run.companyId, ["todo", "in_progress"]); if (actionableTasks.length > 0) { diff --git a/tests/integration/autonomous-chain.spec.ts b/tests/integration/autonomous-chain.spec.ts index e22cd347..a1d895d0 100644 --- a/tests/integration/autonomous-chain.spec.ts +++ b/tests/integration/autonomous-chain.spec.ts @@ -315,47 +315,48 @@ describe("Step 2: CEO phased prompts", () => { // ─── Step 3: Deferred Wake Queue ──────────────────────────────────── -describe("Step 3: Deferred wake queue (database)", () => { - it("enqueue and consume pending wakes", () => { +describe("Step 3: Wakeup request lifecycle (database)", () => { + it("enqueue and list wakeup requests with coalescing", () => { const agentId = createAgent({ name: "TestAgent" }); - // Enqueue two wakes for the same busy agent. The queue deduplicates by agent - // so only the freshest trigger is retained. - db.enqueuePendingWake(companyId, agentId, "subtask_completed"); - db.enqueuePendingWake(companyId, agentId, "comment"); + // Enqueue two wakes for the same busy agent. Coalesced into one row + // with trigger_detail JSON array containing both triggers. + db.enqueueWakeupRequest(companyId, agentId, "subtask_completed", "subtask_completed"); + db.enqueueWakeupRequest(companyId, agentId, "comment", "comment"); - // Consume returns the latest retained wake - const wakes = db.consumePendingWakes(agentId); + // List returns the coalesced wake + const wakes = db.listWakeupRequests(companyId, { agentId, status: "queued" }); expect(wakes).toHaveLength(1); - expect(wakes[0].trigger).toBe("comment"); expect(wakes[0].companyId).toBe(companyId); + expect(wakes[0].coalescedCount).toBe(1); - // Second consume returns empty (they've been deleted) - const wakes2 = db.consumePendingWakes(agentId); - expect(wakes2).toHaveLength(0); + const triggers = JSON.parse(wakes[0].triggerDetail!); + expect(triggers).toHaveLength(2); + expect(triggers[0].trigger).toBe("subtask_completed"); + expect(triggers[1].trigger).toBe("comment"); }); - it("returns empty array when no pending wakes exist", () => { + it("returns empty array when no wakeup requests exist", () => { const agentId = createAgent({ name: "TestAgent" }); - const wakes = db.consumePendingWakes(agentId); + const wakes = db.listWakeupRequests(companyId, { agentId, status: "queued" }); expect(wakes).toHaveLength(0); }); - it("does not consume wakes for other agents", () => { + it("does not return wakes for other agents", () => { const agent1 = createAgent({ name: "Agent1" }); const agent2 = createAgent({ name: "Agent2" }); - db.enqueuePendingWake(companyId, agent1, "comment"); - db.enqueuePendingWake(companyId, agent2, "subtask_completed"); + db.enqueueWakeupRequest(companyId, agent1, "comment", "comment"); + db.enqueueWakeupRequest(companyId, agent2, "subtask_completed", "subtask_completed"); - const wakes1 = db.consumePendingWakes(agent1); + const wakes1 = db.listWakeupRequests(companyId, { agentId: agent1, status: "queued" }); expect(wakes1).toHaveLength(1); - expect(wakes1[0].trigger).toBe("comment"); + expect(wakes1[0].source).toBe("comment"); // Agent2's wake should still be there - const wakes2 = db.consumePendingWakes(agent2); + const wakes2 = db.listWakeupRequests(companyId, { agentId: agent2, status: "queued" }); expect(wakes2).toHaveLength(1); - expect(wakes2[0].trigger).toBe("subtask_completed"); + expect(wakes2[0].source).toBe("subtask_completed"); }); }); @@ -874,11 +875,11 @@ describe("E2E: Full autonomous CEO cold start flow", () => { const subtask = db.getTask(subtaskId); expect(subtask.parentId).toBe(parentTaskId); - // 9. Deferred wake test: enqueue wake while IC is busy - db.enqueuePendingWake(companyId, icId, "comment"); - const pendingWakes = db.consumePendingWakes(icId); - expect(pendingWakes).toHaveLength(1); - expect(pendingWakes[0].trigger).toBe("comment"); + // 9. Wakeup request test: enqueue wake while IC is busy + db.enqueueWakeupRequest(companyId, icId, "comment", "comment"); + const queuedWakes = db.listWakeupRequests(companyId, { agentId: icId, status: "queued" }); + expect(queuedWakes).toHaveLength(1); + expect(queuedWakes[0].source).toBe("comment"); // 10. Verify IC prompt has correct wake reason const icPromptCtx = buildCtx({ @@ -896,23 +897,34 @@ describe("E2E: Full autonomous CEO cold start flow", () => { // ─── E2E: Deferred Wake + Run Lifecycle ───────────────────────────── -describe("E2E: Deferred wake queue with run lifecycle", () => { - it("wake enqueue + run finish + consume cycle", () => { +describe("E2E: Wakeup request lifecycle with run lifecycle", () => { + it("wake enqueue + coalesce + claim + complete cycle", () => { const agentId = createAgent({ name: "Agent" }); - // Simulate: agent is running, wakes come in. Only the freshest reason is - // retained so the agent gets one consolidated follow-up wake. - db.enqueuePendingWake(companyId, agentId, "subtask_completed"); - db.enqueuePendingWake(companyId, agentId, "comment"); - db.enqueuePendingWake(companyId, agentId, "report_blocked"); + // Simulate: agent is running, wakes come in. All triggers are coalesced + // into a single wakeup request with JSON array of trigger details. + db.enqueueWakeupRequest(companyId, agentId, "subtask_completed", "subtask_completed"); + db.enqueueWakeupRequest(companyId, agentId, "comment", "comment"); + db.enqueueWakeupRequest(companyId, agentId, "report_blocked", "report_blocked"); - // Agent finishes run → consume pending wakes - const wakes = db.consumePendingWakes(agentId); + // Verify coalesced into single row with all triggers + const wakes = db.listWakeupRequests(companyId, { agentId, status: "queued" }); expect(wakes).toHaveLength(1); - expect(wakes[0].trigger).toBe("report_blocked"); + expect(wakes[0].coalescedCount).toBe(2); - // All consumed - expect(db.consumePendingWakes(agentId)).toHaveLength(0); + const triggers = JSON.parse(wakes[0].triggerDetail!); + expect(triggers).toHaveLength(3); + expect(triggers[0].trigger).toBe("subtask_completed"); + expect(triggers[1].trigger).toBe("comment"); + expect(triggers[2].trigger).toBe("report_blocked"); + + // Agent finishes run -> claim and complete the wakeup request + const claimed = db.claimWakeupRequest(agentId, companyId, "test-run-id"); + expect(claimed).not.toBeNull(); + db.completeWakeupRequest(claimed!.id); + + // All completed + expect(db.listWakeupRequests(companyId, { agentId, status: "queued" })).toHaveLength(0); }); }); diff --git a/tests/unit/atomic-checkout.test.ts b/tests/unit/atomic-checkout.test.ts index 31d2144b..426963bd 100644 --- a/tests/unit/atomic-checkout.test.ts +++ b/tests/unit/atomic-checkout.test.ts @@ -182,7 +182,7 @@ describe("CAS checkout", () => { db.markRunStatus(runId, "running", ""); db.finishRun({ runId, - status: "completed", + status: "succeeded", summary: "Done", errorMessage: null, exitCode: 0,