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:
1 parent
411294afa6
commit
5e9b7acbc6
95 files changed
+1123
-793
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user