refactor(db): group database domain modules
This commit is contained in:
1 parent
e43a89096d
commit
6da0a58769
160 files changed
+2124
-1999
No files matched your search
@@ -14,7 +14,7 @@ describe("Step 4: Hire approval + decideApproval", () => {
|
||||
const ceoId = createAgent({ name: "CEO", role: "CEO" });
|
||||
const hireId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "pending_approval" as "idle" });
|
||||
|
||||
const approvalId = shared.db.approvalsMod.requestApproval({
|
||||
const approvalId = shared.db.core.approvals.requestApproval({
|
||||
companyId: shared.companyId,
|
||||
requestedByAgentId: ceoId,
|
||||
type: "hire_agent",
|
||||
@@ -23,13 +23,13 @@ describe("Step 4: Hire approval + decideApproval", () => {
|
||||
payloadJson: JSON.stringify({ agentId: hireId }),
|
||||
});
|
||||
|
||||
shared.db.approvalsMod.decideApproval({
|
||||
shared.db.core.approvals.decideApproval({
|
||||
approvalId,
|
||||
state: "approved",
|
||||
decisionNote: "Approved by board",
|
||||
});
|
||||
|
||||
const updatedAgent = shared.db.entityCrud.getAgent(hireId);
|
||||
const updatedAgent = shared.db.core.entity.getAgent(hireId);
|
||||
expect(updatedAgent.status).toBe("idle");
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ describe("Step 4: Hire approval + decideApproval", () => {
|
||||
const ceoId = createAgent({ name: "CEO", role: "CEO" });
|
||||
const hireId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "pending_approval" as "idle" });
|
||||
|
||||
const approvalId = shared.db.approvalsMod.requestApproval({
|
||||
const approvalId = shared.db.core.approvals.requestApproval({
|
||||
companyId: shared.companyId,
|
||||
requestedByAgentId: ceoId,
|
||||
type: "hire_agent",
|
||||
@@ -46,20 +46,20 @@ describe("Step 4: Hire approval + decideApproval", () => {
|
||||
payloadJson: JSON.stringify({ agentId: hireId }),
|
||||
});
|
||||
|
||||
shared.db.approvalsMod.decideApproval({
|
||||
shared.db.core.approvals.decideApproval({
|
||||
approvalId,
|
||||
state: "rejected",
|
||||
decisionNote: "Not needed",
|
||||
});
|
||||
|
||||
const updatedAgent = shared.db.entityCrud.getAgent(hireId);
|
||||
const updatedAgent = shared.db.core.entity.getAgent(hireId);
|
||||
expect(updatedAgent.status).toBe("terminated");
|
||||
});
|
||||
|
||||
it("getApprovalRecord includes payloadJson", () => {
|
||||
const ceoId = createAgent({ name: "CEO", role: "CEO" });
|
||||
const hireId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "pending_approval" as "idle" });
|
||||
const approvalId = shared.db.approvalsMod.requestApproval({
|
||||
const approvalId = shared.db.core.approvals.requestApproval({
|
||||
companyId: shared.companyId,
|
||||
requestedByAgentId: ceoId,
|
||||
type: "hire_agent",
|
||||
@@ -68,7 +68,7 @@ describe("Step 4: Hire approval + decideApproval", () => {
|
||||
payloadJson: JSON.stringify({ agentId: hireId, config: { connector: "openai-codex" } }),
|
||||
});
|
||||
|
||||
const approval = shared.db.approvalsMod.getApprovalRecord(approvalId);
|
||||
const approval = shared.db.core.approvals.getApprovalRecord(approvalId);
|
||||
expect(approval.payloadJson).toBeTruthy();
|
||||
const payload = JSON.parse(approval.payloadJson!);
|
||||
expect(payload.agentId).toBe(hireId);
|
||||
|
||||
@@ -11,7 +11,7 @@ const { createAgent } = shared;
|
||||
|
||||
describe("Automation Rules CRUD", () => {
|
||||
it("creates, reads, toggles, and deletes an automation rule", () => {
|
||||
const ruleId = shared.db.automationMod.saveAutomationRule({
|
||||
const ruleId = shared.db.automation.rules.saveAutomationRule({
|
||||
companyId: shared.companyId,
|
||||
name: "Auto-escalate blocked tasks",
|
||||
description: "When a task is blocked, notify the manager",
|
||||
@@ -28,7 +28,7 @@ describe("Automation Rules CRUD", () => {
|
||||
expect(ruleId).toBeTruthy();
|
||||
|
||||
// Rule appears in snapshot
|
||||
const snapshot = shared.db.snap.listSnapshot();
|
||||
const snapshot = shared.db.projection.snapshot.listSnapshot();
|
||||
const rule = snapshot.automationRules.find(r => r.id === ruleId);
|
||||
expect(rule).toBeTruthy();
|
||||
expect(rule!.name).toBe("Auto-escalate blocked tasks");
|
||||
@@ -39,36 +39,36 @@ describe("Automation Rules CRUD", () => {
|
||||
expect(rule!.executionCount).toBe(0);
|
||||
|
||||
// Toggle to paused
|
||||
shared.db.automationMod.toggleAutomationRule(ruleId, shared.companyId, "paused");
|
||||
const updated = shared.db.snap.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
shared.db.automation.rules.toggleAutomationRule(ruleId, shared.companyId, "paused");
|
||||
const updated = shared.db.projection.snapshot.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
expect(updated!.status).toBe("paused");
|
||||
|
||||
// Record execution
|
||||
shared.db.automationMod.recordAutomationExecution(ruleId, shared.companyId, "Auto-escalate blocked tasks", "task_blocked", "escalate_to_manager", "Escalated task-123");
|
||||
const afterExec = shared.db.snap.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
shared.db.automation.rules.recordAutomationExecution(ruleId, shared.companyId, "Auto-escalate blocked tasks", "task_blocked", "escalate_to_manager", "Escalated task-123");
|
||||
const afterExec = shared.db.projection.snapshot.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
expect(afterExec!.executionCount).toBe(1);
|
||||
expect(afterExec!.lastExecutedAt).toBeTruthy();
|
||||
|
||||
// Automation log
|
||||
const log = shared.db.automationMod.getAutomationLog(shared.companyId);
|
||||
const log = shared.db.automation.rules.getAutomationLog(shared.companyId);
|
||||
expect(log).toHaveLength(1);
|
||||
expect(log[0].ruleName).toBe("Auto-escalate blocked tasks");
|
||||
|
||||
// Delete
|
||||
shared.db.automationMod.deleteAutomationRule(ruleId, shared.companyId);
|
||||
const afterDelete = shared.db.snap.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
shared.db.automation.rules.deleteAutomationRule(ruleId, shared.companyId);
|
||||
const afterDelete = shared.db.projection.snapshot.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
expect(afterDelete).toBeUndefined();
|
||||
});
|
||||
|
||||
it("updates an existing rule", () => {
|
||||
const ruleId = shared.db.automationMod.saveAutomationRule({
|
||||
const ruleId = shared.db.automation.rules.saveAutomationRule({
|
||||
companyId: shared.companyId,
|
||||
name: "Original name",
|
||||
trigger: "task_created",
|
||||
action: "assign_task",
|
||||
});
|
||||
|
||||
shared.db.automationMod.saveAutomationRule({
|
||||
shared.db.automation.rules.saveAutomationRule({
|
||||
id: ruleId,
|
||||
companyId: shared.companyId,
|
||||
name: "Updated name",
|
||||
@@ -77,7 +77,7 @@ describe("Automation Rules CRUD", () => {
|
||||
priority: 90,
|
||||
});
|
||||
|
||||
const rule = shared.db.snap.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
const rule = shared.db.projection.snapshot.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
expect(rule!.name).toBe("Updated name");
|
||||
expect(rule!.action).toBe("notify_agent");
|
||||
expect(rule!.priority).toBe(90);
|
||||
@@ -89,13 +89,13 @@ describe("Agent Messages", () => {
|
||||
let agentBId: string;
|
||||
|
||||
beforeEach(() => {
|
||||
agentAId = shared.db.agents.saveAgent({
|
||||
agentAId = shared.db.core.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.agents.saveAgent({
|
||||
agentBId = shared.db.core.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",
|
||||
@@ -104,7 +104,7 @@ describe("Agent Messages", () => {
|
||||
});
|
||||
|
||||
it("sends and retrieves direct messages", () => {
|
||||
const msgId = shared.db.messages.sendAgentMessage({
|
||||
const msgId = shared.db.core.messages.sendAgentMessage({
|
||||
companyId: shared.companyId,
|
||||
fromAgentId: agentAId,
|
||||
toAgentId: agentBId,
|
||||
@@ -116,7 +116,7 @@ describe("Agent Messages", () => {
|
||||
|
||||
expect(msgId).toBeTruthy();
|
||||
|
||||
const messages = shared.db.messages.listAgentMessages(shared.companyId);
|
||||
const messages = shared.db.core.messages.listAgentMessages(shared.companyId);
|
||||
expect(messages).toHaveLength(1);
|
||||
expect(messages[0].subject).toBe("Need design review");
|
||||
expect(messages[0].fromAgentId).toBe(agentAId);
|
||||
@@ -125,7 +125,7 @@ describe("Agent Messages", () => {
|
||||
});
|
||||
|
||||
it("marks messages as read", () => {
|
||||
const msgId = shared.db.messages.sendAgentMessage({
|
||||
const msgId = shared.db.core.messages.sendAgentMessage({
|
||||
companyId: shared.companyId,
|
||||
fromAgentId: agentAId,
|
||||
toAgentId: agentBId,
|
||||
@@ -133,14 +133,14 @@ describe("Agent Messages", () => {
|
||||
body: "Hello",
|
||||
});
|
||||
|
||||
shared.db.messages.markMessageReadForReader(msgId, shared.companyId);
|
||||
const messages = shared.db.messages.listAgentMessages(shared.companyId);
|
||||
shared.db.core.messages.markMessageReadForReader(msgId, shared.companyId);
|
||||
const messages = shared.db.core.messages.listAgentMessages(shared.companyId);
|
||||
const updatedMessage = messages.find((message) => message.id === msgId);
|
||||
expect(updatedMessage?.readAt).toBeTruthy();
|
||||
});
|
||||
|
||||
it("tracks read state per agent without marking the sender unread", () => {
|
||||
const msgId = shared.db.messages.sendAgentMessage({
|
||||
const msgId = shared.db.core.messages.sendAgentMessage({
|
||||
companyId: shared.companyId,
|
||||
fromAgentId: agentAId,
|
||||
toAgentId: agentBId,
|
||||
@@ -148,22 +148,22 @@ describe("Agent Messages", () => {
|
||||
body: "Hello",
|
||||
});
|
||||
|
||||
const senderView = shared.db.messages.listAgentMessages(shared.companyId, { agentId: agentAId });
|
||||
const senderView = shared.db.core.messages.listAgentMessages(shared.companyId, { agentId: agentAId });
|
||||
expect(senderView).toHaveLength(1);
|
||||
expect(senderView[0].id).toBe(msgId);
|
||||
expect(senderView[0].readAt).toBeTruthy();
|
||||
|
||||
const recipientViewBefore = shared.db.messages.listAgentMessages(shared.companyId, { agentId: agentBId, unreadOnly: true });
|
||||
const recipientViewBefore = shared.db.core.messages.listAgentMessages(shared.companyId, { agentId: agentBId, unreadOnly: true });
|
||||
expect(recipientViewBefore).toHaveLength(1);
|
||||
expect(recipientViewBefore[0].id).toBe(msgId);
|
||||
|
||||
shared.db.messages.markMessageReadForReader(msgId, shared.companyId, agentBId);
|
||||
shared.db.core.messages.markMessageReadForReader(msgId, shared.companyId, agentBId);
|
||||
|
||||
const recipientViewAfter = shared.db.messages.listAgentMessages(shared.companyId, { agentId: agentBId, unreadOnly: true });
|
||||
const recipientViewAfter = shared.db.core.messages.listAgentMessages(shared.companyId, { agentId: agentBId, unreadOnly: true });
|
||||
expect(recipientViewAfter).toHaveLength(0);
|
||||
|
||||
const thirdAgentId = createAgent({ name: "Agent C" });
|
||||
const thirdView = shared.db.messages.listAgentMessages(shared.companyId, { agentId: thirdAgentId });
|
||||
const thirdView = shared.db.core.messages.listAgentMessages(shared.companyId, { agentId: thirdAgentId });
|
||||
expect(thirdView).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -171,14 +171,14 @@ describe("Agent Messages", () => {
|
||||
const engineeringLeadId = createAgent({ name: "Engineering Lead", role: "Lead", department: "engineering" });
|
||||
const productLeadId = createAgent({ name: "Product Lead", role: "Lead", department: "product" });
|
||||
|
||||
const projectId = shared.db.entityCrud.saveProject({
|
||||
const projectId = shared.db.core.entity.saveProject({
|
||||
companyId: shared.companyId,
|
||||
name: "Cross-team Project",
|
||||
description: "Scope",
|
||||
leadAgentId: engineeringLeadId,
|
||||
status: "active",
|
||||
});
|
||||
shared.db.tasks.saveTask({
|
||||
shared.db.core.tasks.saveTask({
|
||||
companyId: shared.companyId,
|
||||
title: "Project task",
|
||||
description: "Do work",
|
||||
@@ -188,7 +188,7 @@ describe("Agent Messages", () => {
|
||||
priority: "medium",
|
||||
});
|
||||
|
||||
const deptMessageId = shared.db.messages.sendAgentMessage({
|
||||
const deptMessageId = shared.db.core.messages.sendAgentMessage({
|
||||
companyId: shared.companyId,
|
||||
fromAgentId: engineeringLeadId,
|
||||
channel: "department",
|
||||
@@ -196,7 +196,7 @@ describe("Agent Messages", () => {
|
||||
subject: "Engineering update",
|
||||
body: "Only engineering should see this",
|
||||
});
|
||||
const projectMessageId = shared.db.messages.sendAgentMessage({
|
||||
const projectMessageId = shared.db.core.messages.sendAgentMessage({
|
||||
companyId: shared.companyId,
|
||||
fromAgentId: engineeringLeadId,
|
||||
channel: "project",
|
||||
@@ -205,31 +205,31 @@ describe("Agent Messages", () => {
|
||||
body: "Only project members should see this",
|
||||
});
|
||||
|
||||
const engineeringView = shared.db.messages.listAgentMessages(shared.companyId, { agentId: engineeringLeadId });
|
||||
const engineeringView = shared.db.core.messages.listAgentMessages(shared.companyId, { agentId: engineeringLeadId });
|
||||
expect(engineeringView.some((message) => message.id === deptMessageId)).toBe(true);
|
||||
expect(engineeringView.some((message) => message.id === projectMessageId)).toBe(true);
|
||||
|
||||
const productView = shared.db.messages.listAgentMessages(shared.companyId, { agentId: productLeadId });
|
||||
const productView = shared.db.core.messages.listAgentMessages(shared.companyId, { agentId: productLeadId });
|
||||
expect(productView.some((message) => message.id === deptMessageId)).toBe(false);
|
||||
expect(productView.some((message) => message.id === projectMessageId)).toBe(false);
|
||||
});
|
||||
|
||||
it("filters messages by channel", () => {
|
||||
shared.db.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, subject: "Dept update", body: "All good", channel: "department", channelTargetId: "engineering" });
|
||||
shared.db.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, toAgentId: agentBId, subject: "DM", body: "Hey", channel: "direct" });
|
||||
shared.db.core.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, subject: "Dept update", body: "All good", channel: "department", channelTargetId: "engineering" });
|
||||
shared.db.core.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, toAgentId: agentBId, subject: "DM", body: "Hey", channel: "direct" });
|
||||
|
||||
const deptMsgs = shared.db.messages.listAgentMessages(shared.companyId, { channel: "department" });
|
||||
const deptMsgs = shared.db.core.messages.listAgentMessages(shared.companyId, { channel: "department" });
|
||||
expect(deptMsgs).toHaveLength(1);
|
||||
expect(deptMsgs[0].subject).toBe("Dept update");
|
||||
|
||||
const directMsgs = shared.db.messages.listAgentMessages(shared.companyId, { channel: "direct" });
|
||||
const directMsgs = shared.db.core.messages.listAgentMessages(shared.companyId, { channel: "direct" });
|
||||
expect(directMsgs).toHaveLength(1);
|
||||
expect(directMsgs[0].subject).toBe("DM");
|
||||
});
|
||||
|
||||
it("appears in snapshot", () => {
|
||||
shared.db.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, channel: "company", subject: "Snapshot test", body: "Hi" });
|
||||
const snapshot = shared.db.snap.listSnapshot();
|
||||
shared.db.core.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, channel: "company", subject: "Snapshot test", body: "Hi" });
|
||||
const snapshot = shared.db.projection.snapshot.listSnapshot();
|
||||
expect(snapshot.agentMessages.length).toBeGreaterThanOrEqual(1);
|
||||
expect(snapshot.agentMessages[0].subject).toBe("Snapshot test");
|
||||
});
|
||||
@@ -240,13 +240,13 @@ describe("Auto Task Assignment", () => {
|
||||
let frontendId: string;
|
||||
|
||||
beforeEach(() => {
|
||||
backendId = shared.db.agents.saveAgent({
|
||||
backendId = shared.db.core.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.agents.saveAgent({
|
||||
frontendId = shared.db.core.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,68 +255,68 @@ describe("Auto Task Assignment", () => {
|
||||
});
|
||||
|
||||
it("assigns to the best-matching agent by capability keywords", () => {
|
||||
const taskId = shared.db.tasks.saveTask({
|
||||
const taskId = shared.db.core.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.tasks.autoAssignTask(taskId, shared.companyId);
|
||||
const assignedId = shared.db.core.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.tasks.saveTask({
|
||||
const taskId = shared.db.core.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.tasks.autoAssignTask(taskId, shared.companyId);
|
||||
const assignedId = shared.db.core.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.tasks.saveTask({
|
||||
const taskId = shared.db.core.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.tasks.autoAssignTask(taskId, shared.companyId);
|
||||
const result = shared.db.core.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.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 });
|
||||
shared.db.core.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.core.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.tasks.saveTask({ companyId: shared.companyId, title: "No budget task", description: "Test", priority: "low", status: "todo" });
|
||||
const result = shared.db.tasks.autoAssignTask(taskId, shared.companyId);
|
||||
const taskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "No budget task", description: "Test", priority: "low", status: "todo" });
|
||||
const result = shared.db.core.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.tasks.saveTask({ companyId: shared.companyId, title: `Backend task ${i}`, description: "", assigneeAgentId: backendId, priority: "medium", status: "in_progress" });
|
||||
shared.db.core.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.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);
|
||||
const taskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Implement feature", description: "Build the feature", priority: "medium", status: "todo" });
|
||||
const assignedId = shared.db.core.tasks.autoAssignTask(taskId, shared.companyId);
|
||||
expect(assignedId).toBe(frontendId); // Frontend has 0 active tasks vs backend's 5
|
||||
});
|
||||
});
|
||||
|
||||
describe("Workflow Pipelines", () => {
|
||||
it("creates, activates, and deletes a workflow", () => {
|
||||
const wfId = shared.db.automationMod.saveWorkflow({
|
||||
const wfId = shared.db.automation.rules.saveWorkflow({
|
||||
companyId: shared.companyId,
|
||||
name: "Code Review Pipeline",
|
||||
description: "Auto-review flow for PRs",
|
||||
@@ -330,14 +330,14 @@ describe("Workflow Pipelines", () => {
|
||||
});
|
||||
|
||||
expect(wfId).toBeTruthy();
|
||||
const snapshot = shared.db.snap.listSnapshot();
|
||||
const snapshot = shared.db.projection.snapshot.listSnapshot();
|
||||
const wf = snapshot.workflows.find(w => w.id === wfId);
|
||||
expect(wf!.name).toBe("Code Review Pipeline");
|
||||
expect(wf!.status).toBe("draft");
|
||||
expect(wf!.runCount).toBe(0);
|
||||
|
||||
// Delete
|
||||
shared.db.automationMod.deleteWorkflow(wfId, shared.companyId);
|
||||
expect(shared.db.snap.listSnapshot().workflows.find(w => w.id === wfId)).toBeUndefined();
|
||||
shared.db.automation.rules.deleteWorkflow(wfId, shared.companyId);
|
||||
expect(shared.db.projection.snapshot.listSnapshot().workflows.find(w => w.id === wfId)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -36,7 +36,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.entityCrud.getAgent(hireId);
|
||||
const hire = shared.db.core.entity.getAgent(hireId);
|
||||
|
||||
const ctx = buildCtx({
|
||||
agentId: ceoId,
|
||||
@@ -86,7 +86,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.entityCrud.getAgent(ctoId);
|
||||
const cto = shared.db.core.entity.getAgent(ctoId);
|
||||
|
||||
const ctx = buildCtx({
|
||||
agentId: ceoId,
|
||||
@@ -107,7 +107,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.entityCrud.getAgent(ctoId);
|
||||
const cto = shared.db.core.entity.getAgent(ctoId);
|
||||
|
||||
const ctx = buildCtx({
|
||||
agentId: ceoId,
|
||||
@@ -137,10 +137,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.entityCrud.getAgent(ceoId);
|
||||
const ceo = shared.db.core.entity.getAgent(ceoId);
|
||||
const managerId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId });
|
||||
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: managerId });
|
||||
const ic = shared.db.entityCrud.getAgent(icId);
|
||||
const ic = shared.db.core.entity.getAgent(icId);
|
||||
|
||||
const ctx = buildCtx({
|
||||
agentId: managerId,
|
||||
@@ -154,7 +154,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.entityCrud.getAgent(ceoId);
|
||||
const ceo = shared.db.core.entity.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.tasks.saveTask({
|
||||
const taskId = shared.db.core.tasks.saveTask({
|
||||
companyId: shared.companyId,
|
||||
title: "In-progress task",
|
||||
description: "Running when crash happened",
|
||||
@@ -23,7 +23,7 @@ describe("Step 7: Worker crash recovery (database layer)", () => {
|
||||
status: "in_progress",
|
||||
});
|
||||
|
||||
const run = shared.db.runs.createRun({
|
||||
const run = shared.db.core.runs.createRun({
|
||||
companyId: shared.companyId,
|
||||
taskId,
|
||||
agentId,
|
||||
@@ -31,10 +31,10 @@ describe("Step 7: Worker crash recovery (database layer)", () => {
|
||||
connectorId: "openai-codex",
|
||||
});
|
||||
|
||||
shared.db.runs.markRunStatus(run.id, "running", "Started");
|
||||
shared.db.core.runs.markRunStatus(run.id, "running", "Started");
|
||||
|
||||
// Verify it's running
|
||||
let snapshot = shared.db.snap.listSnapshot();
|
||||
let snapshot = shared.db.projection.snapshot.listSnapshot();
|
||||
expect(snapshot.runs.find((r) => r.id === run.id)?.status).toBe("running");
|
||||
|
||||
// Simulate app restart — close and re-init
|
||||
@@ -44,14 +44,14 @@ describe("Step 7: Worker crash recovery (database layer)", () => {
|
||||
shared.db = new AppDatabase(profileDir);
|
||||
shared.db.init(); // This calls interruptStaleRuns()
|
||||
|
||||
snapshot = shared.db.snap.listSnapshot();
|
||||
snapshot = shared.db.projection.snapshot.listSnapshot();
|
||||
const recoveredRun = snapshot.runs.find((r) => r.id === run.id);
|
||||
expect(recoveredRun?.status).toBe("interrupted");
|
||||
});
|
||||
|
||||
it("finishRun can mark interrupted runs from crash recovery", () => {
|
||||
const agentId = createAgent({ name: "Agent" });
|
||||
const taskId = shared.db.tasks.saveTask({
|
||||
const taskId = shared.db.core.tasks.saveTask({
|
||||
companyId: shared.companyId,
|
||||
title: "Crash recovery task",
|
||||
description: "Test",
|
||||
@@ -61,7 +61,7 @@ describe("Step 7: Worker crash recovery (database layer)", () => {
|
||||
status: "in_progress",
|
||||
});
|
||||
|
||||
const run = shared.db.runs.createRun({
|
||||
const run = shared.db.core.runs.createRun({
|
||||
companyId: shared.companyId,
|
||||
taskId,
|
||||
agentId,
|
||||
@@ -69,10 +69,10 @@ describe("Step 7: Worker crash recovery (database layer)", () => {
|
||||
connectorId: "openai-codex",
|
||||
});
|
||||
|
||||
shared.db.runs.markRunStatus(run.id, "running", "Started");
|
||||
shared.db.core.runs.markRunStatus(run.id, "running", "Started");
|
||||
|
||||
// Simulate crash recovery - mark as interrupted
|
||||
shared.db.runs.finishRun({
|
||||
shared.db.core.runs.finishRun({
|
||||
runId: run.id,
|
||||
status: "interrupted",
|
||||
summary: "Worker process crashed.",
|
||||
@@ -83,7 +83,7 @@ describe("Step 7: Worker crash recovery (database layer)", () => {
|
||||
unattributedCost: true,
|
||||
});
|
||||
|
||||
const snapshot = shared.db.snap.listSnapshot();
|
||||
const snapshot = shared.db.projection.snapshot.listSnapshot();
|
||||
const recovered = snapshot.runs.find((r) => r.id === run.id);
|
||||
expect(recovered?.status).toBe("interrupted");
|
||||
expect(recovered?.errorMessage).toBe("Worker process exited unexpectedly.");
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
|
||||
const cmoId = createAgent({ name: "Aria", role: "CMO", reportsTo: ceoId, status: "pending_approval" as "idle" });
|
||||
|
||||
// 3. CEO submits org strategy approval
|
||||
const orgApprovalId = shared.db.approvalsMod.requestApproval({
|
||||
const orgApprovalId = shared.db.core.approvals.requestApproval({
|
||||
companyId: shared.companyId,
|
||||
requestedByAgentId: ceoId,
|
||||
type: "approve_ceo_strategy",
|
||||
@@ -40,8 +40,8 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
|
||||
});
|
||||
|
||||
// CEO now in Phase 1
|
||||
const ctoRecord = shared.db.entityCrud.getAgent(ctoId);
|
||||
const cmoRecord = shared.db.entityCrud.getAgent(cmoId);
|
||||
const ctoRecord = shared.db.core.entity.getAgent(ctoId);
|
||||
const cmoRecord = shared.db.core.entity.getAgent(cmoId);
|
||||
const phase1Ctx = buildCtx({
|
||||
agentId: ceoId,
|
||||
directReports: [ctoRecord, cmoRecord],
|
||||
@@ -64,7 +64,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
|
||||
expect(phase1Prompt).toContain("Awaiting Org Strategy Approval");
|
||||
|
||||
// 4. Board approves hires
|
||||
const hireApprovalCTO = shared.db.approvalsMod.requestApproval({
|
||||
const hireApprovalCTO = shared.db.core.approvals.requestApproval({
|
||||
companyId: shared.companyId,
|
||||
requestedByAgentId: ceoId,
|
||||
type: "hire_agent",
|
||||
@@ -72,9 +72,9 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
|
||||
impactSummary: "Test",
|
||||
payloadJson: JSON.stringify({ agentId: ctoId }),
|
||||
});
|
||||
shared.db.approvalsMod.decideApproval({ approvalId: hireApprovalCTO, state: "approved", decisionNote: "Approved" });
|
||||
shared.db.core.approvals.decideApproval({ approvalId: hireApprovalCTO, state: "approved", decisionNote: "Approved" });
|
||||
|
||||
const hireApprovalCMO = shared.db.approvalsMod.requestApproval({
|
||||
const hireApprovalCMO = shared.db.core.approvals.requestApproval({
|
||||
companyId: shared.companyId,
|
||||
requestedByAgentId: ceoId,
|
||||
type: "hire_agent",
|
||||
@@ -82,18 +82,18 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
|
||||
impactSummary: "Test",
|
||||
payloadJson: JSON.stringify({ agentId: cmoId }),
|
||||
});
|
||||
shared.db.approvalsMod.decideApproval({ approvalId: hireApprovalCMO, state: "approved", decisionNote: "Approved" });
|
||||
shared.db.core.approvals.decideApproval({ approvalId: hireApprovalCMO, state: "approved", decisionNote: "Approved" });
|
||||
|
||||
// Agents now idle
|
||||
expect(shared.db.entityCrud.getAgent(ctoId).status).toBe("idle");
|
||||
expect(shared.db.entityCrud.getAgent(cmoId).status).toBe("idle");
|
||||
expect(shared.db.core.entity.getAgent(ctoId).status).toBe("idle");
|
||||
expect(shared.db.core.entity.getAgent(cmoId).status).toBe("idle");
|
||||
|
||||
// 5. Board approves org strategy
|
||||
shared.db.approvalsMod.decideApproval({ approvalId: orgApprovalId, state: "approved", decisionNote: "Go ahead" });
|
||||
shared.db.core.approvals.decideApproval({ approvalId: orgApprovalId, state: "approved", decisionNote: "Go ahead" });
|
||||
|
||||
// 6. CEO now in Phase 2
|
||||
const updatedCto = shared.db.entityCrud.getAgent(ctoId);
|
||||
const updatedCmo = shared.db.entityCrud.getAgent(cmoId);
|
||||
const updatedCto = shared.db.core.entity.getAgent(ctoId);
|
||||
const updatedCmo = shared.db.core.entity.getAgent(cmoId);
|
||||
const phase2Ctx = buildCtx({
|
||||
agentId: ceoId,
|
||||
directReports: [updatedCto, updatedCmo],
|
||||
@@ -106,12 +106,12 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
|
||||
expect(phase2Prompt).toContain("Delegation");
|
||||
|
||||
// 7. Verify hierarchical chain
|
||||
const ctoChain = shared.db.agents.getChainOfCommand(ctoId);
|
||||
const ctoChain = shared.db.core.agents.getChainOfCommand(ctoId);
|
||||
expect(ctoChain).toHaveLength(1);
|
||||
expect(ctoChain[0].id).toBe(ceoId);
|
||||
|
||||
// 8. Create task tree
|
||||
const projectId = shared.db.entityCrud.saveProject({
|
||||
const projectId = shared.db.core.entity.saveProject({
|
||||
companyId: shared.companyId,
|
||||
name: "Launch MVP",
|
||||
description: "Build and launch MVP",
|
||||
@@ -120,7 +120,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
|
||||
status: "in_progress",
|
||||
});
|
||||
|
||||
const parentTaskId = shared.db.tasks.saveTask({
|
||||
const parentTaskId = shared.db.core.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.tasks.saveTask({
|
||||
const subtaskId = shared.db.core.tasks.saveTask({
|
||||
companyId: shared.companyId,
|
||||
title: "Implement auth",
|
||||
description: "JWT auth",
|
||||
@@ -146,12 +146,12 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
|
||||
});
|
||||
|
||||
// Verify parent-child relationship
|
||||
const subtask = shared.db.entityCrud.getTask(subtaskId);
|
||||
const subtask = shared.db.core.entity.getTask(subtaskId);
|
||||
expect(subtask.parentId).toBe(parentTaskId);
|
||||
|
||||
// 9. Wakeup request test: enqueue wake while IC is busy
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, icId, "comment", "comment");
|
||||
const queuedWakes = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId: icId, status: "queued" });
|
||||
shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, icId, "comment", "comment");
|
||||
const queuedWakes = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId: icId, status: "queued" });
|
||||
expect(queuedWakes).toHaveLength(1);
|
||||
expect(queuedWakes[0].source).toBe("comment");
|
||||
|
||||
@@ -159,7 +159,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
|
||||
const icPromptCtx = buildCtx({
|
||||
agentId: icId,
|
||||
task: subtask,
|
||||
chainOfCommand: [shared.db.entityCrud.getAgent(ctoId), shared.db.entityCrud.getAgent(ceoId)],
|
||||
chainOfCommand: [shared.db.core.entity.getAgent(ctoId), shared.db.core.entity.getAgent(ceoId)],
|
||||
wakeReason: "assignment",
|
||||
});
|
||||
const icPrompt = buildTaskPrompt(icPromptCtx);
|
||||
@@ -175,12 +175,12 @@ describe("E2E: Wakeup request lifecycle with run lifecycle", () => {
|
||||
|
||||
// Simulate: agent is running, wakes come in. All triggers are coalesced
|
||||
// into a single wakeup request with JSON array of trigger details.
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "subtask_completed", "subtask_completed");
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "comment", "comment");
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "report_blocked", "report_blocked");
|
||||
shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "subtask_completed", "subtask_completed");
|
||||
shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "comment", "comment");
|
||||
shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "report_blocked", "report_blocked");
|
||||
|
||||
// Verify coalesced into single row with all triggers
|
||||
const wakes = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
|
||||
const wakes = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
|
||||
expect(wakes).toHaveLength(1);
|
||||
expect(wakes[0].coalescedCount).toBe(2);
|
||||
|
||||
@@ -191,7 +191,7 @@ describe("E2E: Wakeup request lifecycle with run lifecycle", () => {
|
||||
expect(triggers[2].trigger).toBe("report_blocked");
|
||||
|
||||
// Wakes remain queued until consumed by the run lifecycle
|
||||
expect(shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" })).toHaveLength(1);
|
||||
expect(shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" })).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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.agents.getChainOfCommand(icId);
|
||||
const chain = shared.db.core.agents.getChainOfCommand(icId);
|
||||
expect(chain[0].id).toBe(ctoId); // immediate manager gets woken
|
||||
|
||||
// When CTO needs to escalate further
|
||||
const ctoChain = shared.db.agents.getChainOfCommand(ctoId);
|
||||
const ctoChain = shared.db.core.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.entityCrud.getAgent(hireId);
|
||||
const hire = shared.db.core.entity.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.entityCrud.getAgent(ctoId);
|
||||
const cto = shared.db.core.entity.getAgent(ctoId);
|
||||
|
||||
const ctx = buildCtx({
|
||||
agentId: ceoId,
|
||||
@@ -287,7 +287,7 @@ describe("Review Fix: Rejected approval wakes agent", () => {
|
||||
it("decideApproval with rejected state still triggers wake (tested via DB)", () => {
|
||||
const ceoId = createAgent({ name: "CEO", role: "CEO" });
|
||||
|
||||
const approvalId = shared.db.approvalsMod.requestApproval({
|
||||
const approvalId = shared.db.core.approvals.requestApproval({
|
||||
companyId: shared.companyId,
|
||||
requestedByAgentId: ceoId,
|
||||
type: "approve_ceo_strategy",
|
||||
@@ -295,14 +295,14 @@ describe("Review Fix: Rejected approval wakes agent", () => {
|
||||
impactSummary: "test",
|
||||
});
|
||||
|
||||
shared.db.approvalsMod.decideApproval({
|
||||
shared.db.core.approvals.decideApproval({
|
||||
approvalId,
|
||||
state: "rejected",
|
||||
decisionNote: "Not ready",
|
||||
});
|
||||
|
||||
// Verify approval state
|
||||
const approval = shared.db.approvalsMod.getApprovalRecord(approvalId);
|
||||
const approval = shared.db.core.approvals.getApprovalRecord(approvalId);
|
||||
expect(approval.state).toBe("rejected");
|
||||
// The IPC handler in index.ts now wakes on rejected too —
|
||||
// we verify the DB layer works; the wakeAgentIfPossible call is in index.ts
|
||||
@@ -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.tasks.saveTask({
|
||||
const taskId = shared.db.core.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.entityCrud.getTask(taskId);
|
||||
const task = shared.db.core.entity.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.agents.getChainOfCommand(ctoId);
|
||||
const chain = shared.db.core.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.entityCrud.getAgent(ceoId);
|
||||
const ceo = shared.db.core.entity.getAgent(ceoId);
|
||||
const icId = createAgent({ name: "IC", role: "Engineer", reportsTo: managerId });
|
||||
const ic = shared.db.entityCrud.getAgent(icId);
|
||||
const ic = shared.db.core.entity.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.entityCrud.getAgent(ceoId);
|
||||
const ceo = shared.db.core.entity.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.tasks.saveTask({
|
||||
const taskId = shared.db.core.tasks.saveTask({
|
||||
companyId: shared.companyId,
|
||||
title: "Failing task",
|
||||
description: "Will fail",
|
||||
@@ -22,7 +22,7 @@ describe("Step 6: Run failure escalation (database layer)", () => {
|
||||
status: "in_progress",
|
||||
});
|
||||
|
||||
const run = shared.db.runs.createRun({
|
||||
const run = shared.db.core.runs.createRun({
|
||||
companyId: shared.companyId,
|
||||
taskId,
|
||||
agentId,
|
||||
@@ -30,9 +30,9 @@ describe("Step 6: Run failure escalation (database layer)", () => {
|
||||
connectorId: "openai-codex",
|
||||
});
|
||||
|
||||
shared.db.runs.markRunStatus(run.id, "running", "Started");
|
||||
shared.db.core.runs.markRunStatus(run.id, "running", "Started");
|
||||
|
||||
const outcome = shared.db.runs.finishRun({
|
||||
const outcome = shared.db.core.runs.finishRun({
|
||||
runId: run.id,
|
||||
status: "failed",
|
||||
summary: null,
|
||||
@@ -45,7 +45,7 @@ describe("Step 6: Run failure escalation (database layer)", () => {
|
||||
|
||||
expect(outcome.budgetHardStopped).toBe(false);
|
||||
|
||||
const snapshot = shared.db.snap.listSnapshot();
|
||||
const snapshot = shared.db.projection.snapshot.listSnapshot();
|
||||
const finishedRun = snapshot.runs.find((r) => r.id === run.id);
|
||||
expect(finishedRun?.status).toBe("failed");
|
||||
expect(finishedRun?.errorMessage).toBe("Process crashed");
|
||||
@@ -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.agents.getChainOfCommand(icId);
|
||||
const chain = shared.db.core.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["agents"]["saveAgent"]>[0]>): string;
|
||||
createAgent(overrides?: Partial<Parameters<AppDatabase["core"]["agents"]["saveAgent"]>[0]>): string;
|
||||
buildCtx(overrides?: Record<string, unknown>): Parameters<typeof buildSystemPrompt>[0];
|
||||
tempDirs: string[];
|
||||
}
|
||||
@@ -54,13 +54,13 @@ export function installSharedSetup(): SharedHandle {
|
||||
state.db = new AppDatabase(profileDir);
|
||||
state.db.init();
|
||||
|
||||
state.companyId = state.db.company.saveCompany({
|
||||
state.companyId = state.db.core.company.saveCompany({
|
||||
name: "Autonomous Test Corp",
|
||||
description: "Test company for autonomous chain",
|
||||
status: "active",
|
||||
});
|
||||
|
||||
state.workspaceId = state.db.workspacesMod.saveWorkspace({
|
||||
state.workspaceId = state.db.core.workspaces.saveWorkspace({
|
||||
companyId: state.companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -80,8 +80,8 @@ export function installSharedSetup(): SharedHandle {
|
||||
return state.db;
|
||||
}
|
||||
|
||||
function createAgent(overrides: Partial<Parameters<AppDatabase["agents"]["saveAgent"]>[0]> = {}): string {
|
||||
return currentDb().agents.saveAgent({
|
||||
function createAgent(overrides: Partial<Parameters<AppDatabase["core"]["agents"]["saveAgent"]>[0]> = {}): string {
|
||||
return currentDb().core.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().entityCrud.getAgent(overrides.agentId as string ?? createAgent());
|
||||
const agent = currentDb().core.entity.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.agents.getChainOfCommand(icId);
|
||||
const chain = shared.db.core.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.agents.getChainOfCommand(agent1);
|
||||
const chain = shared.db.core.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.tasks.saveTask({
|
||||
const parentTaskId = shared.db.core.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.tasks.saveTask({
|
||||
const childTaskId = shared.db.core.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.entityCrud.getTask(childTaskId);
|
||||
const childTask = shared.db.core.entity.getTask(childTaskId);
|
||||
expect(childTask.parentId).toBe(parentTaskId);
|
||||
|
||||
const parentTask = shared.db.entityCrud.getTask(parentTaskId);
|
||||
const parentTask = shared.db.core.entity.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.entityCrud.getAgent(ceoId);
|
||||
const ceo = shared.db.core.entity.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.tasks.saveTask({
|
||||
const taskId = shared.db.core.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.entityCrud.getTask(taskId);
|
||||
const task = shared.db.core.entity.getTask(taskId);
|
||||
|
||||
const ctx = buildCtx({
|
||||
agentId,
|
||||
|
||||
@@ -15,11 +15,11 @@ describe("Step 3: Wakeup request lifecycle (database)", () => {
|
||||
|
||||
// Enqueue two wakes for the same busy agent. Coalesced into one row
|
||||
// with trigger_detail JSON array containing both triggers.
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "subtask_completed", "subtask_completed");
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "comment", "comment");
|
||||
shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "subtask_completed", "subtask_completed");
|
||||
shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "comment", "comment");
|
||||
|
||||
// List returns the coalesced wake
|
||||
const wakes = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
|
||||
const wakes = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
|
||||
expect(wakes).toHaveLength(1);
|
||||
expect(wakes[0].companyId).toBe(shared.companyId);
|
||||
expect(wakes[0].coalescedCount).toBe(1);
|
||||
@@ -32,7 +32,7 @@ describe("Step 3: Wakeup request lifecycle (database)", () => {
|
||||
|
||||
it("returns empty array when no wakeup requests exist", () => {
|
||||
const agentId = createAgent({ name: "TestAgent" });
|
||||
const wakes = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
|
||||
const wakes = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
|
||||
expect(wakes).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -40,15 +40,15 @@ describe("Step 3: Wakeup request lifecycle (database)", () => {
|
||||
const agent1 = createAgent({ name: "Agent1" });
|
||||
const agent2 = createAgent({ name: "Agent2" });
|
||||
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agent1, "comment", "comment");
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agent2, "subtask_completed", "subtask_completed");
|
||||
shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agent1, "comment", "comment");
|
||||
shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agent2, "subtask_completed", "subtask_completed");
|
||||
|
||||
const wakes1 = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId: agent1, status: "queued" });
|
||||
const wakes1 = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId: agent1, status: "queued" });
|
||||
expect(wakes1).toHaveLength(1);
|
||||
expect(wakes1[0].source).toBe("comment");
|
||||
|
||||
// Agent2's wake should still be there
|
||||
const wakes2 = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId: agent2, status: "queued" });
|
||||
const wakes2 = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId: agent2, status: "queued" });
|
||||
expect(wakes2).toHaveLength(1);
|
||||
expect(wakes2[0].source).toBe("subtask_completed");
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user