Promote AgentCompany-New to the repository root and retire the old app

The legacy application - its src, tests, landing site, promo assets, docs,
release tooling, CI workflows, and root configs - leaves the tree; it stays
reachable in history. Everything under AgentCompany-New moves up unchanged:
same package name, so the production userData/database path is untouched,
and e2e/build scripts already resolved paths relative to the project. The
developer cred.txt stays gitignored at its new location. Full verify:ci
passes from the new root.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-07 00:04:13 -04:00
1 parent 2350f744e3
commit 07d0b4587d
1904 files changed
+1133 -282075

No files matched your search

@@ -1,172 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { createBudgetEnforcementService } from "../../src/main/budget-enforcement-service";
import { createRunDispatch } from "../../src/main/orchestration/run-dispatch";
import { createTaskRunner } from "../../src/main/orchestration/task-runner";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
const logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
function createHarness(db: AppDatabase) {
const enqueueWorkerRun = vi.fn();
const budgetService = createBudgetEnforcementService({
db,
logger,
notify: vi.fn(),
publishDomainChanged: vi.fn(),
});
const deps = {
db,
logger,
publishDomainChanged: vi.fn(),
emitEvent: vi.fn(),
notify: vi.fn(),
createRunToken: () => "token",
getApiPort: () => 3210,
dispatchAutomation: vi.fn(),
hasWorkerReady: () => true,
enqueueWorkerRun,
isQuitting: () => false,
listRuntimeSkillEntries: () => [],
budgetService,
checkpointEngine: { shouldPauseHeartbeat: () => false },
};
const runDispatch = createRunDispatch(deps);
const taskRunner = createTaskRunner(
deps,
runDispatch.resolveWorkspace,
runDispatch.getRunEligibilityFailure,
runDispatch.dispatchRunToWorker,
(filter) => db.core.runs.hasActiveRun(filter),
);
return { enqueueWorkerRun, taskRunner };
}
describe("agent model switching", () => {
it("uses the agent's updated model for subsequent runtime dispatches", async () => {
const profileDir = await createTempDir("agentcompany-model-switching-");
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Model Switch Co",
description: "Tests post-hire model switching",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Frontend Engineer",
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: "gpt-5.4",
capabilities: "Build frontend features",
budgetMonthlyUsd: 200,
});
const { enqueueWorkerRun, taskRunner } = createHarness(db);
const firstTaskId = db.core.tasks.saveTask({
companyId,
title: "Initial model task",
description: "Use the first model for this run.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
const firstRun = taskRunner.queueTaskRun(firstTaskId, "manual", "board");
expect(firstRun.ok).toBe(true);
expect(enqueueWorkerRun).toHaveBeenCalledTimes(1);
expect(enqueueWorkerRun.mock.calls[0]?.[0]?.model).toBe("gpt-5.4");
if (!firstRun.ok) {
throw new Error("Expected first run to queue successfully.");
}
db.core.runs.finishRun({
runId: firstRun.data,
status: "succeeded",
summary: "Done",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gpt-5.4",
unattributedCost: true,
});
db.core.agents.saveAgent({
id: agentId,
companyId,
name: "Engineer",
role: "Engineer",
title: "Frontend Engineer",
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: "gpt-5.3-codex",
capabilities: "Build frontend features",
budgetMonthlyUsd: 200,
});
const secondTaskId = db.core.tasks.saveTask({
companyId,
title: "Updated model task",
description: "Use the new model for this run.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
const secondRun = taskRunner.queueTaskRun(secondTaskId, "manual", "board");
expect(secondRun.ok).toBe(true);
expect(enqueueWorkerRun).toHaveBeenCalledTimes(2);
expect(enqueueWorkerRun.mock.calls[1]?.[0]?.model).toBe("gpt-5.3-codex");
db.close();
});
});
+112
View File
@@ -0,0 +1,112 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OnboardingService } from "../../src/main/services/onboarding-service";
import { ProviderService } from "../../src/main/services/provider-service";
import { providerAdapters } from "../../src/main/services/provider-adapters";
import { TaskExecutionService } from "../../src/main/services/task-execution-service";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { claimNextQueuedRun } from "../../src/main/app/task-queue-worker";
import { canonicalMentionHandle } from "../../src/shared/mention-handles";
import type { AgentTurnResult, ProviderToolTurnInput, ToolCall } from "../../src/main/services/provider-tool-turn";
const testVault = {
encryptSecret: (secret: string) => Buffer.from(secret, "utf8"),
decryptSecret: (ciphertext: Buffer) => ciphertext.toString("utf8"),
};
function call(name: string, args: Record<string, unknown>): ToolCall {
return { id: `call_${randomUUID()}`, name, arguments: args };
}
/**
* Scripts two distinct agents off the same provider, branching on the
* instructions (which name the acting agent). The CEO staffs one engineer and
* @mentions them in #engineering; the engineer, once woken, replies in the same
* thread — the round trip that makes the company feel alive.
*/
function scriptedTeam(): (input: ProviderToolTurnInput) => Promise<AgentTurnResult> {
const handle = canonicalMentionHandle("Dana Lee");
let ceo = 0;
let dana = 0;
const ceoScript = [
{ text: "Staffing engineering.", toolCalls: [call("hire_agent", { name: "Dana Lee", role: "Engineer", department: "Engineering" })] },
{ text: "Delegating and pinging.", toolCalls: [
call("create_task", { title: "Build the booking API", description: "Ship the booking API.", assignee_agent_name: "Dana Lee" }),
call("send_message", { channel: "engineering", body: `@${handle} you own the booking API — kicking off here.` }),
] },
{ text: "Kickoff complete.", toolCalls: [] },
];
return async (input: ProviderToolTurnInput): Promise<AgentTurnResult> => {
if ((input.instructions ?? "").includes("Dana Lee")) {
// Reply inside the thread the CEO started (the prompt carries its id).
const firstUser = input.messages.find((message) => message.role === "user");
const threadId = firstUser ? /thread_id="([^"]+)"/.exec(firstUser.text)?.[1] : undefined;
const turn = dana === 0
? { text: "On it.", toolCalls: [call("send_message", { channel: "engineering", body: "On it — booking API design starting now.", ...(threadId ? { thread_id: threadId } : {}) })] }
: { text: "Replied.", toolCalls: [] };
dana += 1;
return { text: turn.text, toolCalls: turn.toolCalls, rawResponseJson: "{}" };
}
const turn = ceoScript[ceo] ?? { text: "done", toolCalls: [] };
ceo += 1;
return { text: turn.text, toolCalls: turn.toolCalls, rawResponseJson: "{}" };
};
}
describe("inter-agent reply", () => {
test("an @mention wakes the teammate, who replies in the same thread", async () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-reply-${randomUUID()}.db`));
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, scriptedTeam());
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
const taskExecution = new TaskExecutionService(connection.db, providers);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
connection.transaction(() => {
orgId = onboarding.bootstrap({
companyName: "OrbitOps AI",
companyDescription: "AI-run operations company.",
employeeName: "Yang",
workspacePath: "",
executionMode: "ai_ceo",
autoApproveHires: true,
reviewDeliverables: false,
goalTitle: "Stand up a service business.",
goalDescription: "Win the first customers.",
providerKey: "openai",
providerSecret: "sk-test-key",
providerModel: "gpt-5.4",
}).orgId;
});
// Run the CEO's kickoff: it hires Dana and @mentions her in #engineering.
const ceoRun = claimNextQueuedRun(connection);
await taskExecution.executeRun(ceoRun!.org_id, ceoRun!.id);
const dana = (connection.db.prepare("select id from agents where org_id = ? and name = 'Dana Lee'").get(orgId) as { id: string });
expect(dana).toBeTruthy();
// The @mention woke Dana: a queued chat_mention run was created for her.
const wake = connection.db.prepare(
"select id, state, trigger from task_runs where org_id = ? and agent_id = ? and trigger = 'chat_mention'",
).get(orgId, dana.id) as { id: string; state: string; trigger: string } | undefined;
expect(wake).toMatchObject({ state: "queued", trigger: "chat_mention" });
// Execute Dana's woken run: she replies in the CEO's thread.
await taskExecution.executeRun(orgId, wake!.id);
const engineering = connection.db.prepare("select id from chat_spaces where org_id = ? and name = 'engineering'").get(orgId) as { id: string };
const messages = snapshot.load().chatMessages.filter((m) => m.orgId === orgId && m.spaceId === engineering.id);
const senders = new Set(messages.map((m) => m.senderId));
// Both the CEO and Dana have spoken in #engineering, in one shared thread.
expect(messages.length).toBe(2);
expect(senders.size).toBe(2);
expect(new Set(messages.map((m) => m.threadId)).size).toBe(1);
expect(messages.some((m) => m.senderId === dana.id && m.bodyMd.includes("On it"))).toBe(true);
connection.close();
});
});
-144
View File
@@ -1,144 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { createAgentToken, initializeAgentTokenSigningKey } from "../../src/main/agent-auth";
import { createApiServer } from "../../src/main/api-server";
import { AppDatabase } from "../../src/main/database";
import { createTestApiServerCallbacks } from "../support/api-server-callbacks";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
async function createHarness() {
const profileDir = await createTempDir("agentcompany-api-meetings-");
initializeAgentTokenSigningKey(profileDir);
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.core.company.saveCompany({ name: "Meeting Co", description: "Meeting API test", status: "active" });
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Mira",
role: "CEO",
title: "Meeting owner",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Run meetings",
budgetMonthlyUsd: 100,
});
const participantAgentId = db.core.agents.saveAgent({
companyId,
name: "Niko",
role: "Engineer",
title: "Participant",
status: "active",
reportsTo: agentId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Join meetings",
budgetMonthlyUsd: 100,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Keep meeting PATCH stable",
description: "Protect existing meeting fields.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex" });
const token = createAgentToken(agentId, companyId, run.id);
const server = createApiServer(db, createTestApiServerCallbacks());
const port = await server.start();
return {
db,
server,
companyId,
agentId,
participantAgentId,
baseUrl: `http://127.0.0.1:${port}`,
headers: {
Authorization: `Bearer ${token}`,
"X-Agent-Company-Run-Id": run.id,
"Content-Type": "application/json",
},
};
}
describe("meeting API", () => {
it("PATCH preserves existing meeting fields when only partial fields are sent", async () => {
const harness = await createHarness();
try {
const participantAgentIds = JSON.stringify([harness.participantAgentId]);
const agendaJson = JSON.stringify([{ title: "Roadmap", owner: harness.agentId }]);
const decisionsJson = JSON.stringify([{ title: "Keep current plan" }]);
const actionItemsJson = JSON.stringify([{ title: "Ship patch", assigneeAgentId: harness.participantAgentId }]);
const meetingId = harness.db.core.knowledge.saveMeeting({
companyId: harness.companyId,
type: "department_sync",
title: "Planning Sync",
organizerAgentId: harness.agentId,
participantAgentIds,
scheduledAt: "2026-05-15T10:00:00.000Z",
durationMinutes: 45,
agendaJson,
notesJson: JSON.stringify([{ text: "Original note" }]),
decisionsJson,
actionItemsJson,
status: "scheduled",
});
const response = await fetch(`${harness.baseUrl}/api/meetings/${meetingId}`, {
method: "PATCH",
headers: harness.headers,
body: JSON.stringify({
notesJson: JSON.stringify([{ text: "Updated note" }]),
status: "completed",
}),
});
expect(response.status).toBe(200);
const updated = harness.db.core.knowledge.getMeeting(meetingId, harness.companyId);
expect(updated).toMatchObject({
type: "department_sync",
title: "Planning Sync",
organizerAgentId: harness.agentId,
participantAgentIds,
scheduledAt: "2026-05-15T10:00:00.000Z",
durationMinutes: 45,
agendaJson,
decisionsJson,
actionItemsJson,
status: "completed",
});
expect(updated?.notesJson).toBe(JSON.stringify([{ text: "Updated note" }]));
} finally {
harness.server.stop();
harness.db.close();
}
});
});
@@ -1,307 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { createAgentToken, initializeAgentTokenSigningKey } from "../../src/main/agent-auth";
import { createApiServer } from "../../src/main/api-server";
import { AppDatabase } from "../../src/main/database";
import { createTestApiServerCallbacks } from "../support/api-server-callbacks";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
async function createHarness() {
const profileDir = await createTempDir("agentcompany-recursive-org-");
initializeAgentTokenSigningKey(profileDir);
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Recursive Org Co",
description: "Tests manager-driven recursive hires",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.core.agents.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "active",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 220,
department: "engineering",
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Build the org",
description: "CTO should hire the first IC under engineering.",
assigneeAgentId: ctoId,
workspaceId,
priority: "high",
status: "in_progress",
});
const run = db.core.runs.createRun({ companyId, taskId, agentId: ctoId, workspaceId, connectorId: "openai-codex" });
const token = createAgentToken(ctoId, companyId, run.id);
const server = createApiServer(db, createTestApiServerCallbacks());
const port = await server.start();
return {
db,
server,
companyId,
workspaceId,
ceoId,
ctoId,
baseUrl: `http://127.0.0.1:${port}`,
headers: {
Authorization: `Bearer ${token}`,
"X-Agent-Company-Run-Id": run.id,
"Content-Type": "application/json; charset=utf-8",
},
};
}
describe("recursive org execution", () => {
it("manager can request a hire under themselves and the hierarchy reflects it after approval", async () => {
const harness = await createHarness();
try {
const response = await fetch(`${harness.baseUrl}/api/companies/${harness.companyId}/agents`, {
method: "POST",
headers: harness.headers,
body: JSON.stringify({
name: "Avery Chen",
role: "Engineer",
title: "Frontend Engineer",
department: "engineering",
connectorId: "openai-codex",
workspaceId: harness.workspaceId,
model: null,
capabilities: "React, TypeScript, Tailwind, frontend delivery",
budgetMonthlyUsd: 180,
}),
});
expect(response.status).toBe(201);
const body = await response.json() as { agentId: string; approvalId: string; autoApproved: boolean };
expect(body.autoApproved).toBe(false);
const pendingHire = harness.db.core.entity.getAgent(body.agentId);
expect(pendingHire.status).toBe("pending_approval");
expect(pendingHire.reportsTo).toBe(harness.ctoId);
const approval = harness.db.core.approvals.getApprovalRecord(body.approvalId);
expect(approval.type).toBe("hire_agent");
expect(approval.requestedByAgentId).toBe(harness.ctoId);
expect(approval.relatedAgentId).toBe(body.agentId);
harness.db.core.approvals.decideApproval({
approvalId: body.approvalId,
state: "approved",
decisionNote: "Approved by board",
});
const approvedHire = harness.db.core.entity.getAgent(body.agentId);
expect(approvedHire.status).toBe("idle");
expect(harness.db.core.agents.getDirectReports(harness.ctoId, harness.companyId).map((agent) => agent.id)).toContain(body.agentId);
expect(harness.db.core.agents.getDirectReports(harness.ceoId, harness.companyId).map((agent) => agent.id)).not.toContain(body.agentId);
expect(harness.db.core.agents.getChainOfCommand(body.agentId).map((agent) => agent.id)).toEqual([harness.ctoId, harness.ceoId]);
const agentResponse = await fetch(`${harness.baseUrl}/api/agents/${body.agentId}`, {
headers: harness.headers,
});
expect(agentResponse.status).toBe(200);
const agentBody = await agentResponse.json() as { manager: { id: string } | null };
expect(agentBody.manager?.id).toBe(harness.ctoId);
const meResponse = await fetch(`${harness.baseUrl}/api/agents/me`, {
headers: harness.headers,
});
expect(meResponse.status).toBe(200);
const meBody = await meResponse.json() as { directReports: Array<{ id: string }> };
expect(meBody.directReports.map((agent) => agent.id)).toContain(body.agentId);
} finally {
harness.server.stop();
harness.db.close();
}
});
it("routes cross-department hires to the relevant department lead when reportsTo is omitted", async () => {
const harness = await createHarness();
try {
const hrId = harness.db.core.agents.saveAgent({
companyId: harness.companyId,
name: "HR Director",
role: "HR Director",
title: "Director of People",
status: "active",
reportsTo: harness.ceoId,
connectorId: "openai-codex",
workspaceId: harness.workspaceId,
model: null,
capabilities: "Coordinate staffing",
budgetMonthlyUsd: 180,
department: "hr",
});
const hrTaskId = harness.db.core.tasks.saveTask({
companyId: harness.companyId,
title: "Fill engineering gap",
description: "Request a backend engineer for the engineering team.",
assigneeAgentId: hrId,
workspaceId: harness.workspaceId,
priority: "high",
status: "in_progress",
});
const hrRun = harness.db.core.runs.createRun({
companyId: harness.companyId,
taskId: hrTaskId,
agentId: hrId,
workspaceId: harness.workspaceId,
connectorId: "openai-codex",
});
const hrToken = createAgentToken(hrId, harness.companyId, hrRun.id);
const response = await fetch(`${harness.baseUrl}/api/companies/${harness.companyId}/agents`, {
method: "POST",
headers: {
Authorization: `Bearer ${hrToken}`,
"X-Agent-Company-Run-Id": hrRun.id,
"Content-Type": "application/json; charset=utf-8",
},
body: JSON.stringify({
name: "Avery Chen",
role: "Backend Engineer",
title: "Backend Engineer Golang",
department: "engineering",
connectorId: "openai-codex",
workspaceId: harness.workspaceId,
model: null,
capabilities: "Go, PostgreSQL, distributed systems",
budgetMonthlyUsd: 180,
}),
});
expect(response.status).toBe(201);
const body = await response.json() as { agentId: string };
const pendingHire = harness.db.core.entity.getAgent(body.agentId);
expect(pendingHire.department).toBe("engineering");
expect(pendingHire.reportsTo).toBe(harness.ctoId);
} finally {
harness.server.stop();
harness.db.close();
}
});
it("rejects multi-word hire names that only repeat the job title", async () => {
const harness = await createHarness();
try {
const response = await fetch(`${harness.baseUrl}/api/companies/${harness.companyId}/agents`, {
method: "POST",
headers: harness.headers,
body: JSON.stringify({
name: "Backend Engineer Golang",
role: "Backend Engineer",
title: "Backend Engineer Golang",
department: "engineering",
connectorId: "openai-codex",
workspaceId: harness.workspaceId,
model: null,
capabilities: "Go, APIs",
budgetMonthlyUsd: 180,
}),
});
expect(response.status).toBe(422);
const body = await response.json() as { error: string };
expect(body.error).toContain("Hire name");
} finally {
harness.server.stop();
harness.db.close();
}
});
it("manager cannot route a hire outside their reporting branch", async () => {
const harness = await createHarness();
try {
const cmoId = harness.db.core.agents.saveAgent({
companyId: harness.companyId,
name: "CMO",
role: "CMO",
title: "Chief Marketing Officer",
status: "active",
reportsTo: harness.ceoId,
connectorId: "openai-codex",
workspaceId: harness.workspaceId,
model: null,
capabilities: "Lead marketing",
budgetMonthlyUsd: 220,
department: "marketing",
});
const response = await fetch(`${harness.baseUrl}/api/companies/${harness.companyId}/agents`, {
method: "POST",
headers: harness.headers,
body: JSON.stringify({
name: "Misrouted Hire",
role: "Engineer",
title: "Misrouted Engineer",
department: "engineering",
reportsTo: cmoId,
connectorId: "openai-codex",
workspaceId: harness.workspaceId,
model: null,
capabilities: "Should not report outside the CTO branch",
budgetMonthlyUsd: 180,
}),
});
expect(response.status).toBe(422);
const body = await response.json() as { error: string };
expect(body.error).toContain("same department");
} finally {
harness.server.stop();
harness.db.close();
}
});
});
-165
View File
@@ -1,165 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { createAgentToken, initializeAgentTokenSigningKey } from "../../src/main/agent-auth";
import { createApiServer } from "../../src/main/api-server";
import { AppDatabase } from "../../src/main/database";
import { createTestApiServerCallbacks } from "../support/api-server-callbacks";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
async function createHarness() {
const profileDir = await createTempDir("agentcompany-api-events-");
initializeAgentTokenSigningKey(profileDir);
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.core.company.saveCompany({ name: "Orbit Foundry", description: "API test", status: "active" });
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Helios",
role: "CEO",
title: "Lead agent",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Operate the company",
budgetMonthlyUsd: 100,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Ship the API",
description: "Capture structured run events.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex" });
const token = createAgentToken(agentId, companyId, run.id);
const server = createApiServer(db, createTestApiServerCallbacks());
const port = await server.start();
return {
db,
server,
runId: run.id,
companyId,
agentId,
baseUrl: `http://127.0.0.1:${port}`,
headers: {
Authorization: `Bearer ${token}`,
"X-Agent-Company-Run-Id": run.id,
},
};
}
async function readUntil(reader: ReadableStreamDefaultReader<Uint8Array>, matcher: (text: string) => boolean, timeoutMs = 4_000) {
const decoder = new TextDecoder();
let text = "";
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const { value, done } = await reader.read();
if (done) break;
text += decoder.decode(value, { stream: true });
if (matcher(text)) {
return text;
}
}
throw new Error(`Timed out waiting for stream match. Received: ${text}`);
}
describe("run event API", () => {
it("returns persisted run events for the active run token", async () => {
const harness = await createHarness();
try {
harness.db.core.runs.appendRunEvent({
companyId: harness.companyId,
runId: harness.runId,
agentId: harness.agentId,
eventType: "run_status",
level: "info",
message: "Run started",
payload: { status: "running" },
});
const response = await fetch(`${harness.baseUrl}/api/runs/${harness.runId}/events?offset=0&limit=10`, {
headers: harness.headers,
});
expect(response.status).toBe(200);
const body = await response.json() as {
events: Array<{ eventType: string; message: string | null; payload: Record<string, unknown> | null }>;
nextOffset: number;
};
expect(body.events).toHaveLength(1);
expect(body.events[0]?.eventType).toBe("run_status");
expect(body.events[0]?.message).toBe("Run started");
expect(body.events[0]?.payload).toMatchObject({ status: "running" });
expect(body.nextOffset).toBe(1);
} finally {
harness.server.stop();
harness.db.close();
}
});
it("streams new run events over SSE for the active run token", async () => {
const harness = await createHarness();
const controller = new AbortController();
try {
const response = await fetch(`${harness.baseUrl}/api/runs/${harness.runId}/events/stream`, {
headers: harness.headers,
signal: controller.signal,
});
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toContain("text/event-stream");
const reader = response.body?.getReader();
expect(reader).toBeDefined();
setTimeout(() => {
harness.db.core.runs.appendRunEvent({
companyId: harness.companyId,
runId: harness.runId,
agentId: harness.agentId,
eventType: "run_log",
stream: "stdout",
message: "hello from sse",
});
}, 100);
const text = await readUntil(reader!, (chunk) => chunk.includes("event: run-event") && chunk.includes("hello from sse"));
expect(text).toContain("event: connected");
expect(text).toContain("\"eventType\":\"run_log\"");
expect(text).toContain("\"message\":\"hello from sse\"");
} finally {
controller.abort();
harness.server.stop();
harness.db.close();
}
});
});
@@ -1,187 +0,0 @@
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { RuntimeBridge } from "../../src/main/agent-runtime";
import { AppDatabase } from "../../src/main/database";
import type { CredentialResolver } from "../../src/main/credentials";
import { workerRpcMethodSchema } from "../../src/main/worker/entry/types";
const tempDirs: string[] = [];
async function createTempDir(prefix: string): Promise<string> {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
async function createHarness() {
const profileDir = await createTempDir("agentcompany-artifact-ack-");
const workspacePath = await createTempDir("agentcompany-artifact-workspace-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.core.company.saveCompany({
name: "Artifact Ack Co",
description: "Durable artifact projection test",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: workspacePath,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Artifact Agent",
role: "Engineer",
title: "Engineer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Writes files",
budgetMonthlyUsd: 100,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Produce artifact",
description: "",
assigneeAgentId: agentId,
workspaceId,
priority: "medium",
status: "in_progress",
});
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId: null,
connectorId: "openai-codex",
});
const logger = {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const bridge = new RuntimeBridge({
db,
emitEvent: vi.fn(),
logger,
credentialResolver: {
resolve: vi.fn(async () => ({ apiKey: "test-api-key", extraEnv: {} })),
} as unknown as CredentialResolver,
});
const prepare = await bridge.handleRpc({
rpcId: "prepare",
method: "prepareRun",
runId: run.id,
payload: {
connectorId: "openai-codex",
taskSessionId: "task-session-artifact-ack",
agentId,
companyId,
taskId,
modelId: "gpt-5.3-codex",
workspaceId: null,
workspacePath,
workspaceMode: "none",
},
});
expect(prepare.ok).toBe(true);
return {
db,
bridge,
companyId,
workspacePath,
agentId,
taskId,
runId: run.id,
logger,
};
}
describe("artifact projection durable ack", () => {
it("keeps artifact projection out of the per-tool RPC method surface", () => {
expect(workerRpcMethodSchema.safeParse("afterToolCall").success).toBe(false);
});
it("runs completion reconciliation before resource release for non-exclusive observation workspaces", async () => {
const harness = await createHarness();
try {
await mkdir(join(harness.workspacePath, "reports"), { recursive: true });
await writeFile(join(harness.workspacePath, "reports", "completion.md"), "completion artifact", "utf8");
harness.bridge.releaseRunResources(harness.runId);
expect(
harness.db.projection.snapshot.listSnapshot().workProducts.some(
(entry) => entry.externalId === "reports/completion.md",
),
).toBe(true);
const events = harness.db.core.runs.listRunEvents(harness.runId, harness.companyId);
expect(events).toHaveLength(1);
expect(events[0]).toMatchObject({
eventType: "artifact_projection",
level: "info",
});
expect(events[0]?.payload).toMatchObject({
phase: "completion_reconcile",
workspaceMode: "none",
counts: { inserted: 1, updated: 0, retired: 0, skipped: 0 },
});
expect(events[0]?.payload).not.toHaveProperty("toolName");
expect(events[0]?.payload).not.toHaveProperty("toolCallId");
} finally {
harness.db.close();
}
});
it("evicts deleted run contexts without writing artifact projection events", async () => {
const harness = await createHarness();
try {
harness.db.core.company.deleteCompany(harness.companyId);
const result = harness.bridge.releaseRunResources(harness.runId);
expect(result).toEqual({ ok: true });
} finally {
harness.db.close();
}
});
it("rejects destructive tool hooks from deleted run contexts before approval lookup", async () => {
const harness = await createHarness();
try {
harness.db.core.company.deleteCompany(harness.companyId);
const response = await harness.bridge.handleRpc({
rpcId: "before-tool-call",
method: "beforeToolCall",
runId: harness.runId,
payload: {
toolName: "bash",
toolCallId: "tool-1",
args: {},
},
});
expect(response).toEqual({
ok: false,
error: expect.stringContaining("stale run"),
});
expect(harness.logger.error).not.toHaveBeenCalled();
} finally {
harness.db.close();
}
});
});
@@ -1,76 +0,0 @@
/**
* Step 4: Hire approval + decideApproval side effects.
* Physically extracted from autonomous-chain.spec.ts without behavior changes.
*/
import { describe, expect, it } from "vitest";
import { installSharedSetup } from "./shared-setup";
const shared = installSharedSetup();
const { createAgent } = shared;
describe("Step 4: Hire approval + decideApproval", () => {
it("decideApproval sets hired agent to idle when approved", () => {
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.core.approvals.requestApproval({
companyId: shared.companyId,
requestedByAgentId: ceoId,
type: "hire_agent",
payloadSummary: "Hire CTO",
impactSummary: "Test impact",
payloadJson: JSON.stringify({ agentId: hireId }),
});
shared.db.core.approvals.decideApproval({
approvalId,
state: "approved",
decisionNote: "Approved by board",
});
const updatedAgent = shared.db.core.entity.getAgent(hireId);
expect(updatedAgent.status).toBe("idle");
});
it("decideApproval sets hired agent to terminated when rejected", () => {
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.core.approvals.requestApproval({
companyId: shared.companyId,
requestedByAgentId: ceoId,
type: "hire_agent",
payloadSummary: "Hire CTO",
impactSummary: "Test impact",
payloadJson: JSON.stringify({ agentId: hireId }),
});
shared.db.core.approvals.decideApproval({
approvalId,
state: "rejected",
decisionNote: "Not needed",
});
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.core.approvals.requestApproval({
companyId: shared.companyId,
requestedByAgentId: ceoId,
type: "hire_agent",
payloadSummary: "Hire CTO",
impactSummary: "Test",
payloadJson: JSON.stringify({ agentId: hireId, config: { connector: "openai-codex" } }),
});
const approval = shared.db.core.approvals.getApprovalRecord(approvalId);
expect(approval.payloadJson).toBeTruthy();
const payload = JSON.parse(approval.payloadJson!);
expect(payload.agentId).toBe(hireId);
});
});
@@ -1,343 +0,0 @@
/**
* Automation rules, agent messages, auto-assignment, and workflow CRUD.
* Physically extracted from autonomous-chain.spec.ts without behavior changes.
*/
import { beforeEach, describe, expect, it } from "vitest";
import { installSharedSetup } from "./shared-setup";
const shared = installSharedSetup();
const { createAgent } = shared;
describe("Automation Rules CRUD", () => {
it("creates, reads, toggles, and deletes an automation rule", () => {
const ruleId = shared.db.automation.rules.saveAutomationRule({
companyId: shared.companyId,
name: "Auto-escalate blocked tasks",
description: "When a task is blocked, notify the manager",
trigger: "task_blocked",
conditionsJson: JSON.stringify({ department: "engineering" }),
action: "escalate_to_manager",
actionConfigJson: JSON.stringify({ message: "Task blocked" }),
sourceDepartment: "engineering",
targetDepartment: "executive",
priority: 80,
status: "active",
});
expect(ruleId).toBeTruthy();
// Rule appears in snapshot
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");
expect(rule!.trigger).toBe("task_blocked");
expect(rule!.action).toBe("escalate_to_manager");
expect(rule!.status).toBe("active");
expect(rule!.priority).toBe(80);
expect(rule!.executionCount).toBe(0);
// Toggle to paused
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.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.automation.rules.getAutomationLog(shared.companyId);
expect(log).toHaveLength(1);
expect(log[0].ruleName).toBe("Auto-escalate blocked tasks");
// Delete
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.automation.rules.saveAutomationRule({
companyId: shared.companyId,
name: "Original name",
trigger: "task_created",
action: "assign_task",
});
shared.db.automation.rules.saveAutomationRule({
id: ruleId,
companyId: shared.companyId,
name: "Updated name",
trigger: "task_created",
action: "notify_agent",
priority: 90,
});
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);
});
});
describe("Agent Messages", () => {
let agentAId: string;
let agentBId: string;
beforeEach(() => {
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.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",
budgetMonthlyUsd: 100,
});
});
it("sends and retrieves direct messages", () => {
const msgId = shared.db.core.messages.sendAgentMessage({
companyId: shared.companyId,
fromAgentId: agentAId,
toAgentId: agentBId,
channel: "direct",
subject: "Need design review",
body: "Can you review the login page mockup?",
priority: "normal",
});
expect(msgId).toBeTruthy();
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);
expect(messages[0].toAgentId).toBe(agentBId);
expect(messages[0].readAt).toBeNull();
});
it("marks messages as read", () => {
const msgId = shared.db.core.messages.sendAgentMessage({
companyId: shared.companyId,
fromAgentId: agentAId,
toAgentId: agentBId,
subject: "Test",
body: "Hello",
});
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.core.messages.sendAgentMessage({
companyId: shared.companyId,
fromAgentId: agentAId,
toAgentId: agentBId,
subject: "Test",
body: "Hello",
});
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.core.messages.listAgentMessages(shared.companyId, { agentId: agentBId, unreadOnly: true });
expect(recipientViewBefore).toHaveLength(1);
expect(recipientViewBefore[0].id).toBe(msgId);
shared.db.core.messages.markMessageReadForReader(msgId, shared.companyId, agentBId);
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.core.messages.listAgentMessages(shared.companyId, { agentId: thirdAgentId });
expect(thirdView).toHaveLength(0);
});
it("limits department and project messages to relevant members", () => {
const engineeringLeadId = createAgent({ name: "Engineering Lead", role: "Lead", department: "engineering" });
const productLeadId = createAgent({ name: "Product Lead", role: "Lead", department: "product" });
const projectId = shared.db.core.entity.saveProject({
companyId: shared.companyId,
name: "Cross-team Project",
description: "Scope",
leadAgentId: engineeringLeadId,
status: "active",
});
shared.db.core.tasks.saveTask({
companyId: shared.companyId,
title: "Project task",
description: "Do work",
assigneeAgentId: engineeringLeadId,
projectId,
status: "todo",
priority: "medium",
});
const deptMessageId = shared.db.core.messages.sendAgentMessage({
companyId: shared.companyId,
fromAgentId: engineeringLeadId,
channel: "department",
channelTargetId: "engineering",
subject: "Engineering update",
body: "Only engineering should see this",
});
const projectMessageId = shared.db.core.messages.sendAgentMessage({
companyId: shared.companyId,
fromAgentId: engineeringLeadId,
channel: "project",
channelTargetId: projectId,
subject: "Project update",
body: "Only project members should see this",
});
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.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.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.core.messages.listAgentMessages(shared.companyId, { channel: "department" });
expect(deptMsgs).toHaveLength(1);
expect(deptMsgs[0].subject).toBe("Dept update");
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.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");
});
});
describe("Auto Task Assignment", () => {
let backendId: string;
let frontendId: string;
beforeEach(() => {
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.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",
budgetMonthlyUsd: 200,
});
});
it("assigns to the best-matching agent by capability keywords", () => {
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.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.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.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.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.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.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.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.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.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.automation.rules.saveWorkflow({
companyId: shared.companyId,
name: "Code Review Pipeline",
description: "Auto-review flow for PRs",
triggerType: "task_status_changed",
triggerConfigJson: JSON.stringify({ toStatus: "in_review" }),
stepsJson: JSON.stringify([
{ id: "s1", name: "Run tests", action: "run_task", status: "pending" },
{ id: "s2", name: "Notify reviewer", action: "notify_agent", status: "pending" },
]),
status: "draft",
});
expect(wfId).toBeTruthy();
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.automation.rules.deleteWorkflow(wfId, shared.companyId);
expect(shared.db.projection.snapshot.listSnapshot().workflows.find(w => w.id === wfId)).toBeUndefined();
});
});
@@ -1,192 +0,0 @@
/**
* Step 2: CEO phased prompts + pending approvals + credential escalation.
* Physically extracted from autonomous-chain.spec.ts without behavior changes.
*/
import { describe, expect, it } from "vitest";
import { buildSystemPrompt, buildTaskPrompt } from "../../../src/main/prompt-builder/index";
import type { ApprovalRecord } from "../../../src/shared/types";
import { installSharedSetup } from "./shared-setup";
const shared = installSharedSetup();
const { createAgent, buildCtx } = shared;
describe("Step 2: CEO phased prompts", () => {
it("Phase 0: shows team building instructions when no reports at all", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ctx = buildCtx({
agentId: ceoId,
directReports: [], // truly no reports
chainOfCommand: [],
pendingApprovals: [],
});
const stablePrompt = buildSystemPrompt(ctx);
const prompt = buildTaskPrompt(ctx);
expect(stablePrompt).not.toContain("Team Building Phase");
expect(stablePrompt).not.toContain("Awaiting Hire Approvals");
expect(stablePrompt).not.toContain("Awaiting Org Strategy Approval");
expect(prompt).toContain("Team Building Phase");
expect(prompt).toContain("file 3–5 `hire_agent` tool calls");
expect(prompt).toContain("Do this first — file the hires");
expect(prompt).not.toContain("file_approval.strategy.before_hires");
expect(prompt).not.toContain("file_approval.strategy.already_filed");
expect(prompt).not.toContain("Awaiting Org Strategy Approval");
expect(prompt).not.toContain("Standup & Situational Assessment");
expect(prompt).not.toContain("Goal Decomposition");
expect(prompt).not.toContain("Proactive Behaviors");
});
it("Phase 0.5: shows awaiting hire approvals when pending_approval hires exist but no org approval", () => {
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.core.entity.getAgent(hireId);
const ctx = buildCtx({
agentId: ceoId,
directReports: [hire],
chainOfCommand: [],
pendingApprovals: [],
});
const stablePrompt = buildSystemPrompt(ctx);
const prompt = buildTaskPrompt(ctx);
expect(stablePrompt).not.toContain("Team Building Phase");
expect(stablePrompt).not.toContain("Awaiting Hire Approvals");
expect(stablePrompt).not.toContain("Awaiting Org Strategy Approval");
expect(prompt).toContain("Awaiting Hire Approvals");
expect(prompt).toContain("call `file_approval` once");
expect(prompt).toContain("file_approval.strategy.before_hires");
expect(prompt).not.toContain("Team Building Phase");
expect(prompt).not.toContain("Standup & Situational Assessment");
expect(prompt).not.toContain("Goal Decomposition");
expect(prompt).not.toContain("Proactive Behaviors");
});
it("Phase 1: shows awaiting org approval when pending approve_ceo_strategy exists", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ctx = buildCtx({
agentId: ceoId,
directReports: [],
chainOfCommand: [],
pendingApprovals: [
{
id: "approval-1",
companyId: shared.companyId,
type: "approve_ceo_strategy",
state: "pending",
payloadSummary: "Org structure: CEO + CTO + CMO",
impactSummary: "Team of 3",
requestedByAgentId: ceoId,
relatedTaskId: null,
decisionNote: "",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
} as ApprovalRecord,
],
});
const stablePrompt = buildSystemPrompt(ctx);
const prompt = buildTaskPrompt(ctx);
expect(stablePrompt).not.toContain("Team Building Phase");
expect(stablePrompt).not.toContain("Awaiting Hire Approvals");
expect(stablePrompt).not.toContain("Awaiting Org Strategy Approval");
expect(prompt).toContain("Awaiting Org Strategy Approval");
expect(prompt).not.toContain("Team Building Phase");
expect(prompt).not.toContain("Standup & Situational Assessment");
expect(prompt).not.toContain("Goal Decomposition");
expect(prompt).not.toContain("Proactive Behaviors");
});
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.core.entity.getAgent(ctoId);
const ctx = buildCtx({
agentId: ceoId,
directReports: [cto],
chainOfCommand: [],
pendingApprovals: [],
});
const prompt = buildTaskPrompt(ctx);
expect(prompt).toContain("Executive / CEO");
expect(prompt).toContain("Goal Decomposition");
expect(prompt).toContain("Delegation");
expect(prompt).toContain("Unblock & Coordinate");
expect(prompt).toContain("Proactive Behaviors");
expect(prompt).not.toContain("Team Building Phase");
expect(prompt).not.toContain("Awaiting Org Approval");
});
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.core.entity.getAgent(ctoId);
const ctx = buildCtx({
agentId: ceoId,
directReports: [cto],
chainOfCommand: [],
pendingApprovals: [
{
id: "approval-x",
companyId: shared.companyId,
type: "secret_access",
state: "pending",
payloadSummary: "Need Twitter API keys",
impactSummary: "",
requestedByAgentId: ceoId,
relatedTaskId: null,
decisionNote: "",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
} as ApprovalRecord,
],
});
const prompt = buildTaskPrompt(ctx);
expect(prompt).toContain("Pending Approvals");
expect(prompt).toContain("Need Twitter API keys");
});
it("Manager prompt includes credential escalation guidance", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
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.core.entity.getAgent(icId);
const ctx = buildCtx({
agentId: managerId,
directReports: [ic],
chainOfCommand: [ceo],
});
const prompt = buildSystemPrompt(ctx);
expect(prompt).toContain("Break down work and delegate it");
});
it("IC prompt includes credential escalation guidance", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ceo = shared.db.core.entity.getAgent(ceoId);
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ceoId });
const ctx = buildCtx({
agentId: icId,
directReports: [],
chainOfCommand: [ceo],
});
const prompt = buildSystemPrompt(ctx);
expect(prompt).toContain("Your manager is auto-notified and will help");
expect(prompt).toContain("explain what API key/account you need");
});
});
@@ -1,91 +0,0 @@
/**
* Step 7: Worker crash recovery (database layer).
* Physically extracted from autonomous-chain.spec.ts without behavior changes.
*/
import { describe, expect, it } from "vitest";
import { AppDatabase } from "../../../src/main/database";
import { installSharedSetup } from "./shared-setup";
const shared = installSharedSetup();
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.core.tasks.saveTask({
companyId: shared.companyId,
title: "In-progress task",
description: "Running when crash happened",
assigneeAgentId: agentId,
workspaceId: shared.workspaceId,
priority: "medium",
status: "in_progress",
});
const run = shared.db.core.runs.createRun({
companyId: shared.companyId,
taskId,
agentId,
workspaceId: shared.workspaceId,
connectorId: "openai-codex",
});
shared.db.core.runs.markRunStatus(run.id, "running", "Started");
// Verify it's running
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
shared.db.close();
const profileDir = shared.tempDirs[shared.tempDirs.length - 1]!;
shared.db = new AppDatabase(profileDir);
shared.db.init(); // This calls interruptStaleRuns()
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.core.tasks.saveTask({
companyId: shared.companyId,
title: "Crash recovery task",
description: "Test",
assigneeAgentId: agentId,
workspaceId: shared.workspaceId,
priority: "medium",
status: "in_progress",
});
const run = shared.db.core.runs.createRun({
companyId: shared.companyId,
taskId,
agentId,
workspaceId: shared.workspaceId,
connectorId: "openai-codex",
});
shared.db.core.runs.markRunStatus(run.id, "running", "Started");
// Simulate crash recovery - mark as interrupted
shared.db.core.runs.finishRun({
runId: run.id,
status: "interrupted",
summary: "Worker process crashed.",
errorMessage: "Worker process exited unexpectedly.",
exitCode: 1,
signal: null,
model: null,
unattributedCost: true,
});
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.");
});
});
@@ -1,336 +0,0 @@
/**
* End-to-end chain flows + adversarial review-fix regression tests.
* Physically extracted from autonomous-chain.spec.ts without behavior changes.
*/
import { describe, expect, it } from "vitest";
import { buildTaskPrompt } from "../../../src/main/prompt-builder/index";
import type { ApprovalRecord } from "../../../src/shared/types";
import { installSharedSetup } from "./shared-setup";
const shared = installSharedSetup();
const { createAgent, buildCtx } = shared;
describe("E2E: Full autonomous CEO cold start flow", () => {
it("simulates complete chain: CEO creation → team build → org approval → delegation → completion", () => {
// 1. Create CEO agent
const ceoId = createAgent({ name: "Atlas", role: "CEO", status: "idle" });
// CEO has no team yet → Phase 0
const phase0Ctx = buildCtx({
agentId: ceoId,
directReports: [],
chainOfCommand: [],
pendingApprovals: [],
});
const phase0Prompt = buildTaskPrompt(phase0Ctx);
expect(phase0Prompt).toContain("Team Building Phase");
// 2. CEO submits hire requests
const ctoId = createAgent({ name: "Nova", role: "CTO", reportsTo: ceoId, status: "pending_approval" as "idle" });
const cmoId = createAgent({ name: "Aria", role: "CMO", reportsTo: ceoId, status: "pending_approval" as "idle" });
// 3. CEO submits org strategy approval
const orgApprovalId = shared.db.core.approvals.requestApproval({
companyId: shared.companyId,
requestedByAgentId: ceoId,
type: "approve_ceo_strategy",
payloadSummary: "Org: CEO + CTO + CMO",
impactSummary: "3-person team",
});
// CEO now in Phase 1
const ctoRecord = shared.db.core.entity.getAgent(ctoId);
const cmoRecord = shared.db.core.entity.getAgent(cmoId);
const phase1Ctx = buildCtx({
agentId: ceoId,
directReports: [ctoRecord, cmoRecord],
chainOfCommand: [],
pendingApprovals: [{
id: orgApprovalId,
companyId: shared.companyId,
type: "approve_ceo_strategy",
state: "pending",
payloadSummary: "Org: CEO + CTO + CMO",
impactSummary: "3-person team",
requestedByAgentId: ceoId,
relatedTaskId: null,
decisionNote: "",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
} as ApprovalRecord],
});
const phase1Prompt = buildTaskPrompt(phase1Ctx);
expect(phase1Prompt).toContain("Awaiting Org Strategy Approval");
// 4. Board approves hires
const hireApprovalCTO = shared.db.core.approvals.requestApproval({
companyId: shared.companyId,
requestedByAgentId: ceoId,
type: "hire_agent",
payloadSummary: "Hire Nova as CTO",
impactSummary: "Test",
payloadJson: JSON.stringify({ agentId: ctoId }),
});
shared.db.core.approvals.decideApproval({ approvalId: hireApprovalCTO, state: "approved", decisionNote: "Approved" });
const hireApprovalCMO = shared.db.core.approvals.requestApproval({
companyId: shared.companyId,
requestedByAgentId: ceoId,
type: "hire_agent",
payloadSummary: "Hire Aria as CMO",
impactSummary: "Test",
payloadJson: JSON.stringify({ agentId: cmoId }),
});
shared.db.core.approvals.decideApproval({ approvalId: hireApprovalCMO, state: "approved", decisionNote: "Approved" });
// Agents now 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.core.approvals.decideApproval({ approvalId: orgApprovalId, state: "approved", decisionNote: "Go ahead" });
// 6. CEO now in Phase 2
const updatedCto = shared.db.core.entity.getAgent(ctoId);
const updatedCmo = shared.db.core.entity.getAgent(cmoId);
const phase2Ctx = buildCtx({
agentId: ceoId,
directReports: [updatedCto, updatedCmo],
chainOfCommand: [],
pendingApprovals: [], // all approved now
});
const phase2Prompt = buildTaskPrompt(phase2Ctx);
expect(phase2Prompt).toContain("Executive / CEO");
expect(phase2Prompt).toContain("Goal Decomposition");
expect(phase2Prompt).toContain("Delegation");
// 7. Verify hierarchical chain
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.core.entity.saveProject({
companyId: shared.companyId,
name: "Launch MVP",
description: "Build and launch MVP",
goalId: null,
leadAgentId: ctoId,
status: "in_progress",
});
const parentTaskId = shared.db.core.tasks.saveTask({
companyId: shared.companyId,
title: "Build backend",
description: "Implement API",
assigneeAgentId: ctoId,
projectId,
workspaceId: shared.workspaceId,
priority: "high",
status: "in_progress",
});
// 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.core.tasks.saveTask({
companyId: shared.companyId,
title: "Implement auth",
description: "JWT auth",
assigneeAgentId: icId,
parentId: parentTaskId,
projectId,
workspaceId: shared.workspaceId,
priority: "medium",
status: "todo",
});
// Verify parent-child relationship
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.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");
// 10. Verify IC prompt has correct wake reason
const icPromptCtx = buildCtx({
agentId: icId,
task: subtask,
chainOfCommand: [shared.db.core.entity.getAgent(ctoId), shared.db.core.entity.getAgent(ceoId)],
wakeReason: "assignment",
});
const icPrompt = buildTaskPrompt(icPromptCtx);
expect(icPrompt).toContain("Wake Reason");
expect(icPrompt).toContain("You have been assigned a new task");
expect(icPrompt).toContain("Implement auth");
});
});
describe("E2E: Wakeup request lifecycle with run lifecycle", () => {
it("wake enqueue + coalesce + claim + complete cycle", () => {
const agentId = createAgent({ name: "Agent" });
// Simulate: agent is running, wakes come in. All triggers are coalesced
// into a single wakeup request with JSON array of trigger details.
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.automation.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
expect(wakes).toHaveLength(1);
expect(wakes[0].coalescedCount).toBe(2);
const triggers = JSON.parse(wakes[0].triggerDetail!);
expect(triggers).toHaveLength(3);
expect(triggers[0].trigger).toBe("subtask_completed");
expect(triggers[1].trigger).toBe("comment");
expect(triggers[2].trigger).toBe("report_blocked");
// Wakes remain queued until consumed by the run lifecycle
expect(shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" })).toHaveLength(1);
});
});
describe("E2E: Blocked escalation through org hierarchy", () => {
it("getChainOfCommand provides escalation path for blocked IC", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId });
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ctoId });
// When IC marks task blocked, we need to find its manager
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.core.agents.getChainOfCommand(ctoId);
expect(ctoChain[0].id).toBe(ceoId);
});
});
describe("Review Fix: CEO phase ordering edge case", () => {
it("shows awaiting hire approvals when org strategy approved first but hires still pending", () => {
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.core.entity.getAgent(hireId);
// Org strategy already approved (not in pendingApprovals), but hires still pending
const ctx = buildCtx({
agentId: ceoId,
directReports: [hire], // only pending_approval
chainOfCommand: [],
pendingApprovals: [], // org strategy already resolved
});
const prompt = buildTaskPrompt(ctx);
// Should NOT get Phase 0 (team building) — hires are already submitted
expect(prompt).not.toContain("Team Building Phase");
// Should get the awaiting hire approvals phase
expect(prompt).toContain("Awaiting Hire Approvals");
});
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.core.entity.getAgent(ctoId);
const ctx = buildCtx({
agentId: ceoId,
directReports: [cto], // active report
chainOfCommand: [],
pendingApprovals: [],
});
const prompt = buildTaskPrompt(ctx);
expect(prompt).toContain("Executive / CEO");
expect(prompt).not.toContain("Awaiting Hire Approvals");
expect(prompt).not.toContain("Team Building Phase");
});
});
describe("Review Fix: Org gate consistency (revision_requested)", () => {
it("org-strategy revision_requested blocks the CEO in Phase 1", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ctx = buildCtx({
agentId: ceoId,
directReports: [],
chainOfCommand: [],
pendingApprovals: [
{
id: "approval-rev",
companyId: shared.companyId,
type: "approve_ceo_strategy",
state: "revision_requested",
payloadSummary: "Org plan needs changes",
impactSummary: "",
requestedByAgentId: ceoId,
relatedTaskId: null,
decisionNote: "Please add a CPO",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
} as ApprovalRecord,
],
});
const prompt = buildTaskPrompt(ctx);
expect(prompt).toContain("Awaiting Org Strategy Approval");
expect(prompt).toContain("revision_requested");
});
});
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.core.approvals.requestApproval({
companyId: shared.companyId,
requestedByAgentId: ceoId,
type: "approve_ceo_strategy",
payloadSummary: "Org plan",
impactSummary: "test",
});
shared.db.core.approvals.decideApproval({
approvalId,
state: "rejected",
decisionNote: "Not ready",
});
// Verify approval state
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
});
});
describe("Review Fix: Root task completion notification", () => {
it("root task (no parentId) should still have org hierarchy for manager lookup", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId });
// CTO has a root-level task (no parentId)
const taskId = shared.db.core.tasks.saveTask({
companyId: shared.companyId,
title: "Standalone task from CEO",
description: "No parent",
assigneeAgentId: ctoId,
workspaceId: shared.workspaceId,
priority: "high",
status: "done",
});
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.core.agents.getChainOfCommand(ctoId);
expect(chain).toHaveLength(1);
expect(chain[0].id).toBe(ceoId);
});
});
@@ -1,65 +0,0 @@
/**
* Step 1: CEO role detection fix.
* Physically extracted from autonomous-chain.spec.ts without behavior changes.
*/
import { describe, expect, it } from "vitest";
import { buildSystemPrompt, buildTaskPrompt } from "../../../src/main/prompt-builder/index";
import { installSharedSetup } from "./shared-setup";
const shared = installSharedSetup();
const { createAgent, buildCtx } = shared;
describe("Step 1: CEO role detection", () => {
it("identifies CEO when chainOfCommand is empty, even with no direct reports", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ctx = buildCtx({
agentId: ceoId,
directReports: [], // no reports yet!
chainOfCommand: [], // top-level
});
const prompt = buildTaskPrompt(ctx);
// CEO should NOT get IC instructions
expect(prompt).not.toContain("Individual Contributor");
// CEO should get executive instructions (Phase 0: Team Building)
expect(prompt).toContain("CEO");
expect(prompt).toContain("Team Building");
});
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.core.entity.getAgent(ceoId);
const icId = createAgent({ name: "IC", role: "Engineer", reportsTo: managerId });
const ic = shared.db.core.entity.getAgent(icId);
const ctx = buildCtx({
agentId: managerId,
directReports: [ic],
chainOfCommand: [ceo], // reports to CEO
});
const prompt = buildSystemPrompt(ctx);
expect(prompt).toContain("Operating Instructions (Manager)");
expect(prompt).not.toContain("Individual Contributor");
expect(prompt).not.toContain("Executive / CEO");
});
it("identifies IC when has chain of command and no direct reports", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ceo = shared.db.core.entity.getAgent(ceoId);
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ceoId });
const ctx = buildCtx({
agentId: icId,
directReports: [],
chainOfCommand: [ceo],
});
const prompt = buildSystemPrompt(ctx);
expect(prompt).toContain("Individual Contributor");
expect(prompt).not.toContain("Operating Instructions (Manager)");
});
});
@@ -1,64 +0,0 @@
/**
* Step 6: Run failure escalation (database layer).
* Physically extracted from autonomous-chain.spec.ts without behavior changes.
*/
import { describe, expect, it } from "vitest";
import { installSharedSetup } from "./shared-setup";
const shared = installSharedSetup();
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.core.tasks.saveTask({
companyId: shared.companyId,
title: "Failing task",
description: "Will fail",
assigneeAgentId: agentId,
workspaceId: shared.workspaceId,
priority: "medium",
status: "in_progress",
});
const run = shared.db.core.runs.createRun({
companyId: shared.companyId,
taskId,
agentId,
workspaceId: shared.workspaceId,
connectorId: "openai-codex",
});
shared.db.core.runs.markRunStatus(run.id, "running", "Started");
const outcome = shared.db.core.runs.finishRun({
runId: run.id,
status: "failed",
summary: null,
errorMessage: "Process crashed",
exitCode: 1,
signal: null,
model: "gpt-5-codex",
unattributedCost: false,
});
expect(outcome.budgetHardStopped).toBe(false);
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");
});
it("getChainOfCommand returns manager for failure escalation", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId });
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ctoId });
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");
});
});
@@ -1,135 +0,0 @@
/**
* Shared setup for the autonomous-chain integration test suite.
*
* The original 50KB autonomous-chain.spec.ts shared a module-level `db`,
* `companyId`, and `workspaceId` across every describe block via top-level
* `beforeEach`/`afterEach` hooks. This module exports `installSharedSetup`,
* which each split spec file calls to wire equivalent hooks into its own
* vitest context and hand back a live handle.
*
* The helpers preserve the exact behavior of the original file — tests are
* physically moved, not rewritten.
*/
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, beforeEach } from "vitest";
import { AppDatabase } from "../../../src/main/database";
import { buildSystemPrompt } from "../../../src/main/prompt-builder/index";
import type { AgentRecord, ApprovalRecord, TaskRecord } from "../../../src/shared/types";
export interface SharedContext {
db: AppDatabase;
companyId: string;
workspaceId: string;
}
export interface SharedHandle {
get db(): AppDatabase;
set db(value: AppDatabase);
get companyId(): string;
get workspaceId(): string;
createAgent(overrides?: Partial<Parameters<AppDatabase["core"]["agents"]["saveAgent"]>[0]>): string;
buildCtx(overrides?: Record<string, unknown>): Parameters<typeof buildSystemPrompt>[0];
tempDirs: string[];
}
export function installSharedSetup(): SharedHandle {
const state = {
db: null as AppDatabase | null,
companyId: "",
workspaceId: "",
};
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
beforeEach(async () => {
const profileDir = await createTempDir("agentcompany-chain-test-");
state.db = new AppDatabase(profileDir);
state.db.init();
state.companyId = state.db.core.company.saveCompany({
name: "Autonomous Test Corp",
description: "Test company for autonomous chain",
status: "active",
});
state.workspaceId = state.db.core.workspaces.saveWorkspace({
companyId: state.companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "https://example.com/repo.git",
repoRef: "main",
isPrimary: true,
});
});
afterEach(async () => {
state.db?.close();
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
function currentDb(): AppDatabase {
if (!state.db) throw new Error("shared-setup: db accessed outside test hook");
return state.db;
}
function createAgent(overrides: Partial<Parameters<AppDatabase["core"]["agents"]["saveAgent"]>[0]> = {}): string {
return currentDb().core.agents.saveAgent({
companyId: state.companyId,
name: "TestAgent",
role: "CEO",
title: "Test",
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
workspaceId: state.workspaceId,
model: null,
capabilities: "Test capabilities",
budgetMonthlyUsd: 200,
...overrides,
});
}
function buildCtx(overrides: Record<string, unknown> = {}): Parameters<typeof buildSystemPrompt>[0] {
const agent = currentDb().core.entity.getAgent(overrides.agentId as string ?? createAgent());
return {
agent,
apiUrl: "http://127.0.0.1:3000",
apiKey: "test-key",
runId: "test-run-id",
task: null,
companyName: "Autonomous Test Corp",
goals: [],
projects: [],
directReports: overrides.directReports as AgentRecord[] ?? [],
chainOfCommand: overrides.chainOfCommand as AgentRecord[] ?? [],
assignedTasks: [],
allAgents: [],
pendingApprovals: overrides.pendingApprovals as ApprovalRecord[] ?? [],
tasksAwaitingReview: overrides.tasksAwaitingReview as TaskRecord[] ?? [],
wakeReason: overrides.wakeReason as string | null ?? null,
allTasks: [],
recentComments: [],
companyDescription: "",
socialAccounts: [],
...overrides,
} as Parameters<typeof buildSystemPrompt>[0];
}
return {
get db() { return currentDb(); },
set db(value: AppDatabase) { state.db = value; },
get companyId() { return state.companyId; },
get workspaceId() { return state.workspaceId; },
createAgent,
buildCtx,
tempDirs,
};
}
@@ -1,63 +0,0 @@
/**
* Step 5: Task status + comment wake propagation.
* Physically extracted from autonomous-chain.spec.ts without behavior changes.
*/
import { describe, expect, it } from "vitest";
import { installSharedSetup } from "./shared-setup";
const shared = installSharedSetup();
const { createAgent } = shared;
describe("Step 5: Task status + comment wake propagation", () => {
it("getChainOfCommand returns correct hierarchy", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO", reportsTo: null });
const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId });
const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ctoId });
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
});
it("getChainOfCommand handles circular references safely", () => {
// 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.core.agents.getChainOfCommand(agent1);
expect(chain).toHaveLength(0);
});
it("task tree supports parent-child relationships", () => {
const ceoId = createAgent({ name: "CEO", role: "CEO" });
const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId });
const parentTaskId = shared.db.core.tasks.saveTask({
companyId: shared.companyId,
title: "Build feature X",
description: "Top-level task",
assigneeAgentId: ctoId,
workspaceId: shared.workspaceId,
priority: "high",
status: "in_progress",
});
const childTaskId = shared.db.core.tasks.saveTask({
companyId: shared.companyId,
title: "Implement module A",
description: "Subtask",
assigneeAgentId: ctoId,
parentId: parentTaskId,
workspaceId: shared.workspaceId,
priority: "medium",
status: "todo",
});
const childTask = shared.db.core.entity.getTask(childTaskId);
expect(childTask.parentId).toBe(parentTaskId);
const parentTask = shared.db.core.entity.getTask(parentTaskId);
expect(parentTask.assigneeAgentId).toBe(ctoId);
});
});
@@ -1,119 +0,0 @@
/**
* Step 8: Wake reason injection into task prompt.
* Physically extracted from autonomous-chain.spec.ts without behavior changes.
*/
import { describe, expect, it } from "vitest";
import { buildTaskPrompt } from "../../../src/main/prompt-builder/index";
import { installSharedSetup } from "./shared-setup";
const shared = installSharedSetup();
const { createAgent, buildCtx } = shared;
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.core.entity.getAgent(ceoId);
const ctx = buildCtx({
agentId,
chainOfCommand: [ceo],
wakeReason: "subtask_completed",
});
const prompt = buildTaskPrompt(ctx);
expect(prompt).toContain("Wake Reason");
expect(prompt).toContain("subtask you delegated has been completed");
});
it("includes wake reason for timer triggers", () => {
const agentId = createAgent({ name: "Agent" });
const ctx = buildCtx({
agentId,
wakeReason: "timer",
});
const prompt = buildTaskPrompt(ctx);
expect(prompt).toContain("Wake Reason");
expect(prompt).toContain("Regular heartbeat check-in");
});
it("includes wake reason for manual triggers", () => {
const agentId = createAgent({ name: "Agent" });
const ctx = buildCtx({
agentId,
wakeReason: "manual",
});
const prompt = buildTaskPrompt(ctx);
expect(prompt).toContain("Wake Reason");
expect(prompt).toContain("manually triggered by the human operator");
});
it("includes correct message for each wake reason", () => {
const agentId = createAgent({ name: "Agent" });
const reasons: Record<string, string> = {
assignment: "You have been assigned a new task",
goal_activated: "goal you own has been activated",
approval_resolved: "approval you requested has been decided",
subtask_completed: "subtask you delegated has been completed",
comment: "new comment has been posted",
report_blocked: "reports is blocked",
report_failed: "reports has failed",
};
for (const [reason, expectedText] of Object.entries(reasons)) {
const ctx = buildCtx({
agentId,
wakeReason: reason,
});
const prompt = buildTaskPrompt(ctx);
expect(prompt).toContain("Wake Reason");
expect(prompt).toContain(expectedText);
}
});
it("handles unknown wake reason gracefully", () => {
const agentId = createAgent({ name: "Agent" });
const ctx = buildCtx({
agentId,
wakeReason: "custom_trigger_xyz",
});
const prompt = buildTaskPrompt(ctx);
expect(prompt).toContain("Wake Reason");
expect(prompt).toContain("Trigger: custom_trigger_xyz");
});
it("includes wake reason before task description", () => {
const agentId = createAgent({ name: "Agent" });
const taskId = shared.db.core.tasks.saveTask({
companyId: shared.companyId,
title: "Do the thing",
description: "Test task",
assigneeAgentId: agentId,
workspaceId: shared.workspaceId,
priority: "medium",
status: "todo",
});
const task = shared.db.core.entity.getTask(taskId);
const ctx = buildCtx({
agentId,
task,
wakeReason: "assignment",
});
const prompt = buildTaskPrompt(ctx);
const wakeIndex = prompt.indexOf("Wake Reason");
const taskIndex = prompt.indexOf("Your immediate task");
expect(wakeIndex).toBeGreaterThan(-1);
expect(taskIndex).toBeGreaterThan(-1);
expect(wakeIndex).toBeLessThan(taskIndex);
});
});
@@ -1,55 +0,0 @@
/**
* Step 3: Deferred wake queue lifecycle.
* Physically extracted from autonomous-chain.spec.ts without behavior changes.
*/
import { describe, expect, it } from "vitest";
import { installSharedSetup } from "./shared-setup";
const shared = installSharedSetup();
const { createAgent } = shared;
describe("Step 3: Wakeup request lifecycle (database)", () => {
it("enqueue and list wakeup requests with coalescing", () => {
const agentId = createAgent({ name: "TestAgent" });
// Enqueue two wakes for the same busy agent. Coalesced into one row
// with trigger_detail JSON array containing both triggers.
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.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);
const triggers = JSON.parse(wakes[0].triggerDetail!);
expect(triggers).toHaveLength(2);
expect(triggers[0].trigger).toBe("subtask_completed");
expect(triggers[1].trigger).toBe("comment");
});
it("returns empty array when no wakeup requests exist", () => {
const agentId = createAgent({ name: "TestAgent" });
const wakes = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
expect(wakes).toHaveLength(0);
});
it("does not return wakes for other agents", () => {
const agent1 = createAgent({ name: "Agent1" });
const agent2 = createAgent({ name: "Agent2" });
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.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.automation.wakeup.listWakeupRequests(shared.companyId, { agentId: agent2, status: "queued" });
expect(wakes2).toHaveLength(1);
expect(wakes2[0].source).toBe("subtask_completed");
});
});
+229
View File
@@ -0,0 +1,229 @@
import { existsSync, mkdirSync, readFileSync } from "node:fs";
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OnboardingService } from "../../src/main/services/onboarding-service";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ProviderService } from "../../src/main/services/provider-service";
import { providerAdapters } from "../../src/main/services/provider-adapters";
import { TaskExecutionService } from "../../src/main/services/task-execution-service";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { claimNextQueuedRun } from "../../src/main/app/task-queue-worker";
import type { AgentTurnMessage, AgentTurnResult, ProviderToolTurnInput, ToolCall } from "../../src/main/services/provider-tool-turn";
const testVault = {
encryptSecret: (secret: string) => Buffer.from(secret, "utf8"),
decryptSecret: (ciphertext: Buffer) => ciphertext.toString("utf8"),
};
function call(name: string, args: Record<string, unknown>): ToolCall {
return { id: `call_${randomUUID()}`, name, arguments: args };
}
/** Scripts the CEO: staff two reports, delegate work, hand off the file in chat, then stop. */
function scriptedCeo(): (input: ProviderToolTurnInput) => Promise<AgentTurnResult> {
const script: Array<{ text: string; toolCalls: ToolCall[] }> = [
{ text: "Staffing.", toolCalls: [
call("hire_agent", { name: "Mara Quinn", role: "Growth Marketer", department: "Marketing" }),
call("hire_agent", { name: "Owen Diaz", role: "Operations Lead", department: "Operations" }),
] },
{ text: "Delegating.", toolCalls: [
call("create_task", { title: "Launch the booking site", description: "Ship landing + booking flow.", assignee_agent_name: "Mara Quinn" }),
] },
{ text: "Writing the plan.", toolCalls: [
call("write_deliverable", { path: "plan/operating-plan.md", content: "# OrbitOps Operating Plan\n\n- Validate demand\n- Launch booking site\n" }),
] },
{ text: "Handing off the plan.", toolCalls: [] },
{ text: "Team staffed, work delegated, plan written.", toolCalls: [] },
];
let index = 0;
return async (input) => {
const fileId = lastToolOutput(input.messages, "write_deliverable").match(/file_id=([^),\s]+)/)?.[1];
if (fileId && index === 3) {
index += 1;
return {
text: "Handing off the plan.",
toolCalls: [
call("send_message", {
channel: "general",
body: "/handoff @Mara-Quinn operating plan for booking-site execution. @Owen-Diaz coordinate launch operations in this thread.",
attachment_file_ids: [fileId],
}),
],
rawResponseJson: JSON.stringify({ id: `resp_${index}` }),
};
}
const turn = script[index] ?? { text: "done", toolCalls: [] };
index += 1;
return { text: turn.text, toolCalls: turn.toolCalls, rawResponseJson: JSON.stringify({ id: `resp_${index}` }) };
};
}
function lastToolOutput(messages: AgentTurnMessage[], toolName: string): string {
for (let index = messages.length - 1; index >= 0; index -= 1) {
const message = messages[index];
if (message.role !== "tool") continue;
const result = message.results.find((item) => item.toolName === toolName);
if (result) return result.output;
}
return "";
}
describe("autonomous bootstrap run", () => {
test("the queued kickoff run autonomously hires, delegates, and writes a deliverable", async () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-auto-${randomUUID()}.db`));
const workspacePath = join(tmpdir(), `ac-auto-ws-${randomUUID()}`);
mkdirSync(workspacePath, { recursive: true });
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, scriptedCeo());
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
const snapshot = new SnapshotService(connection.db);
let orgId = "";
connection.transaction(() => {
const summary = onboarding.bootstrap({
companyName: "OrbitOps AI",
companyDescription: "AI-run operations company.",
employeeName: "Yang",
workspacePath,
executionMode: "ai_ceo",
autoApproveHires: true,
reviewDeliverables: false,
goalTitle: "Stand up a local service business in 30 days.",
goalDescription: "Validate the market and win the first paying customers.",
providerKey: "openai",
providerSecret: "sk-test-key",
providerModel: "gpt-5.4",
});
orgId = summary.orgId;
});
// The bootstrap created exactly one queued run; the worker claims it.
const claimed = claimNextQueuedRun(connection);
expect(claimed?.org_id).toBe(orgId);
expect(claimNextQueuedRun(connection)).toBeNull();
const taskExecution = new TaskExecutionService(connection.db, providers);
await taskExecution.executeRun(claimed!.org_id, claimed!.id);
const state = snapshot.load();
const agentNames = state.agents.filter((a) => a.orgId === orgId).map((a) => a.name).sort();
expect(agentNames).toEqual(["CEO", "Mara Quinn", "Owen Diaz"]);
const delegated = state.tasks.find((t) => t.orgId === orgId && t.title === "Launch the booking site");
expect(delegated).toBeTruthy();
const mara = state.agents.find((a) => a.orgId === orgId && a.name === "Mara Quinn");
expect(delegated?.assigneeAgentId).toBe(mara?.id);
const deliverable = join(workspacePath, "plan", "operating-plan.md");
expect(existsSync(deliverable)).toBe(true);
expect(readFileSync(deliverable, "utf8")).toContain("OrbitOps Operating Plan");
// The deliverable is registered as a file object owned by the writing agent,
// so it appears on the Files page (previously it only existed on disk).
const ceo = state.agents.find((a) => a.orgId === orgId && a.name === "CEO");
const planFile = state.files.find((f) => f.orgId === orgId && f.name === "operating-plan.md");
expect(planFile).toBeTruthy();
expect(planFile).toMatchObject({ ownerType: "agent", ownerId: ceo?.id, kind: "md", scanState: "approved" });
// The CEO posts a file handoff in the company channel, so chat carries the
// actual artifact and recipient mentions instead of a plain status note.
const general = state.chatSpaces.find((s) => s.orgId === orgId && s.name === "general");
const kickoff = state.chatMessages.find((m) => m.orgId === orgId && m.spaceId === general?.id && m.bodyMd.includes("/handoff"));
expect(kickoff).toMatchObject({ senderType: "agent", senderId: ceo?.id });
expect(kickoff?.bodyMd).toContain("/handoff @Mara-Quinn");
expect(state.chatAttachments).toContainEqual(expect.objectContaining({ messageId: kickoff?.id, fileId: planFile?.id }));
expect(state.chatMentions).toEqual(expect.arrayContaining([
expect.objectContaining({ messageId: kickoff?.id, targetType: "agent", targetId: mara?.id }),
expect.objectContaining({ messageId: kickoff?.id, targetType: "agent", targetId: state.agents.find((a) => a.orgId === orgId && a.name === "Owen Diaz")?.id }),
]));
expect(state.fileHandoffRecords).toContainEqual(expect.objectContaining({
messageId: kickoff?.id,
fileId: planFile?.id,
targetPrincipalType: "agent",
targetPrincipalId: mara?.id,
policyDecision: "approved_for_handoff",
}));
const run = state.taskRuns.find((r) => r.id === claimed!.id);
expect(run?.state).toBe("completed");
// Stage 4 heartbeat: the CEO-delegated task (assigned to a hired, heartbeat-enabled
// agent, with no run yet) is enqueued exactly once for autonomous execution.
const enqueued = taskExecution.enqueueHeartbeatRuns();
expect(enqueued).toHaveLength(1);
const queued = connection.db.prepare("select task_id, state, trigger from task_runs where id = ?").get(enqueued[0]) as { task_id: string; state: string; trigger: string };
expect(queued.task_id).toBe(delegated!.id);
expect(queued.state).toBe("queued");
expect(queued.trigger).toBe("heartbeat");
// Idempotent: the delegated task now has a run, so it is not enqueued again.
expect(taskExecution.enqueueHeartbeatRuns()).toEqual([]);
connection.close();
});
test("a goal created after onboarding reaches the CEO and runs autonomously", async () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-goal-${randomUUID()}.db`));
const providers = new ProviderService(connection.db, providerAdapters, testVault, undefined, async () => ({
text: "Reviewed the goal and posted the operating plan.",
toolCalls: [],
rawResponseJson: JSON.stringify({ id: "resp_goal" }),
}));
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (input) => providers.saveCredential(input) });
const organization = new OrganizationService(connection.db);
const taskExecution = new TaskExecutionService(connection.db, providers);
let orgId = "";
let ceoAgentId = "";
connection.transaction(() => {
const summary = onboarding.bootstrap({
companyName: "OrbitOps AI",
companyDescription: "AI-run operations company.",
employeeName: "Yang",
workspacePath: "",
executionMode: "ai_ceo",
autoApproveHires: true,
reviewDeliverables: false,
goalTitle: "Stand up a local service business in 30 days.",
goalDescription: "Validate the market and win the first paying customers.",
providerKey: "openai",
providerSecret: "sk-test-key",
providerModel: "gpt-5.4",
});
orgId = summary.orgId;
ceoAgentId = summary.ceoAgentId;
});
// Drain the bootstrap kickoff so only the new goal's work remains.
const bootstrapRun = claimNextQueuedRun(connection);
await taskExecution.executeRun(bootstrapRun!.org_id, bootstrapRun!.id);
let goalId = "";
connection.transaction(() => {
goalId = organization.createGoal({
orgId,
title: "Open the Berlin market",
description: "Win the first ten customers.",
ownerAgentId: null,
});
});
const kickoff = connection.db.prepare("select id, assignee_agent_id, status from tasks where goal_id = ?")
.get(goalId) as { id: string; assignee_agent_id: string; status: string };
expect(kickoff.assignee_agent_id).toBe(ceoAgentId);
expect(kickoff.status).toBe("backlog");
// The autonomous worker's heartbeat picks the kickoff up and completes it.
const enqueued = connection.transaction(() => taskExecution.enqueueHeartbeatRuns());
expect(enqueued).toHaveLength(1);
const claimed = claimNextQueuedRun(connection);
expect(claimed?.id).toBe(enqueued[0]);
await taskExecution.executeRun(claimed!.org_id, claimed!.id);
const run = connection.db.prepare("select state, task_id, trigger from task_runs where id = ?")
.get(claimed!.id) as { state: string; task_id: string; trigger: string };
expect(run).toMatchObject({ state: "completed", task_id: kickoff.id, trigger: "heartbeat" });
connection.close();
});
});
@@ -0,0 +1,119 @@
import { mkdirSync, writeFileSync } from "node:fs";
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { ApprovalService } from "../../src/main/services/approval-service";
import { ChatService } from "../../src/main/services/chat-service";
import { OrganizationService } from "../../src/main/services/organization-service";
import { SdlcService } from "../../src/main/services/sdlc-service";
import { SkillService } from "../../src/main/services/skill-service";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { APPROVAL_STATE, EVAL_SUITE_KEY, SDLC_RUN_STATE } from "../../src/shared/domain";
function createConnection(): DatabaseConnection {
return new DatabaseConnection(join(tmpdir(), `agentcompany-new-integration-${randomUUID()}.db`));
}
function createOrganization(organization: OrganizationService): string {
return organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
}
function createSkillDirectory(): string {
const root = join(tmpdir(), `agentcompany-new-skill-${randomUUID()}`);
mkdirSync(join(root, "checks"), { recursive: true });
writeFileSync(join(root, "SKILL.md"), "Review release evidence and produce a concise gate decision.\n", "utf8");
writeFileSync(join(root, "checks", "acceptance.txt"), "release evidence accepted\n", "utf8");
writeFileSync(join(root, "skill.manifest.json"), JSON.stringify({
name: "Release Gate",
version: "1.0.0",
entrypoint: "SKILL.md",
capabilities: ["release-review"],
required_tools: ["git"],
compatible_models: ["gpt-5.4"],
risk_level: "low",
tests: ["checks/acceptance.txt"],
inputs: {},
outputs: {},
}, null, 2), "utf8");
return root;
}
describe("collaboration integration flow", () => {
test("connects chat escalation, approval routing, skill resolution, and integration eval", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const approvals = new ApprovalService(connection.db);
const chat = new ChatService(connection.db);
const skills = new SkillService(connection.db, join(tmpdir(), `agentcompany-new-artifacts-${randomUUID()}`));
const sdlc = new SdlcService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let employeeId = "";
let agentId = "";
let approvalId = "";
let skillId = "";
let revisionId = "";
let suiteId = "";
let evalRunId = "";
connection.transaction(() => {
orgId = createOrganization(organization);
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
agentId = organization.createAgent({
orgId,
name: "Ada",
role: "Release reviewer",
department: "Engineering",
modelProviderId: null,
modelId: null,
budgetCents: 10000,
managerAgentId: null,
});
const spaceId = chat.createSpace({
orgId,
spaceType: "project",
name: "release",
topic: "Release coordination",
visibility: "workspace",
});
chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/escalate review release acceptance package",
attachmentFileIds: [],
});
approvalId = (connection.db.prepare("select id from approvals where org_id = ? and kind = ?")
.get(orgId, "chat_escalation") as { id: string }).id;
approvals.decide({ orgId, approvalId, state: APPROVAL_STATE.APPROVED, decidedByEmployeeId: employeeId });
revisionId = skills.importDirectory({ orgId, directoryPath: createSkillDirectory(), versionSemver: "1.0.0" });
skillId = (connection.db.prepare("select skill_id from skill_revisions where id = ?").get(revisionId) as { skill_id: string }).skill_id;
skills.bind({ orgId, agentId, skillId, revisionId });
skills.resolveRuntimeSnapshot({ orgId, agentId, runId: `run_${randomUUID().replaceAll("-", "")}` });
suiteId = (connection.db.prepare("select id from eval_suites where org_id = ? and key = ?")
.get(orgId, EVAL_SUITE_KEY.INTEGRATION) as { id: string }).id;
evalRunId = sdlc.runEvalSuite({ orgId, suiteId });
});
const state = snapshot.load();
const evalRun = state.evalRuns.find((run) => run.id === evalRunId);
const snapshotRecord = state.skillRuntimeSnapshots[0];
if (!evalRun) throw new Error("Integration eval run was not created.");
if (!snapshotRecord) throw new Error("Skill runtime snapshot was not created.");
expect(state.approvals.find((approval) => approval.id === approvalId)?.state).toBe(APPROVAL_STATE.APPROVED);
expect(state.approvalAssignments.some((assignment) => assignment.approvalId === approvalId)).toBe(true);
expect(snapshotRecord.compiledPrompt).toContain("Release Gate");
expect(evalRun).toMatchObject({ state: SDLC_RUN_STATE.PASSED, failedCount: 0 });
expect(state.operationalEvents.some((event) => event.signalKey === "skill_resolve_success")).toBe(true);
connection.close();
});
});
@@ -1,289 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { createBudgetEnforcementService } from "../../src/main/budget-enforcement-service";
import { createRunDispatch } from "../../src/main/orchestration/run-dispatch";
import { createTaskRunner } from "../../src/main/orchestration/task-runner";
import { resolveModels } from "../../src/main/model-resolver";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
const logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
function createOrchestrationHarness(db: AppDatabase) {
const budgetService = createBudgetEnforcementService({
db,
logger,
notify: vi.fn(),
publishDomainChanged: vi.fn(),
});
const deps = {
db,
logger,
publishDomainChanged: vi.fn(),
emitEvent: vi.fn(),
notify: vi.fn(),
createRunToken: () => "test-token",
getApiPort: () => 3210,
dispatchAutomation: vi.fn(),
hasWorkerReady: () => true,
enqueueWorkerRun: vi.fn(),
isQuitting: () => false,
listRuntimeSkillEntries: () => [],
budgetService,
checkpointEngine: { shouldPauseHeartbeat: () => false },
};
const runDispatch = createRunDispatch(deps);
const taskRunner = createTaskRunner(
deps,
runDispatch.resolveWorkspace,
runDispatch.getRunEligibilityFailure,
runDispatch.dispatchRunToWorker,
(filter) => db.core.runs.hasActiveRun(filter),
);
return { deps, taskRunner };
}
describe("connector model enforcement", () => {
it("auto picker excludes disabled connector models before run eligibility validation", async () => {
const profileDir = await createTempDir("agentcompany-auto-model-pool-");
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
db.core.connectors.updateConnectorHealth({
id: "zai",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Auto Model Pool Co",
description: "Routes around disabled connector model pools",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "zai",
workspaceId,
model: "glm-5.1",
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const architectId = db.core.agents.saveAgent({
companyId,
name: "Architect",
role: "Architect",
title: "Principal Architect",
status: "idle",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: "gpt-5.4",
modelStrategy: "auto",
capabilities: "Plan technical architecture",
budgetMonthlyUsd: 250,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Plan architecture",
description: "Needs a hard reasoning model.",
assigneeAgentId: architectId,
workspaceId,
priority: "high",
status: "todo",
});
db.core.settings.setDisabledModels(
companyId,
"openai-codex",
resolveModels("openai-codex").map((model) => ({ id: model.id, label: model.name })),
);
const { deps, taskRunner } = createOrchestrationHarness(db);
const result = taskRunner.queueTaskRun(taskId, "manual", "board");
expect(result.ok).toBe(true);
expect(deps.enqueueWorkerRun).toHaveBeenCalledTimes(1);
expect(deps.enqueueWorkerRun.mock.calls[0]?.[0]?.connectorId).toBe("zai");
expect(deps.enqueueWorkerRun.mock.calls[0]?.[0]?.model).toMatch(/^glm-/);
db.close();
});
it("blocks queueTaskRun when an agent keeps a disabled explicit model", async () => {
const profileDir = await createTempDir("agentcompany-model-enforcement-");
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Runtime Enforcement Co",
description: "Blocks runs when agent models are disabled",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: "gpt-5.4",
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Run with invalid model",
description: "This task should be blocked because the agent model is disabled.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
db.core.settings.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
const { taskRunner } = createOrchestrationHarness(db);
const result = taskRunner.queueTaskRun(taskId, "manual", "board");
expect(result.ok).toBe(false);
if (result.ok) {
throw new Error("Expected queueTaskRun to reject the disabled model.");
}
expect(result.error.code).toBe("CONNECTOR_MODEL_NOT_ALLOWED");
expect(db.projection.snapshot.listSnapshot().runs).toHaveLength(0);
db.close();
});
it("fails recovered queued runs when the persisted agent model becomes invalid", async () => {
const profileDir = await createTempDir("agentcompany-model-recovery-");
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Recovered Run Co",
description: "Rejects invalid queued runs during recovery",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: "gpt-5.4",
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Recover queued run",
description: "This queued run should fail on recovery because the model is disabled.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
const checkoutSuccess = db.core.tasks.checkoutTask(taskId, run.id);
expect(checkoutSuccess).toBe(true);
db.core.settings.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
const { taskRunner } = createOrchestrationHarness(db);
taskRunner.resumeQueuedRuns();
const recoveredRun = db.projection.snapshot.listSnapshot().runs.find((entry) => entry.id === run.id);
expect(recoveredRun?.status).toBe("failed");
expect(recoveredRun?.errorMessage).toContain("Connector model is disabled or invalid");
db.close();
});
});
-506
View File
@@ -1,506 +0,0 @@
/**
* Cross-subsystem integration tests exercising ALL 5 major subsystems (D-04):
* 1. Task Quality Gates (validateTaskCreation, checkCapabilityMatch)
* 2. CAS Checkout (checkoutTask)
* 3. Budget Enforcement (evaluateBudgetGate)
* 4. Session Management (accumulateTokenUsage, evaluateRotation, generateHandoff)
* 5. Run Completion (finishRun with cost entry creation)
*
* Uses a real AppDatabase with temporary directories (no mocks except electron safeStorage).
*/
import { join } from "node:path";
import { tmpdir } from "node:os";
import { mkdtemp, rm } from "node:fs/promises";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
// Mock electron safeStorage — same pattern as process-recovery.spec.ts
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase } from "../../src/main/database";
import { createBudgetEnforcementService } from "../../src/main/budget-enforcement-service";
import { createSessionManagementService } from "../../src/main/session-management-service";
import { validateTaskCreation, checkCapabilityMatch as _checkCapabilityMatch } from "../../src/main/task-quality-gates";
import type { ConnectorId } from "../../src/shared/types";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
interface ScaffoldResult {
companyId: string;
workspaceId: string;
agentId: string;
goalId: string;
projectId: string;
connectorId: ConnectorId;
policyId: string;
}
const mockLogger = {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
/**
* Creates a full entity scaffold with a budget policy:
* company, workspace, connector (gemini-cli), agent, goal, project, budget policy.
*/
function scaffold(db: AppDatabase, budgetAmountCents = 500): ScaffoldResult {
const connectorId: ConnectorId = "gemini-cli";
db.core.connectors.saveConnector({
id: connectorId,
model: "gemini-2.5-pro",
envBindingText: "",
notes: "Non-exempt connector for session rotation tests",
});
const companyId = db.core.company.saveCompany({
name: "Pipeline Test Corp",
description: "Cross-subsystem pipeline integration tests",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary Workspace",
localPath: "/tmp/pipeline-test",
repoUrl: "https://github.com/example/pipeline-test",
repoRef: "main",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "PipelineAgent",
role: "IC",
title: "Full-Stack Engineer",
status: "idle",
reportsTo: null,
connectorId,
workspaceId,
model: null,
capabilities: "Full-stack development, authentication, login page, testing",
budgetMonthlyUsd: 100,
});
const goalId = db.core.entity.saveGoal({
companyId,
title: "Implement Authentication",
description: "Build full authentication system",
ownerAgentId: agentId,
status: "active",
});
const projectId = db.core.entity.saveProject({
companyId,
goalId,
name: "Auth Project",
description: "Authentication module",
leadAgentId: agentId,
status: "active",
targetDate: null,
});
// Budget policy: company-level with hardStop
const policyId = db.core.budget.saveBudgetPolicy({
companyId,
scopeType: "company",
scopeId: companyId,
amount: budgetAmountCents,
hardStopEnabled: true,
isActive: true,
});
return { companyId, workspaceId, agentId, goalId, projectId, connectorId, policyId };
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
let tempDir: string;
let db: AppDatabase;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-pipeline-"));
db = new AppDatabase(tempDir);
db.init();
vi.clearAllMocks();
});
afterEach(async () => {
db?.close();
await rm(tempDir, { recursive: true, force: true });
});
// =========================================================================
// Cross-Subsystem Pipeline (D-04)
// =========================================================================
describe("Cross-Subsystem Pipeline (D-04)", () => {
it("full pipeline: quality gate -> checkout -> budget -> session -> completion", () => {
const s = scaffold(db);
const budgetService = createBudgetEnforcementService({
db,
logger: mockLogger,
notify: vi.fn(),
publishDomainChanged: vi.fn(),
});
const sessionService = createSessionManagementService({
db,
logger: mockLogger,
});
// 1. Quality gate: validate task creation
const validation = validateTaskCreation(
{
title: "Build login page",
description:
"Implement the login page with email and password authentication. Done when: form renders, validation works, and API called on submit.",
},
);
expect(validation.valid).toBe(true);
expect(validation.errors).toHaveLength(0);
// Create the task in the DB
const taskId = db.core.tasks.saveTask({
companyId: s.companyId,
title: "Build login page",
description:
"Implement the login page with email and password authentication. Done when: form renders, validation works, and API called on submit.",
assigneeAgentId: s.agentId,
workspaceId: s.workspaceId,
priority: "high",
status: "todo",
});
// 2. CAS checkout: create run and checkout task
const { id: runId } = db.core.runs.createRun({
companyId: s.companyId,
taskId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
const checkoutResult = db.core.tasks.checkoutTask(taskId, runId);
expect(checkoutResult).toBe(true);
// 3. Budget gate: verify run is allowed
const budgetResult = budgetService.evaluateBudgetGate(
s.companyId,
s.agentId,
s.projectId,
);
expect(budgetResult.allowed).toBe(true);
expect(budgetResult.blockedBy).toHaveLength(0);
// 4. Execute run
db.core.runs.markRunStatus(runId, "running", "Starting");
db.core.runs.recordCostEntry({ runId, companyId: s.companyId, agentId: s.agentId, taskId, connectorId: "gemini-cli", amountUsd: 0.05, unattributed: false });
db.core.runs.finishRun({
runId,
status: "succeeded",
summary: "Login page built with form validation",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-2.5-pro",
unattributedCost: false,
});
// 5. Session management: create session and track usage
const sessionId = db.core.tasks.createNewSession({
companyId: s.companyId,
agentId: s.agentId,
adapterType: "gemini-cli",
});
sessionService.accumulateTokenUsage(sessionId, 5000, 2000, runId);
// 6. Verify final state
const run = db.core.runs.getRun(runId);
expect(run.status).toBe("succeeded");
const task = db.core.entity.getTask(taskId);
expect(task.costUsd).toBeGreaterThan(0);
const session = db.core.tasks.getSessionById(sessionId);
expect(session).not.toBeNull();
expect(session!.totalInputTokens).toBe(5000);
expect(session!.totalOutputTokens).toBe(2000);
expect(session!.runCount).toBe(1);
});
it("quality gate rejects short description before pipeline starts", () => {
const result = validateTaskCreation(
{ title: "Fix bug", description: "Short." },
);
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThan(0);
expect(result.errors[0]).toContain("at least 50 characters");
});
it("CAS prevents double checkout within pipeline", () => {
const s = scaffold(db);
const taskId = db.core.tasks.saveTask({
companyId: s.companyId,
title: "Concurrent Task",
description:
"A task that two agents will try to check out concurrently. Done when: only one succeeds.",
assigneeAgentId: null,
workspaceId: s.workspaceId,
priority: "medium",
status: "todo",
});
// First agent checks out
const { id: runId1 } = db.core.runs.createRun({
companyId: s.companyId,
taskId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
expect(db.core.tasks.checkoutTask(taskId, runId1)).toBe(true);
// Second agent creates a run but cannot check out
const agent2Id = db.core.agents.saveAgent({
companyId: s.companyId,
name: "Agent2",
role: "IC",
title: "Engineer 2",
status: "idle",
reportsTo: null,
connectorId: s.connectorId,
workspaceId: s.workspaceId,
model: null,
capabilities: "Backend development",
budgetMonthlyUsd: 100,
});
const { id: runId2 } = db.core.runs.createRun({
companyId: s.companyId,
taskId,
agentId: agent2Id,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
expect(db.core.tasks.checkoutTask(taskId, runId2)).toBe(false);
});
it("budget gate blocks run when policy exhausted", () => {
// Create scaffold with $1 budget (100 cents)
const s = scaffold(db, 100);
const budgetService = createBudgetEnforcementService({
db,
logger: mockLogger,
notify: vi.fn(),
publishDomainChanged: vi.fn(),
});
// Create and complete a run that exceeds the budget
const taskId = db.core.tasks.saveTask({
companyId: s.companyId,
title: "Expensive Task",
description:
"A task that costs more than the budget allows. Done when: budget gate correctly blocks.",
assigneeAgentId: s.agentId,
workspaceId: s.workspaceId,
priority: "medium",
status: "todo",
});
const { id: runId } = db.core.runs.createRun({
companyId: s.companyId,
taskId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
db.core.tasks.checkoutTask(taskId, runId);
db.core.runs.markRunStatus(runId, "running", "Starting");
db.core.runs.recordCostEntry({ runId, companyId: s.companyId, agentId: s.agentId, taskId, connectorId: s.connectorId, amountUsd: 1.5, unattributed: false });
db.core.runs.finishRun({
runId,
status: "succeeded",
summary: "Done",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-2.5-pro",
unattributedCost: false,
});
// Budget gate should now block
const result = budgetService.evaluateBudgetGate(
s.companyId,
s.agentId,
s.projectId,
);
expect(result.allowed).toBe(false);
expect(result.blockedBy.length).toBeGreaterThan(0);
expect(result.blockedBy[0].scopeType).toBe("company");
});
it("session usage accumulates across multiple runs", () => {
const s = scaffold(db);
const sessionService = createSessionManagementService({
db,
logger: mockLogger,
});
const taskId = db.core.tasks.saveTask({
companyId: s.companyId,
title: "Multi-Run Task",
description:
"A task that spans multiple runs to test session accumulation. Done when: token counts sum correctly.",
assigneeAgentId: s.agentId,
workspaceId: s.workspaceId,
priority: "medium",
status: "todo",
});
// Create session
const sessionId = db.core.tasks.createNewSession({
companyId: s.companyId,
agentId: s.agentId,
adapterType: "gemini-cli",
});
// Run 1: create run and accumulate tokens
const { id: runId1 } = db.core.runs.createRun({
companyId: s.companyId,
taskId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
sessionService.accumulateTokenUsage(sessionId, 5000, 2000, runId1);
// Run 2: create another run and accumulate more tokens
const { id: runId2 } = db.core.runs.createRun({
companyId: s.companyId,
taskId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
sessionService.accumulateTokenUsage(sessionId, 3000, 1000, runId2);
// Verify accumulated session state
const session = db.core.tasks.getSessionById(sessionId);
expect(session).not.toBeNull();
expect(session!.runCount).toBe(2);
expect(session!.totalInputTokens).toBe(8000);
expect(session!.totalOutputTokens).toBe(3000);
});
it("full pipeline with budget enforcement end-to-end", () => {
// Budget = $5 (500 cents)
const s = scaffold(db, 500);
const budgetService = createBudgetEnforcementService({
db,
logger: mockLogger,
notify: vi.fn(),
publishDomainChanged: vi.fn(),
});
// --- First run: costs $3, budget allows ---
const taskId1 = db.core.tasks.saveTask({
companyId: s.companyId,
title: "First Pipeline Task",
description:
"A task in the budget pipeline that costs $3. Done when: run completes successfully under budget.",
assigneeAgentId: s.agentId,
workspaceId: s.workspaceId,
priority: "medium",
status: "todo",
});
const { id: runId1 } = db.core.runs.createRun({
companyId: s.companyId,
taskId: taskId1,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
db.core.tasks.checkoutTask(taskId1, runId1);
db.core.runs.markRunStatus(runId1, "running", "Starting");
db.core.runs.recordCostEntry({ runId: runId1, companyId: s.companyId, agentId: s.agentId, taskId: taskId1, connectorId: s.connectorId, amountUsd: 3.0, unattributed: false });
db.core.runs.finishRun({
runId: runId1,
status: "succeeded",
summary: "First task done",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-2.5-pro",
unattributedCost: false,
});
// Budget gate should still allow (spent $3 of $5)
const afterFirst = budgetService.evaluateBudgetGate(
s.companyId,
s.agentId,
s.projectId,
);
expect(afterFirst.allowed).toBe(true);
// --- Second run: costs $3, now total = $6 > $5 budget ---
const taskId2 = db.core.tasks.saveTask({
companyId: s.companyId,
title: "Second Pipeline Task",
description:
"A task that will push total spend over the budget limit. Done when: budget gate blocks third run.",
assigneeAgentId: s.agentId,
workspaceId: s.workspaceId,
priority: "medium",
status: "todo",
});
const { id: runId2 } = db.core.runs.createRun({
companyId: s.companyId,
taskId: taskId2,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
db.core.tasks.checkoutTask(taskId2, runId2);
db.core.runs.markRunStatus(runId2, "running", "Starting");
db.core.runs.recordCostEntry({ runId: runId2, companyId: s.companyId, agentId: s.agentId, taskId: taskId2, connectorId: s.connectorId, amountUsd: 3.0, unattributed: false });
db.core.runs.finishRun({
runId: runId2,
status: "succeeded",
summary: "Second task done",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-2.5-pro",
unattributedCost: false,
});
// Budget gate should now block (spent $6 of $5)
const afterSecond = budgetService.evaluateBudgetGate(
s.companyId,
s.agentId,
s.projectId,
);
expect(afterSecond.allowed).toBe(false);
expect(afterSecond.blockedBy.length).toBeGreaterThan(0);
});
});
-257
View File
@@ -1,257 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
beforeAll(() => {
process.env.AGENTCOMPANY_ALLOW_INSECURE_VAULT = "true";
});
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase } from "../../src/main/database";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("AppDatabase", () => {
it("persists core entities, secrets, run logs, and backup restore state", async () => {
const profileDir = await createTempDir("agentcompany-profile-");
const backupDir = await createTempDir("agentcompany-backup-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.core.company.saveCompany({
name: "Orbit Foundry",
description: "Test company",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "https://example.com/repo.git",
repoRef: "main",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Helios",
role: "CEO",
title: "Lead agent",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Operate the company",
budgetMonthlyUsd: 200,
});
const goalId = db.core.entity.saveGoal({
companyId,
title: "Ship the desktop app",
description: "Reach packaged desktop parity.",
ownerAgentId: agentId,
status: "active",
});
const projectId = db.core.entity.saveProject({
companyId,
goalId,
name: "Desktop rebuild",
description: "Rebuild as Electron desktop app.",
leadAgentId: agentId,
status: "in_progress",
});
const taskId = db.core.tasks.saveTask({
companyId,
projectId,
goalId,
title: "Validate connector state",
description: "Run a health check and capture output.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
db.core.entity.saveSecret({
companyId,
name: "vault-openai",
description: "Test key",
value: "sk-test-123",
});
db.core.connectors.saveConnector({
id: "openai-codex",
model: null,
envBindingText: "OPENAI_API_KEY=vault-openai",
notes: "Connector note",
});
expect(db.core.entity.resolveSecretEnv("OPENAI_API_KEY=vault-openai", companyId)).toEqual({
OPENAI_API_KEY: "sk-test-123",
});
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
await db.core.runs.appendRunLog(run.id, "stdout", "hello from run\n");
expect(db.core.runs.readRunLog(run.id)).toContain("hello from run");
db.core.runs.markRunStatus(run.id, "running", "Connector launched");
db.core.runs.finishRun({
runId: run.id,
status: "succeeded",
summary: "Completed successfully",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gpt-5-codex",
unattributedCost: false,
});
const snapshotBeforeBackup = db.projection.snapshot.listSnapshot();
expect(snapshotBeforeBackup.companies).toHaveLength(1);
expect(snapshotBeforeBackup.runs[0]?.status).toBe("succeeded");
expect(snapshotBeforeBackup.tasks[0]?.activeRunId).toBeNull();
await db.core.backup.exportBackup(backupDir);
db.core.company.saveCompany({
name: "Temporary company",
description: "Should disappear after restore",
status: "active",
});
expect(db.projection.snapshot.listSnapshot().companies).toHaveLength(2);
await db.core.backup.restoreFromBackup(backupDir);
const restoredSnapshot = db.projection.snapshot.listSnapshot();
expect(restoredSnapshot.companies).toHaveLength(1);
expect(restoredSnapshot.companies[0]?.name).toBe("Orbit Foundry");
expect(restoredSnapshot.runs[0]?.summary).toBe("Completed successfully");
db.close();
});
it("returns a renderer snapshot scoped to the current company while preserving global selectors", async () => {
const profileDir = await createTempDir("agentcompany-renderer-snapshot-");
const db = new AppDatabase(profileDir);
db.init();
const alphaCompanyId = db.core.company.saveCompany({
name: "Alpha",
description: "Alpha company",
status: "active",
});
const betaCompanyId = db.core.company.saveCompany({
name: "Beta",
description: "Beta company",
status: "active",
});
const alphaWorkspaceId = db.core.workspaces.saveWorkspace({
companyId: alphaCompanyId,
name: "Alpha Workspace",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const betaWorkspaceId = db.core.workspaces.saveWorkspace({
companyId: betaCompanyId,
name: "Beta Workspace",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const alphaAgentId = db.core.agents.saveAgent({
companyId: alphaCompanyId,
name: "Alpha CEO",
role: "CEO",
title: "",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId: alphaWorkspaceId,
model: null,
capabilities: "Lead alpha",
budgetMonthlyUsd: 100,
});
db.core.agents.saveAgent({
companyId: betaCompanyId,
name: "Beta CEO",
role: "CEO",
title: "",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId: betaWorkspaceId,
model: null,
capabilities: "Lead beta",
budgetMonthlyUsd: 100,
});
db.core.tasks.saveTask({
companyId: alphaCompanyId,
title: "Alpha task",
description: "Alpha work",
assigneeAgentId: alphaAgentId,
workspaceId: alphaWorkspaceId,
priority: "medium",
status: "todo",
});
db.core.tasks.saveTask({
companyId: betaCompanyId,
title: "Beta task",
description: "Beta work",
assigneeAgentId: null,
workspaceId: betaWorkspaceId,
priority: "medium",
status: "todo",
});
db.core.settings.setCurrentCompany(alphaCompanyId);
const rendererSnapshot = db.projection.snapshot.listRendererSnapshot();
expect(rendererSnapshot.currentCompanyId).toBe(alphaCompanyId);
expect(rendererSnapshot.companies).toHaveLength(2);
expect(rendererSnapshot.connectors.length).toBeGreaterThan(0);
expect(rendererSnapshot.workspaces.map((workspace) => workspace.companyId)).toEqual([alphaCompanyId]);
expect(rendererSnapshot.agents.every((agent) => agent.companyId === alphaCompanyId)).toBe(true);
expect(rendererSnapshot.tasks.every((task) => task.companyId === alphaCompanyId)).toBe(true);
db.close();
});
});
@@ -1,319 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createAgentToken, initializeAgentTokenSigningKey } from "../../src/main/agent-auth";
import { createApiServer } from "../../src/main/api-server";
import { AppDatabase } from "../../src/main/database";
import { processRunCompletion } from "../../src/main/automation/automation-run-completion";
import { createDocumentWithLifecycle } from "../../src/main/document-lifecycle";
import { createTestApiServerCallbacks } from "../support/api-server-callbacks";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
async function createHarness() {
const profileDir = await createTempDir("agentcompany-doc-provenance-");
initializeAgentTokenSigningKey(profileDir);
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Document Provenance Co",
description: "Tests document revision provenance through run completion",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Research Lead",
role: "Researcher",
title: "Research Lead",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Research and synthesis",
budgetMonthlyUsd: 180,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Refresh the market report",
description: "Update the report with new findings and submit it for review.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "in_progress",
});
db.core.tasks.setTaskType(taskId, "research");
const existingDocumentId = db.core.documents.saveDocument({
companyId,
type: "knowledge_article",
title: "Market report",
content: "Initial report body",
authorAgentId: agentId,
projectId: null,
goalId: null,
status: "draft",
});
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
const token = createAgentToken(agentId, companyId, run.id);
const silentLogger = { warn: () => {}, error: () => {} };
const noopLifecycleProcessor = { drainForDocument: () => {} };
const server = createApiServer(db, createTestApiServerCallbacks({
createDocument: (input) =>
createDocumentWithLifecycle(
{
db,
threadIntegration: {
onDocumentCreated: () => {},
} as never,
checkpointEngine: { onDocumentDrafted: () => null } as never,
lifecycleProcessor: noopLifecycleProcessor,
publishDomainChanged: () => {},
logger: silentLogger,
},
input,
),
}));
const port = await server.start();
return {
db,
server,
run,
companyId,
taskId,
existingDocumentId,
baseUrl: `http://127.0.0.1:${port}`,
headers: {
Authorization: `Bearer ${token}`,
"X-Agent-Company-Run-Id": run.id,
"Content-Type": "application/json; charset=utf-8",
},
};
}
describe("document run provenance", () => {
it("keeps sourceRunId on revised documents so run completion still counts them as deliverables", async () => {
const harness = await createHarness();
try {
const patchResponse = await fetch(
`${harness.baseUrl}/api/companies/${harness.companyId}/documents/${harness.existingDocumentId}`,
{
method: "PATCH",
headers: harness.headers,
body: JSON.stringify({
content: "Updated report with new evidence and recommendations.",
changeSummary: "Add latest market evidence",
}),
},
);
expect(patchResponse.status).toBe(200);
const updatedDocument = harness.db.queryAll<{ sourceRunId: string | null }>(
"select source_run_id as sourceRunId from documents where id = ? limit 1",
harness.existingDocumentId,
)[0];
expect(updatedDocument?.sourceRunId).toBe(harness.run.id);
const persistedRun = harness.db.projection.snapshot.listSnapshot().runs.find((entry) => entry.id === harness.run.id);
if (!persistedRun) {
throw new Error("Expected persisted run record for provenance test.");
}
processRunCompletion({
db: harness.db,
logger: { info: () => {}, warn: () => {}, error: () => {} },
publishDomainChanged: () => {},
notify: vi.fn(),
wakeAgentIfPossible: vi.fn().mockReturnValue(null),
}, {
dispatchAutomation: vi.fn(),
handleTaskStatusChange: vi.fn(),
}, persistedRun, {
status: "succeeded",
summary: "Updated the market report",
errorMessage: null,
}, () => false);
// ai_ceo + top-level CEO without `reviewDeliverables` advances
// straight to `done` — there is no reviewer in that configuration
// so the prior `in_review` state was a dead letter.
const task = harness.db.core.entity.getTask(harness.taskId);
expect(task.status).toBe("done");
} finally {
harness.server.stop();
harness.db.close();
}
});
it("increments document version across multiple edits and rejects invalid reviewers without partial mutation", async () => {
const harness = await createHarness();
try {
const firstPatch = await fetch(
`${harness.baseUrl}/api/companies/${harness.companyId}/documents/${harness.existingDocumentId}`,
{
method: "PATCH",
headers: harness.headers,
body: JSON.stringify({
content: "First revision of the report",
changeSummary: "Initial revision",
}),
},
);
expect(firstPatch.status).toBe(200);
const revisionState = harness.db.queryAll<{ version: number; latestRevisionId: string | null }>(
"select version, latest_revision_id as latestRevisionId from documents where id = ? limit 1",
harness.existingDocumentId,
)[0];
expect(revisionState?.version).toBe(2);
expect(revisionState?.latestRevisionId).not.toBeNull();
const invalidReviewerPatch = await fetch(
`${harness.baseUrl}/api/companies/${harness.companyId}/documents/${harness.existingDocumentId}`,
{
method: "PATCH",
headers: harness.headers,
body: JSON.stringify({
reviewerAgentId: "00000000-0000-0000-0000-000000000000",
content: "This write should be rejected before any mutation occurs",
changeSummary: "Invalid reviewer should block this patch",
}),
},
);
expect(invalidReviewerPatch.status).toBe(400);
const rejectedState = harness.db.queryAll<{ version: number; content: string; revisionCount: number }>(
`select d.version as version, d.content as content,
(select count(*) from document_revisions where document_id = d.id and company_id = d.company_id) as revisionCount
from documents d where d.id = ? limit 1`,
harness.existingDocumentId,
)[0];
expect(rejectedState?.version).toBe(2);
expect(rejectedState?.content).toBe("First revision of the report");
expect(rejectedState?.revisionCount).toBe(1);
const secondPatch = await fetch(
`${harness.baseUrl}/api/companies/${harness.companyId}/documents/${harness.existingDocumentId}`,
{
method: "PATCH",
headers: harness.headers,
body: JSON.stringify({
content: "Second revision of the report",
changeSummary: "Follow-up revision",
}),
},
);
expect(secondPatch.status).toBe(200);
const finalState = harness.db.queryAll<{ version: number; revisionCount: number }>(
`select d.version as version,
(select count(*) from document_revisions where document_id = d.id and company_id = d.company_id) as revisionCount
from documents d where d.id = ? limit 1`,
harness.existingDocumentId,
)[0];
expect(finalState?.version).toBe(3);
expect(finalState?.revisionCount).toBe(2);
} finally {
harness.server.stop();
harness.db.close();
}
});
it("does not stamp sourceRunId for metadata-only document patches", async () => {
const harness = await createHarness();
try {
const metadataPatch = await fetch(
`${harness.baseUrl}/api/companies/${harness.companyId}/documents/${harness.existingDocumentId}`,
{
method: "PATCH",
headers: harness.headers,
body: JSON.stringify({
title: "Renamed market report",
}),
},
);
expect(metadataPatch.status).toBe(200);
const updatedDocument = harness.db.queryAll<{ sourceRunId: string | null; title: string }>(
"select source_run_id as sourceRunId, title from documents where id = ? limit 1",
harness.existingDocumentId,
)[0];
expect(updatedDocument?.sourceRunId).toBeNull();
expect(updatedDocument?.title).toBe("Renamed market report");
} finally {
harness.server.stop();
harness.db.close();
}
});
it("creates deliverable review approvals that stay linked to the originating task", async () => {
const harness = await createHarness();
try {
harness.db.core.company.saveCompany({
id: harness.companyId,
name: "Document Provenance Co",
description: "Tests document review task linkage",
status: "active",
reviewDeliverables: true,
});
const createResponse = await fetch(
`${harness.baseUrl}/api/companies/${harness.companyId}/documents`,
{
method: "POST",
headers: harness.headers,
body: JSON.stringify({
type: "prd",
title: "Run-linked PRD",
content: "Initial draft that should trigger deliverable review.",
status: "draft",
}),
},
);
expect(createResponse.status).toBe(201);
const approvals = harness.db.projection.snapshot.listSnapshot().approvals.filter((approval) => approval.type === "deliverable_review");
expect(approvals).toHaveLength(1);
expect(approvals[0]?.relatedTaskId).toBe(harness.taskId);
} finally {
harness.server.stop();
harness.db.close();
}
});
});
@@ -1,327 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { createOnboardingService } from "../../src/main/onboarding-service";
import { buildRunPayload } from "../../src/main/orchestration/turn-context";
import { getConnectorDefinition } from "../../src/main/connectors";
import { getModels } from "@mariozechner/pi-ai";
function listProviderModels(provider: string): Array<{ id: string; label: string }> {
try {
const models = getModels(provider as never);
return models.map((model) => ({ id: model.id, label: model.name ?? model.id }));
} catch {
return [];
}
}
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
function createNoopLogger() {
return {
info: () => {},
warn: () => {},
error: () => {},
};
}
function createBootstrapHarness(db: AppDatabase) {
const noop = () => {};
return createOnboardingService({
db,
seedDefaultAutomationRules: noop,
publishDomainChanged: noop,
createTaskRunForDeferredDispatch: () => ({
ok: true,
data: { runId: "run-1", dispatchAfterCommit: noop },
}),
});
}
function buildKickoffPayload(db: AppDatabase, summary: { companyId: string; leadAgentId: string | null; taskId: string | null; workspaceId: string | null }) {
if (!summary.leadAgentId || !summary.taskId) {
throw new Error("Missing lead agent or task in onboarding summary.");
}
const agent = db.core.entity.getAgent(summary.leadAgentId);
const task = db.core.entity.getTask(summary.taskId);
const connector = db.core.connectors.getConnector(agent.connectorId);
const workspace = summary.workspaceId ? db.core.workspaces.getWorkspace(summary.workspaceId) : null;
const company = db.projection.snapshot.listSnapshot().companies.find((entry) => entry.id === summary.companyId);
if (!company) {
throw new Error(`Company ${summary.companyId} not found in snapshot.`);
}
return buildRunPayload({
runId: "run-1",
companyId: summary.companyId,
agent,
task,
connector,
workspace,
workspaceMode: "exclusive",
companyName: company.name,
companyDescription: company.description,
snapshot: db.projection.snapshot.listSnapshot(),
trigger: "bootstrap",
}, {
db,
logger: createNoopLogger(),
getApiPort: () => 3210,
createRunToken: () => "test-token",
listRuntimeSkillEntries: () => [],
});
}
describe("onboarding model pool integration", () => {
it("persists an explicit CEO model and excludes disabled native models from runtime prompt context", async () => {
const profileDir = await createTempDir("agentcompany-onboarding-models-");
const workspaceDir = await createTempDir("agentcompany-onboarding-workspace-");
const db = new AppDatabase(profileDir);
db.init();
const codexDefinition = getConnectorDefinition("openai-codex");
const enabledModel = listProviderModels(codexDefinition.provider)[0]?.id;
const disabledModel = listProviderModels(codexDefinition.provider)[1]?.id;
if (!enabledModel || !disabledModel) {
throw new Error("openai-codex requires at least two models for this integration test.");
}
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const summary = createBootstrapHarness(db).bootstrapOnboarding({
companyName: "Model Governance Co",
companyDescription: "Tests onboarding model governance",
workspacePath: workspaceDir,
autoApproveHires: true,
reviewDeliverables: false,
executionMode: "ai_ceo",
goalTitle: "Launch runtime-safe company",
goalDescription: "Ensure onboarding connector models flow into runtime decisions.",
connectorId: "openai-codex",
connectorModel: enabledModel,
connectorPoolSelections: [
{ connectorId: "openai-codex", disabledModels: [{ id: disabledModel, label: disabledModel }] },
],
});
if (!summary.leadAgentId) {
throw new Error("Expected lead agent id from onboarding bootstrap.");
}
const agent = db.core.entity.getAgent(summary.leadAgentId);
expect(agent.connectorId).toBe("openai-codex");
expect(agent.model).toBe(enabledModel);
expect(db.core.settings.getDisabledModels(summary.companyId, "openai-codex")).toEqual([{ id: disabledModel, label: disabledModel }]);
expect(db.core.settings.getCurrentCompanyId()).toBe(summary.companyId);
const payload = buildKickoffPayload(db, summary);
expect(payload.model).toBe(enabledModel);
expect(payload.systemPrompt).not.toBeNull();
const systemPrompt = payload.systemPrompt ?? "";
expect(systemPrompt).toContain("Enabled models:");
expect(systemPrompt).toContain(enabledModel);
const codexConnectorLine = systemPrompt
.split("\n")
.find((line) => line.includes("ChatGPT Plus/Pro (Codex)"));
expect(codexConnectorLine).toBeDefined();
expect(codexConnectorLine).not.toContain(`, ${disabledModel},`);
expect(codexConnectorLine).not.toContain(` ${disabledModel}.`);
db.close();
});
it("excludes disabled pi-ai models from anthropic connector runtime prompt context", async () => {
const profileDir = await createTempDir("agentcompany-onboarding-compat-");
const workspaceDir = await createTempDir("agentcompany-onboarding-compat-workspace-");
const db = new AppDatabase(profileDir);
db.init();
const anthropicDefinition = getConnectorDefinition("anthropic");
const anthropicModels = listProviderModels(anthropicDefinition.provider);
const enabledModel = anthropicModels[0]?.id;
const disabledModel = anthropicModels[1]?.id;
if (!enabledModel || !disabledModel) {
throw new Error("anthropic provider must expose at least two models from pi-ai.");
}
db.core.connectors.updateConnectorHealth({
id: "anthropic",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const summary = createBootstrapHarness(db).bootstrapOnboarding({
companyName: "Anthropic Governance Co",
companyDescription: "Tests anthropic model governance",
workspacePath: workspaceDir,
autoApproveHires: true,
reviewDeliverables: false,
executionMode: "ai_ceo",
goalTitle: "Launch anthropic company",
goalDescription: "Ensure anthropic model pools survive onboarding and runtime prompt assembly.",
connectorId: "anthropic",
connectorModel: enabledModel,
connectorPoolSelections: [
{ connectorId: "anthropic", disabledModels: [{ id: disabledModel, label: disabledModel }] },
],
});
const payload = buildKickoffPayload(db, summary);
expect(payload.systemPrompt).not.toBeNull();
const systemPrompt = payload.systemPrompt ?? "";
expect(systemPrompt).toContain("Enabled models:");
expect(systemPrompt).toContain(enabledModel);
const anthropicLine = systemPrompt
.split("\n")
.find((line) => line.includes(anthropicDefinition.label));
expect(anthropicLine).toBeDefined();
expect(anthropicLine).not.toContain(`, ${disabledModel},`);
expect(anthropicLine).not.toContain(` ${disabledModel}.`);
db.close();
});
it("stores a null CEO model when onboarding leaves the connector model at default", async () => {
const profileDir = await createTempDir("agentcompany-onboarding-default-");
const workspaceDir = await createTempDir("agentcompany-onboarding-default-workspace-");
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const summary = createBootstrapHarness(db).bootstrapOnboarding({
companyName: "Default Model Co",
companyDescription: "Tests default connector model behavior",
workspacePath: workspaceDir,
autoApproveHires: true,
reviewDeliverables: false,
executionMode: "ai_ceo",
goalTitle: "Launch default-mode company",
goalDescription: "Leave the CEO model unset so runtime falls back to connector defaults.",
connectorId: "openai-codex",
connectorModel: "",
});
if (!summary.leadAgentId) {
throw new Error("Expected lead agent id from onboarding bootstrap.");
}
const agent = db.core.entity.getAgent(summary.leadAgentId);
expect(agent.model).toBeNull();
const payload = buildKickoffPayload(db, summary);
const defaultModel = getConnectorDefinition("openai-codex").defaultModel;
expect(payload.model).toBe(defaultModel);
db.close();
});
it("reroutes default runtime selection to the first enabled model when the connector default is disabled", async () => {
const profileDir = await createTempDir("agentcompany-onboarding-reroute-");
const workspaceDir = await createTempDir("agentcompany-onboarding-reroute-workspace-");
const db = new AppDatabase(profileDir);
db.init();
const codexDefinition = getConnectorDefinition("openai-codex");
const defaultModel = codexDefinition.defaultModel;
const fallbackModel = listProviderModels(codexDefinition.provider).find((model) => model.id !== defaultModel)?.id;
if (!defaultModel || !fallbackModel) {
throw new Error("openai-codex requires a default and fallback model for reroute testing.");
}
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const summary = createBootstrapHarness(db).bootstrapOnboarding({
companyName: "Default Reroute Co",
companyDescription: "Tests runtime reroute when the connector default is disabled",
workspacePath: workspaceDir,
autoApproveHires: true,
reviewDeliverables: false,
executionMode: "ai_ceo",
goalTitle: "Launch rerouted company",
goalDescription: "Ensure runtime picks the first enabled model when connector default is disabled.",
connectorId: "openai-codex",
connectorModel: "",
connectorPoolSelections: [
{ connectorId: "openai-codex", disabledModels: [{ id: defaultModel, label: defaultModel }] },
],
});
const payload = buildKickoffPayload(db, summary);
expect(payload.model).toBe(fallbackModel);
db.close();
});
it("rejects an explicit onboarding model that is disabled for the selected connector", async () => {
const profileDir = await createTempDir("agentcompany-onboarding-reject-");
const workspaceDir = await createTempDir("agentcompany-onboarding-reject-workspace-");
const db = new AppDatabase(profileDir);
db.init();
const codexDefinition = getConnectorDefinition("openai-codex");
const disabledModel = listProviderModels(codexDefinition.provider)[0]?.id;
if (!disabledModel) {
throw new Error("openai-codex requires a model to validate onboarding rejection.");
}
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
expect(() => createBootstrapHarness(db).bootstrapOnboarding({
companyName: "Rejected Model Co",
companyDescription: "Tests invalid onboarding model rejection",
workspacePath: workspaceDir,
autoApproveHires: true,
reviewDeliverables: false,
executionMode: "ai_ceo",
goalTitle: "Reject disabled model bootstrap",
goalDescription: "Invalid explicit models should fail before the CEO is created.",
connectorId: "openai-codex",
connectorModel: disabledModel,
connectorPoolSelections: [
{ connectorId: "openai-codex", disabledModels: [{ id: disabledModel, label: disabledModel }] },
],
})).toThrow(`${codexDefinition.label} does not allow model "${disabledModel}".`);
db.close();
});
});
@@ -1,121 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { getModels } from "@mariozechner/pi-ai";
import { AppDatabase } from "../../src/main/database";
import { getConnectorDefinition } from "../../src/main/connectors";
import { createOnboardingService } from "../../src/main/onboarding-service";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
function markConnectorReady(db: AppDatabase, connectorId: "openai-codex") {
db.core.connectors.updateConnectorHealth({
id: connectorId,
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
}
function buildBootstrapInput(workspacePath: string, overrides: Partial<Parameters<ReturnType<typeof createOnboardingService>["bootstrapOnboarding"]>[0]> = {}) {
return {
companyName: "Atomic Onboarding Co",
companyDescription: "Verifies onboarding transaction boundaries",
workspacePath,
autoApproveHires: true,
reviewDeliverables: false,
executionMode: "ai_ceo",
goalTitle: "Create one atomic company",
goalDescription: "Every onboarding write commits together.",
connectorId: "openai-codex" as const,
connectorModel: "",
...overrides,
};
}
function expectNoOnboardingState(db: AppDatabase) {
expect(db.projection.snapshot.listSnapshot().companies).toHaveLength(0);
expect(db.core.settings.getCurrentCompanyId()).toBeNull();
expect(db.queryAll("select id from agents")).toHaveLength(0);
expect(db.queryAll("select id from goals")).toHaveLength(0);
expect(db.queryAll("select id from projects")).toHaveLength(0);
expect(db.queryAll("select id from tasks")).toHaveLength(0);
expect(db.queryAll("select id from runs")).toHaveLength(0);
}
describe("onboarding transaction", () => {
it("rolls back every persisted onboarding row when model validation rejects the selected model", async () => {
const profileDir = await createTempDir("agentcompany-onboarding-tx-model-");
const workspaceDir = await createTempDir("agentcompany-onboarding-tx-workspace-");
const db = new AppDatabase(profileDir);
db.init();
markConnectorReady(db, "openai-codex");
const definition = getConnectorDefinition("openai-codex");
const disabledModel = getModels(definition.provider as never)[0]?.id;
if (!disabledModel) {
throw new Error("openai-codex requires a model for onboarding transaction coverage.");
}
const service = createOnboardingService({
db,
seedDefaultAutomationRules: () => {},
publishDomainChanged: () => {},
createTaskRunForDeferredDispatch: () => ({
ok: true,
data: { runId: "run-1", dispatchAfterCommit: () => {} },
}),
});
expect(() => service.bootstrapOnboarding(buildBootstrapInput(workspaceDir, {
connectorModel: disabledModel,
connectorPoolSelections: [
{ connectorId: "openai-codex", disabledModels: [{ id: disabledModel, label: disabledModel }] },
],
}))).toThrow(`${definition.label} does not allow model "${disabledModel}".`);
expectNoOnboardingState(db);
db.close();
});
it("rolls back every persisted onboarding row and skips external side effects when initial run creation fails", async () => {
const profileDir = await createTempDir("agentcompany-onboarding-tx-run-");
const workspaceDir = await createTempDir("agentcompany-onboarding-tx-run-workspace-");
const db = new AppDatabase(profileDir);
db.init();
markConnectorReady(db, "openai-codex");
let publishCount = 0;
let scanCount = 0;
const service = createOnboardingService({
db,
seedDefaultAutomationRules: () => {},
publishDomainChanged: () => {
publishCount += 1;
},
scanAndAssignSkills: async () => {
scanCount += 1;
},
createTaskRunForDeferredDispatch: () => ({ ok: false, error: { code: "RUN_CREATE_FAILED", message: "run insert failed" } }),
});
expect(() => service.bootstrapOnboarding(buildBootstrapInput(workspaceDir))).toThrow("run insert failed");
expectNoOnboardingState(db);
expect(publishCount).toBe(0);
expect(scanCount).toBe(0);
db.close();
});
});
+75
View File
@@ -0,0 +1,75 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OnboardingService } from "../../src/main/services/onboarding-service";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ProviderService } from "../../src/main/services/provider-service";
import { providerAdapters } from "../../src/main/services/provider-adapters";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { createId } from "../../src/shared/ids";
const testVault = {
encryptSecret: (secret: string) => Buffer.from(secret, "utf8"),
decryptSecret: (ciphertext: Buffer) => ciphertext.toString("utf8"),
};
function countFor(db: DatabaseConnection["db"], table: string, orgId: string): number {
return (db.prepare(`select count(*) as count from ${table} where org_id = ?`).get(orgId) as { count: number }).count;
}
describe("organization deletion", () => {
test("deleting an organization cascades through credentials, audit events, and social audit records", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-del-${randomUUID()}.db`));
const providers = new ProviderService(connection.db, providerAdapters, testVault);
const organization = new OrganizationService(connection.db);
const onboarding = new OnboardingService({ db: connection.db, saveProviderCredential: (i) => providers.saveCredential(i) });
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let survivorId = "";
connection.transaction(() => {
const summary = onboarding.bootstrap({
companyName: "OrbitOps AI", companyDescription: "AI-run operations company.", employeeName: "Yang",
workspacePath: "", executionMode: "ai_ceo", autoApproveHires: true, reviewDeliverables: false,
goalTitle: "Stand up a local service business.", goalDescription: "Win the first paying customers.",
providerKey: "openai", providerSecret: "sk-test-key", providerModel: "gpt-5.4",
});
orgId = summary.orgId;
survivorId = organization.createOrganization({
organizationName: "Keepers Inc", employeeName: "Dana", mission: "Stay.", goal: "Persist.",
});
organization.setActiveOrganization(orgId);
// A social audit record is immutable (no-delete trigger); it must still cascade on org teardown.
connection.db.prepare(`
insert into social_audit_records(
id, org_id, social_account_id, source_type, source_id, event_type, actor_type, actor_id,
policy_decision, evidence_file_id, metadata_json, created_at
) values (?, ?, null, 'post', ?, 'created', 'system', null, 'allow', null, '{}', datetime('now'))
`).run(createId("sar"), orgId, createId("src"));
});
// Precondition: every blocking row type is present for the org.
expect(countFor(connection.db, "audit_events", orgId)).toBeGreaterThan(0);
expect(countFor(connection.db, "provider_auth_profiles", orgId)).toBeGreaterThan(0);
expect(countFor(connection.db, "social_audit_records", orgId)).toBe(1);
expect(countFor(connection.db, "agents", orgId)).toBeGreaterThan(0);
// The delete must succeed (previously the FK restrict + immutability triggers rolled it back).
expect(() => connection.transaction(() => organization.deleteOrganization(orgId))).not.toThrow();
const state = snapshot.load();
expect(state.organizations.map((o) => o.id)).toEqual([survivorId]);
expect(state.activeOrgId).toBe(survivorId);
for (const table of ["audit_events", "provider_auth_profiles", "social_audit_records", "agents", "providers", "credentials", "tasks", "approvals"]) {
expect(countFor(connection.db, table, orgId)).toBe(0);
}
// The immutability trigger still blocks tampering with a living org's audit log.
const survivorAudit = connection.db.prepare("select id from audit_events where org_id = ? limit 1").get(survivorId) as { id: string };
expect(() => connection.db.prepare("delete from audit_events where id = ?").run(survivorAudit.id)).toThrow(/immutable/);
connection.close();
});
});
@@ -1,123 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeAll, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
beforeAll(() => {
// SecretVault gated for test contexts. Used by the credentials path
// touched by migration 28's downstream consumers.
process.env.AGENTCOMPANY_ALLOW_INSECURE_VAULT = "true";
});
const tempDirs: string[] = [];
async function freshProfile(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
/**
* Migration 27 (`session_entries`) and 28 (`credentials`) ship in the rebuild.
* These tests verify both migrations:
* - run cleanly on a fresh SQLite profile;
* - are idempotent on subsequent `init()` calls (existing-user upgrade);
* - leave their tables queryable.
*/
describe("pi-mono migrations 27 + 28", () => {
it("creates session_entries on first init and accepts inserts", async () => {
const dir = await freshProfile("agentcompany-migration-27-fresh-");
const db = new AppDatabase(dir);
db.init();
db.projection.sessionEntries.append({
taskSessionId: "test-session",
kind: "message",
role: "user",
payloadJson: JSON.stringify({ role: "user", content: "hello" }),
});
const rows = db.projection.sessionEntries.listActive("test-session");
expect(rows).toHaveLength(1);
expect(rows[0]?.kind).toBe("message");
db.close();
});
it("creates credentials on first init and accepts encrypted inserts", async () => {
const dir = await freshProfile("agentcompany-migration-28-fresh-");
const db = new AppDatabase(dir);
db.init();
const cipher = db.vault.encrypt(JSON.stringify({ source: "literal", value: "sk-test-1234567890" }));
db.core.credentials.upsert({
connectorId: "test-anthropic",
kind: "api_key",
backend: cipher.backend,
ciphertext: cipher.payload,
displayHint: "sk-test-...7890",
status: "active",
expiresAt: null,
lastRefreshedAt: null,
lastError: null,
});
const rows = db.core.credentials.list();
expect(rows.some((row) => row.connectorId === "test-anthropic")).toBe(true);
db.close();
});
it("re-running init() on an existing profile does not re-apply migrations or duplicate rows", async () => {
const dir = await freshProfile("agentcompany-migration-idempotent-");
const dbA = new AppDatabase(dir);
dbA.init();
dbA.projection.sessionEntries.append({
taskSessionId: "session-x",
kind: "message",
role: "user",
payloadJson: JSON.stringify({ role: "user", content: "first" }),
});
const cipher = dbA.vault.encrypt(JSON.stringify({ source: "literal", value: "sk-keep-me" }));
dbA.core.credentials.upsert({
connectorId: "test-keep",
kind: "api_key",
backend: cipher.backend,
ciphertext: cipher.payload,
displayHint: "sk-keep-...e",
status: "active",
expiresAt: null,
lastRefreshedAt: null,
lastError: null,
});
dbA.close();
const dbB = new AppDatabase(dir);
dbB.init();
expect(dbB.projection.sessionEntries.listActive("session-x")).toHaveLength(1);
expect(dbB.core.credentials.list().filter((row) => row.connectorId === "test-keep")).toHaveLength(1);
dbB.close();
});
it("seedConnectors prunes legacy connector rows and inserts the full pi-mono + Fireworks catalog", async () => {
const dir = await freshProfile("agentcompany-migration-seed-");
const db = new AppDatabase(dir);
db.init();
const rows = db.projection.snapshot.listSnapshot().connectors.map((entry) => entry.id);
expect(rows.length).toBe(28);
expect(rows).toContain("claude-pro");
expect(rows).toContain("amazon-bedrock");
expect(rows).toContain("google-vertex");
expect(rows).toContain("fireworks");
expect(rows).toContain("fireworks-firepass");
expect(rows).toContain("ollama");
expect(rows).toContain("lm-studio");
expect(rows.find((id) => (id as string) === "claude_local")).toBeUndefined();
expect(rows.find((id) => (id as string) === "codex_local")).toBeUndefined();
expect(rows.find((id) => (id as string) === "claude_compatible")).toBeUndefined();
db.close();
});
});
@@ -1,142 +0,0 @@
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Agent } from "@mariozechner/pi-agent-core";
import type { Model } from "@mariozechner/pi-ai";
import { loadSkills } from "@mariozechner/pi-coding-agent";
import { describe, expect, it } from "vitest";
import { buildAgentHooks } from "../../src/main/agent-runtime/agent-hooks";
import {
createPiAgent,
resolveThinkingLevel,
} from "../../src/main/worker/runtime/pi-agent-factory";
import {
createRunScopedBashOptions,
createRunScopedCodingTools,
} from "../../src/main/worker/runtime/run-tools";
import type { WorkerRpcClient } from "../../src/main/worker/entry/rpc";
function createModel(): Model<"openai-responses"> {
return {
id: "contract-model",
name: "Contract Model",
api: "openai-responses",
provider: "openai",
baseUrl: "https://example.invalid",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 8192,
maxTokens: 2048,
};
}
function createRpc(): WorkerRpcClient {
return {
invoke: async () => {
throw new Error("Unexpected RPC call in pi runtime contract test.");
},
} as unknown as WorkerRpcClient;
}
describe("pi runtime integration contract", () => {
it("builds the AgentCompany pi Agent boundary with sequential execution and hooks", () => {
const agent = createPiAgent({
runId: "run-contract",
taskSessionId: "session-contract",
systemPrompt: "Contract prompt",
model: createModel(),
tools: [],
priorMessages: [],
thinkingLevel: resolveThinkingLevel("high"),
apiKey: "contract-key",
extraHeaders: { "x-contract": "true" },
rpc: createRpc(),
});
expect(agent).toBeInstanceOf(Agent);
expect(agent.sessionId).toBe("session-contract");
expect(agent.toolExecution).toBe("sequential");
expect(agent.state.systemPrompt).toBe("Contract prompt");
expect(agent.state.thinkingLevel).toBe("high");
expect(agent.state.tools).toEqual([]);
expect(agent.state.messages).toEqual([]);
expect(typeof agent.beforeToolCall).toBe("function");
expect(typeof agent.transformContext).toBe("function");
expect(agent.afterToolCall).toBeUndefined();
});
it("keeps the coding-tool surface expected by worker runs", () => {
const workspaceDir = mkdtempSync(join(tmpdir(), "agentcompany-pi-tools-"));
try {
const tools = createRunScopedCodingTools(workspaceDir, { AGENTCOMPANY_CONTRACT: "1" });
expect(tools.map((tool) => tool.name)).toEqual(["read", "bash", "edit", "write"]);
for (const tool of tools) {
expect(typeof tool.execute).toBe("function");
}
} finally {
rmSync(workspaceDir, { recursive: true, force: true });
}
});
it("preserves run-scoped environment through the bash spawn hook", () => {
const options = createRunScopedBashOptions({
AGENTCOMPANY_CONTRACT: "1",
PATH: "/contract/bin",
});
const result = options.spawnHook?.({
command: "pwd",
cwd: "/tmp",
env: {
HOME: "/home/contract",
PATH: "/base/bin",
},
});
expect(result?.command).toBe("pwd");
expect(result?.cwd).toBe("/tmp");
expect(result?.env.HOME).toBe("/home/contract");
expect(result?.env.PATH).toBe("/contract/bin");
expect(result?.env.AGENTCOMPANY_CONTRACT).toBe("1");
});
it("loads progressive-disclosure skills through the pinned pi runtime", () => {
const root = mkdtempSync(join(tmpdir(), "agentcompany-pi-skills-"));
const skillDir = join(root, "contract-skill");
mkdirSync(skillDir);
writeFileSync(
join(skillDir, "SKILL.md"),
[
"---",
"name: contract-skill",
"description: Contract skill loaded by the pinned pi runtime.",
"---",
"# Contract Skill",
"",
"Use this skill to prove the runtime loader contract.",
].join("\n"),
"utf8",
);
try {
const result = loadSkills({ skillPaths: [skillDir], includeDefaults: false });
expect(result.diagnostics).toEqual([]);
expect(result.skills).toHaveLength(1);
expect(result.skills[0]?.name).toBe("contract-skill");
expect(result.skills[0]?.description).toBe("Contract skill loaded by the pinned pi runtime.");
expect(result.skills[0]?.filePath).toBe(join(skillDir, "SKILL.md"));
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("keeps the aggregated hook bundle assignable to pi Agent options at runtime", () => {
const hooks = buildAgentHooks({ runId: "run-contract", rpc: createRpc() });
expect(Object.keys(hooks).sort()).toEqual([
"beforeToolCall",
"transformContext",
]);
expect(typeof hooks.beforeToolCall).toBe("function");
expect(typeof hooks.transformContext).toBe("function");
});
});
-584
View File
@@ -1,584 +0,0 @@
/**
* Integration tests for process loss detection and recovery.
*
* Tests worker crash detection (EXEC-07), lock release, agent reset,
* context preservation via session handoff (D-02), and database consistency.
*
* Uses a real AppDatabase with temporary directories (no mocks except electron safeStorage).
*/
import { join } from "node:path";
import { tmpdir } from "node:os";
import { mkdtemp, rm } from "node:fs/promises";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
// Mock electron safeStorage — same pattern as atomic-checkout.test.ts
import { vi } from "vitest";
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase } from "../../src/main/database";
import { createSessionManagementService } from "../../src/main/session-management-service";
import type { ConnectorId } from "../../src/shared/types";
type RawDb = {
db: {
prepare: (sql: string) => {
run: (...args: unknown[]) => void;
get: (...args: unknown[]) => unknown;
};
};
};
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
interface ScaffoldResult {
companyId: string;
workspaceId: string;
agentId: string;
goalId: string;
projectId: string;
connectorId: ConnectorId;
}
/** Creates a full entity scaffold: company, workspace, connector, agent, goal, project. */
function scaffold(db: AppDatabase): ScaffoldResult {
const connectorId: ConnectorId = "gemini-cli";
// Connector
db.core.connectors.saveConnector({
id: connectorId,
model: "gemini-2.5-pro",
envBindingText: "",
notes: "Non-exempt connector for session rotation tests",
});
// Company
const companyId = db.core.company.saveCompany({
name: "Recovery Test Corp",
description: "Process recovery integration tests",
status: "active",
});
// Workspace
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary Workspace",
localPath: "/tmp/recovery-test",
repoUrl: "https://github.com/example/recovery-test",
repoRef: "main",
isPrimary: true,
});
// Agent
const agentId = db.core.agents.saveAgent({
companyId,
name: "RecoveryAgent",
role: "IC",
title: "Engineer",
status: "idle",
reportsTo: null,
connectorId,
workspaceId,
model: null,
capabilities: "Full-stack development, testing, debugging",
budgetMonthlyUsd: 100,
});
// Goal
const goalId = db.core.entity.saveGoal({
companyId,
title: "Build login page",
description: "Implement login page with authentication",
ownerAgentId: agentId,
status: "active",
});
// Project
const projectId = db.core.entity.saveProject({
companyId,
goalId,
name: "Auth Module",
description: "Authentication module project",
leadAgentId: agentId,
status: "active",
targetDate: null,
});
return { companyId, workspaceId, agentId, goalId, projectId, connectorId };
}
/**
* Simulates a worker crash by calling the same DB operations that
* worker-service.ts performs on process exit: interruptStaleRuns().
*
* This mirrors worker-service.ts exit handler logic (lines 111-230).
*/
function simulateWorkerCrash(db: AppDatabase): number {
return db.interruptStaleRuns();
}
/**
* Creates a task, run, checks out the task, and marks the run as running.
* Optionally claims the workspace lock.
*/
function createAndStartRun(
db: AppDatabase,
opts: {
companyId: string;
taskId?: string;
agentId: string;
workspaceId: string;
connectorId: ConnectorId;
taskTitle?: string;
taskDescription?: string;
claimWorkspace?: boolean;
},
): { runId: string; taskId: string } {
const taskId =
opts.taskId ??
db.core.tasks.saveTask({
companyId: opts.companyId,
title: opts.taskTitle ?? "Test Task",
description:
opts.taskDescription ??
"A test task for recovery testing with sufficient length for validation purposes.",
assigneeAgentId: opts.agentId,
workspaceId: opts.workspaceId,
priority: "medium",
status: "todo",
});
const { id: runId } = db.core.runs.createRun({
companyId: opts.companyId,
taskId,
agentId: opts.agentId,
workspaceId: opts.workspaceId,
connectorId: opts.connectorId,
});
db.core.tasks.checkoutTask(taskId, runId);
db.core.runs.markRunStatus(runId, "running", "Starting execution");
if (opts.claimWorkspace !== false) {
db.core.workspaces.claimWorkspaceLock(opts.workspaceId, runId);
}
// Set agent status to "running" to simulate active execution
(db as unknown as RawDb).db
.prepare("UPDATE agents SET status = 'running' WHERE id = ?")
.run(opts.agentId);
return { runId, taskId };
}
/**
* Verifies database consistency after crash recovery:
* - No runs in "running" status
* - No agents in "running" status
* - No orphaned task leases pointing to non-existent runs
*/
function verifyDbConsistency(db: AppDatabase): {
runningRuns: number;
runningAgents: number;
orphanedTaskLeases: number;
} {
const runningRuns = (
(db as unknown as RawDb).db
.prepare("SELECT count(*) as cnt FROM runs WHERE status = 'running'")
.get() as { cnt: number }
).cnt;
const runningAgents = (
(db as unknown as RawDb).db
.prepare("SELECT count(*) as cnt FROM agents WHERE status = 'running'")
.get() as { cnt: number }
).cnt;
const orphanedTaskLeases = (
(db as unknown as RawDb).db
.prepare(
`SELECT count(*) as cnt FROM task_leases l
LEFT JOIN runs r ON l.run_id = r.id
WHERE r.id IS NULL`,
)
.get() as { cnt: number }
).cnt;
return { runningRuns, runningAgents, orphanedTaskLeases };
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
let tempDir: string;
let db: AppDatabase;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-recovery-"));
db = new AppDatabase(tempDir);
db.init();
});
afterEach(async () => {
db?.close();
await rm(tempDir, { recursive: true, force: true });
});
// =========================================================================
// Process Recovery -- Worker Crash Detection
// =========================================================================
describe("Process Recovery -- Worker Crash Detection", () => {
it("marks all running runs as interrupted on worker crash", () => {
const s = scaffold(db);
// Create a second agent for a second run
const agent2Id = db.core.agents.saveAgent({
companyId: s.companyId,
name: "Agent2",
role: "IC",
title: "Engineer 2",
status: "idle",
reportsTo: null,
connectorId: s.connectorId,
workspaceId: s.workspaceId,
model: null,
capabilities: "Backend development",
budgetMonthlyUsd: 100,
});
const run1 = createAndStartRun(db, {
companyId: s.companyId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
taskTitle: "Task 1",
claimWorkspace: false,
});
const run2 = createAndStartRun(db, {
companyId: s.companyId,
agentId: agent2Id,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
taskTitle: "Task 2",
claimWorkspace: false,
});
const interruptedCount = simulateWorkerCrash(db);
expect(interruptedCount).toBe(2);
expect(db.core.runs.getRun(run1.runId).status).toBe("interrupted");
expect(db.core.runs.getRun(run2.runId).status).toBe("interrupted");
});
it("releases task locks after crash", () => {
const s = scaffold(db);
const { runId, taskId } = createAndStartRun(db, {
companyId: s.companyId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
// Verify lock is held before crash
const leaseBefore = (db as unknown as RawDb).db
.prepare(
"SELECT run_id FROM task_leases WHERE task_id = ?",
)
.get(taskId) as { run_id: string | null };
expect(leaseBefore.run_id).toBe(runId);
simulateWorkerCrash(db);
const leaseAfter = (db as unknown as RawDb).db
.prepare("SELECT run_id FROM task_leases WHERE task_id = ?")
.get(taskId);
expect(leaseAfter).toBeUndefined();
const task = db.core.entity.getTask(taskId);
expect(task.status).toBe("blocked");
});
it("resets agents from running to idle after crash", () => {
const s = scaffold(db);
createAndStartRun(db, {
companyId: s.companyId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
// Verify agent is running before crash
expect(db.core.entity.getAgent(s.agentId).status).toBe("running");
simulateWorkerCrash(db);
expect(db.core.entity.getAgent(s.agentId).status).toBe("idle");
});
it("releases workspace locks after crash", () => {
const s = scaffold(db);
createAndStartRun(db, {
companyId: s.companyId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
claimWorkspace: true,
});
// Verify workspace is locked before crash
const wsBefore = (db as unknown as RawDb).db
.prepare(
"SELECT lock_holder_run_id FROM workspaces WHERE id = ?",
)
.get(s.workspaceId) as { lock_holder_run_id: string | null };
expect(wsBefore.lock_holder_run_id).not.toBeNull();
simulateWorkerCrash(db);
// Verify workspace lock is released
const wsAfter = (db as unknown as RawDb).db
.prepare(
"SELECT lock_holder_run_id FROM workspaces WHERE id = ?",
)
.get(s.workspaceId) as { lock_holder_run_id: string | null };
expect(wsAfter.lock_holder_run_id).toBeNull();
// Verify we can re-claim the workspace (lock is truly released)
const agent2Id = db.core.agents.saveAgent({
companyId: s.companyId,
name: "Agent2",
role: "IC",
title: "Recovery Agent",
status: "idle",
reportsTo: null,
connectorId: s.connectorId,
workspaceId: s.workspaceId,
model: null,
capabilities: "Testing",
budgetMonthlyUsd: 100,
});
const taskId2 = db.core.tasks.saveTask({
companyId: s.companyId,
title: "Recovery Task",
description: "A recovery task after crash to verify workspace lock is released properly.",
assigneeAgentId: agent2Id,
workspaceId: s.workspaceId,
priority: "medium",
status: "todo",
});
const { id: newRunId } = db.core.runs.createRun({
companyId: s.companyId,
taskId: taskId2,
agentId: agent2Id,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
expect(db.core.workspaces.claimWorkspaceLock(s.workspaceId, newRunId)).toBe(true);
});
it("tasks set to blocked after crash", () => {
const s = scaffold(db);
const { taskId } = createAndStartRun(db, {
companyId: s.companyId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
// Verify task is in_progress before crash
expect(db.core.entity.getTask(taskId).status).toBe("in_progress");
simulateWorkerCrash(db);
// Task should be blocked (prevents heartbeat re-queuing)
expect(db.core.entity.getTask(taskId).status).toBe("blocked");
});
});
// =========================================================================
// Process Recovery -- Context Preservation (D-02)
// =========================================================================
describe("Process Recovery -- Context Preservation (D-02)", () => {
it("task retains agent assignment after crash", () => {
const s = scaffold(db);
const { taskId } = createAndStartRun(db, {
companyId: s.companyId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
// Verify assignment before crash
expect(db.core.entity.getTask(taskId).assigneeAgentId).toBe(s.agentId);
simulateWorkerCrash(db);
// Agent assignment must be preserved for retry context
expect(db.core.entity.getTask(taskId).assigneeAgentId).toBe(s.agentId);
});
it("session handoff markdown generated on crash", () => {
const s = scaffold(db);
const { runId } = createAndStartRun(db, {
companyId: s.companyId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
// Create a task session (simulating what happens during normal execution)
const sessionId = db.core.tasks.createNewSession({
companyId: s.companyId,
agentId: s.agentId,
adapterType: "gemini-cli",
});
// Drive the session past the maxRuns=20 threshold so rotation fires.
for (let i = 0; i < 20; i++) {
db.core.tasks.incrementSessionUsage(sessionId, 1000, 500, `${runId}-${i}`);
}
const sessionService = createSessionManagementService({
db,
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
});
const session = db.core.tasks.getSessionById(sessionId);
expect(session).not.toBeNull();
const rotationResult = sessionService.evaluateRotation(session!);
expect(rotationResult.shouldRotate).toBe(true);
expect(rotationResult.reason).toBe("max_runs");
// Generate handoff markdown (agent-scoped, single-argument signature).
const handoff = sessionService.generateHandoff(session!);
expect(handoff).toContain("# Session Handoff");
expect(handoff).toContain("Session stats:");
});
it("database consistency verified after crash", () => {
const s = scaffold(db);
createAndStartRun(db, {
companyId: s.companyId,
agentId: s.agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
});
simulateWorkerCrash(db);
const consistency = verifyDbConsistency(db);
expect(consistency.runningRuns).toBe(0);
expect(consistency.runningAgents).toBe(0);
expect(consistency.orphanedTaskLeases).toBe(0);
});
});
// =========================================================================
// Process Recovery -- Multiple Concurrent Runs
// =========================================================================
describe("Process Recovery -- Multiple Concurrent Runs", () => {
it("all concurrent runs get interrupted", () => {
const s = scaffold(db);
// Create 3 agents for 3 concurrent runs
const agents = [s.agentId];
for (let i = 2; i <= 3; i++) {
agents.push(
db.core.agents.saveAgent({
companyId: s.companyId,
name: `Agent${i}`,
role: "IC",
title: `Engineer ${i}`,
status: "idle",
reportsTo: null,
connectorId: s.connectorId,
workspaceId: s.workspaceId,
model: null,
capabilities: "Development tasks",
budgetMonthlyUsd: 100,
}),
);
}
const runs = agents.map((agentId, i) =>
createAndStartRun(db, {
companyId: s.companyId,
agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
taskTitle: `Concurrent Task ${i + 1}`,
claimWorkspace: false, // only one can hold workspace lock
}),
);
const interruptedCount = simulateWorkerCrash(db);
expect(interruptedCount).toBe(3);
for (const run of runs) {
expect(db.core.runs.getRun(run.runId).status).toBe("interrupted");
}
});
it("all task locks released for concurrent runs", () => {
const s = scaffold(db);
const agents = [s.agentId];
for (let i = 2; i <= 3; i++) {
agents.push(
db.core.agents.saveAgent({
companyId: s.companyId,
name: `Agent${i}`,
role: "IC",
title: `Engineer ${i}`,
status: "idle",
reportsTo: null,
connectorId: s.connectorId,
workspaceId: s.workspaceId,
model: null,
capabilities: "Development tasks",
budgetMonthlyUsd: 100,
}),
);
}
const runs = agents.map((agentId, i) =>
createAndStartRun(db, {
companyId: s.companyId,
agentId,
workspaceId: s.workspaceId,
connectorId: s.connectorId,
taskTitle: `Concurrent Task ${i + 1}`,
claimWorkspace: false,
}),
);
simulateWorkerCrash(db);
// All tasks should be blocked (crash recovery sets them to blocked)
for (const run of runs) {
const task = db.core.entity.getTask(run.taskId);
expect(task.status).toBe("blocked");
}
// Verify DB consistency across all entities
const consistency = verifyDbConsistency(db);
expect(consistency.runningRuns).toBe(0);
expect(consistency.runningAgents).toBe(0);
expect(consistency.orphanedTaskLeases).toBe(0);
});
});
@@ -1,224 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { randomUUID } from "node:crypto";
import { AppDatabase } from "../../src/main/database";
import { buildTaskPrompt } from "../../src/main/prompt-builder/index";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("skill-aware capability registry", () => {
it("autoAssignTask prefers an agent whose assigned skill matches the task, even when capability text is generic", async () => {
const profileDir = await createTempDir("agentcompany-skill-assign-");
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Skill Match Co",
description: "Tests skill-aware assignment",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
db.core.agents.saveAgent({
companyId,
name: "Generic Engineer",
role: "Engineer",
title: "Engineer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "General software delivery",
budgetMonthlyUsd: 200,
});
const styledEngineerId = db.core.agents.saveAgent({
companyId,
name: "Frontend Specialist",
role: "Engineer",
title: "Frontend Specialist",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "General software delivery",
budgetMonthlyUsd: 200,
});
const skillId = randomUUID();
const now = new Date().toISOString();
db.core.skills.upsertCompanySkill({
id: skillId,
companyId,
key: "frontend/react-ui",
slug: "react-ui",
name: "React UI",
description: "Build React components with hooks, Tailwind, and accessible layouts",
sourceType: "local_path",
sourceLocator: null,
sourceRef: null,
trustLevel: "markdown_only",
compatibility: "compatible",
fileInventory: "[]",
metadata: null,
createdAt: now,
updatedAt: now,
});
db.core.skills.assignSkillToAgent(companyId, styledEngineerId, skillId);
const taskId = db.core.tasks.saveTask({
companyId,
title: "Build React dashboard widget",
description: "Create an accessible React component with hooks and Tailwind styling.",
workspaceId,
priority: "high",
status: "todo",
});
const assignedId = db.core.tasks.autoAssignTask(taskId, companyId);
expect(assignedId).toBe(styledEngineerId);
db.close();
});
it("injects the company capability registry into the task prompt", async () => {
const profileDir = await createTempDir("agentcompany-capability-prompt-");
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Registry Co",
description: "Tests capability registry prompt injection",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const skillId = randomUUID();
const now = new Date().toISOString();
db.core.skills.upsertCompanySkill({
id: skillId,
companyId,
key: "frontend/react-ui",
slug: "react-ui",
name: "React UI",
description: "Use when building React interfaces",
sourceType: "local_path",
sourceLocator: null,
sourceRef: null,
trustLevel: "markdown_only",
compatibility: "compatible",
fileInventory: "[]",
metadata: null,
createdAt: now,
updatedAt: now,
});
const snapshot = db.projection.snapshot.listSnapshot();
const prompt = buildTaskPrompt({
agent: db.core.entity.getAgent(agentId),
apiUrl: "http://127.0.0.1:3210",
apiKey: "token",
runId: "run-1",
task: null,
companyName: "Registry Co",
companyDescription: "Tests capability registry prompt injection",
goals: [],
projects: [],
directReports: [],
chainOfCommand: [],
assignedTasks: [],
allAgents: snapshot.agents.filter((entry) => entry.companyId === companyId),
pendingApprovals: [],
tasksAwaitingReview: [],
wakeReason: "assignment",
allTasks: [],
recentComments: [],
socialAccounts: [],
allConnectors: snapshot.connectors
.filter((connector) => connector.id === "openai-codex")
.map((connector) => ({ id: connector.id, label: connector.label, status: connector.status, enabledModels: ["gpt-5.4"] })),
companySkillCatalog: db.core.skills.listCompanySkills(companyId).map((skill) => ({
name: skill.name,
slug: skill.slug,
description: skill.description,
})),
companyWorkspaces: snapshot.workspaces
.filter((workspace) => workspace.companyId === companyId)
.map((workspace) => ({ id: workspace.id, name: workspace.name, localPath: workspace.localPath })),
companyDocuments: [],
skills: [
{
name: "react-standards",
description: "Use when building React UI with hooks and Tailwind.",
filePath: "/tmp/react-standards/SKILL.md",
baseDir: "/tmp/react-standards",
},
],
});
expect(prompt).toContain("# Company Capability Registry");
expect(prompt).toContain("React UI (`react-ui`)");
expect(prompt).toMatch(/Primary \(`[0-9a-f-]+`\):/);
expect(prompt).toContain("Ready Connectors");
expect(prompt).toContain("<available_skills>");
expect(prompt).toContain("<name>react-standards</name>");
db.close();
});
});
@@ -0,0 +1,63 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ChatService } from "../../src/main/services/chat-service";
import { dispatchAgentTool, type AgentToolContext } from "../../src/main/services/agent-tools";
function call(name: string, args: Record<string, unknown>) {
return { id: `call_${randomUUID()}`, name, arguments: args };
}
describe("thread participant wake", () => {
test("human reply in a thread wakes agent participants without explicit @mention", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-threadwake-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let employeeId = "";
let providerId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "WakeCorp", employeeName: "Yang", mission: "m", goal: "g" });
providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
connection.db.prepare("update providers set enabled = 1 where id = ?").run(providerId);
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null,
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
});
const chat = new ChatService(connection.db);
const deps = { db: connection.db, organization, chat };
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null, taskGoalId: null, workspace: null,
};
const firstResult = dispatchAgentTool(deps, ctx, call("send_message", { channel: "general", body: "The plan is ready for review." }));
const threadId = /thread_id="([^"]+)"/.exec(firstResult)?.[1];
expect(threadId).toBeTruthy();
const runsBeforeReply = connection.db.prepare("select count(*) as c from task_runs where org_id = ? and trigger = 'chat_mention'")
.get(orgId) as { c: number };
connection.transaction(() => {
chat.sendMessage({
orgId,
spaceId: (connection.db.prepare("select id from chat_spaces where org_id = ? and name = 'general'").get(orgId) as { id: string }).id,
threadId: threadId!,
senderType: "employee",
senderId: employeeId,
bodyMd: "Looks good, please proceed with the deployment.",
attachmentFileIds: [],
});
});
const runsAfterReply = connection.db.prepare("select count(*) as c from task_runs where org_id = ? and trigger = 'chat_mention'")
.get(orgId) as { c: number };
expect(runsAfterReply.c).toBeGreaterThan(runsBeforeReply.c);
});
});
@@ -1,444 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { createThreadIntegration } from "../../src/main/thread-integration";
import { createCheckpointEngine } from "../../src/main/checkpoint-engine";
import { dispatchAgentMessage, HUMAN_SENDER_ID } from "../../src/main/messaging/dispatch";
import { isAppError } from "../../src/main/errors";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
function createNoopLogger() {
return {
info: () => {},
warn: () => {},
error: () => {},
};
}
function createScaffold(db: AppDatabase) {
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Thread Wait Co",
description: "Tests cofounder wait states and replies",
status: "active",
executionMode: "ai_cofounder",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: "/tmp/thread-wait",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const engineerId = db.core.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Lead Engineer",
status: "idle",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Ship features",
budgetMonthlyUsd: 180,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Ship the feature",
description: "Implement the approved deliverable and wait for co-founder review.",
assigneeAgentId: engineerId,
workspaceId,
priority: "high",
status: "in_progress",
});
return { companyId, workspaceId, ceoId, engineerId, taskId };
}
function expectThreadIntegrationFailure(error: unknown, code: string) {
expect(isAppError(error)).toBe(true);
if (isAppError(error)) {
expect(error.kind).toBe("upstream");
expect(error.code).toBe(code);
}
}
describe("thread wait + heartbeat integration", () => {
it("pauses the assignee heartbeat on successful run completion until a human reply resolves the wait state", async () => {
const profileDir = await createTempDir("agentcompany-thread-wait-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, engineerId, taskId } = createScaffold(db);
const notifications: Array<{ title: string; body: string }> = [];
const threadIntegration = createThreadIntegration({
db,
logger: createNoopLogger(),
notify: (notification) => {
notifications.push(notification);
},
});
const checkpointEngine = createCheckpointEngine({
db,
logger: createNoopLogger(),
threadIntegration,
publishDomainChanged: () => {},
notify: () => {},
});
threadIntegration.onTaskCreated(taskId, companyId);
threadIntegration.onRunFinished("run-1", taskId, engineerId, companyId, "succeeded", null);
const thread = db.core.threads.listThreads({
companyId,
type: "task",
relatedEntityType: "task",
relatedEntityId: taskId,
limit: 1,
})[0];
if (!thread) {
throw new Error("Expected a task thread to exist for the completed run.");
}
let waitStates = db.core.threads.listThreadWaitStates(companyId).filter((entry) => entry.threadId === thread.id);
expect(waitStates).toHaveLength(1);
expect(waitStates[0]?.waitingFor).toBe("user_reply");
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true);
const otherAgentId = db.core.agents.saveAgent({
companyId,
name: "Marketer",
role: "CMO",
title: "Chief Marketing Officer",
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
workspaceId: db.core.entity.getTask(taskId).workspaceId ?? null,
model: null,
capabilities: "Drive marketing work",
budgetMonthlyUsd: 180,
});
expect(checkpointEngine.shouldPauseHeartbeat(companyId, otherAgentId)).toBe(false);
expect(notifications).toHaveLength(1);
expect(notifications[0]?.body).toContain("@Board review needed");
dispatchAgentMessage({
db,
onHumanMessage: (currentCompanyId, toAgentId, threadId, waitStateId) =>
threadIntegration.onHumanMessage(currentCompanyId, toAgentId, threadId, waitStateId),
}, {
companyId,
fromAgentId: HUMAN_SENDER_ID,
fromHumanUser: true,
channel: "direct",
toAgentId: engineerId,
threadId: thread.id,
body: "Board reviewed this deliverable. Continue.",
});
waitStates = db.core.threads.listThreadWaitStates(companyId).filter((entry) => entry.threadId === thread.id);
expect(waitStates).toHaveLength(0);
expect(threadIntegration.hasUnresolvedWaitStatesForAgent(companyId, engineerId)).toBe(false);
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(false);
db.close();
});
it("resolves approval-linked wait states only when the approval is actually decided", async () => {
const profileDir = await createTempDir("agentcompany-approval-wait-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, engineerId, taskId } = createScaffold(db);
const threadIntegration = createThreadIntegration({
db,
logger: createNoopLogger(),
});
const checkpointEngine = createCheckpointEngine({
db,
logger: createNoopLogger(),
threadIntegration,
publishDomainChanged: () => {},
notify: () => {},
});
threadIntegration.onTaskCreated(taskId, companyId);
const thread = db.core.threads.listThreads({
companyId,
type: "task",
relatedEntityType: "task",
relatedEntityId: taskId,
limit: 1,
})[0];
if (!thread) {
throw new Error("Expected a task thread to exist for approval resolution testing.");
}
const approvalId = db.core.approvals.requestApproval({
companyId,
relatedTaskId: taskId,
requestedByAgentId: engineerId,
type: "draft_review",
payloadSummary: "Review the draft",
impactSummary: "Board approval is required before this work can continue.",
payloadJson: "{}",
});
threadIntegration.onApprovalCreated(approvalId, companyId, taskId, "draft_review", "Draft review needed");
let waitStates = db.core.threads.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId);
expect(waitStates).toHaveLength(1);
expect(waitStates[0]?.waitingFor).toBe("user_approval");
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true);
dispatchAgentMessage({
db,
onHumanMessage: (currentCompanyId, toAgentId, threadId, waitStateId) =>
threadIntegration.onHumanMessage(currentCompanyId, toAgentId, threadId, waitStateId),
}, {
companyId,
fromAgentId: HUMAN_SENDER_ID,
fromHumanUser: true,
channel: "direct",
toAgentId: engineerId,
threadId: thread.id,
body: "Board left a comment but did not decide approval yet.",
});
waitStates = db.core.threads.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId);
expect(waitStates).toHaveLength(1);
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true);
db.core.approvals.decideApproval({
approvalId,
state: "approved",
decisionNote: "Approved by board",
});
threadIntegration.onApprovalResolved(approvalId, companyId, "approved");
waitStates = db.core.threads.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId);
expect(waitStates).toHaveLength(0);
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(false);
db.close();
});
it("keeps co-founder heartbeat paused when wait-state lookup fails", async () => {
const profileDir = await createTempDir("agentcompany-wait-query-fail-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, engineerId } = createScaffold(db);
const threadIntegration = createThreadIntegration({
db,
logger: createNoopLogger(),
});
const checkpointEngine = createCheckpointEngine({
db,
logger: createNoopLogger(),
threadIntegration,
publishDomainChanged: () => {},
notify: () => {},
});
vi.spyOn(db.core.threads, "hasUnresolvedWaitStateForAgent").mockImplementation(() => {
throw new Error("wait-state query failed");
});
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true);
db.close();
});
it("rolls back approval-created thread writes when wait-state creation fails", async () => {
const profileDir = await createTempDir("agentcompany-approval-create-fail-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, engineerId, taskId } = createScaffold(db);
const threadIntegration = createThreadIntegration({
db,
logger: createNoopLogger(),
});
const approvalId = db.core.approvals.requestApproval({
companyId,
relatedTaskId: taskId,
requestedByAgentId: engineerId,
type: "draft_review",
payloadSummary: "Review the draft",
impactSummary: "Board approval is required before this work can continue.",
payloadJson: "{}",
});
vi.spyOn(db.core.threads, "setThreadWaitState").mockImplementation(() => {
throw new Error("wait-state write failed");
});
let thrown: unknown;
try {
threadIntegration.onApprovalCreated(approvalId, companyId, taskId, "draft_review", "Draft review needed");
} catch (error) {
thrown = error;
}
expectThreadIntegrationFailure(thrown, "thread.integration.onApprovalCreated.failed");
const taskThreads = db.core.threads.listThreads({
companyId,
type: "task",
relatedEntityType: "task",
relatedEntityId: taskId,
limit: 1,
});
expect(taskThreads).toHaveLength(0);
db.close();
});
it("rolls back approval resolution when decision event recording fails", async () => {
const profileDir = await createTempDir("agentcompany-approval-resolve-fail-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, engineerId, taskId } = createScaffold(db);
const threadIntegration = createThreadIntegration({
db,
logger: createNoopLogger(),
});
threadIntegration.onTaskCreated(taskId, companyId);
const approvalId = db.core.approvals.requestApproval({
companyId,
relatedTaskId: taskId,
requestedByAgentId: engineerId,
type: "draft_review",
payloadSummary: "Review the draft",
impactSummary: "Board approval is required before this work can continue.",
payloadJson: "{}",
});
threadIntegration.onApprovalCreated(approvalId, companyId, taskId, "draft_review", "Draft review needed");
const waitStateId = db.core.threads.listThreadWaitStates(companyId)
.find((entry) => entry.relatedApprovalId === approvalId)?.id;
expect(waitStateId).toBeTruthy();
const addThreadEvent = db.core.threads.addThreadEvent.bind(db.core.threads);
vi.spyOn(db.core.threads, "addThreadEvent").mockImplementation((input) => {
if (input.eventType === "approval_decision") {
throw new Error("decision event write failed");
}
return addThreadEvent(input);
});
let thrown: unknown;
try {
threadIntegration.onApprovalResolved(approvalId, companyId, "approved");
} catch (error) {
thrown = error;
}
expectThreadIntegrationFailure(thrown, "thread.integration.onApprovalResolved.failed");
const unresolved = db.core.threads.listThreadWaitStates(companyId)
.filter((entry) => entry.relatedApprovalId === approvalId);
expect(unresolved.map((entry) => entry.id)).toEqual([waitStateId]);
db.close();
});
it("rolls back human-message wait-state resolution when one resolution fails", async () => {
const profileDir = await createTempDir("agentcompany-human-message-fail-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, taskId } = createScaffold(db);
const threadIntegration = createThreadIntegration({
db,
logger: createNoopLogger(),
});
threadIntegration.onTaskCreated(taskId, companyId);
const thread = db.core.threads.listThreads({
companyId,
type: "task",
relatedEntityType: "task",
relatedEntityId: taskId,
limit: 1,
})[0];
if (!thread) {
throw new Error("Expected task thread for human-message failure testing.");
}
const first = db.core.threads.setThreadWaitState({
threadId: thread.id,
companyId,
waitingFor: "user_reply",
relatedApprovalId: null,
reason: "First reply needed",
});
const second = db.core.threads.setThreadWaitState({
threadId: thread.id,
companyId,
waitingFor: "user_review",
relatedApprovalId: null,
reason: "Second review needed",
});
const resolveThreadWaitState = db.core.threads.resolveThreadWaitState.bind(db.core.threads);
let resolutionCount = 0;
vi.spyOn(db.core.threads, "resolveThreadWaitState").mockImplementation((waitStateId, currentCompanyId) => {
resolutionCount += 1;
if (resolutionCount === 2) {
throw new Error("wait-state resolve failed");
}
resolveThreadWaitState(waitStateId, currentCompanyId);
});
let thrown: unknown;
try {
threadIntegration.onHumanMessage(companyId, null, thread.id, null);
} catch (error) {
thrown = error;
}
expectThreadIntegrationFailure(thrown, "thread.integration.onHumanMessage.failed");
const unresolvedIds = db.core.threads.listThreadWaitStates(companyId)
.filter((entry) => entry.threadId === thread.id)
.map((entry) => entry.id)
.sort();
expect(unresolvedIds).toEqual([first.id, second.id].sort());
db.close();
});
});
@@ -1,251 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { DatabaseSync } from "node:sqlite";
import { afterEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
import type { WorkProductType } from "../../src/shared/types";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
async function createHarness() {
const profileDir = await createTempDir("agentcompany-primary-wp-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.core.company.saveCompany({
name: "Primary Work Product Co",
description: "Tests primary invariant",
status: "active",
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Writer",
role: "Engineer",
title: "Writer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
model: null,
capabilities: "Writes artifacts",
budgetMonthlyUsd: 100,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Create deliverables",
description: "",
assigneeAgentId: agentId,
priority: "medium",
status: "in_progress",
});
return { db, companyId, taskId };
}
function createWorkProduct(db: AppDatabase, companyId: string, taskId: string, title: string, isPrimary = false, type: WorkProductType = "file") {
return db.core.documents.createWorkProduct({
companyId,
taskId,
type,
provider: "filesystem",
externalId: `${title}.md`,
title,
isPrimary,
});
}
function listPrimaryIds(db: AppDatabase, companyId: string, taskId: string) {
return listPrimaryIdsByType(db, companyId, taskId, "file");
}
function listPrimaryIdsByType(db: AppDatabase, companyId: string, taskId: string, type: string) {
return db.queryAll<{ id: string }>(
"select id from work_products where company_id = ? and task_id = ? and type = ? and is_primary = 1 and status <> 'archived' order by updated_at desc",
companyId,
taskId,
type,
).map((row) => row.id);
}
describe("work product primary invariant", () => {
it("installs the database unique index for active primary rows", async () => {
const { db } = await createHarness();
try {
const index = db.queryAll<{ sql: string | null }>(
"select sql from sqlite_master where type = 'index' and name = 'idx_work_products_primary_active' limit 1",
)[0];
expect(index?.sql).toContain("ON work_products(company_id, task_id, type)");
expect(index?.sql).toContain("is_primary = 1");
expect(index?.sql).toContain("status <> 'archived'");
} finally {
db.close();
}
});
it("normalizes duplicate active primaries during v56 migration", async () => {
const profileDir = await createTempDir("agentcompany-primary-migration-");
const dbPath = join(profileDir, "profile.sqlite");
const rawDb = new DatabaseSync(dbPath);
const ts = "2026-04-24T00:00:00.000Z";
try {
rawDb.exec(`
create table settings (key text primary key, value text not null);
create table companies (id text primary key, name text not null, description text not null, status text not null, created_at text not null, updated_at text not null);
create table agents (id text primary key, company_id text not null, status text not null, reports_to text, department text, connector_id text not null default 'openai-codex', updated_at text not null);
create table tasks (
id text primary key,
company_id text not null,
title text not null,
description text not null,
status text not null,
priority text not null,
created_at text not null,
updated_at text not null
);
create table workspaces (id text primary key, company_id text not null, local_path text not null, state text not null, lock_holder_run_id text, lock_acquired_at text, updated_at text not null);
create table agent_messages (id text primary key, company_id text not null, channel text not null, channel_target_id text, created_at text not null);
create table model_capabilities (
provider TEXT NOT NULL,
model_id TEXT NOT NULL,
reasoning INTEGER NOT NULL,
coding INTEGER NOT NULL,
writing INTEGER NOT NULL,
tier TEXT NOT NULL,
cost_per_mtok_out REAL NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (provider, model_id)
);
create table work_products (
id TEXT PRIMARY KEY,
company_id TEXT NOT NULL,
project_id TEXT,
task_id TEXT,
workspace_id TEXT,
type TEXT NOT NULL,
provider TEXT NOT NULL,
external_id TEXT,
title TEXT NOT NULL,
url TEXT,
status TEXT NOT NULL DEFAULT 'active',
review_state TEXT NOT NULL DEFAULT 'none',
is_primary INTEGER NOT NULL DEFAULT 0,
summary TEXT,
metadata_json TEXT,
created_by_run_id TEXT,
last_touched_by_run_id TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE
);
create table schema_migrations (version integer primary key, name text not null, applied_at text not null);
`);
for (let version = 1; version <= 55; version += 1) {
rawDb
.prepare("insert into schema_migrations (version, name, applied_at) values (?, ?, ?)")
.run(version, `legacy-${version}`, ts);
}
rawDb
.prepare("insert into companies (id, name, description, status, created_at, updated_at) values (?, ?, ?, ?, ?, ?)")
.run("company-1", "Legacy Co", "", "active", ts, ts);
rawDb
.prepare("insert into tasks (id, company_id, title, description, status, priority, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?)")
.run("task-1", "company-1", "Legacy task", "", "in_progress", "medium", ts, ts);
rawDb
.prepare("insert into work_products (id, company_id, task_id, type, provider, external_id, title, status, review_state, is_primary, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("older-primary", "company-1", "task-1", "file", "filesystem", "older.md", "Older", "active", "none", 1, "2026-04-24T00:00:01.000Z", "2026-04-24T00:00:01.000Z");
rawDb
.prepare("insert into work_products (id, company_id, task_id, type, provider, external_id, title, status, review_state, is_primary, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("newer-primary", "company-1", "task-1", "file", "filesystem", "newer.md", "Newer", "active", "none", 1, "2026-04-24T00:00:02.000Z", "2026-04-24T00:00:02.000Z");
} finally {
rawDb.close();
}
const appDb = new AppDatabase(profileDir);
appDb.init();
try {
expect(listPrimaryIdsByType(appDb, "company-1", "task-1", "file")).toEqual(["newer-primary"]);
const older = appDb.queryAll<{ isPrimary: number }>("select is_primary as isPrimary from work_products where id = 'older-primary'")[0];
expect(older?.isPrimary).toBe(0);
const migration = appDb.queryAll<{ version: number }>("select version from schema_migrations where version = 56")[0];
expect(migration?.version).toBe(56);
const index = appDb.queryAll<{ sql: string | null }>(
"select sql from sqlite_master where type = 'index' and name = 'idx_work_products_primary_active' limit 1",
)[0];
expect(index?.sql).toContain("idx_work_products_primary_active");
} finally {
appDb.close();
}
});
it("rejects direct SQL writes that violate the primary invariant", async () => {
const { db, companyId, taskId } = await createHarness();
try {
createWorkProduct(db, companyId, taskId, "first", true);
expect(() => db.queryAll(
`insert into work_products (
id, company_id, task_id, type, provider, external_id, title, status, review_state, is_primary, created_at, updated_at
) values (
'direct-primary', ?, ?, 'file', 'filesystem', 'direct.md', 'Direct', 'active', 'none', 1, '2026-04-24T00:00:00.000Z', '2026-04-24T00:00:00.000Z'
)`,
companyId,
taskId,
)).toThrow(/UNIQUE constraint failed/);
} finally {
db.close();
}
});
it("moves primary status when PATCH promotes another work product", async () => {
const { db, companyId, taskId } = await createHarness();
try {
const firstId = createWorkProduct(db, companyId, taskId, "first", true);
const secondId = createWorkProduct(db, companyId, taskId, "second", false);
db.core.documents.updateWorkProduct(secondId, companyId, { isPrimary: true });
expect(listPrimaryIds(db, companyId, taskId)).toEqual([secondId]);
const first = db.queryAll<{ isPrimary: number }>("select is_primary as isPrimary from work_products where id = ?", firstId)[0];
expect(first?.isPrimary).toBe(0);
} finally {
db.close();
}
});
it("keeps the primary invariant when create inserts a new primary", async () => {
const { db, companyId, taskId } = await createHarness();
try {
const firstId = createWorkProduct(db, companyId, taskId, "first", true);
const secondId = createWorkProduct(db, companyId, taskId, "second", true);
expect(listPrimaryIds(db, companyId, taskId)).toEqual([secondId]);
const first = db.queryAll<{ isPrimary: number }>("select is_primary as isPrimary from work_products where id = ?", firstId)[0];
expect(first?.isPrimary).toBe(0);
} finally {
db.close();
}
});
it("moves primary status when a primary work product changes type", async () => {
const { db, companyId, taskId } = await createHarness();
try {
const firstId = createWorkProduct(db, companyId, taskId, "first", true, "file");
const secondId = createWorkProduct(db, companyId, taskId, "second", true, "link");
db.core.documents.updateWorkProduct(firstId, companyId, { type: "link" });
expect(listPrimaryIdsByType(db, companyId, taskId, "link")).toEqual([firstId]);
const second = db.queryAll<{ isPrimary: number }>("select is_primary as isPrimary from work_products where id = ?", secondId)[0];
expect(second?.isPrimary).toBe(0);
} finally {
db.close();
}
});
});
@@ -1,377 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { createAgentToken, initializeAgentTokenSigningKey } from "../../src/main/agent-auth";
import { createApiServer } from "../../src/main/api-server";
import { AppDatabase } from "../../src/main/database";
import { createTestApiServerCallbacks } from "../support/api-server-callbacks";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
async function createHarness() {
const profileDir = await createTempDir("agentcompany-work-product-url-");
initializeAgentTokenSigningKey(profileDir);
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "URL Policy Co",
description: "Tests work product URL trust policy",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Reporter",
role: "Researcher",
title: "Reporter",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Writes reports",
budgetMonthlyUsd: 100,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Publish external report",
description: "Attach a report link.",
assigneeAgentId: agentId,
workspaceId,
priority: "medium",
status: "in_progress",
});
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
const token = createAgentToken(agentId, companyId, run.id);
const server = createApiServer(db, createTestApiServerCallbacks());
const port = await server.start();
return {
db,
server,
companyId,
taskId,
baseUrl: `http://127.0.0.1:${port}`,
headers: {
Authorization: `Bearer ${token}`,
"X-Agent-Company-Run-Id": run.id,
"Content-Type": "application/json; charset=utf-8",
},
};
}
describe("work product URL API policy", () => {
it("rejects JSON bodies that declare a non UTF-8 charset", async () => {
const harness = await createHarness();
try {
const response = await fetch(`${harness.baseUrl}/api/issues/${harness.taskId}/work-products`, {
method: "POST",
headers: {
...harness.headers,
"Content-Type": "application/json; charset=gbk",
},
body: JSON.stringify({ type: "link", provider: "external", title: "Report" }),
});
expect(response.status).toBe(415);
expect(harness.db.queryAll("select id from work_products")).toHaveLength(0);
} finally {
harness.server.stop();
harness.db.close();
}
});
it("rejects JSON bodies with duplicate charset declarations", async () => {
const harness = await createHarness();
try {
const response = await fetch(`${harness.baseUrl}/api/issues/${harness.taskId}/work-products`, {
method: "POST",
headers: {
...harness.headers,
"Content-Type": "application/json; charset=gbk; charset=utf-8",
},
body: JSON.stringify({ type: "link", provider: "external", title: "Report" }),
});
expect(response.status).toBe(415);
expect(harness.db.queryAll("select id from work_products")).toHaveLength(0);
} finally {
harness.server.stop();
harness.db.close();
}
});
it("rejects unknown work product taxonomy during creation", async () => {
const harness = await createHarness();
try {
const cases = [
{ type: "spreadsheet", provider: "external", title: "Bad type" },
{ type: "link", provider: "slack", title: "Bad provider" },
{ type: "link", provider: "external", title: "Bad status", status: "ship_it" },
{ type: "link", provider: "external", title: "Bad review", reviewState: "rubber_stamp" },
];
for (const body of cases) {
const response = await fetch(`${harness.baseUrl}/api/issues/${harness.taskId}/work-products`, {
method: "POST",
headers: harness.headers,
body: JSON.stringify(body),
});
expect(response.status).toBe(400);
}
expect(harness.db.queryAll("select id from work_products")).toHaveLength(0);
} finally {
harness.server.stop();
harness.db.close();
}
});
it("rejects unknown work product taxonomy during updates", async () => {
const harness = await createHarness();
try {
const id = harness.db.core.documents.createWorkProduct({
companyId: harness.companyId,
taskId: harness.taskId,
type: "link",
provider: "external",
title: "External report",
status: "active",
reviewState: "none",
});
const cases = [
{ type: "spreadsheet" },
{ provider: "slack" },
{ status: "ship_it" },
{ reviewState: "rubber_stamp" },
];
for (const body of cases) {
const response = await fetch(`${harness.baseUrl}/api/work-products/${id}`, {
method: "PATCH",
headers: harness.headers,
body: JSON.stringify(body),
});
expect(response.status).toBe(400);
}
const row = harness.db.queryAll<{
type: string;
provider: string;
status: string;
review_state: string;
}>("select type, provider, status, review_state from work_products where id = ? limit 1", id)[0];
expect(row).toEqual({
type: "link",
provider: "external",
status: "active",
review_state: "none",
});
} finally {
harness.server.stop();
harness.db.close();
}
});
it("rejects unknown work product taxonomy at the database boundary", async () => {
const harness = await createHarness();
try {
expect(() => harness.db.core.documents.createWorkProduct({
companyId: harness.companyId,
taskId: harness.taskId,
type: "link",
// @ts-expect-error verifies runtime validation for direct DB callers.
provider: "slack",
title: "Bad provider",
})).toThrow();
expect(harness.db.queryAll("select id from work_products")).toHaveLength(0);
expect(() => harness.db.core.documents.createWorkProduct({
companyId: harness.companyId,
taskId: harness.taskId,
type: "link",
provider: "external",
title: "Empty status",
// @ts-expect-error verifies runtime validation for direct DB callers.
status: "",
})).toThrow();
expect(harness.db.queryAll("select id from work_products")).toHaveLength(0);
expect(() => harness.db.core.documents.createWorkProduct({
companyId: harness.companyId,
taskId: harness.taskId,
type: "link",
provider: "external",
title: "Empty review state",
// @ts-expect-error verifies runtime validation for direct DB callers.
reviewState: "",
})).toThrow();
expect(harness.db.queryAll("select id from work_products")).toHaveLength(0);
const id = harness.db.core.documents.createWorkProduct({
companyId: harness.companyId,
taskId: harness.taskId,
type: "link",
provider: "external",
title: "External report",
});
expect(() => harness.db.core.documents.updateWorkProduct(id, harness.companyId, {
// @ts-expect-error verifies runtime validation for direct DB callers.
status: "ship_it",
})).toThrow();
const row = harness.db.queryAll<{ status: string }>("select status from work_products where id = ? limit 1", id)[0];
expect(row?.status).toBe("active");
} finally {
harness.server.stop();
harness.db.close();
}
});
it("rejects blank URLs during work product creation", async () => {
const harness = await createHarness();
try {
const response = await fetch(`${harness.baseUrl}/api/issues/${harness.taskId}/work-products`, {
method: "POST",
headers: harness.headers,
body: JSON.stringify({
type: "link",
provider: "external",
title: "Blank URL",
url: " ",
}),
});
expect(response.status).toBe(400);
expect(harness.db.queryAll("select id from work_products")).toHaveLength(0);
} finally {
harness.server.stop();
harness.db.close();
}
});
it("rejects custom schemes during work product creation", async () => {
const harness = await createHarness();
try {
const response = await fetch(`${harness.baseUrl}/api/issues/${harness.taskId}/work-products`, {
method: "POST",
headers: harness.headers,
body: JSON.stringify({
type: "link",
provider: "external",
title: "Local scheme",
url: "vscode://file/etc/passwd",
}),
});
expect(response.status).toBe(400);
expect(harness.db.queryAll("select id from work_products")).toHaveLength(0);
} finally {
harness.server.stop();
harness.db.close();
}
});
it("rejects credential-bearing URLs during work product creation", async () => {
const harness = await createHarness();
try {
const response = await fetch(`${harness.baseUrl}/api/issues/${harness.taskId}/work-products`, {
method: "POST",
headers: harness.headers,
body: JSON.stringify({
type: "link",
provider: "external",
title: "Credential URL",
url: "https://user:pass@example.com/reports/q1",
}),
});
expect(response.status).toBe(400);
expect(harness.db.queryAll("select id from work_products")).toHaveLength(0);
} finally {
harness.server.stop();
harness.db.close();
}
});
it("stores http links for later confirmation during open", async () => {
const harness = await createHarness();
try {
const response = await fetch(`${harness.baseUrl}/api/issues/${harness.taskId}/work-products`, {
method: "POST",
headers: harness.headers,
body: JSON.stringify({
type: "link",
provider: "external",
title: "External report",
url: " https://example.com/reports/q1 ",
}),
});
expect(response.status).toBe(201);
const row = harness.db.queryAll<{ url: string }>("select url from work_products limit 1")[0];
expect(row?.url).toBe("https://example.com/reports/q1");
} finally {
harness.server.stop();
harness.db.close();
}
});
it("rejects custom schemes during work product updates", async () => {
const harness = await createHarness();
try {
const id = harness.db.core.documents.createWorkProduct({
companyId: harness.companyId,
taskId: harness.taskId,
type: "link",
provider: "external",
title: "External report",
url: "https://example.com/reports/q1",
});
const response = await fetch(`${harness.baseUrl}/api/work-products/${id}`, {
method: "PATCH",
headers: harness.headers,
body: JSON.stringify({ url: "file:///etc/passwd" }),
});
expect(response.status).toBe(400);
const row = harness.db.queryAll<{ url: string }>("select url from work_products where id = ? limit 1", id)[0];
expect(row?.url).toBe("https://example.com/reports/q1");
} finally {
harness.server.stop();
harness.db.close();
}
});
});
@@ -1,482 +0,0 @@
import { mkdirSync, writeFileSync } from "node:fs";
import { mkdtemp, mkdir, rm, writeFile, unlink } from "node:fs/promises";
import { dirname, join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
import {
reconcileWorkspaceArtifacts,
type ArtifactProjectorRunContext,
} from "../../src/main/agent-runtime/artifact-projector";
/**
* Regression coverage for the `work_products` retirement + visibility
* contract. The projector stamps `status = 'archived'` when a file
* disappears; every reader (renderer snapshot, task list, open target
* resolver, deliverable signal) must honour that tombstone so the
* DocumentCenter stops surfacing removed files and the review gate
* stops counting deleted artifacts as produced output.
*/
const tempDirs: string[] = [];
async function createTempDir(prefix: string): Promise<string> {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
interface Harness {
db: AppDatabase;
companyId: string;
workspaceId: string;
workspacePath: string;
agentId: string;
taskId: string;
runId: string;
}
async function createHarness(): Promise<Harness> {
const profileDir = await createTempDir("agentcompany-workproducts-");
const workspacePath = await createTempDir("agentcompany-workspace-");
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Work Products Co",
description: "Tests archived visibility, bulk retirement, deliverable signal",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: workspacePath,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Artifact Engineer",
role: "Engineer",
title: "Artifact Engineer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Writes files",
budgetMonthlyUsd: 100,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Produce artifact",
description: "",
assigneeAgentId: agentId,
workspaceId,
priority: "medium",
status: "in_progress",
});
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
return {
db,
companyId,
workspaceId,
workspacePath,
agentId,
taskId,
runId: run.id,
};
}
function registerArtifact(harness: Harness, relativePath: string): string {
const absPath = join(harness.workspacePath, relativePath);
mkdirSync(dirname(absPath), { recursive: true });
writeFileSync(absPath, "tracked artifact", "utf8");
return harness.db.core.documents.createWorkProduct({
companyId: harness.companyId,
taskId: harness.taskId,
workspaceId: harness.workspaceId,
type: "file",
provider: "filesystem",
externalId: relativePath,
title: relativePath,
url: null,
createdByRunId: harness.runId,
});
}
describe("work_products archived visibility", () => {
it("excludes archived rows from the renderer snapshot", async () => {
const harness = await createHarness();
try {
const activeId = registerArtifact(harness, "docs/active.md");
const archivedId = registerArtifact(harness, "docs/retired.md");
harness.db.core.documents.updateWorkProduct(archivedId, harness.companyId, {
status: "archived",
lastTouchedByRunId: harness.runId,
});
const snapshot = harness.db.projection.snapshot.listSnapshot();
const visibleIds = snapshot.workProducts.map((wp) => wp.id);
expect(visibleIds).toContain(activeId);
expect(visibleIds).not.toContain(archivedId);
} finally {
harness.db.close();
}
});
it("excludes archived rows from the task-scoped listing", async () => {
const harness = await createHarness();
try {
const activeId = registerArtifact(harness, "src/feature.ts");
const archivedId = registerArtifact(harness, "src/removed.ts");
harness.db.core.documents.updateWorkProduct(archivedId, harness.companyId, {
status: "archived",
lastTouchedByRunId: harness.runId,
});
const rows = harness.db.core.documents.listWorkProductsForTask(harness.taskId, harness.companyId);
const ids = rows.map((r) => r.id);
expect(ids).toContain(activeId);
expect(ids).not.toContain(archivedId);
} finally {
harness.db.close();
}
});
it("refuses to resolve an archived work product open target", async () => {
const harness = await createHarness();
try {
const id = registerArtifact(harness, "notes/ghost.md");
expect(
harness.db.core.documents.resolveWorkProductTarget(id, harness.companyId),
).not.toBeNull();
harness.db.core.documents.updateWorkProduct(id, harness.companyId, {
status: "archived",
lastTouchedByRunId: harness.runId,
});
expect(
harness.db.core.documents.resolveWorkProductTarget(id, harness.companyId),
).toMatchObject({ kind: "error", reason: "archived" });
} finally {
harness.db.close();
}
});
it("resolves workspace-prefixed filesystem rows through another company workspace when the bound root misses", async () => {
const harness = await createHarness();
try {
const parentPath = await createTempDir("agentcompany-parent-workspace-");
const projectPath = join(parentPath, "booking-app");
await mkdir(projectPath, { recursive: true });
await writeFile(join(projectPath, "AGENTS.md"), "instructions", "utf8");
harness.db.core.workspaces.saveWorkspace({
companyId: harness.companyId,
name: "Parent",
localPath: parentPath,
repoUrl: "",
repoRef: "",
isPrimary: false,
});
const id = harness.db.core.documents.createWorkProduct({
companyId: harness.companyId,
taskId: harness.taskId,
workspaceId: harness.workspaceId,
type: "file",
provider: "filesystem",
externalId: "booking-app/AGENTS.md",
title: "AGENTS.md",
url: null,
createdByRunId: harness.runId,
});
expect(
harness.db.core.documents.resolveWorkProductTarget(id, harness.companyId),
).toEqual({ kind: "path", value: join(projectPath, "AGENTS.md") });
} finally {
harness.db.close();
}
});
it("reports missing filesystem rows when no company workspace contains the file", async () => {
const harness = await createHarness();
try {
const id = registerArtifact(harness, "docs/missing.md");
await unlink(join(harness.workspacePath, "docs/missing.md"));
expect(
harness.db.core.documents.resolveWorkProductTarget(id, harness.companyId),
).toMatchObject({ kind: "error", reason: "path_missing" });
} finally {
harness.db.close();
}
});
});
describe("work_products bulk retirement", () => {
it("registers changed artifacts from non-exclusive observation workspaces", async () => {
const harness = await createHarness();
try {
const ctx: ArtifactProjectorRunContext = {
runId: harness.runId,
agentId: harness.agentId,
companyId: harness.companyId,
taskId: harness.taskId,
workspaceId: null,
workspacePath: harness.workspacePath,
workspaceMode: "none",
workspaceBaseline: {
mtimeByRelPath: new Map<string, number>(),
capturedAt: Date.now(),
truncated: false,
},
};
await mkdir(join(harness.workspacePath, "reports"), { recursive: true });
await writeFile(join(harness.workspacePath, "reports", "non-exclusive.md"), "observed", "utf8");
const outcomes = reconcileWorkspaceArtifacts(harness.db, ctx);
expect(outcomes).toEqual([
expect.objectContaining({
kind: "inserted",
relativePath: "reports/non-exclusive.md",
}),
]);
expect(
harness.db.projection.snapshot.listSnapshot().workProducts.some(
(entry) => entry.externalId === "reports/non-exclusive.md",
),
).toBe(true);
} finally {
harness.db.close();
}
});
it("registers every changed artifact past the previous 50-path budget", async () => {
const harness = await createHarness();
try {
const paths: string[] = [];
await mkdir(join(harness.workspacePath, "bulk"), { recursive: true });
for (let i = 0; i < 120; i += 1) {
const rel = `bulk/new-${i}.md`;
paths.push(rel);
await writeFile(join(harness.workspacePath, rel), `artifact ${i}`, "utf8");
}
const outcomes = reconcileWorkspaceArtifacts(harness.db, {
runId: harness.runId,
agentId: harness.agentId,
companyId: harness.companyId,
taskId: harness.taskId,
workspaceId: null,
workspacePath: harness.workspacePath,
workspaceMode: "none",
workspaceBaseline: {
mtimeByRelPath: new Map<string, number>(),
capturedAt: Date.now(),
truncated: false,
},
});
expect(outcomes.filter((o) => o.kind === "inserted")).toHaveLength(120);
const visible = new Set(
harness.db.projection.snapshot.listSnapshot().workProducts.map((entry) => entry.externalId),
);
for (const path of paths) {
expect(visible.has(path)).toBe(true);
}
} finally {
harness.db.close();
}
});
it("retires deleted artifacts from non-exclusive observation workspaces", async () => {
const harness = await createHarness();
try {
const relPath = "reports/non-exclusive-retired.md";
const workProductId = registerArtifact(harness, relPath);
await unlink(join(harness.workspacePath, relPath));
const outcomes = reconcileWorkspaceArtifacts(harness.db, {
runId: harness.runId,
agentId: harness.agentId,
companyId: harness.companyId,
taskId: harness.taskId,
workspaceId: null,
workspacePath: harness.workspacePath,
workspaceMode: "none",
workspaceBaseline: {
mtimeByRelPath: new Map<string, number>([[relPath, Date.now()]]),
capturedAt: Date.now(),
truncated: false,
},
});
expect(outcomes).toEqual([
expect.objectContaining({
kind: "retired",
relativePath: relPath,
}),
]);
expect(
harness.db.core.documents.resolveWorkProductTarget(workProductId, harness.companyId),
).toMatchObject({ kind: "error", reason: "archived" });
} finally {
harness.db.close();
}
});
it("archives every deletion past the previous 50-path budget", async () => {
const harness = await createHarness();
try {
const ids: string[] = [];
const paths: string[] = [];
for (let i = 0; i < 120; i += 1) {
const rel = `build/asset-${i}.bin`;
paths.push(rel);
ids.push(registerArtifact(harness, rel));
}
const archived = harness.db.core.documents.archiveWorkProductsByExternalPaths({
companyId: harness.companyId,
workspaceId: harness.workspaceId,
provider: "filesystem",
paths,
lastTouchedByRunId: harness.runId,
});
expect(archived.length).toBe(120);
const archivedIdSet = new Set(archived.map((r) => r.id));
for (const id of ids) {
expect(archivedIdSet.has(id)).toBe(true);
}
const visible = harness.db.projection.snapshot.listSnapshot().workProducts;
expect(visible.length).toBe(0);
} finally {
harness.db.close();
}
});
it("skips rows that are already archived so repeated sweeps stay idempotent", async () => {
const harness = await createHarness();
try {
const id = registerArtifact(harness, "tmp/scratch.log");
harness.db.core.documents.archiveWorkProductsByExternalPaths({
companyId: harness.companyId,
workspaceId: harness.workspaceId,
provider: "filesystem",
paths: ["tmp/scratch.log"],
lastTouchedByRunId: harness.runId,
});
const second = harness.db.core.documents.archiveWorkProductsByExternalPaths({
companyId: harness.companyId,
workspaceId: harness.workspaceId,
provider: "filesystem",
paths: ["tmp/scratch.log"],
lastTouchedByRunId: harness.runId,
});
expect(second).toHaveLength(0);
expect(
harness.db.core.documents.resolveWorkProductTarget(id, harness.companyId),
).toMatchObject({ kind: "error", reason: "archived" });
} finally {
harness.db.close();
}
});
it("archives rows via the reconcile sweep when the baseline detects a missing file", async () => {
const harness = await createHarness();
try {
await mkdir(join(harness.workspacePath, "reports"), { recursive: true });
const relPath = "reports/summary.md";
const absPath = join(harness.workspacePath, relPath);
await writeFile(absPath, "initial content", "utf8");
const workProductId = registerArtifact(harness, relPath);
expect(
harness.db.core.documents.resolveWorkProductTarget(workProductId, harness.companyId),
).not.toBeNull();
const baseline = {
mtimeByRelPath: new Map<string, number>([[relPath, Date.now() / 1000]]),
capturedAt: Date.now(),
truncated: false,
};
await unlink(absPath);
const ctx: ArtifactProjectorRunContext = {
runId: harness.runId,
agentId: harness.agentId,
companyId: harness.companyId,
taskId: harness.taskId,
workspaceId: harness.workspaceId,
workspacePath: harness.workspacePath,
workspaceMode: "exclusive",
workspaceBaseline: baseline,
};
const outcomes = reconcileWorkspaceArtifacts(harness.db, ctx);
const retired = outcomes.filter((o) => o.kind === "retired");
expect(retired.map((o) => (o as { relativePath: string }).relativePath)).toContain(relPath);
expect(
harness.db.core.documents.resolveWorkProductTarget(workProductId, harness.companyId),
).toMatchObject({ kind: "error", reason: "archived" });
} finally {
harness.db.close();
}
});
});
describe("work_products deliverable signal", () => {
it("does not count an archived row as a produced deliverable", async () => {
const harness = await createHarness();
try {
const id = registerArtifact(harness, "deliverables/final.md");
const beforeArchive = harness.db.core.knowledge.collectRunDeliverableSignals(
harness.runId,
harness.companyId,
harness.taskId,
);
expect(beforeArchive.hasWorkProduct).toBe(true);
harness.db.core.documents.updateWorkProduct(id, harness.companyId, {
status: "archived",
lastTouchedByRunId: harness.runId,
});
const afterArchive = harness.db.core.knowledge.collectRunDeliverableSignals(
harness.runId,
harness.companyId,
harness.taskId,
);
expect(afterArchive.hasWorkProduct).toBe(false);
} finally {
harness.db.close();
}
});
});