Codemod db pass-through calls to domain modules: tasks + agents (C1 part 1/3)

Rewrite ~525 call sites from `db.<method>()` to `db.<module>.<method>()`
for tasks and agents domains (including `getAgent`/`findAgent`/`getTask`
which live on the `entityCrud` field). Delete the corresponding
pass-through methods on AppDatabaseBase. Structural types that narrowed
`db` to a bag of flat methods (CapabilityMatchDb, EscalationDeps,
SweepStaleReviewsDeps) are reshaped to match the new namespace. Internal
callback references inside database.ts/database-base.ts now invoke the
owning module directly (e.g. `this.agents.saveAgent`) rather than the
deleted pass-through.

The codemod lives at scripts/codemod/remove-appdatabase-passthrough.mjs
and filters by owning module + optional explicit method list. It is
idempotent and does not re-rewrite already-namespaced calls.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-20 13:17:01 -04:00
1 parent 411294afa6
commit 5e9b7acbc6
95 files changed
+1123 -793

No files matched your search

@@ -91,7 +91,7 @@ describe("agent model switching", () => {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
@@ -107,7 +107,7 @@ describe("agent model switching", () => {
const { enqueueWorkerRun, taskRunner } = createHarness(db);
const firstTaskId = db.saveTask({
const firstTaskId = db.tasks.saveTask({
companyId,
title: "Initial model task",
description: "Use the first model for this run.",
@@ -137,7 +137,7 @@ describe("agent model switching", () => {
unattributedCost: true,
});
db.saveAgent({
db.agents.saveAgent({
id: agentId,
companyId,
name: "Engineer",
@@ -152,7 +152,7 @@ describe("agent model switching", () => {
budgetMonthlyUsd: 200,
});
const secondTaskId = db.saveTask({
const secondTaskId = db.tasks.saveTask({
companyId,
title: "Updated model task",
description: "Use the new model for this run.",
@@ -45,7 +45,7 @@ async function createHarness() {
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
const ceoId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -58,7 +58,7 @@ async function createHarness() {
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
const ctoId = db.agents.saveAgent({
companyId,
name: "CTO",
role: "CTO",
@@ -72,7 +72,7 @@ async function createHarness() {
budgetMonthlyUsd: 220,
department: "engineering",
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Build the org",
description: "CTO should hire the first IC under engineering.",
@@ -126,7 +126,7 @@ describe("recursive org execution", () => {
const body = await response.json() as { agentId: string; approvalId: string; autoApproved: boolean };
expect(body.autoApproved).toBe(false);
const pendingHire = harness.db.getAgent(body.agentId);
const pendingHire = harness.db.entityCrud.getAgent(body.agentId);
expect(pendingHire.status).toBe("pending_approval");
expect(pendingHire.reportsTo).toBe(harness.ctoId);
@@ -141,11 +141,11 @@ describe("recursive org execution", () => {
decisionNote: "Approved by board",
});
const approvedHire = harness.db.getAgent(body.agentId);
const approvedHire = harness.db.entityCrud.getAgent(body.agentId);
expect(approvedHire.status).toBe("idle");
expect(harness.db.getDirectReports(harness.ctoId, harness.companyId).map((agent) => agent.id)).toContain(body.agentId);
expect(harness.db.getDirectReports(harness.ceoId, harness.companyId).map((agent) => agent.id)).not.toContain(body.agentId);
expect(harness.db.getChainOfCommand(body.agentId).map((agent) => agent.id)).toEqual([harness.ctoId, harness.ceoId]);
expect(harness.db.agents.getDirectReports(harness.ctoId, harness.companyId).map((agent) => agent.id)).toContain(body.agentId);
expect(harness.db.agents.getDirectReports(harness.ceoId, harness.companyId).map((agent) => agent.id)).not.toContain(body.agentId);
expect(harness.db.agents.getChainOfCommand(body.agentId).map((agent) => agent.id)).toEqual([harness.ctoId, harness.ceoId]);
const agentResponse = await fetch(`${harness.baseUrl}/api/agents/${body.agentId}`, {
headers: harness.headers,
@@ -169,7 +169,7 @@ describe("recursive org execution", () => {
it("routes cross-department hires to the relevant department lead when reportsTo is omitted", async () => {
const harness = await createHarness();
try {
const hrId = harness.db.saveAgent({
const hrId = harness.db.agents.saveAgent({
companyId: harness.companyId,
name: "HR Director",
role: "HR Director",
@@ -183,7 +183,7 @@ describe("recursive org execution", () => {
budgetMonthlyUsd: 180,
department: "hr",
});
const hrTaskId = harness.db.saveTask({
const hrTaskId = harness.db.tasks.saveTask({
companyId: harness.companyId,
title: "Fill engineering gap",
description: "Request a backend engineer for the engineering team.",
@@ -223,7 +223,7 @@ describe("recursive org execution", () => {
expect(response.status).toBe(201);
const body = await response.json() as { agentId: string };
const pendingHire = harness.db.getAgent(body.agentId);
const pendingHire = harness.db.entityCrud.getAgent(body.agentId);
expect(pendingHire.department).toBe("engineering");
expect(pendingHire.reportsTo).toBe(harness.ctoId);
} finally {
@@ -263,7 +263,7 @@ describe("recursive org execution", () => {
it("manager cannot route a hire outside their reporting branch", async () => {
const harness = await createHarness();
try {
const cmoId = harness.db.saveAgent({
const cmoId = harness.db.agents.saveAgent({
companyId: harness.companyId,
name: "CMO",
role: "CMO",
+2 -2
View File
@@ -33,7 +33,7 @@ async function createHarness() {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Helios",
role: "CEO",
@@ -46,7 +46,7 @@ async function createHarness() {
capabilities: "Operate the company",
budgetMonthlyUsd: 100,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Ship the API",
description: "Capture structured run events.",
@@ -29,7 +29,7 @@ describe("Step 4: Hire approval + decideApproval", () => {
decisionNote: "Approved by board",
});
const updatedAgent = shared.db.getAgent(hireId);
const updatedAgent = shared.db.entityCrud.getAgent(hireId);
expect(updatedAgent.status).toBe("idle");
});
@@ -52,7 +52,7 @@ describe("Step 4: Hire approval + decideApproval", () => {
decisionNote: "Not needed",
});
const updatedAgent = shared.db.getAgent(hireId);
const updatedAgent = shared.db.entityCrud.getAgent(hireId);
expect(updatedAgent.status).toBe("terminated");
});
@@ -89,13 +89,13 @@ describe("Agent Messages", () => {
let agentBId: string;
beforeEach(() => {
agentAId = shared.db.saveAgent({
agentAId = shared.db.agents.saveAgent({
companyId: shared.companyId, name: "Alice", role: "Engineer", title: "Senior Engineer",
department: "engineering", connectorId: "claude-pro", status: "active",
capabilities: "Backend development, API design, code review",
budgetMonthlyUsd: 100,
});
agentBId = shared.db.saveAgent({
agentBId = shared.db.agents.saveAgent({
companyId: shared.companyId, name: "Bob", role: "Designer", title: "UX Designer",
department: "design", connectorId: "claude-pro", status: "active",
capabilities: "UI design, wireframing, user research",
@@ -178,7 +178,7 @@ describe("Agent Messages", () => {
leadAgentId: engineeringLeadId,
status: "active",
});
shared.db.saveTask({
shared.db.tasks.saveTask({
companyId: shared.companyId,
title: "Project task",
description: "Do work",
@@ -240,13 +240,13 @@ describe("Auto Task Assignment", () => {
let frontendId: string;
beforeEach(() => {
backendId = shared.db.saveAgent({
backendId = shared.db.agents.saveAgent({
companyId: shared.companyId, name: "Backend Dev", role: "Backend Engineer", title: "Senior Backend",
department: "engineering", connectorId: "openai-codex", status: "active",
capabilities: "API development, database design, Node.js, PostgreSQL, backend testing",
budgetMonthlyUsd: 200,
});
frontendId = shared.db.saveAgent({
frontendId = shared.db.agents.saveAgent({
companyId: shared.companyId, name: "Frontend Dev", role: "Frontend Engineer", title: "Senior Frontend",
department: "engineering", connectorId: "openai-codex", status: "active",
capabilities: "React, TypeScript, CSS, Tailwind, responsive design, frontend testing, UI components",
@@ -255,61 +255,61 @@ describe("Auto Task Assignment", () => {
});
it("assigns to the best-matching agent by capability keywords", () => {
const taskId = shared.db.saveTask({
const taskId = shared.db.tasks.saveTask({
companyId: shared.companyId, title: "Build React component for user dashboard",
description: "Create a responsive React component with Tailwind CSS styling",
priority: "medium", status: "todo",
});
const assignedId = shared.db.autoAssignTask(taskId, shared.companyId);
const assignedId = shared.db.tasks.autoAssignTask(taskId, shared.companyId);
expect(assignedId).toBe(frontendId); // "React", "responsive", "Tailwind" match frontend
});
it("assigns backend task to backend agent", () => {
const taskId = shared.db.saveTask({
const taskId = shared.db.tasks.saveTask({
companyId: shared.companyId, title: "Design database schema for orders",
description: "PostgreSQL database design with API endpoints for order management",
priority: "high", status: "todo",
});
const assignedId = shared.db.autoAssignTask(taskId, shared.companyId);
const assignedId = shared.db.tasks.autoAssignTask(taskId, shared.companyId);
expect(assignedId).toBe(backendId); // "database", "PostgreSQL", "API" match backend
});
it("returns existing assignee if task already assigned", () => {
const taskId = shared.db.saveTask({
const taskId = shared.db.tasks.saveTask({
companyId: shared.companyId, title: "Already assigned task",
description: "This task has an assignee",
assigneeAgentId: frontendId,
priority: "medium", status: "todo",
});
const result = shared.db.autoAssignTask(taskId, shared.companyId);
const result = shared.db.tasks.autoAssignTask(taskId, shared.companyId);
expect(result).toBe(frontendId);
});
it("returns null if all agents have exhausted their budget", () => {
// Set positive budgets that are fully spent
shared.db.saveAgent({ id: backendId, companyId: shared.companyId, name: "Backend Dev", role: "Backend", title: "Backend", connectorId: "openai-codex", status: "active", capabilities: "backend", budgetMonthlyUsd: 100 });
shared.db.saveAgent({ id: frontendId, companyId: shared.companyId, name: "Frontend Dev", role: "Frontend", title: "Frontend", connectorId: "openai-codex", status: "active", capabilities: "frontend", budgetMonthlyUsd: 100 });
shared.db.agents.saveAgent({ id: backendId, companyId: shared.companyId, name: "Backend Dev", role: "Backend", title: "Backend", connectorId: "openai-codex", status: "active", capabilities: "backend", budgetMonthlyUsd: 100 });
shared.db.agents.saveAgent({ id: frontendId, companyId: shared.companyId, name: "Frontend Dev", role: "Frontend", title: "Frontend", connectorId: "openai-codex", status: "active", capabilities: "frontend", budgetMonthlyUsd: 100 });
// Exhaust budgets by recording spending via direct SQL (since finishRun would need a real run)
(shared.db as unknown as { db: { prepare: (sql: string) => { run: (...args: unknown[]) => void } } }).db.prepare("update agents set spent_monthly_usd = 100 where id = ?").run(backendId);
(shared.db as unknown as { db: { prepare: (sql: string) => { run: (...args: unknown[]) => void } } }).db.prepare("update agents set spent_monthly_usd = 100 where id = ?").run(frontendId);
const taskId = shared.db.saveTask({ companyId: shared.companyId, title: "No budget task", description: "Test", priority: "low", status: "todo" });
const result = shared.db.autoAssignTask(taskId, shared.companyId);
const taskId = shared.db.tasks.saveTask({ companyId: shared.companyId, title: "No budget task", description: "Test", priority: "low", status: "todo" });
const result = shared.db.tasks.autoAssignTask(taskId, shared.companyId);
expect(result).toBeNull();
});
it("penalizes agents with many active tasks", () => {
// Give backend agent 5 active tasks
for (let i = 0; i < 5; i++) {
shared.db.saveTask({ companyId: shared.companyId, title: `Backend task ${i}`, description: "", assigneeAgentId: backendId, priority: "medium", status: "in_progress" });
shared.db.tasks.saveTask({ companyId: shared.companyId, title: `Backend task ${i}`, description: "", assigneeAgentId: backendId, priority: "medium", status: "in_progress" });
}
// Generic task with no strong keyword match — should go to less busy frontend agent
const taskId = shared.db.saveTask({ companyId: shared.companyId, title: "Implement feature", description: "Build the feature", priority: "medium", status: "todo" });
const assignedId = shared.db.autoAssignTask(taskId, shared.companyId);
const taskId = shared.db.tasks.saveTask({ companyId: shared.companyId, title: "Implement feature", description: "Build the feature", priority: "medium", status: "todo" });
const assignedId = shared.db.tasks.autoAssignTask(taskId, shared.companyId);
expect(assignedId).toBe(frontendId); // Frontend has 0 active tasks vs backend's 5
});
});
@@ -40,7 +40,7 @@ describe("Step 2: CEO phased prompts", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const hireId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "pending_approval" as "idle" });
const hire = shared.db.getAgent(hireId);
const hire = shared.db.entityCrud.getAgent(hireId);
const ctx = buildCtx({
agentId: ceoId,
@@ -88,7 +88,7 @@ describe("Step 2: CEO phased prompts", () => {
it("Phase 2: shows normal executive instructions when active reports and no pending org approval", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "idle" });
const cto = shared.db.getAgent(ctoId);
const cto = shared.db.entityCrud.getAgent(ctoId);
const ctx = buildCtx({
agentId: ceoId,
@@ -109,7 +109,7 @@ describe("Step 2: CEO phased prompts", () => {
it("includes pending approvals section in prompt", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "idle" });
const cto = shared.db.getAgent(ctoId);
const cto = shared.db.entityCrud.getAgent(ctoId);
const ctx = buildCtx({
agentId: ceoId,
@@ -139,10 +139,10 @@ describe("Step 2: CEO phased prompts", () => {
it("Manager prompt includes credential escalation guidance", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ceo = shared.db.getAgent(ceoId);
const ceo = shared.db.entityCrud.getAgent(ceoId);
const managerId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId });
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: managerId });
const ic = shared.db.getAgent(icId);
const ic = shared.db.entityCrud.getAgent(icId);
const ctx = buildCtx({
agentId: managerId,
@@ -156,7 +156,7 @@ describe("Step 2: CEO phased prompts", () => {
it("IC prompt includes credential escalation guidance", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ceo = shared.db.getAgent(ceoId);
const ceo = shared.db.entityCrud.getAgent(ceoId);
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ceoId });
const ctx = buildCtx({
@@ -13,7 +13,7 @@ const { createAgent } = shared;
describe("Step 7: Worker crash recovery (database layer)", () => {
it("interruptStaleRuns clears running and queued runs on init", async () => {
const agentId = createAgent({ name: "Agent" });
const taskId = shared.db.saveTask({
const taskId = shared.db.tasks.saveTask({
companyId: shared.companyId,
title: "In-progress task",
description: "Running when crash happened",
@@ -51,7 +51,7 @@ describe("Step 7: Worker crash recovery (database layer)", () => {
it("finishRun can mark interrupted runs from crash recovery", () => {
const agentId = createAgent({ name: "Agent" });
const taskId = shared.db.saveTask({
const taskId = shared.db.tasks.saveTask({
companyId: shared.companyId,
title: "Crash recovery task",
description: "Test",
@@ -40,8 +40,8 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
});
// CEO now in Phase 1
const ctoRecord = shared.db.getAgent(ctoId);
const cmoRecord = shared.db.getAgent(cmoId);
const ctoRecord = shared.db.entityCrud.getAgent(ctoId);
const cmoRecord = shared.db.entityCrud.getAgent(cmoId);
const phase1Ctx = buildCtx({
agentId: ceoId,
directReports: [ctoRecord, cmoRecord],
@@ -85,15 +85,15 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
shared.db.decideApproval({ approvalId: hireApprovalCMO, state: "approved", decisionNote: "Approved" });
// Agents now idle
expect(shared.db.getAgent(ctoId).status).toBe("idle");
expect(shared.db.getAgent(cmoId).status).toBe("idle");
expect(shared.db.entityCrud.getAgent(ctoId).status).toBe("idle");
expect(shared.db.entityCrud.getAgent(cmoId).status).toBe("idle");
// 5. Board approves org strategy
shared.db.decideApproval({ approvalId: orgApprovalId, state: "approved", decisionNote: "Go ahead" });
// 6. CEO now in Phase 2
const updatedCto = shared.db.getAgent(ctoId);
const updatedCmo = shared.db.getAgent(cmoId);
const updatedCto = shared.db.entityCrud.getAgent(ctoId);
const updatedCmo = shared.db.entityCrud.getAgent(cmoId);
const phase2Ctx = buildCtx({
agentId: ceoId,
directReports: [updatedCto, updatedCmo],
@@ -106,7 +106,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
expect(phase2Prompt).toContain("Delegation");
// 7. Verify hierarchical chain
const ctoChain = shared.db.getChainOfCommand(ctoId);
const ctoChain = shared.db.agents.getChainOfCommand(ctoId);
expect(ctoChain).toHaveLength(1);
expect(ctoChain[0].id).toBe(ceoId);
@@ -120,7 +120,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
status: "in_progress",
});
const parentTaskId = shared.db.saveTask({
const parentTaskId = shared.db.tasks.saveTask({
companyId: shared.companyId,
title: "Build backend",
description: "Implement API",
@@ -133,7 +133,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
// CTO creates subtask for IC (would be done via API)
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ctoId, status: "idle" });
const subtaskId = shared.db.saveTask({
const subtaskId = shared.db.tasks.saveTask({
companyId: shared.companyId,
title: "Implement auth",
description: "JWT auth",
@@ -146,7 +146,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
});
// Verify parent-child relationship
const subtask = shared.db.getTask(subtaskId);
const subtask = shared.db.entityCrud.getTask(subtaskId);
expect(subtask.parentId).toBe(parentTaskId);
// 9. Wakeup request test: enqueue wake while IC is busy
@@ -159,7 +159,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
const icPromptCtx = buildCtx({
agentId: icId,
task: subtask,
chainOfCommand: [shared.db.getAgent(ctoId), shared.db.getAgent(ceoId)],
chainOfCommand: [shared.db.entityCrud.getAgent(ctoId), shared.db.entityCrud.getAgent(ceoId)],
wakeReason: "assignment",
});
const icPrompt = buildTaskPrompt(icPromptCtx);
@@ -202,11 +202,11 @@ describe("E2E: Blocked escalation through org hierarchy", () => {
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ctoId });
// When IC marks task blocked, we need to find its manager
const chain = shared.db.getChainOfCommand(icId);
const chain = shared.db.agents.getChainOfCommand(icId);
expect(chain[0].id).toBe(ctoId); // immediate manager gets woken
// When CTO needs to escalate further
const ctoChain = shared.db.getChainOfCommand(ctoId);
const ctoChain = shared.db.agents.getChainOfCommand(ctoId);
expect(ctoChain[0].id).toBe(ceoId);
});
});
@@ -216,7 +216,7 @@ describe("Review Fix: CEO phase ordering edge case", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
// Hires submitted but not yet approved
const hireId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "pending_approval" as "idle" });
const hire = shared.db.getAgent(hireId);
const hire = shared.db.entityCrud.getAgent(hireId);
// Org strategy already approved (not in pendingApprovals), but hires still pending
const ctx = buildCtx({
@@ -236,7 +236,7 @@ describe("Review Fix: CEO phase ordering edge case", () => {
it("moves to Phase 2 once hires are approved (no pending org approval)", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "idle" });
const cto = shared.db.getAgent(ctoId);
const cto = shared.db.entityCrud.getAgent(ctoId);
const ctx = buildCtx({
agentId: ceoId,
@@ -315,7 +315,7 @@ describe("Review Fix: Root task completion notification", () => {
const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId });
// CTO has a root-level task (no parentId)
const taskId = shared.db.saveTask({
const taskId = shared.db.tasks.saveTask({
companyId: shared.companyId,
title: "Standalone task from CEO",
description: "No parent",
@@ -325,11 +325,11 @@ describe("Review Fix: Root task completion notification", () => {
status: "done",
});
const task = shared.db.getTask(taskId);
const task = shared.db.entityCrud.getTask(taskId);
expect(task.parentId).toBeNull();
// CTO's chain of command includes CEO — so api-server can wake CEO on completion
const chain = shared.db.getChainOfCommand(ctoId);
const chain = shared.db.agents.getChainOfCommand(ctoId);
expect(chain).toHaveLength(1);
expect(chain[0].id).toBe(ceoId);
});
@@ -31,9 +31,9 @@ describe("Step 1: CEO role detection", () => {
it("identifies manager when has reports and has chain of command", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const managerId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId });
const ceo = shared.db.getAgent(ceoId);
const ceo = shared.db.entityCrud.getAgent(ceoId);
const icId = createAgent({ name: "IC", role: "Engineer", reportsTo: managerId });
const ic = shared.db.getAgent(icId);
const ic = shared.db.entityCrud.getAgent(icId);
const ctx = buildCtx({
agentId: managerId,
@@ -49,7 +49,7 @@ describe("Step 1: CEO role detection", () => {
it("identifies IC when has chain of command and no direct reports", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ceo = shared.db.getAgent(ceoId);
const ceo = shared.db.entityCrud.getAgent(ceoId);
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ceoId });
const ctx = buildCtx({
@@ -12,7 +12,7 @@ const { createAgent } = shared;
describe("Step 6: Run failure escalation (database layer)", () => {
it("finishRun marks run as failed and returns outcome", () => {
const agentId = createAgent({ name: "IC", role: "Engineer" });
const taskId = shared.db.saveTask({
const taskId = shared.db.tasks.saveTask({
companyId: shared.companyId,
title: "Failing task",
description: "Will fail",
@@ -56,7 +56,7 @@ describe("Step 6: Run failure escalation (database layer)", () => {
const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId });
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ctoId });
const chain = shared.db.getChainOfCommand(icId);
const chain = shared.db.agents.getChainOfCommand(icId);
// First in chain is the immediate manager (CTO)
expect(chain.length).toBeGreaterThan(0);
expect(chain[0].name).toBe("CTO");
@@ -30,7 +30,7 @@ export interface SharedHandle {
set db(value: AppDatabase);
get companyId(): string;
get workspaceId(): string;
createAgent(overrides?: Partial<Parameters<AppDatabase["saveAgent"]>[0]>): string;
createAgent(overrides?: Partial<Parameters<AppDatabase["agents"]["saveAgent"]>[0]>): string;
buildCtx(overrides?: Record<string, unknown>): Parameters<typeof buildSystemPrompt>[0];
tempDirs: string[];
}
@@ -80,8 +80,8 @@ export function installSharedSetup(): SharedHandle {
return state.db;
}
function createAgent(overrides: Partial<Parameters<AppDatabase["saveAgent"]>[0]> = {}): string {
return currentDb().saveAgent({
function createAgent(overrides: Partial<Parameters<AppDatabase["agents"]["saveAgent"]>[0]> = {}): string {
return currentDb().agents.saveAgent({
companyId: state.companyId,
name: "TestAgent",
role: "CEO",
@@ -98,7 +98,7 @@ export function installSharedSetup(): SharedHandle {
}
function buildCtx(overrides: Record<string, unknown> = {}): Parameters<typeof buildSystemPrompt>[0] {
const agent = currentDb().getAgent(overrides.agentId as string ?? createAgent());
const agent = currentDb().entityCrud.getAgent(overrides.agentId as string ?? createAgent());
return {
agent,
apiUrl: "http://127.0.0.1:3000",
@@ -15,7 +15,7 @@ describe("Step 5: Task status + comment wake propagation", () => {
const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId });
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ctoId });
const chain = shared.db.getChainOfCommand(icId);
const chain = shared.db.agents.getChainOfCommand(icId);
expect(chain).toHaveLength(2);
expect(chain[0].id).toBe(ctoId); // immediate manager
expect(chain[1].id).toBe(ceoId); // top level
@@ -25,7 +25,7 @@ describe("Step 5: Task status + comment wake propagation", () => {
// Create a potential cycle (shouldn't happen in practice, but guard against it)
const agent1 = createAgent({ name: "Agent1" });
// agent1 reports to no one (CEO), so chain should be empty
const chain = shared.db.getChainOfCommand(agent1);
const chain = shared.db.agents.getChainOfCommand(agent1);
expect(chain).toHaveLength(0);
});
@@ -33,7 +33,7 @@ describe("Step 5: Task status + comment wake propagation", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId });
const parentTaskId = shared.db.saveTask({
const parentTaskId = shared.db.tasks.saveTask({
companyId: shared.companyId,
title: "Build feature X",
description: "Top-level task",
@@ -43,7 +43,7 @@ describe("Step 5: Task status + comment wake propagation", () => {
status: "in_progress",
});
const childTaskId = shared.db.saveTask({
const childTaskId = shared.db.tasks.saveTask({
companyId: shared.companyId,
title: "Implement module A",
description: "Subtask",
@@ -54,10 +54,10 @@ describe("Step 5: Task status + comment wake propagation", () => {
status: "todo",
});
const childTask = shared.db.getTask(childTaskId);
const childTask = shared.db.entityCrud.getTask(childTaskId);
expect(childTask.parentId).toBe(parentTaskId);
const parentTask = shared.db.getTask(parentTaskId);
const parentTask = shared.db.entityCrud.getTask(parentTaskId);
expect(parentTask.assigneeAgentId).toBe(ctoId);
});
});
@@ -14,7 +14,7 @@ describe("Step 8: Wake reason injection", () => {
it("includes wake reason section for non-timer triggers", () => {
const agentId = createAgent({ name: "CTO", role: "CTO" });
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ceo = shared.db.getAgent(ceoId);
const ceo = shared.db.entityCrud.getAgent(ceoId);
const ctx = buildCtx({
agentId,
@@ -92,7 +92,7 @@ describe("Step 8: Wake reason injection", () => {
it("includes wake reason before task description", () => {
const agentId = createAgent({ name: "Agent" });
const taskId = shared.db.saveTask({
const taskId = shared.db.tasks.saveTask({
companyId: shared.companyId,
title: "Do the thing",
description: "Test task",
@@ -101,7 +101,7 @@ describe("Step 8: Wake reason injection", () => {
priority: "medium",
status: "todo",
});
const task = shared.db.getTask(taskId);
const task = shared.db.entityCrud.getTask(taskId);
const ctx = buildCtx({
agentId,
@@ -90,7 +90,7 @@ describe("connector model enforcement", () => {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -103,7 +103,7 @@ describe("connector model enforcement", () => {
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Run with invalid model",
description: "This task should be blocked because the agent model is disabled.",
@@ -154,7 +154,7 @@ describe("connector model enforcement", () => {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -167,7 +167,7 @@ describe("connector model enforcement", () => {
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Recover queued run",
description: "This queued run should fail on recovery because the model is disabled.",
@@ -184,7 +184,7 @@ describe("connector model enforcement", () => {
workspaceId,
connectorId: "openai-codex",
});
const checkoutSuccess = db.checkoutTask(taskId, run.id);
const checkoutSuccess = db.tasks.checkoutTask(taskId, run.id);
expect(checkoutSuccess).toBe(true);
db.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
+19 -19
View File
@@ -79,7 +79,7 @@ function scaffold(db: AppDatabase, budgetAmountCents = 500): ScaffoldResult {
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "PipelineAgent",
role: "IC",
@@ -175,7 +175,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
expect(validation.errors).toHaveLength(0);
// Create the task in the DB
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId: s.companyId,
title: "Build login page",
description:
@@ -194,7 +194,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
const checkoutResult = db.checkoutTask(taskId, runId);
const checkoutResult = db.tasks.checkoutTask(taskId, runId);
expect(checkoutResult).toBe(true);
// 3. Budget gate: verify run is allowed
@@ -221,7 +221,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
});
// 5. Session management: create session and track usage
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId: s.companyId,
agentId: s.agentId,
adapterType: "gemini-cli",
@@ -232,10 +232,10 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
const run = db.getRun(runId);
expect(run.status).toBe("succeeded");
const task = db.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
expect(task.costUsd).toBeGreaterThan(0);
const session = db.getSessionById(sessionId);
const session = db.tasks.getSessionById(sessionId);
expect(session).not.toBeNull();
expect(session!.totalInputTokens).toBe(5000);
expect(session!.totalOutputTokens).toBe(2000);
@@ -254,7 +254,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
it("CAS prevents double checkout within pipeline", () => {
const s = scaffold(db);
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId: s.companyId,
title: "Concurrent Task",
description:
@@ -273,10 +273,10 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
expect(db.checkoutTask(taskId, runId1)).toBe(true);
expect(db.tasks.checkoutTask(taskId, runId1)).toBe(true);
// Second agent creates a run but cannot check out
const agent2Id = db.saveAgent({
const agent2Id = db.agents.saveAgent({
companyId: s.companyId,
name: "Agent2",
role: "IC",
@@ -296,7 +296,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
expect(db.checkoutTask(taskId, runId2)).toBe(false);
expect(db.tasks.checkoutTask(taskId, runId2)).toBe(false);
});
it("budget gate blocks run when policy exhausted", () => {
@@ -311,7 +311,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
});
// Create and complete a run that exceeds the budget
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId: s.companyId,
title: "Expensive Task",
description:
@@ -330,7 +330,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
connectorId: s.connectorId,
});
db.checkoutTask(taskId, runId);
db.tasks.checkoutTask(taskId, runId);
db.markRunStatus(runId, "running", "Starting");
db.recordCostEntry({ runId, companyId: s.companyId, agentId: s.agentId, taskId, connectorId: s.connectorId, amountUsd: 1.5, unattributed: false });
db.finishRun({
@@ -363,7 +363,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
logger: mockLogger,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId: s.companyId,
title: "Multi-Run Task",
description:
@@ -375,7 +375,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
});
// Create session
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId: s.companyId,
agentId: s.agentId,
adapterType: "gemini-cli",
@@ -402,7 +402,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
sessionService.accumulateTokenUsage(sessionId, 3000, 1000, runId2);
// Verify accumulated session state
const session = db.getSessionById(sessionId);
const session = db.tasks.getSessionById(sessionId);
expect(session).not.toBeNull();
expect(session!.runCount).toBe(2);
expect(session!.totalInputTokens).toBe(8000);
@@ -421,7 +421,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
});
// --- First run: costs $3, budget allows ---
const taskId1 = db.saveTask({
const taskId1 = db.tasks.saveTask({
companyId: s.companyId,
title: "First Pipeline Task",
description:
@@ -439,7 +439,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
db.checkoutTask(taskId1, runId1);
db.tasks.checkoutTask(taskId1, runId1);
db.markRunStatus(runId1, "running", "Starting");
db.recordCostEntry({ runId: runId1, companyId: s.companyId, agentId: s.agentId, taskId: taskId1, connectorId: s.connectorId, amountUsd: 3.0, unattributed: false });
db.finishRun({
@@ -462,7 +462,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
expect(afterFirst.allowed).toBe(true);
// --- Second run: costs $3, now total = $6 > $5 budget ---
const taskId2 = db.saveTask({
const taskId2 = db.tasks.saveTask({
companyId: s.companyId,
title: "Second Pipeline Task",
description:
@@ -480,7 +480,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => {
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
db.checkoutTask(taskId2, runId2);
db.tasks.checkoutTask(taskId2, runId2);
db.markRunStatus(runId2, "running", "Starting");
db.recordCostEntry({ runId: runId2, companyId: s.companyId, agentId: s.agentId, taskId: taskId2, connectorId: s.connectorId, amountUsd: 3.0, unattributed: false });
db.finishRun({
+6 -6
View File
@@ -52,7 +52,7 @@ describe("AppDatabase", () => {
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Helios",
role: "CEO",
@@ -83,7 +83,7 @@ describe("AppDatabase", () => {
status: "in_progress",
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
projectId,
goalId,
@@ -194,7 +194,7 @@ describe("AppDatabase", () => {
isPrimary: true,
});
const alphaAgentId = db.saveAgent({
const alphaAgentId = db.agents.saveAgent({
companyId: alphaCompanyId,
name: "Alpha CEO",
role: "CEO",
@@ -208,7 +208,7 @@ describe("AppDatabase", () => {
budgetMonthlyUsd: 100,
});
db.saveAgent({
db.agents.saveAgent({
companyId: betaCompanyId,
name: "Beta CEO",
role: "CEO",
@@ -222,7 +222,7 @@ describe("AppDatabase", () => {
budgetMonthlyUsd: 100,
});
db.saveTask({
db.tasks.saveTask({
companyId: alphaCompanyId,
title: "Alpha task",
description: "Alpha work",
@@ -232,7 +232,7 @@ describe("AppDatabase", () => {
status: "todo",
});
db.saveTask({
db.tasks.saveTask({
companyId: betaCompanyId,
title: "Beta task",
description: "Beta work",
@@ -47,7 +47,7 @@ async function createHarness() {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Research Lead",
role: "Researcher",
@@ -60,7 +60,7 @@ async function createHarness() {
capabilities: "Research and synthesis",
budgetMonthlyUsd: 180,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Refresh the market report",
description: "Update the report with new findings and submit it for review.",
@@ -69,7 +69,7 @@ async function createHarness() {
priority: "high",
status: "in_progress",
});
db.setTaskType(taskId, "research");
db.tasks.setTaskType(taskId, "research");
const existingDocumentId = db.saveDocument({
companyId,
@@ -172,7 +172,7 @@ describe("document run provenance", () => {
// ai_ceo + top-level CEO without `reviewDeliverables` advances
// straight to `done` — there is no reviewer in that configuration
// so the prior `in_review` state was a dead letter.
const task = harness.db.getTask(harness.taskId);
const task = harness.db.entityCrud.getTask(harness.taskId);
expect(task.status).toBe("done");
} finally {
harness.server.stop();
@@ -52,8 +52,8 @@ function buildKickoffPayload(db: AppDatabase, summary: { companyId: string; lead
throw new Error("Missing lead agent or task in onboarding summary.");
}
const agent = db.getAgent(summary.leadAgentId);
const task = db.getTask(summary.taskId);
const agent = db.entityCrud.getAgent(summary.leadAgentId);
const task = db.entityCrud.getTask(summary.taskId);
const connector = db.getConnector(agent.connectorId);
const workspace = summary.workspaceId ? db.getWorkspace(summary.workspaceId) : null;
const company = db.listSnapshot().companies.find((entry) => entry.id === summary.companyId);
@@ -125,7 +125,7 @@ describe("onboarding model pool integration", () => {
throw new Error("Expected lead agent id from onboarding bootstrap.");
}
const agent = db.getAgent(summary.leadAgentId);
const agent = db.entityCrud.getAgent(summary.leadAgentId);
expect(agent.connectorId).toBe("openai-codex");
expect(agent.model).toBe(enabledModel);
expect(db.getDisabledModels(summary.companyId, "openai-codex")).toEqual([{ id: disabledModel, label: disabledModel }]);
@@ -230,7 +230,7 @@ describe("onboarding model pool integration", () => {
throw new Error("Expected lead agent id from onboarding bootstrap.");
}
const agent = db.getAgent(summary.leadAgentId);
const agent = db.entityCrud.getAgent(summary.leadAgentId);
expect(agent.model).toBeNull();
const payload = buildKickoffPayload(db, summary);
+19 -19
View File
@@ -79,7 +79,7 @@ function scaffold(db: AppDatabase): ScaffoldResult {
});
// Agent
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "RecoveryAgent",
role: "IC",
@@ -145,7 +145,7 @@ function createAndStartRun(
): { runId: string; taskId: string } {
const taskId =
opts.taskId ??
db.saveTask({
db.tasks.saveTask({
companyId: opts.companyId,
title: opts.taskTitle ?? "Test Task",
description:
@@ -165,7 +165,7 @@ function createAndStartRun(
connectorId: opts.connectorId,
});
db.checkoutTask(taskId, runId);
db.tasks.checkoutTask(taskId, runId);
db.markRunStatus(runId, "running", "Starting execution");
if (opts.claimWorkspace !== false) {
@@ -243,7 +243,7 @@ describe("Process Recovery -- Worker Crash Detection", () => {
const s = scaffold(db);
// Create a second agent for a second run
const agent2Id = db.saveAgent({
const agent2Id = db.agents.saveAgent({
companyId: s.companyId,
name: "Agent2",
role: "IC",
@@ -306,7 +306,7 @@ describe("Process Recovery -- Worker Crash Detection", () => {
// However, after crash, the task is set to "blocked" and a subsequent
// stale lock recovery pass handles the checkout_run_id.
// For this test, verify the task status is blocked (the recovery mechanism).
const task = db.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
expect(task.status).toBe("blocked");
});
@@ -320,11 +320,11 @@ describe("Process Recovery -- Worker Crash Detection", () => {
});
// Verify agent is running before crash
expect(db.getAgent(s.agentId).status).toBe("running");
expect(db.entityCrud.getAgent(s.agentId).status).toBe("running");
simulateWorkerCrash(db);
expect(db.getAgent(s.agentId).status).toBe("idle");
expect(db.entityCrud.getAgent(s.agentId).status).toBe("idle");
});
it("releases workspace locks after crash", () => {
@@ -356,7 +356,7 @@ describe("Process Recovery -- Worker Crash Detection", () => {
expect(wsAfter.lock_holder_run_id).toBeNull();
// Verify we can re-claim the workspace (lock is truly released)
const agent2Id = db.saveAgent({
const agent2Id = db.agents.saveAgent({
companyId: s.companyId,
name: "Agent2",
role: "IC",
@@ -369,7 +369,7 @@ describe("Process Recovery -- Worker Crash Detection", () => {
capabilities: "Testing",
budgetMonthlyUsd: 100,
});
const taskId2 = db.saveTask({
const taskId2 = db.tasks.saveTask({
companyId: s.companyId,
title: "Recovery Task",
description: "A recovery task after crash to verify workspace lock is released properly.",
@@ -398,12 +398,12 @@ describe("Process Recovery -- Worker Crash Detection", () => {
});
// Verify task is in_progress before crash
expect(db.getTask(taskId).status).toBe("in_progress");
expect(db.entityCrud.getTask(taskId).status).toBe("in_progress");
simulateWorkerCrash(db);
// Task should be blocked (prevents heartbeat re-queuing)
expect(db.getTask(taskId).status).toBe("blocked");
expect(db.entityCrud.getTask(taskId).status).toBe("blocked");
});
});
@@ -422,12 +422,12 @@ describe("Process Recovery -- Context Preservation (D-02)", () => {
});
// Verify assignment before crash
expect(db.getTask(taskId).assigneeAgentId).toBe(s.agentId);
expect(db.entityCrud.getTask(taskId).assigneeAgentId).toBe(s.agentId);
simulateWorkerCrash(db);
// Agent assignment must be preserved for retry context
expect(db.getTask(taskId).assigneeAgentId).toBe(s.agentId);
expect(db.entityCrud.getTask(taskId).assigneeAgentId).toBe(s.agentId);
});
it("session handoff markdown generated on crash", () => {
@@ -440,7 +440,7 @@ describe("Process Recovery -- Context Preservation (D-02)", () => {
});
// Create a task session (simulating what happens during normal execution)
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId: s.companyId,
agentId: s.agentId,
adapterType: "gemini-cli",
@@ -448,7 +448,7 @@ describe("Process Recovery -- Context Preservation (D-02)", () => {
// Drive the session past the maxRuns=20 threshold so rotation fires.
for (let i = 0; i < 20; i++) {
db.incrementSessionUsage(sessionId, 1000, 500, `${runId}-${i}`);
db.tasks.incrementSessionUsage(sessionId, 1000, 500, `${runId}-${i}`);
}
const sessionService = createSessionManagementService({
@@ -456,7 +456,7 @@ describe("Process Recovery -- Context Preservation (D-02)", () => {
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
});
const session = db.getSessionById(sessionId);
const session = db.tasks.getSessionById(sessionId);
expect(session).not.toBeNull();
const rotationResult = sessionService.evaluateRotation(session!);
@@ -499,7 +499,7 @@ describe("Process Recovery -- Multiple Concurrent Runs", () => {
const agents = [s.agentId];
for (let i = 2; i <= 3; i++) {
agents.push(
db.saveAgent({
db.agents.saveAgent({
companyId: s.companyId,
name: `Agent${i}`,
role: "IC",
@@ -540,7 +540,7 @@ describe("Process Recovery -- Multiple Concurrent Runs", () => {
const agents = [s.agentId];
for (let i = 2; i <= 3; i++) {
agents.push(
db.saveAgent({
db.agents.saveAgent({
companyId: s.companyId,
name: `Agent${i}`,
role: "IC",
@@ -571,7 +571,7 @@ describe("Process Recovery -- Multiple Concurrent Runs", () => {
// All tasks should be blocked (crash recovery sets them to blocked)
for (const run of runs) {
const task = db.getTask(run.taskId);
const task = db.entityCrud.getTask(run.taskId);
expect(task.status).toBe("blocked");
}
@@ -46,7 +46,7 @@ describe("skill-aware capability registry", () => {
isPrimary: true,
});
db.saveAgent({
db.agents.saveAgent({
companyId,
name: "Generic Engineer",
role: "Engineer",
@@ -59,7 +59,7 @@ describe("skill-aware capability registry", () => {
capabilities: "General software delivery",
budgetMonthlyUsd: 200,
});
const styledEngineerId = db.saveAgent({
const styledEngineerId = db.agents.saveAgent({
companyId,
name: "Frontend Specialist",
role: "Engineer",
@@ -94,7 +94,7 @@ describe("skill-aware capability registry", () => {
});
db.assignSkillToAgent(styledEngineerId, skillId);
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Build React dashboard widget",
description: "Create an accessible React component with hooks and Tailwind styling.",
@@ -103,7 +103,7 @@ describe("skill-aware capability registry", () => {
status: "todo",
});
const assignedId = db.autoAssignTask(taskId, companyId);
const assignedId = db.tasks.autoAssignTask(taskId, companyId);
expect(assignedId).toBe(styledEngineerId);
db.close();
@@ -135,7 +135,7 @@ describe("skill-aware capability registry", () => {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -171,7 +171,7 @@ describe("skill-aware capability registry", () => {
const snapshot = db.listSnapshot();
const prompt = buildTaskPrompt({
agent: db.getAgent(agentId),
agent: db.entityCrud.getAgent(agentId),
apiUrl: "http://127.0.0.1:3210",
apiKey: "token",
runId: "run-1",
@@ -50,7 +50,7 @@ function createScaffold(db: AppDatabase) {
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
const ceoId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -63,7 +63,7 @@ function createScaffold(db: AppDatabase) {
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const engineerId = db.saveAgent({
const engineerId = db.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
@@ -76,7 +76,7 @@ function createScaffold(db: AppDatabase) {
capabilities: "Ship features",
budgetMonthlyUsd: 180,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Ship the feature",
description: "Implement the approved deliverable and wait for co-founder review.",
@@ -133,7 +133,7 @@ describe("thread wait + heartbeat integration", () => {
expect(waitStates[0]?.waitingFor).toBe("user_reply");
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true);
const otherAgentId = db.saveAgent({
const otherAgentId = db.agents.saveAgent({
companyId,
name: "Marketer",
role: "CMO",
@@ -141,7 +141,7 @@ describe("thread wait + heartbeat integration", () => {
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
workspaceId: db.getTask(taskId).workspaceId ?? null,
workspaceId: db.entityCrud.getTask(taskId).workspaceId ?? null,
model: null,
capabilities: "Drive marketing work",
budgetMonthlyUsd: 180,
@@ -66,7 +66,7 @@ async function createHarness(): Promise<Harness> {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Artifact Engineer",
role: "Engineer",
@@ -79,7 +79,7 @@ async function createHarness(): Promise<Harness> {
capabilities: "Writes files",
budgetMonthlyUsd: 100,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Produce artifact",
description: "",
+2 -2
View File
@@ -42,7 +42,7 @@ describe("agent budget policy synchronization", () => {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
@@ -70,7 +70,7 @@ describe("agent budget policy synchronization", () => {
isActive: true,
});
db.saveAgent({
db.agents.saveAgent({
id: agentId,
companyId,
name: "Engineer",
+3 -3
View File
@@ -43,7 +43,7 @@ describe("hire routing guards", () => {
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
const ceoId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -57,7 +57,7 @@ describe("hire routing guards", () => {
budgetMonthlyUsd: 250,
department: "executive",
});
const hrId = db.saveAgent({
const hrId = db.agents.saveAgent({
companyId,
name: "HR Lead",
role: "HR Lead",
@@ -71,7 +71,7 @@ describe("hire routing guards", () => {
budgetMonthlyUsd: 180,
department: "hr",
});
const ctoId = db.saveAgent({
const ctoId = db.agents.saveAgent({
companyId,
name: "CTO",
role: "CTO",
+12 -12
View File
@@ -44,7 +44,7 @@ describe("canManageOrganization", () => {
isPrimary: true,
});
const ceoId = db.saveAgent({
const ceoId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -57,7 +57,7 @@ describe("canManageOrganization", () => {
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
const ctoId = db.agents.saveAgent({
companyId,
name: "CTO",
role: "CTO",
@@ -71,7 +71,7 @@ describe("canManageOrganization", () => {
budgetMonthlyUsd: 220,
department: "engineering",
});
const engineerId = db.saveAgent({
const engineerId = db.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
@@ -113,7 +113,7 @@ describe("canManageOrganization", () => {
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
const ceoId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -126,7 +126,7 @@ describe("canManageOrganization", () => {
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Operations Lead",
role: "Specialist",
@@ -142,7 +142,7 @@ describe("canManageOrganization", () => {
});
expect(canManageOrganization(db, companyId, agentId)).toBe(false);
db.setAgentPermission(agentId, companyId, "organization_manage", true);
db.agents.setAgentPermission(agentId, companyId, "organization_manage", true);
expect(canManageOrganization(db, companyId, agentId)).toBe(true);
expect(canRequestHire(db, companyId, agentId)).toBe(true);
@@ -167,7 +167,7 @@ describe("canManageOrganization", () => {
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
const ceoId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -180,7 +180,7 @@ describe("canManageOrganization", () => {
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
const ctoId = db.agents.saveAgent({
companyId,
name: "CTO",
role: "CTO",
@@ -194,7 +194,7 @@ describe("canManageOrganization", () => {
budgetMonthlyUsd: 220,
department: "engineering",
});
const cmoId = db.saveAgent({
const cmoId = db.agents.saveAgent({
companyId,
name: "CMO",
role: "CMO",
@@ -208,7 +208,7 @@ describe("canManageOrganization", () => {
budgetMonthlyUsd: 220,
department: "marketing",
});
const ctoReportId = db.saveAgent({
const ctoReportId = db.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
@@ -223,7 +223,7 @@ describe("canManageOrganization", () => {
department: "engineering",
});
const cmoTaskId = db.saveTask({
const cmoTaskId = db.tasks.saveTask({
companyId,
title: "Launch campaign",
description: "Owned by the marketing branch",
@@ -242,7 +242,7 @@ describe("canManageOrganization", () => {
expect(canManageProject(db, companyId, ctoId, cmoId)).toBe(false);
expect(canManageGoal(db, companyId, ceoId, cmoId)).toBe(true);
expect(canManageProject(db, companyId, ceoId, cmoId)).toBe(true);
expect(db.getTask(cmoTaskId).assigneeAgentId).toBe(cmoId);
expect(db.entityCrud.getTask(cmoTaskId).assigneeAgentId).toBe(cmoId);
db.close();
});
+3 -3
View File
@@ -44,7 +44,7 @@ describe("hire approval routing", () => {
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
const ceoId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -57,7 +57,7 @@ describe("hire approval routing", () => {
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
const ctoId = db.agents.saveAgent({
companyId,
name: "CTO",
role: "CTO",
@@ -116,7 +116,7 @@ describe("hire approval routing", () => {
trackTimer: vi.fn(),
}, approvalId, companyId, "approved");
expect(db.getAgent(hireId).status).toBe("idle");
expect(db.entityCrud.getAgent(hireId).status).toBe("idle");
expect(dispatchAutomation).toHaveBeenCalledWith("approval_resolved", expect.objectContaining({ companyId, agentId: ctoId }));
expect(dispatchAutomation).toHaveBeenCalledWith("hire_approved", expect.objectContaining({ companyId, agentId: hireId }));
expect(wakeAgentIfPossible).toHaveBeenCalledWith(hireId, companyId, "assignment");
@@ -44,7 +44,7 @@ describe("deliverable review feedback writeback", () => {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Research Lead",
role: "Researcher",
@@ -58,7 +58,7 @@ describe("deliverable review feedback writeback", () => {
budgetMonthlyUsd: 180,
department: "research",
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Draft market report",
description: "Produce a market report with concrete evidence and business insight.",
@@ -116,7 +116,7 @@ describe("deliverable review feedback writeback", () => {
expect(knowledgeEntries[0]?.content).toContain("revision_requested");
expect(knowledgeEntries[0]?.content).toContain("stronger competitor evidence");
expect(wakeAgentIfPossible).toHaveBeenCalledWith(agentId, companyId, "revision_needed");
expect(db.getTask(taskId).status).toBe("in_progress");
expect(db.entityCrud.getTask(taskId).status).toBe("in_progress");
const document = db.listSnapshot().documents.find((entry) => entry.id === documentId);
expect(document?.status).toBe("draft");
+29 -29
View File
@@ -56,7 +56,7 @@ function createTestWorkspace(db: AppDatabase, companyId: string): string {
}
function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string, name = "Agent"): string {
return db.saveAgent({
return db.agents.saveAgent({
companyId,
name,
role: "IC",
@@ -72,7 +72,7 @@ function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string
}
function createTestTask(db: AppDatabase, companyId: string, agentId: string | null, workspaceId: string): string {
return db.saveTask({
return db.tasks.saveTask({
companyId,
title: "Test Task",
description: "A test task for checkout",
@@ -117,10 +117,10 @@ describe("CAS checkout", () => {
connectorId: "openai-codex",
});
const result = db.checkoutTask(taskId, runId);
const result = db.tasks.checkoutTask(taskId, runId);
expect(result).toBe(true);
const task = db.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
expect(task.status).toBe("in_progress");
});
@@ -146,8 +146,8 @@ describe("CAS checkout", () => {
connectorId: "openai-codex",
});
expect(db.checkoutTask(taskId, runId1)).toBe(true);
expect(db.checkoutTask(taskId, runId2)).toBe(false);
expect(db.tasks.checkoutTask(taskId, runId1)).toBe(true);
expect(db.tasks.checkoutTask(taskId, runId2)).toBe(false);
});
it("CAS checkout rejects already-in-progress tasks", () => {
@@ -168,7 +168,7 @@ describe("CAS checkout", () => {
connectorId: "openai-codex",
});
expect(db.checkoutTask(taskId, runId)).toBe(false);
expect(db.tasks.checkoutTask(taskId, runId)).toBe(false);
});
it("lock release in finishRun clears checkout fields", () => {
@@ -186,7 +186,7 @@ describe("CAS checkout", () => {
connectorId: "openai-codex",
});
db.checkoutTask(taskId, runId);
db.tasks.checkoutTask(taskId, runId);
db.markRunStatus(runId, "running", "");
db.finishRun({
runId,
@@ -222,11 +222,11 @@ describe("CAS checkout", () => {
connectorId: "openai-codex",
});
db.checkoutTask(taskId, runId);
db.tasks.checkoutTask(taskId, runId);
db.markRunStatus(runId, "running", "");
// Deleting the agent triggers cancelRunForDeletion for active runs
db.deleteAgent(agentId, companyId);
db.agents.deleteAgent(agentId, companyId);
const row = (db as unknown as RawDb).db
.prepare("SELECT checkout_run_id, execution_locked_at FROM tasks WHERE id = ?")
@@ -251,7 +251,7 @@ describe("CAS checkout", () => {
connectorId: "openai-codex",
});
db.checkoutTask(taskId, runId);
db.tasks.checkoutTask(taskId, runId);
// Backdate execution_locked_at to 20 minutes ago
const backdated = new Date(Date.now() - 20 * 60 * 1000).toISOString();
@@ -265,7 +265,7 @@ describe("CAS checkout", () => {
expect(recovered[0].taskId).toBe(taskId);
expect(recovered[0].companyId).toBe(companyId);
const task = db.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
expect(task.status).toBe("todo");
const row = (db as unknown as RawDb).db
@@ -291,12 +291,12 @@ describe("CAS checkout", () => {
connectorId: "openai-codex",
});
db.checkoutTask(taskId, runId);
db.tasks.checkoutTask(taskId, runId);
const recovered = db.recoverStaleLocks();
expect(recovered.length).toBe(0);
const task = db.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
expect(task.status).toBe("in_progress");
});
@@ -323,8 +323,8 @@ describe("CAS checkout", () => {
workspaceId,
connectorId: "openai-codex",
});
db.checkoutTask(taskId, runId);
expect(db.getTask(taskId).status).toBe("in_progress");
db.tasks.checkoutTask(taskId, runId);
expect(db.entityCrud.getTask(taskId).status).toBe("in_progress");
// Never call markRunStatus('running', ...) — this simulates the
// "workspace already locked" bail-out inside dispatchRunToWorker.
@@ -339,7 +339,7 @@ describe("CAS checkout", () => {
unattributedCost: true,
});
expect(db.getTask(taskId).status).toBe("todo");
expect(db.entityCrud.getTask(taskId).status).toBe("todo");
// And the next CAS checkout must succeed — proving the heartbeat
// retry path is unblocked.
@@ -350,7 +350,7 @@ describe("CAS checkout", () => {
workspaceId,
connectorId: "openai-codex",
});
expect(db.checkoutTask(taskId, retryRunId)).toBe(true);
expect(db.tasks.checkoutTask(taskId, retryRunId)).toBe(true);
});
it("cancelled run that never started also rolls task back to 'todo'", () => {
@@ -371,7 +371,7 @@ describe("CAS checkout", () => {
workspaceId,
connectorId: "openai-codex",
});
db.checkoutTask(taskId, runId);
db.tasks.checkoutTask(taskId, runId);
db.finishRun({
runId,
status: "cancelled",
@@ -383,7 +383,7 @@ describe("CAS checkout", () => {
unattributedCost: true,
});
expect(db.getTask(taskId).status).toBe("todo");
expect(db.entityCrud.getTask(taskId).status).toBe("todo");
});
it("failed run that did start stays 'in_progress' (human retry path)", () => {
@@ -404,7 +404,7 @@ describe("CAS checkout", () => {
workspaceId,
connectorId: "openai-codex",
});
db.checkoutTask(taskId, runId);
db.tasks.checkoutTask(taskId, runId);
db.markRunStatus(runId, "running", "");
db.finishRun({
runId,
@@ -417,7 +417,7 @@ describe("CAS checkout", () => {
unattributedCost: false,
});
expect(db.getTask(taskId).status).toBe("in_progress");
expect(db.entityCrud.getTask(taskId).status).toBe("in_progress");
});
it("advanceTaskStatus('in_review') rejects a CEO self-review that has no reviewer", () => {
@@ -433,9 +433,9 @@ describe("CAS checkout", () => {
const ceoId = createTestAgent(db, companyId, workspaceId, "CEO");
const taskId = createTestTask(db, companyId, ceoId, workspaceId);
expect(() => db.advanceTaskStatus(taskId, "in_review")).toThrow(/no reviewer/);
expect(() => db.tasks.advanceTaskStatus(taskId, "in_review")).toThrow(/no reviewer/);
// Task status must remain unchanged after the rejected transition.
expect(db.getTask(taskId).status).toBe("todo");
expect(db.entityCrud.getTask(taskId).status).toBe("todo");
});
it("advanceTaskStatus('in_review') accepts a CEO assignee flagged requires_user_review=1", () => {
@@ -445,10 +445,10 @@ describe("CAS checkout", () => {
const workspaceId = createTestWorkspace(db, companyId);
const ceoId = createTestAgent(db, companyId, workspaceId, "CEO");
const taskId = createTestTask(db, companyId, ceoId, workspaceId);
db.setTaskRequiresUserReview(taskId, true);
db.tasks.setTaskRequiresUserReview(taskId, true);
db.advanceTaskStatus(taskId, "in_review");
expect(db.getTask(taskId).status).toBe("in_review");
db.tasks.advanceTaskStatus(taskId, "in_review");
expect(db.entityCrud.getTask(taskId).status).toBe("in_review");
});
it("interruptStaleRuns surfaces shutdown-era running runs without startup task rewrites", () => {
@@ -472,7 +472,7 @@ describe("CAS checkout", () => {
workspaceId,
connectorId: "openai-codex",
});
db.checkoutTask(taskId, liveRunId);
db.tasks.checkoutTask(taskId, liveRunId);
db.markRunStatus(liveRunId, "running", "");
db.close();
@@ -480,6 +480,6 @@ describe("CAS checkout", () => {
db.init();
expect(db.lastInterruptedCount).toBe(1);
expect(db.getTask(taskId).status).toBe("blocked");
expect(db.entityCrud.getTask(taskId).status).toBe("blocked");
});
});
+6 -6
View File
@@ -56,7 +56,7 @@ describe("company mode switching", () => {
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
const ceoId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -69,7 +69,7 @@ describe("company mode switching", () => {
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const engineerId = db.saveAgent({
const engineerId = db.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
@@ -83,7 +83,7 @@ describe("company mode switching", () => {
budgetMonthlyUsd: 180,
department: "engineering",
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Ship feature",
description: "Waits for cofounder review.",
@@ -100,7 +100,7 @@ describe("company mode switching", () => {
threadIntegration.onTaskCreated(taskId, companyId);
threadIntegration.onRunFinished("run-1", taskId, engineerId, companyId, "succeeded", null);
db.setTaskRequiresUserReview(taskId, true);
db.tasks.setTaskRequiresUserReview(taskId, true);
const approvalId = db.requestApproval({
companyId,
relatedTaskId: taskId,
@@ -113,7 +113,7 @@ describe("company mode switching", () => {
threadIntegration.onApprovalCreated(approvalId, companyId, taskId, "deliverable_review", "Review deliverable");
expect(db.listThreadWaitStates(companyId).length).toBeGreaterThan(0);
expect(db.getTask(taskId).requiresUserReview).toBe(true);
expect(db.entityCrud.getTask(taskId).requiresUserReview).toBe(true);
expect(db.getApprovalRecord(approvalId).state).toBe("pending");
const handlers = new Map<string, IpcHandler>();
@@ -149,7 +149,7 @@ describe("company mode switching", () => {
expect(result.ok).toBe(true);
expect(db.getCompanyExecutionMode(companyId)).toBe("ai_ceo");
expect(db.listThreadWaitStates(companyId)).toHaveLength(0);
expect(db.getTask(taskId).requiresUserReview).toBe(false);
expect(db.entityCrud.getTask(taskId).requiresUserReview).toBe(false);
expect(db.getApprovalRecord(approvalId).state).toBe("approved");
db.close();
+22 -22
View File
@@ -47,7 +47,7 @@ describe("AppDatabase", () => {
repoRef: "main",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Chief Operator",
role: "CEO",
@@ -76,7 +76,7 @@ describe("AppDatabase", () => {
status: "in_progress",
targetDate: null,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
projectId,
goalId,
@@ -181,7 +181,7 @@ describe("AppDatabase", () => {
repoRef: "main",
isPrimary: true,
});
const leadId = db.saveAgent({
const leadId = db.agents.saveAgent({
companyId,
name: "Lead",
role: "CEO",
@@ -200,7 +200,7 @@ describe("AppDatabase", () => {
envBindingText: "",
notes: "Test connector",
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Claim me",
description: "First agent wins the checkout.",
@@ -225,10 +225,10 @@ describe("AppDatabase", () => {
connectorId: "openai-codex",
});
expect(db.checkoutTask(taskId, runId1)).toBe(true);
expect(db.checkoutTask(taskId, runId2)).toBe(false);
expect(db.tasks.checkoutTask(taskId, runId1)).toBe(true);
expect(db.tasks.checkoutTask(taskId, runId2)).toBe(false);
const task = db.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
expect(task.status).toBe("in_progress");
});
@@ -246,7 +246,7 @@ describe("AppDatabase", () => {
repoRef: "main",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Budget Owner",
role: "CTO",
@@ -259,7 +259,7 @@ describe("AppDatabase", () => {
capabilities: "Ships work",
budgetMonthlyUsd: 10,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Spend budget",
description: "One run should exhaust the allowance.",
@@ -300,8 +300,8 @@ describe("AppDatabase", () => {
});
expect(outcome.budgetHardStopped).toBe(true);
expect(db.getAgent(agentId).status).toBe("paused");
expect(db.getAgent(agentId).spentMonthlyUsd).toBe(12);
expect(db.entityCrud.getAgent(agentId).status).toBe("paused");
expect(db.entityCrud.getAgent(agentId).spentMonthlyUsd).toBe(12);
});
it("activates pending hires when the corresponding approval is granted", () => {
@@ -310,7 +310,7 @@ describe("AppDatabase", () => {
description: "Approval-driven hiring",
status: "active",
});
const managerId = db.saveAgent({
const managerId = db.agents.saveAgent({
companyId,
name: "Manager",
role: "CEO",
@@ -323,7 +323,7 @@ describe("AppDatabase", () => {
capabilities: "Manages",
budgetMonthlyUsd: 100,
});
const pendingHireId = db.saveAgent({
const pendingHireId = db.agents.saveAgent({
companyId,
name: "Pending Hire",
role: "IC",
@@ -353,7 +353,7 @@ describe("AppDatabase", () => {
decisionNote: "Approved",
});
expect(db.getAgent(pendingHireId).status).toBe("idle");
expect(db.entityCrud.getAgent(pendingHireId).status).toBe("idle");
});
it("detects pending-approval agents via isAgentPendingApproval", () => {
@@ -362,7 +362,7 @@ describe("AppDatabase", () => {
description: "Approval gate validation",
status: "active",
});
const activeAgentId = db.saveAgent({
const activeAgentId = db.agents.saveAgent({
companyId,
name: "Active Agent",
role: "CTO",
@@ -375,7 +375,7 @@ describe("AppDatabase", () => {
capabilities: "Ships",
budgetMonthlyUsd: 100,
});
const pendingAgentId = db.saveAgent({
const pendingAgentId = db.agents.saveAgent({
companyId,
name: "Pending Agent",
role: "IC",
@@ -389,8 +389,8 @@ describe("AppDatabase", () => {
budgetMonthlyUsd: 50,
});
expect(db.isAgentPendingApproval(activeAgentId)).toBe(false);
expect(db.isAgentPendingApproval(pendingAgentId)).toBe(true);
expect(db.agents.isAgentPendingApproval(activeAgentId)).toBe(false);
expect(db.agents.isAgentPendingApproval(pendingAgentId)).toBe(true);
});
it("runs forward-only migrations and creates schema_migrations table", () => {
@@ -479,7 +479,7 @@ describe("AppDatabase", () => {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Purge Agent",
role: "IC",
@@ -492,7 +492,7 @@ describe("AppDatabase", () => {
capabilities: "test",
budgetMonthlyUsd: 100,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Purge task",
description: "test",
@@ -542,7 +542,7 @@ describe("AppDatabase", () => {
description: "test",
status: "active",
});
const managerId = db.saveAgent({
const managerId = db.agents.saveAgent({
companyId,
name: "Manager",
role: "CEO",
@@ -555,7 +555,7 @@ describe("AppDatabase", () => {
capabilities: "manage",
budgetMonthlyUsd: 100,
});
const hireId = db.saveAgent({
const hireId = db.agents.saveAgent({
companyId,
name: "New Hire",
role: "IC",
+10 -10
View File
@@ -39,7 +39,7 @@ function scaffold(db: AppDatabase) {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Worker Agent",
role: "developer",
@@ -68,7 +68,7 @@ function scaffold(db: AppDatabase) {
status: "in_progress",
targetDate: null,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
projectId,
goalId,
@@ -135,11 +135,11 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
workspaceId,
connectorId: "gemini-cli",
});
db.checkoutTask(taskId, run1.id);
db.tasks.checkoutTask(taskId, run1.id);
db.markRunStatus(run1.id, "running", "");
// Create a second task and run that exhausts budget WHILE Run-1 is in-flight
const taskId2 = db.saveTask({
const taskId2 = db.tasks.saveTask({
companyId,
title: "Other Task",
description: "Another task that uses up budget",
@@ -155,7 +155,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
workspaceId,
connectorId: "gemini-cli",
});
db.checkoutTask(taskId2, run0.id);
db.tasks.checkoutTask(taskId2, run0.id);
db.markRunStatus(run0.id, "running", "");
// Complete Run-0 with $6 cost (exceeds $5 budget)
db.recordCostEntry({ runId: run0.id, companyId, agentId, taskId: taskId2, connectorId: "gemini-cli", amountUsd: 6.0, unattributed: false });
@@ -221,7 +221,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
workspaceId,
connectorId: "gemini-cli",
});
db.checkoutTask(taskId, run.id);
db.tasks.checkoutTask(taskId, run.id);
db.markRunStatus(run.id, "running", "");
db.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 6.0, unattributed: false });
db.finishRun({
@@ -269,7 +269,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
workspaceId,
connectorId: "gemini-cli",
});
db.checkoutTask(taskId, run.id);
db.tasks.checkoutTask(taskId, run.id);
db.markRunStatus(run.id, "running", "");
db.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 6.0, unattributed: false });
db.finishRun({
@@ -326,7 +326,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
workspaceId,
connectorId: "gemini-cli",
});
db.checkoutTask(taskId, run.id);
db.tasks.checkoutTask(taskId, run.id);
db.markRunStatus(run.id, "running", "");
db.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 1.5, unattributed: false });
db.finishRun({
@@ -378,7 +378,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
workspaceId,
connectorId: "gemini-cli",
});
db.checkoutTask(taskId, run.id);
db.tasks.checkoutTask(taskId, run.id);
db.markRunStatus(run.id, "running", "");
db.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 4.0, unattributed: false });
db.finishRun({
@@ -424,7 +424,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
workspaceId,
connectorId: "gemini-cli",
});
db.checkoutTask(taskId, run.id);
db.tasks.checkoutTask(taskId, run.id);
db.markRunStatus(run.id, "running", "");
// Complete with cost that exceeds budget -- cost entry is still recorded
+20 -20
View File
@@ -56,7 +56,7 @@ function createTestWorkspace(db: AppDatabase, companyId: string): string {
}
function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string, name = "Agent"): string {
return db.saveAgent({
return db.agents.saveAgent({
companyId,
name,
role: "IC",
@@ -72,7 +72,7 @@ function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string
}
function createTestTask(db: AppDatabase, companyId: string, workspaceId: string): string {
return db.saveTask({
return db.tasks.saveTask({
companyId,
title: "Concurrent Test Task",
description: "A task for testing concurrent checkout edge cases",
@@ -126,15 +126,15 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => {
});
// Agent-A claims first
const resultA = db.checkoutTask(taskId, runIdA);
const resultA = db.tasks.checkoutTask(taskId, runIdA);
expect(resultA).toBe(true);
// Agent-B tries to claim the same task
const resultB = db.checkoutTask(taskId, runIdB);
const resultB = db.tasks.checkoutTask(taskId, runIdB);
expect(resultB).toBe(false);
// Task status must be in_progress (set by first successful checkout)
const task = db.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
expect(task.status).toBe("in_progress");
});
@@ -162,9 +162,9 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => {
});
// Agent-A wins first task
expect(db.checkoutTask(taskId1, runIdA)).toBe(true);
expect(db.tasks.checkoutTask(taskId1, runIdA)).toBe(true);
// Agent-B loses
expect(db.checkoutTask(taskId1, runIdB1)).toBe(false);
expect(db.tasks.checkoutTask(taskId1, runIdB1)).toBe(false);
// Agent-B can still checkout a DIFFERENT task
const taskId2 = createTestTask(db, companyId, workspaceId);
@@ -175,11 +175,11 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => {
workspaceId,
connectorId: "gemini-cli",
});
const resultB2 = db.checkoutTask(taskId2, runIdB2);
const resultB2 = db.tasks.checkoutTask(taskId2, runIdB2);
expect(resultB2).toBe(true);
// Verify second task is now in_progress
const task2 = db.getTask(taskId2);
const task2 = db.entityCrud.getTask(taskId2);
expect(task2.status).toBe("in_progress");
});
@@ -199,7 +199,7 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => {
workspaceId,
connectorId: "gemini-cli",
});
expect(db.checkoutTask(taskId, runIdA)).toBe(true);
expect(db.tasks.checkoutTask(taskId, runIdA)).toBe(true);
// Task is now in_progress. A new run from Agent-B tries to checkout
const { id: runIdB } = db.createRun({
@@ -209,11 +209,11 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => {
workspaceId,
connectorId: "gemini-cli",
});
const result = db.checkoutTask(taskId, runIdB);
const result = db.tasks.checkoutTask(taskId, runIdB);
expect(result).toBe(false);
// Confirm status is still in_progress
expect(db.getTask(taskId).status).toBe("in_progress");
expect(db.entityCrud.getTask(taskId).status).toBe("in_progress");
});
it("after lock release, CAS guard still checks task status", () => {
@@ -232,10 +232,10 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => {
workspaceId,
connectorId: "gemini-cli",
});
expect(db.checkoutTask(taskId, runIdA)).toBe(true);
expect(db.tasks.checkoutTask(taskId, runIdA)).toBe(true);
// Release the lock (sets checkout_run_id = NULL)
db.releaseTaskLock(taskId, runIdA);
db.tasks.releaseTaskLock(taskId, runIdA);
// Verify lock fields are cleared
const row = (db as unknown as RawDb).db
@@ -253,7 +253,7 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => {
workspaceId,
connectorId: "gemini-cli",
});
const result = db.checkoutTask(taskId, runIdB);
const result = db.tasks.checkoutTask(taskId, runIdB);
expect(result).toBe(false);
});
@@ -271,7 +271,7 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => {
workspaceId,
connectorId: "gemini-cli",
});
expect(db.checkoutTask(taskId, runIdA)).toBe(true);
expect(db.tasks.checkoutTask(taskId, runIdA)).toBe(true);
// Backdate execution_locked_at to well past the stale timeout
(db as unknown as RawDb).db
@@ -307,7 +307,7 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => {
workspaceId,
connectorId: "gemini-cli",
});
expect(db.checkoutTask(taskId, runIdA)).toBe(true);
expect(db.tasks.checkoutTask(taskId, runIdA)).toBe(true);
// Backdate to make stale
(db as unknown as RawDb).db
@@ -319,7 +319,7 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => {
expect(recovered.length).toBe(1);
// Verify task is back to "todo"
expect(db.getTask(taskId).status).toBe("todo");
expect(db.entityCrud.getTask(taskId).status).toBe("todo");
// Agent-B can now checkout the recovered task
const { id: runIdB } = db.createRun({
@@ -329,9 +329,9 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => {
workspaceId,
connectorId: "gemini-cli",
});
const result = db.checkoutTask(taskId, runIdB);
const result = db.tasks.checkoutTask(taskId, runIdB);
expect(result).toBe(true);
expect(db.getTask(taskId).status).toBe("in_progress");
expect(db.entityCrud.getTask(taskId).status).toBe("in_progress");
});
it("STALE_LOCK_TIMEOUT_MS matches expected 15 minute value", () => {
+26 -26
View File
@@ -43,7 +43,7 @@ function scaffold(db: AppDatabase, connectorId: ConnectorId = "gemini-cli") {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Worker Agent",
role: "developer",
@@ -72,7 +72,7 @@ function scaffold(db: AppDatabase, connectorId: ConnectorId = "gemini-cli") {
status: "in_progress",
targetDate: null,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
projectId,
goalId,
@@ -114,7 +114,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
const sessionService = createSessionManagementService({ db, logger: mockLogger });
// Create a session
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
@@ -122,11 +122,11 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
// Simulate runs until we reach the maxRuns threshold (20)
for (let i = 0; i < 20; i++) {
db.incrementSessionUsage(sessionId, 1000, 500, `run-${i}`);
db.tasks.incrementSessionUsage(sessionId, 1000, 500, `run-${i}`);
}
// Get the updated session from DB
const session = db.getSessionById(sessionId);
const session = db.tasks.getSessionById(sessionId);
expect(session).not.toBeNull();
expect(session!.runCount).toBe(20); // matches maxRuns threshold in session-management-service
@@ -140,16 +140,16 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
const { companyId, agentId, taskId } = scaffold(db);
const sessionService = createSessionManagementService({ db, logger: mockLogger });
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
// Accumulate tokens past the 500_000 maxTokens threshold in one big call
db.incrementSessionUsage(sessionId, 300000, 250000, "big-run");
db.tasks.incrementSessionUsage(sessionId, 300000, 250000, "big-run");
const session = db.getSessionById(sessionId);
const session = db.tasks.getSessionById(sessionId);
expect(session).not.toBeNull();
const totalTokens = session!.totalInputTokens + session!.totalOutputTokens;
expect(totalTokens).toBe(550000);
@@ -163,7 +163,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
const { companyId, agentId, taskId, workspaceId } = scaffold(db);
const sessionService = createSessionManagementService({ db, logger: mockLogger });
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
@@ -171,7 +171,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
// Accumulate enough to trigger rotation
for (let i = 0; i < 20; i++) {
db.incrementSessionUsage(sessionId, 1000, 500, `run-${i}`);
db.tasks.incrementSessionUsage(sessionId, 1000, 500, `run-${i}`);
}
// Create a completed run for history (so generateHandoff has run data)
@@ -193,7 +193,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
unattributedCost: false,
});
const session = db.getSessionById(sessionId)!;
const session = db.tasks.getSessionById(sessionId)!;
// generateHandoff is agent-scoped: it walks the agent's runs across
// all tasks + taskless heartbeats, not a single task.
const handoff = sessionService.generateHandoff(session);
@@ -203,7 +203,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
expect(handoff).toContain("## Recent Run History");
// Archive the old session with handoff
db.archiveSession(sessionId, "max_runs", handoff);
db.tasks.archiveSession(sessionId, "max_runs", handoff);
// Verify archived session has handoff stored
const archivedRows = db.queryAll<{ handoffMarkdown: string | null }>(
@@ -217,27 +217,27 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
const { companyId, agentId, taskId } = scaffold(db);
// Create initial session
const oldSessionId = db.createNewSession({
const oldSessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
// Add usage to the old session
db.incrementSessionUsage(oldSessionId, 300000, 250000, "run-final");
db.tasks.incrementSessionUsage(oldSessionId, 300000, 250000, "run-final");
// Archive the old session
db.archiveSession(oldSessionId, "max_tokens", "# Session Handoff\nPrevious work summary.");
db.tasks.archiveSession(oldSessionId, "max_tokens", "# Session Handoff\nPrevious work summary.");
// Create new session
const newSessionId = db.createNewSession({
const newSessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
// Get the new active session
const newSession = db.getSessionById(newSessionId);
const newSession = db.tasks.getSessionById(newSessionId);
expect(newSession).not.toBeNull();
expect(newSession!.id).toBe(newSessionId);
expect(newSession!.id).not.toBe(oldSessionId);
@@ -252,7 +252,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
const { companyId, agentId, taskId } = scaffold(db, "gemini-cli");
const sessionService = createSessionManagementService({ db, logger: mockLogger });
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "claude-pro",
@@ -260,10 +260,10 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
// Accumulate way past all thresholds
for (let i = 0; i < 25; i++) {
db.incrementSessionUsage(sessionId, 30000, 20000, `run-${i}`);
db.tasks.incrementSessionUsage(sessionId, 30000, 20000, `run-${i}`);
}
const session = db.getSessionById(sessionId)!;
const session = db.tasks.getSessionById(sessionId)!;
// Pi-mono delegates compaction to pi-agent-core; AgentCompany still applies rotation uniformly.
const result = sessionService.evaluateRotation(session);
expect(result.shouldRotate).toBe(true);
@@ -274,7 +274,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
const { companyId, agentId, taskId } = scaffold(db, "gemini-cli");
const sessionService = createSessionManagementService({ db, logger: mockLogger });
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "openai-codex",
@@ -282,10 +282,10 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
// Accumulate way past all thresholds
for (let i = 0; i < 25; i++) {
db.incrementSessionUsage(sessionId, 30000, 20000, `run-${i}`);
db.tasks.incrementSessionUsage(sessionId, 30000, 20000, `run-${i}`);
}
const session = db.getSessionById(sessionId)!;
const session = db.tasks.getSessionById(sessionId)!;
// Pi-mono delegates compaction to pi-agent-core; AgentCompany still applies rotation uniformly.
const result = sessionService.evaluateRotation(session);
expect(result.shouldRotate).toBe(true);
@@ -297,12 +297,12 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
const sessionService = createSessionManagementService({ db, logger: mockLogger });
// Create session and add usage
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
db.incrementSessionUsage(sessionId, 300000, 200000, "run-1");
db.tasks.incrementSessionUsage(sessionId, 300000, 200000, "run-1");
// Create a completed run for run history
const run = db.createRun({
@@ -324,7 +324,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
});
// Rotate the session (archives old, creates new)
const oldSession = db.getSessionById(sessionId)!;
const oldSession = db.tasks.getSessionById(sessionId)!;
sessionService.rotateSession(oldSession);
// After rotation, the handoff should be retrievable for the next run
+40 -36
View File
@@ -22,12 +22,16 @@ const RETRY_COOLDOWN_MS = 30_000;
function createMockDeps(): EscalationDeps {
return {
db: {
getTaskRetryCount: vi.fn().mockReturnValue(0),
incrementTaskRetryCount: vi.fn(),
resetTaskRetryCount: vi.fn(),
getChainOfCommand: vi.fn().mockReturnValue([]),
getCeoForCompany: vi.fn().mockReturnValue(null),
advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_progress" }),
tasks: {
getTaskRetryCount: vi.fn().mockReturnValue(0),
incrementTaskRetryCount: vi.fn(),
resetTaskRetryCount: vi.fn(),
advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_progress" }),
},
agents: {
getChainOfCommand: vi.fn().mockReturnValue([]),
getCeoForCompany: vi.fn().mockReturnValue(null),
},
},
wakeAgentIfPossible: vi.fn().mockReturnValue("wake-id"),
notify: vi.fn(),
@@ -58,15 +62,15 @@ describe("Failure Escalation Pipeline", () => {
describe("Retry path (count < MAX_RETRY_ATTEMPTS)", () => {
it("retries on first failure (retry_count=0)", () => {
(deps.db.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(0);
(deps.db.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(0);
const result = handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("retry");
expect(deps.db.incrementTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.db.advanceTaskStatus).toHaveBeenCalledWith(
expect(deps.db.tasks.incrementTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.db.tasks.advanceTaskStatus).toHaveBeenCalledWith(
"task-1",
"todo",
expect.stringContaining("attempt 1/2"),
@@ -75,15 +79,15 @@ describe("Failure Escalation Pipeline", () => {
});
it("retries on second failure (retry_count=1)", () => {
(deps.db.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(1);
(deps.db.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(1);
const result = handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("retry");
expect(deps.db.incrementTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.db.advanceTaskStatus).toHaveBeenCalledWith(
expect(deps.db.tasks.incrementTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.db.tasks.advanceTaskStatus).toHaveBeenCalledWith(
"task-1",
"todo",
expect.stringContaining("attempt 2/2"),
@@ -92,7 +96,7 @@ describe("Failure Escalation Pipeline", () => {
});
it("calls wakeAgentIfPossible via scheduleRetry callback", () => {
(deps.db.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(0);
(deps.db.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(0);
handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
@@ -107,8 +111,8 @@ describe("Failure Escalation Pipeline", () => {
describe("Escalation path (count >= MAX_RETRY_ATTEMPTS)", () => {
it("escalates to chain[0] manager after max retries", () => {
(deps.db.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([
(deps.db.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.agents.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([
{ id: "manager-1", name: "Manager Alpha" },
]);
@@ -117,32 +121,32 @@ describe("Failure Escalation Pipeline", () => {
);
expect(result.action).toBe("escalated");
expect(deps.db.resetTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.db.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.wakeAgentIfPossible).toHaveBeenCalledWith("manager-1", "company-1", "report_failed");
expect(deps.notify).not.toHaveBeenCalled();
});
it("falls through to CEO when chain is empty", () => {
(deps.db.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([]);
(deps.db.getCeoForCompany as ReturnType<typeof vi.fn>).mockReturnValue({ id: "ceo-1", name: "CEO Boss" });
(deps.db.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.agents.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([]);
(deps.db.agents.getCeoForCompany as ReturnType<typeof vi.fn>).mockReturnValue({ id: "ceo-1", name: "CEO Boss" });
const result = handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("escalated");
expect(deps.db.resetTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.db.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.wakeAgentIfPossible).toHaveBeenCalledWith("ceo-1", "company-1", "report_failed");
expect(deps.notify).not.toHaveBeenCalled();
});
it("falls through to CEO when chain lookup throws", () => {
(deps.db.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.getChainOfCommand as ReturnType<typeof vi.fn>).mockImplementation(() => {
(deps.db.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.agents.getChainOfCommand as ReturnType<typeof vi.fn>).mockImplementation(() => {
throw new Error("Agent deleted");
});
(deps.db.getCeoForCompany as ReturnType<typeof vi.fn>).mockReturnValue({ id: "ceo-1", name: "CEO Boss" });
(deps.db.agents.getCeoForCompany as ReturnType<typeof vi.fn>).mockReturnValue({ id: "ceo-1", name: "CEO Boss" });
const result = handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
@@ -153,9 +157,9 @@ describe("Failure Escalation Pipeline", () => {
});
it("notifies user when both chain and CEO are unavailable", () => {
(deps.db.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([]);
(deps.db.getCeoForCompany as ReturnType<typeof vi.fn>).mockReturnValue(null);
(deps.db.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.agents.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([]);
(deps.db.agents.getCeoForCompany as ReturnType<typeof vi.fn>).mockReturnValue(null);
const result = handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
@@ -173,9 +177,9 @@ describe("Failure Escalation Pipeline", () => {
});
it("uses taskId as fallback when task is null", () => {
(deps.db.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([]);
(deps.db.getCeoForCompany as ReturnType<typeof vi.fn>).mockReturnValue(null);
(deps.db.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.agents.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([]);
(deps.db.agents.getCeoForCompany as ReturnType<typeof vi.fn>).mockReturnValue(null);
const result = handleFailureEscalation(
deps, RUN, null, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
@@ -191,19 +195,19 @@ describe("Failure Escalation Pipeline", () => {
describe("Timed out runs follow same path", () => {
it("retries timed_out runs the same as failed runs", () => {
(deps.db.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(0);
(deps.db.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(0);
const result = handleFailureEscalation(
deps, RUN, TASK, "timed_out", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("retry");
expect(deps.db.incrementTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.db.tasks.incrementTaskRetryCount).toHaveBeenCalledWith("task-1");
});
it("escalates timed_out runs after max retries", () => {
(deps.db.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([
(deps.db.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.agents.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([
{ id: "manager-1", name: "Manager Alpha" },
]);
@@ -212,18 +216,18 @@ describe("Failure Escalation Pipeline", () => {
);
expect(result.action).toBe("escalated");
expect(deps.db.resetTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.db.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1");
});
});
describe("Success resets retry count", () => {
it("resets retry_count on successful completion", () => {
handleSuccessReset(deps.db, "task-1");
expect(deps.db.resetTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.db.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1");
});
it("handles reset errors gracefully", () => {
(deps.db.resetTaskRetryCount as ReturnType<typeof vi.fn>).mockImplementation(() => {
(deps.db.tasks.resetTaskRetryCount as ReturnType<typeof vi.fn>).mockImplementation(() => {
throw new Error("DB error");
});
+1 -1
View File
@@ -52,7 +52,7 @@ describe("ipc-core-agent-graph-handlers model enforcement", () => {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
+6 -6
View File
@@ -41,7 +41,7 @@ function createScaffold(db: AppDatabase) {
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
const ceoId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -54,7 +54,7 @@ function createScaffold(db: AppDatabase) {
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
const ctoId = db.agents.saveAgent({
companyId,
name: "CTO",
role: "CTO",
@@ -68,7 +68,7 @@ function createScaffold(db: AppDatabase) {
budgetMonthlyUsd: 200,
department: "engineering",
});
const cmoId = db.saveAgent({
const cmoId = db.agents.saveAgent({
companyId,
name: "CMO",
role: "CMO",
@@ -82,7 +82,7 @@ function createScaffold(db: AppDatabase) {
budgetMonthlyUsd: 200,
department: "marketing",
});
const pendingId = db.saveAgent({
const pendingId = db.agents.saveAgent({
companyId,
name: "Pending Recruit",
role: "IC",
@@ -96,7 +96,7 @@ function createScaffold(db: AppDatabase) {
budgetMonthlyUsd: 100,
department: "engineering",
});
const terminatedId = db.saveAgent({
const terminatedId = db.agents.saveAgent({
companyId,
name: "Former COO",
role: "COO",
@@ -111,7 +111,7 @@ function createScaffold(db: AppDatabase) {
department: "operations",
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
title: "Review architecture plan",
description: "Need board feedback in the thread.",
+2 -2
View File
@@ -32,7 +32,7 @@ describe("AppDatabase.searchMessages", () => {
status: "active",
});
agentA = db.saveAgent({
agentA = db.agents.saveAgent({
companyId,
name: "Agent Alpha",
role: "Engineer",
@@ -46,7 +46,7 @@ describe("AppDatabase.searchMessages", () => {
budgetMonthlyUsd: 100,
});
agentB = db.saveAgent({
agentB = db.agents.saveAgent({
companyId,
name: "Agent Beta",
role: "Designer",
+32 -32
View File
@@ -34,7 +34,7 @@ function scaffold(db: AppDatabase) {
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
const agentId = db.agents.saveAgent({
companyId,
name: "Worker Agent",
role: "developer",
@@ -63,7 +63,7 @@ function scaffold(db: AppDatabase) {
status: "in_progress",
targetDate: null,
});
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId,
projectId,
goalId,
@@ -96,19 +96,19 @@ describe("task_sessions CRUD", () => {
});
it("getSessionById returns null for an unknown id", () => {
const session = db.getSessionById("nonexistent");
const session = db.tasks.getSessionById("nonexistent");
expect(session).toBeNull();
});
it("getSessionById returns TaskSessionRecord when valid session exists", () => {
const { companyId, agentId } = scaffold(db);
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "claude-pro",
});
const session = db.getSessionById(sessionId);
const session = db.tasks.getSessionById(sessionId);
expect(session).not.toBeNull();
expect(session!.id).toBe(sessionId);
expect(session!.companyId).toBe(companyId);
@@ -124,23 +124,23 @@ describe("task_sessions CRUD", () => {
it("incrementSessionUsage atomically updates counters", () => {
const { companyId, agentId } = scaffold(db);
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "claude-pro",
});
db.incrementSessionUsage(sessionId, 1000, 500, "run-001");
db.tasks.incrementSessionUsage(sessionId, 1000, 500, "run-001");
const session = db.getSessionById(sessionId);
const session = db.tasks.getSessionById(sessionId);
expect(session!.runCount).toBe(1);
expect(session!.totalInputTokens).toBe(1000);
expect(session!.totalOutputTokens).toBe(500);
expect(session!.lastRunId).toBe("run-001");
// Second increment accumulates
db.incrementSessionUsage(sessionId, 2000, 800, "run-002");
const session2 = db.getSessionById(sessionId);
db.tasks.incrementSessionUsage(sessionId, 2000, 800, "run-002");
const session2 = db.tasks.getSessionById(sessionId);
expect(session2!.runCount).toBe(2);
expect(session2!.totalInputTokens).toBe(3000);
expect(session2!.totalOutputTokens).toBe(1300);
@@ -149,15 +149,15 @@ describe("task_sessions CRUD", () => {
it("archiveSession sets rotated_at, rotation_reason, handoff_markdown", () => {
const { companyId, agentId } = scaffold(db);
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "claude-pro",
});
db.archiveSession(sessionId, "max_runs", "# Handoff\nSession reached 20 runs.");
db.tasks.archiveSession(sessionId, "max_runs", "# Handoff\nSession reached 20 runs.");
const session = db.getSessionById(sessionId);
const session = db.tasks.getSessionById(sessionId);
expect(session!.rotatedAt).toBeTruthy();
expect(session!.rotationReason).toBe("max_runs");
expect(session!.handoffMarkdown).toBe("# Handoff\nSession reached 20 runs.");
@@ -166,7 +166,7 @@ describe("task_sessions CRUD", () => {
it("createNewSession returns an id; adapter type persists", () => {
const { companyId, agentId } = scaffold(db);
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
@@ -175,7 +175,7 @@ describe("task_sessions CRUD", () => {
expect(typeof sessionId).toBe("string");
expect(sessionId.length).toBeGreaterThan(0);
const session = db.getSessionById(sessionId);
const session = db.tasks.getSessionById(sessionId);
expect(session).not.toBeNull();
expect(session!.id).toBe(sessionId);
expect(session!.adapterType).toBe("gemini-cli");
@@ -209,7 +209,7 @@ describe("task_sessions CRUD", () => {
unattributedCost: false,
});
const runs = db.getRecentRunsForTask(taskId, 5);
const runs = db.tasks.getRecentRunsForTask(taskId, 5);
expect(runs.length).toBe(2);
// Most recent first
expect(runs[0].id).toBe(run2.id);
@@ -401,7 +401,7 @@ describe("session management service", () => {
describe("token accumulation", () => {
it("accumulateTokenUsage calls db.incrementSessionUsage with correct args", () => {
const { taskId, companyId, agentId } = scaffold(db);
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "claude-pro",
@@ -410,7 +410,7 @@ describe("session management service", () => {
svc.accumulateTokenUsage(sessionId, 5000, 2000, "run-abc");
const session = db.getSessionById(sessionId);
const session = db.tasks.getSessionById(sessionId);
expect(session!.runCount).toBe(1);
expect(session!.totalInputTokens).toBe(5000);
expect(session!.totalOutputTokens).toBe(2000);
@@ -424,7 +424,7 @@ describe("session management service", () => {
describe("handoff generation", () => {
it("generateHandoff produces markdown with task title and run history", () => {
const { taskId, companyId, agentId, workspaceId } = scaffold(db);
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
@@ -444,9 +444,9 @@ describe("session management service", () => {
});
// Increment session usage to have stats
db.incrementSessionUsage(sessionId, 10000, 5000, run.id);
db.tasks.incrementSessionUsage(sessionId, 10000, 5000, run.id);
const session = db.getSessionById(sessionId)!;
const session = db.tasks.getSessionById(sessionId)!;
const svc = createSessionManagementService({ db, logger: mockLogger });
// Handoff is now agent-scoped: it walks the agent's recent runs
// (across tasks + heartbeats) rather than a single task. The task
@@ -468,16 +468,16 @@ describe("session management service", () => {
describe("session rotation", () => {
it("rotateSession archives old session, creates new, copies session_params", () => {
const { taskId, companyId, agentId } = scaffold(db);
const oldSessionId = db.createNewSession({
const oldSessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
// Add some usage
db.incrementSessionUsage(oldSessionId, 300000, 200000, "run-10");
db.tasks.incrementSessionUsage(oldSessionId, 300000, 200000, "run-10");
const oldSession = db.getSessionById(oldSessionId)!;
const oldSession = db.tasks.getSessionById(oldSessionId)!;
const svc = createSessionManagementService({ db, logger: mockLogger });
const newSessionId = svc.rotateSession(oldSession);
@@ -493,7 +493,7 @@ describe("session management service", () => {
expect(archivedRows[0].handoffMarkdown).toContain("# Session Handoff");
// New session is a distinct row with fresh counters.
const newSession = db.getSessionById(newSessionId)!;
const newSession = db.tasks.getSessionById(newSessionId)!;
expect(newSession.id).toBe(newSessionId);
expect(newSession.runCount).toBe(0);
expect(newSession.totalInputTokens).toBe(0);
@@ -510,13 +510,13 @@ describe("session management service", () => {
const svc = createSessionManagementService({ db, logger: mockLogger });
// Create and rotate a session
const session1Id = db.createNewSession({
const session1Id = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
db.incrementSessionUsage(session1Id, 100000, 50000, "run-1");
const session1 = db.getSessionById(session1Id)!;
db.tasks.incrementSessionUsage(session1Id, 100000, 50000, "run-1");
const session1 = db.tasks.getSessionById(session1Id)!;
svc.rotateSession(session1);
const handoff = svc.getHandoffContext(taskId);
@@ -526,7 +526,7 @@ describe("session management service", () => {
it("getHandoffContext returns null when no rotated sessions exist", () => {
const { taskId, companyId, agentId } = scaffold(db);
db.createNewSession({
db.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
@@ -544,16 +544,16 @@ describe("session management service", () => {
describe("usage delta", () => {
it("getUsageDelta returns token difference between snapshot and current totals", () => {
const { taskId, companyId, agentId } = scaffold(db);
const sessionId = db.createNewSession({
const sessionId = db.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
// Pre-populate some usage
db.incrementSessionUsage(sessionId, 1000, 500, "run-1");
db.tasks.incrementSessionUsage(sessionId, 1000, 500, "run-1");
const session = db.getSessionById(sessionId)!;
const session = db.tasks.getSessionById(sessionId)!;
const svc = createSessionManagementService({ db, logger: mockLogger });
// Snapshot current state
+50 -64
View File
@@ -14,11 +14,15 @@ describe("sweepStaleReviews", () => {
function makeSweepDeps(overrides: Partial<SweepStaleReviewsDeps> = {}): SweepStaleReviewsDeps {
return {
db: {
listReviewTasksForSweep: vi.fn().mockReturnValue([]),
tasks: {
listReviewTasksForSweep: vi.fn().mockReturnValue([]),
advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_review" }),
},
agents: {
getManagerForAgent: vi.fn().mockReturnValue(null),
getChainOfCommand: vi.fn().mockReturnValue([]),
},
findPendingApprovalForTask: vi.fn().mockReturnValue(null),
getManagerForAgent: vi.fn().mockReturnValue(null),
getChainOfCommand: vi.fn().mockReturnValue([]),
advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_review" }),
} as unknown as AppDatabase,
getCompanyExecutionMode: vi.fn().mockReturnValue("ai_ceo"),
dispatchAgentMessage: vi.fn().mockReturnValue({ ok: true }),
@@ -34,20 +38,27 @@ describe("sweepStaleReviews", () => {
};
}
it("escalates to manager's manager when task in_review for 35 minutes", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
type SweepDbMock = {
tasks: {
listReviewTasksForSweep: ReturnType<typeof vi.fn>;
findPendingApprovalForTask: ReturnType<typeof vi.fn>;
advanceTaskStatus: ReturnType<typeof vi.fn>;
};
agents: {
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
};
findPendingApprovalForTask: ReturnType<typeof vi.fn>;
};
dbMock.listReviewTasksForSweep.mockReturnValueOnce([
it("escalates to manager's manager when task in_review for 35 minutes", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as SweepDbMock;
dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager One", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-manager-1", name: "Higher Manager" }]);
dbMock.agents.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager One", companyId: "company-1" });
dbMock.agents.getChainOfCommand.mockReturnValueOnce([{ id: "higher-manager-1", name: "Higher Manager" }]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(1);
@@ -66,9 +77,9 @@ describe("sweepStaleReviews", () => {
it("does not escalate when task in_review for only 25 minutes", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { listReviewTasksForSweep: ReturnType<typeof vi.fn> };
const dbMock = deps.db as unknown as SweepDbMock;
dbMock.listReviewTasksForSweep.mockReturnValueOnce([
dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: TWENTY_FIVE_MIN_AGO, title: "Build login" },
]);
@@ -80,19 +91,14 @@ describe("sweepStaleReviews", () => {
it("notifies the human when the reviewer has no higher manager", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
listReviewTasksForSweep: ReturnType<typeof vi.fn>;
findPendingApprovalForTask: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
};
const dbMock = deps.db as unknown as SweepDbMock;
dbMock.listReviewTasksForSweep.mockReturnValueOnce([
dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.findPendingApprovalForTask.mockReturnValueOnce(null);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "ceo-1", name: "CEO", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([]);
dbMock.agents.getManagerForAgent.mockReturnValueOnce({ id: "ceo-1", name: "CEO", companyId: "company-1" });
dbMock.agents.getChainOfCommand.mockReturnValueOnce([]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(1);
@@ -109,18 +115,13 @@ describe("sweepStaleReviews", () => {
it("auto-approves pending approval linked to a stale review task", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
listReviewTasksForSweep: ReturnType<typeof vi.fn>;
findPendingApprovalForTask: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
};
const dbMock = deps.db as unknown as SweepDbMock;
dbMock.listReviewTasksForSweep.mockReturnValueOnce([
dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.agents.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.agents.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.findPendingApprovalForTask.mockReturnValueOnce({ id: "approval-1", type: "deliverable_review" });
sweepStaleReviews("company-1", deps, NOW);
@@ -139,18 +140,13 @@ describe("sweepStaleReviews", () => {
it("swallows resolveApprovalDecision races gracefully", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
listReviewTasksForSweep: ReturnType<typeof vi.fn>;
findPendingApprovalForTask: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
};
const dbMock = deps.db as unknown as SweepDbMock;
dbMock.listReviewTasksForSweep.mockReturnValueOnce([
dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.agents.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.agents.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.findPendingApprovalForTask.mockReturnValueOnce({ id: "approval-1", type: "deliverable_review" });
(deps.resolveApprovalDecision as ReturnType<typeof vi.fn>).mockImplementationOnce(() => {
throw new Error("Approval already decided");
@@ -161,24 +157,18 @@ describe("sweepStaleReviews", () => {
it("advances task status to done after escalation", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
listReviewTasksForSweep: ReturnType<typeof vi.fn>;
findPendingApprovalForTask: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
advanceTaskStatus: ReturnType<typeof vi.fn>;
};
const dbMock = deps.db as unknown as SweepDbMock;
dbMock.listReviewTasksForSweep.mockReturnValueOnce([
dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.agents.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.agents.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.findPendingApprovalForTask.mockReturnValueOnce(null);
sweepStaleReviews("company-1", deps, NOW);
expect(dbMock.advanceTaskStatus).toHaveBeenCalledWith(
expect(dbMock.tasks.advanceTaskStatus).toHaveBeenCalledWith(
"task-1",
"done",
"Auto-advanced: review timeout exceeded 30 minutes.",
@@ -188,9 +178,9 @@ describe("sweepStaleReviews", () => {
it("skips tasks without an assignee", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { listReviewTasksForSweep: ReturnType<typeof vi.fn> };
const dbMock = deps.db as unknown as SweepDbMock;
dbMock.listReviewTasksForSweep.mockReturnValueOnce([
dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: null, updatedAt: THIRTY_FIVE_MIN_AGO, title: "Orphan task" },
]);
@@ -208,9 +198,9 @@ describe("sweepStaleReviews", () => {
const deps = makeSweepDeps({
getCompanyExecutionMode: vi.fn().mockReturnValue("ai_cofounder"),
});
const dbMock = deps.db as unknown as { listReviewTasksForSweep: ReturnType<typeof vi.fn> };
const dbMock = deps.db as unknown as SweepDbMock;
dbMock.listReviewTasksForSweep.mockReturnValueOnce([
dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: TWENTY_FIVE_MIN_AGO, title: "Review me" },
]);
@@ -219,24 +209,20 @@ describe("sweepStaleReviews", () => {
expect(deps.notify).not.toHaveBeenCalled();
expect(deps.resolveApprovalDecision).not.toHaveBeenCalled();
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.db.advanceTaskStatus).not.toHaveBeenCalled();
expect(dbMock.tasks.advanceTaskStatus).not.toHaveBeenCalled();
});
it("sends a critical cofounder review reminder without auto-approving", () => {
const deps = makeSweepDeps({
getCompanyExecutionMode: vi.fn().mockReturnValue("ai_cofounder"),
});
const dbMock = deps.db as unknown as {
listReviewTasksForSweep: ReturnType<typeof vi.fn>;
findPendingApprovalForTask: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
};
const dbMock = deps.db as unknown as SweepDbMock;
dbMock.listReviewTasksForSweep.mockReturnValueOnce([
dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Review me" },
]);
dbMock.findPendingApprovalForTask.mockReturnValueOnce({ id: "approval-1", type: "deliverable_review" });
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.agents.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
@@ -250,6 +236,6 @@ describe("sweepStaleReviews", () => {
);
expect(deps.resolveApprovalDecision).not.toHaveBeenCalled();
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.db.advanceTaskStatus).not.toHaveBeenCalled();
expect(dbMock.tasks.advanceTaskStatus).not.toHaveBeenCalled();
});
});
+3 -1
View File
@@ -151,7 +151,9 @@ describe("ACCEPTANCE_CRITERIA_KEYWORDS", () => {
describe("checkCapabilityMatch", () => {
function makeMockDb(agent: { name: string; capabilities: string }, skillAssignments: string[] = [], skills: Record<string, { name: string; description: string | null; slug: string }> = {}) {
return {
getAgent: (_id: string) => agent,
entityCrud: {
getAgent: (_id: string) => agent,
},
listAgentSkillAssignments: (_id: string) => skillAssignments,
getCompanySkill: (_companyId: string, skillId: string) => skills[skillId] ?? null,
};
+2 -2
View File
@@ -55,7 +55,7 @@ function createTestWorkspace(db: AppDatabase, companyId: string): string {
}
function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string, name = "Agent"): string {
return db.saveAgent({
return db.agents.saveAgent({
companyId,
name,
role: "IC",
@@ -76,7 +76,7 @@ function createTestTask(
agentId: string,
workspaceId: string,
): string {
return db.saveTask({
return db.tasks.saveTask({
companyId,
title: "Phase helper task",
description: "dual-write seed",
+1 -1
View File
@@ -47,7 +47,7 @@ function createTestWorkspace(db: AppDatabase, companyId: string): string {
}
function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string, name = "Agent"): string {
return db.saveAgent({
return db.agents.saveAgent({
companyId,
name,
role: "IC",