This commit is contained in:
ZacharyZhang-NY committed 2026-04-04 12:03:50 -04:00
1 parent 66158e222e
commit 67c757f481
314 files changed
+37945 -22962

No files matched your search

+136 -56
View File
@@ -5,7 +5,33 @@ import { tmpdir } from "node:os";
import { _electron as electron, expect, test } from "@playwright/test";
import { getElectronExecutablePath, getMainEntryPath } from "./launch";
test("first launch onboarding creates a company and opens the desktop shell", async () => {
function buildLaunchEnv(profileDir: string) {
const env: Record<string, string | undefined> = {
...process.env,
AGENTCOMPANY_PROFILE_DIR: profileDir,
AGENTCOMPANY_DISABLE_UPDATES: "true",
AGENTCOMPANY_DISABLE_SINGLE_INSTANCE: "true",
};
delete env.NO_COLOR;
return Object.fromEntries(
Object.entries(env).filter((entry): entry is [string, string] => typeof entry[1] === "string"),
);
}
async function advanceFromWelcome(page: import("@playwright/test").Page) {
await expect(page.getByText("AgentCompany")).toBeVisible();
await expect(page.getByText("Build your AI company here.")).toBeVisible();
await page.getByRole("button", { name: /Get Started/i }).click();
}
async function advancePastNodeCheck(page: import("@playwright/test").Page) {
const continueButton = page.getByRole("button", { name: /^Continue$/i });
await expect(page.getByText("Check Node.js")).toBeVisible();
await expect(continueButton).toBeEnabled({ timeout: 15_000 });
await continueButton.click();
}
test("first launch onboarding advances into runtime setup and shell-ready flow", async () => {
const entry = getMainEntryPath();
expect(existsSync(entry)).toBeTruthy();
@@ -16,46 +42,38 @@ test("first launch onboarding creates a company and opens the desktop shell", as
executablePath: getElectronExecutablePath(),
args: [entry],
cwd: process.cwd(),
env: {
...process.env,
AGENTCOMPANY_PROFILE_DIR: profileDir,
AGENTCOMPANY_DISABLE_UPDATES: "true",
AGENTCOMPANY_DISABLE_SINGLE_INSTANCE: "true",
},
env: buildLaunchEnv(profileDir),
});
try {
const page = await app.firstWindow();
await expect(page.getByText("AgentCompany Desktop")).toBeVisible();
await advanceFromWelcome(page);
await advancePastNodeCheck(page);
await expect(page.getByText("Set up runtimes")).toBeVisible();
const providersContinue = page.getByRole("button", { name: /^Continue$/i });
if (!(await providersContinue.isEnabled())) {
await expect(page.getByText(/Set up one runtime to continue\./i)).toBeVisible();
await expect(page.getByText(/Ready|Login required|Not installed|Checking|Error/i).first()).toBeVisible();
return;
}
await providersContinue.click();
await expect(page.getByLabel("Company name")).toBeVisible();
await page.getByLabel("Company name").fill("Orbit Foundry");
await page.getByLabel("Workspace folder").fill(workspaceDir);
const continueButton = page.getByRole("button", { name: /^Continue$/i });
if (await continueButton.isEnabled()) {
await continueButton.click();
await page.getByLabel("Goal").fill("Launch the company");
await page.getByRole("button", { name: /Launch Company/i }).click();
await expect(page.getByRole("heading", { name: /Orbit Foundry is live/i })).toBeVisible();
await expect(page.getByText("Initial run")).toBeVisible();
await page.getByRole("button", { name: /Open control center/i }).click();
await page.getByRole("button", { name: /^Continue$/i }).click();
await expect(page.getByRole("heading", { name: "Runs" })).toBeVisible();
// Company name is rendered in a custom dropdown trigger button in the sidebar.
await expect(page.locator("aside button", { hasText: "Orbit Foundry" }).first()).toBeVisible();
const showConsoleButton = page.getByRole("button", { name: /show console/i });
const hideConsoleButton = page.getByRole("button", { name: /hide console/i });
if (await hideConsoleButton.isVisible().catch(() => false)) {
await expect(page.getByText("Console", { exact: true })).toBeVisible();
await hideConsoleButton.click();
await expect(page.getByText("Console", { exact: true })).toBeHidden();
} else {
await expect(showConsoleButton).toBeVisible();
}
await showConsoleButton.click();
await expect(page.getByText("Console", { exact: true })).toBeVisible();
} else {
await expect(page.getByText(/Install, authenticate, and fully validate at least one supported CLI runtime/i)).toBeVisible();
await expect(page.getByText(/cannot be used for autonomous execution/i).first()).toBeVisible();
}
await expect(page.getByText("How should your company operate?")).toBeVisible();
await page.getByRole("button", { name: /^Continue$/i }).click();
await expect(page.getByLabel("Goal")).toBeVisible();
await page.getByLabel("Goal").fill("Launch the company");
await page.getByRole("button", { name: /Launch Company/i }).click();
await expect(page.getByRole("heading", { name: /Orbit Foundry.*is live/i })).toBeVisible();
await expect(page.getByRole("button", { name: /Open Control Center/i })).toBeVisible();
} finally {
await app.close();
await rm(profileDir, { recursive: true, force: true });
@@ -63,8 +81,6 @@ test("first launch onboarding creates a company and opens the desktop shell", as
}
});
// NOTE: On headless Linux (CI), run via: xvfb-run pnpm test:e2e
// See RESEARCH.md Pitfall 5 for details.
test("platform launch: app opens, window renders, screenshot captured", async () => {
const entry = getMainEntryPath();
expect(existsSync(entry)).toBeTruthy();
@@ -77,41 +93,105 @@ test("platform launch: app opens, window renders, screenshot captured", async ()
executablePath: getElectronExecutablePath(),
args: [entry],
cwd: process.cwd(),
env: {
...process.env,
AGENTCOMPANY_PROFILE_DIR: profileDir,
AGENTCOMPANY_DISABLE_UPDATES: "true",
AGENTCOMPANY_DISABLE_SINGLE_INSTANCE: "true",
},
env: buildLaunchEnv(profileDir),
});
try {
const page = await app.firstWindow();
await expect(page.getByText("AgentCompany")).toBeVisible({ timeout: 30_000 });
await expect(page.getByText("Build your AI company here.")).toBeVisible();
// Verify the window appeared and has content
await expect(page.getByText("AgentCompany Desktop")).toBeVisible({ timeout: 30_000 });
// Verify the window has reasonable dimensions (not collapsed or zero-size)
const viewport = page.viewportSize();
expect(viewport).not.toBeNull();
if (viewport) {
expect(viewport.width).toBeGreaterThan(400);
expect(viewport.height).toBeGreaterThan(300);
}
const dimensions = viewport ?? await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight,
}));
expect(dimensions.width).toBeGreaterThan(400);
expect(dimensions.height).toBeGreaterThan(300);
// Capture screenshot for D-07 validation evidence
const platformName = process.platform === "darwin" ? "macos"
: process.platform === "win32" ? "windows"
: "linux";
const screenshotPath = join(screenshotDir, `launch-${platformName}.png`);
await page.screenshot({ path: screenshotPath, fullPage: true });
// Verify screenshot was actually written
expect(existsSync(screenshotPath)).toBeTruthy();
console.log(`[platform-launch] Screenshot saved: ${screenshotPath}`);
} finally {
await app.close();
await rm(profileDir, { recursive: true, force: true });
}
});
test("onboarding model pool persists across relaunch for a ready connector", async () => {
const entry = getMainEntryPath();
expect(existsSync(entry)).toBeTruthy();
const profileDir = await mkdtemp(join(tmpdir(), "agentcompany-model-pool-"));
const workspaceDir = await mkdtemp(join(tmpdir(), "agentcompany-model-pool-workspace-"));
const launchApp = async () => electron.launch({
executablePath: getElectronExecutablePath(),
args: [entry],
cwd: process.cwd(),
env: buildLaunchEnv(profileDir),
});
let app = await launchApp();
try {
const page = await app.firstWindow();
await advanceFromWelcome(page);
await advancePastNodeCheck(page);
await expect(page.getByText("Set up runtimes")).toBeVisible();
const providersContinue = page.getByRole("button", { name: /^Continue$/i });
if (!(await providersContinue.isEnabled())) {
await expect(page.getByText(/Set up one runtime to continue\./i)).toBeVisible();
return;
}
await providersContinue.click();
await expect(page.getByLabel("Company name")).toBeVisible();
const enabledCountLabel = page.getByText(/Models \(\d+\/\d+ enabled\)/i);
await expect(enabledCountLabel).toBeVisible();
const initialCount = await enabledCountLabel.textContent();
const switches = page.getByRole("switch");
const switchCount = await switches.count();
if (switchCount < 2) {
return;
}
await switches.nth(1).click();
const updatedCount = await enabledCountLabel.textContent();
expect(updatedCount).not.toBe(initialCount);
} finally {
await app.close();
}
app = await launchApp();
try {
const page = await app.firstWindow();
await advanceFromWelcome(page);
await advancePastNodeCheck(page);
const providersContinue = page.getByRole("button", { name: /^Continue$/i });
if (!(await providersContinue.isEnabled())) {
await expect(page.getByText(/Set up at least one provider to power your agents\./i)).toBeVisible();
return;
}
await providersContinue.click();
await expect(page.getByLabel("Company name")).toBeVisible();
const enabledCountLabel = page.getByText(/Models \(\d+\/\d+ enabled\)/i);
await expect(enabledCountLabel).toBeVisible();
await page.getByLabel("Company name").fill("Persisted Model Pool Co");
await page.getByLabel("Workspace folder").fill(workspaceDir);
await page.getByRole("button", { name: /^Continue$/i }).click();
await expect(page.getByText("How should your company operate?")).toBeVisible();
} finally {
await app.close();
await rm(profileDir, { recursive: true, force: true });
await rm(workspaceDir, { recursive: true, force: true });
}
});
+2
View File
@@ -1,4 +1,5 @@
import { execFileSync } from "node:child_process";
import { createElectronLaunchEnv } from "./launch";
/**
* Playwright globalSetup: build the Electron app from source before E2E tests.
@@ -9,6 +10,7 @@ export default function globalSetup() {
stdio: "inherit",
cwd: process.cwd(),
timeout: 120_000,
env: createElectronLaunchEnv({}),
shell: process.platform === "win32",
});
}
+36 -2
View File
@@ -1,12 +1,46 @@
import { createRequire } from "node:module";
import { resolve } from "node:path";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const require = createRequire(import.meta.url);
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
const EXPERIMENTAL_WARNING_FLAG = "--disable-warning=ExperimentalWarning";
export function getElectronExecutablePath() {
return require("electron");
}
export function getMainEntryPath() {
return resolve(process.cwd(), "out/main/index.js");
return resolve(repoRoot, "out/main/index.js");
}
export function createElectronLaunchEnv(
overrides: Record<string, string>,
) : Record<string, string> {
const env: Record<string, string> = {};
for (const [key, value] of Object.entries(process.env)) {
if (typeof value === "string") {
env[key] = value;
}
}
Object.assign(env, overrides);
if (env.FORCE_COLOR && env.NO_COLOR) {
delete env.NO_COLOR;
}
const existingNodeOptions = env.NODE_OPTIONS?.trim();
if (!existingNodeOptions) {
env.NODE_OPTIONS = EXPERIMENTAL_WARNING_FLAG;
return env;
}
if (!existingNodeOptions.includes(EXPERIMENTAL_WARNING_FLAG)) {
env.NODE_OPTIONS = `${existingNodeOptions} ${EXPERIMENTAL_WARNING_FLAG}`;
}
return env;
}
@@ -0,0 +1,174 @@
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.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.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Model Switch Co",
description: "Tests post-hire model switching",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Frontend Engineer",
status: "idle",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: "gpt-5.4",
capabilities: "Build frontend features",
budgetMonthlyUsd: 200,
});
const { enqueueWorkerRun, taskRunner } = createHarness(db);
const firstTaskId = db.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.finishRun({
runId: firstRun.data,
status: "succeeded",
summary: "Done",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gpt-5.4",
sessionDisplayId: null,
costUsd: null,
unattributedCost: true,
});
db.saveAgent({
id: agentId,
companyId,
name: "Engineer",
role: "Engineer",
title: "Frontend Engineer",
status: "idle",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: "gpt-5.3-codex",
capabilities: "Build frontend features",
budgetMonthlyUsd: 200,
});
const secondTaskId = db.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();
});
});
@@ -0,0 +1,212 @@
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";
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.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Recursive Org Co",
description: "Tests manager-driven recursive hires",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 220,
department: "engineering",
});
const taskId = db.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.createRun({ companyId, taskId, agentId: ctoId, workspaceId, connectorId: "codex_local" });
const token = createAgentToken(ctoId, companyId, run.id);
const server = createApiServer(db, () => {}, () => null);
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: "Engineering IC",
role: "Engineer",
title: "Frontend Engineer",
department: "engineering",
connectorId: "codex_local",
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.getAgent(body.agentId);
expect(pendingHire.status).toBe("pending_approval");
expect(pendingHire.reportsTo).toBe(harness.ctoId);
const approval = harness.db.getApprovalRecord(body.approvalId);
expect(approval.type).toBe("hire_agent");
expect(approval.requestedByAgentId).toBe(harness.ctoId);
expect(approval.relatedAgentId).toBe(body.agentId);
harness.db.decideApproval({
approvalId: body.approvalId,
state: "approved",
decisionNote: "Approved by board",
});
const approvedHire = harness.db.getAgent(body.agentId);
expect(approvedHire.status).toBe("idle");
expect(harness.db.getDirectReports(harness.ctoId, harness.companyId).map((agent) => agent.id)).toContain(body.agentId);
expect(harness.db.getDirectReports(harness.ceoId, harness.companyId).map((agent) => agent.id)).not.toContain(body.agentId);
expect(harness.db.getChainOfCommand(body.agentId).map((agent) => agent.id)).toEqual([harness.ctoId, harness.ceoId]);
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("manager cannot route a hire outside their reporting branch", async () => {
const harness = await createHarness();
try {
const cmoId = harness.db.saveAgent({
companyId: harness.companyId,
name: "CMO",
role: "CMO",
title: "Chief Marketing Officer",
status: "active",
reportsTo: harness.ceoId,
connectorId: "codex_local",
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: "codex_local",
workspaceId: harness.workspaceId,
model: null,
capabilities: "Should not report outside the CTO branch",
budgetMonthlyUsd: 180,
}),
});
expect(response.status).toBe(403);
const body = await response.json() as { error: string };
expect(body.error).toContain("must report to you or a manager in your reporting chain");
} finally {
harness.server.stop();
harness.db.close();
}
});
});
+164
View File
@@ -0,0 +1,164 @@
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";
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.saveCompany({ name: "Orbit Foundry", description: "API test", status: "active" });
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "Helios",
role: "CEO",
title: "Lead agent",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Operate the company",
budgetMonthlyUsd: 100,
});
const taskId = db.saveTask({
companyId,
title: "Ship the API",
description: "Capture structured run events.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
const run = db.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "codex_local" });
const token = createAgentToken(agentId, companyId, run.id);
const server = createApiServer(db, () => {}, () => null);
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.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.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();
}
});
});
@@ -0,0 +1,201 @@
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 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.hasActiveRun(filter),
);
return { deps, taskRunner };
}
describe("connector model enforcement", () => {
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.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Runtime Enforcement Co",
description: "Blocks runs when agent models are disabled",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: "gpt-5.4",
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const taskId = db.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.setDisabledModels("codex_local", [{ 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.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.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Recovered Run Co",
description: "Rejects invalid queued runs during recovery",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: "gpt-5.4",
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const taskId = db.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.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "codex_local",
});
const checkoutSuccess = db.checkoutTask(taskId, run.id);
expect(checkoutSuccess).toBe(true);
db.setDisabledModels("codex_local", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
const { taskRunner } = createOrchestrationHarness(db);
taskRunner.resumeQueuedRuns();
const recoveredRun = db.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();
});
});
+1 -1
View File
@@ -27,7 +27,7 @@ vi.mock("electron", () => ({
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 } from "../../src/main/task-quality-gates";
import { validateTaskCreation, checkCapabilityMatch as _checkCapabilityMatch } from "../../src/main/task-quality-gates";
import type { ConnectorId } from "../../src/shared/types";
// ---------------------------------------------------------------------------
@@ -0,0 +1,298 @@
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";
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.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Document Provenance Co",
description: "Tests document revision provenance through run completion",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "Research Lead",
role: "Researcher",
title: "Research Lead",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Research and synthesis",
budgetMonthlyUsd: 180,
});
const taskId = db.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.setTaskType(taskId, "research");
const existingDocumentId = db.saveDocument({
companyId,
type: "knowledge_article",
title: "Market report",
content: "Initial report body",
authorAgentId: agentId,
projectId: null,
goalId: null,
status: "draft",
});
const run = db.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "codex_local",
});
const token = createAgentToken(agentId, companyId, run.id);
const server = createApiServer(db, () => {}, () => null);
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.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,
costUsd: null,
}, () => false);
const task = harness.db.getTask(harness.taskId);
expect(task.status).toBe("in_review");
} 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.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.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();
}
});
});
@@ -0,0 +1,303 @@
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";
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,
queueTaskRun: () => ({ ok: true, data: "run-1" }),
});
}
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.getAgent(summary.leadAgentId);
const task = db.getTask(summary.taskId);
const connector = db.getConnector(agent.connectorId);
const workspace = summary.workspaceId ? db.getWorkspace(summary.workspaceId) : null;
const company = db.listSnapshot().companies.find((entry) => entry.id === summary.companyId);
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.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("codex_local");
const enabledModel = codexDefinition.module.models?.[0]?.id;
const disabledModel = codexDefinition.module.models?.[1]?.id;
if (!enabledModel || !disabledModel) {
throw new Error("codex_local requires at least two models for this integration test.");
}
db.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
db.setDisabledModels("codex_local", [{ id: disabledModel, label: disabledModel }]);
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: "codex_local",
connectorModel: enabledModel,
});
if (!summary.leadAgentId) {
throw new Error("Expected lead agent id from onboarding bootstrap.");
}
const agent = db.getAgent(summary.leadAgentId);
expect(agent.connectorId).toBe("codex_local");
expect(agent.model).toBe(enabledModel);
expect(db.getDisabledModels("codex_local")).toEqual([{ id: disabledModel, label: disabledModel }]);
expect(db.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("**Codex**"));
expect(codexConnectorLine).toBeDefined();
expect(codexConnectorLine).not.toContain(`, ${disabledModel},`);
expect(codexConnectorLine).not.toContain(` ${disabledModel}.`);
db.close();
});
it("uses compat connector csv models minus disabled entries in 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();
db.saveConnector({
id: "claude_compatible",
command: "claude",
model: "model-alpha,model-beta,model-gamma",
envBindingText: "",
notes: "Custom provider",
});
db.updateConnectorHealth({
id: "claude_compatible",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
db.setDisabledModels("claude_compatible", [{ id: "model-beta", label: "model-beta" }]);
const summary = createBootstrapHarness(db).bootstrapOnboarding({
companyName: "Compat Governance Co",
companyDescription: "Tests compat model governance",
workspacePath: workspaceDir,
autoApproveHires: true,
reviewDeliverables: false,
executionMode: "ai_ceo",
goalTitle: "Launch compat company",
goalDescription: "Ensure compat model pools survive onboarding and runtime prompt assembly.",
connectorId: "claude_compatible",
connectorModel: "model-alpha",
});
const payload = buildKickoffPayload(db, summary);
expect(payload.systemPrompt).not.toBeNull();
const systemPrompt = payload.systemPrompt ?? "";
expect(systemPrompt).toContain("Enabled models: model-alpha, model-gamma.");
expect(systemPrompt).not.toContain("model-beta");
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.updateConnectorHealth({
id: "codex_local",
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: "codex_local",
connectorModel: "",
});
if (!summary.leadAgentId) {
throw new Error("Expected lead agent id from onboarding bootstrap.");
}
const agent = db.getAgent(summary.leadAgentId);
expect(agent.model).toBeNull();
const payload = buildKickoffPayload(db, summary);
const defaultModel = getConnectorDefinition("codex_local").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("codex_local");
const defaultModel = codexDefinition.defaultModel;
const fallbackModel = codexDefinition.module.models?.find((model) => model.id !== defaultModel)?.id;
if (!defaultModel || !fallbackModel) {
throw new Error("codex_local requires a default and fallback model for reroute testing.");
}
db.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
db.setDisabledModels("codex_local", [{ id: defaultModel, label: defaultModel }]);
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: "codex_local",
connectorModel: "",
});
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("codex_local");
const disabledModel = codexDefinition.module.models?.[0]?.id;
if (!disabledModel) {
throw new Error("codex_local requires a model to validate onboarding rejection.");
}
db.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
db.setDisabledModels("codex_local", [{ id: disabledModel, label: disabledModel }]);
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: "codex_local",
connectorModel: disabledModel,
})).toThrow(`Codex does not allow model "${disabledModel}".`);
db.close();
});
});
@@ -0,0 +1,227 @@
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";
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.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Skill Match Co",
description: "Tests skill-aware assignment",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
db.saveAgent({
companyId,
name: "Generic Engineer",
role: "Engineer",
title: "Engineer",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "General software delivery",
budgetMonthlyUsd: 200,
});
const styledEngineerId = db.saveAgent({
companyId,
name: "Frontend Specialist",
role: "Engineer",
title: "Frontend Specialist",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "General software delivery",
budgetMonthlyUsd: 200,
});
const skillId = randomUUID();
const now = new Date().toISOString();
db.upsertCompanySkill({
id: skillId,
companyId,
key: "frontend/react-ui",
slug: "react-ui",
name: "React UI",
description: "Build React components with hooks, Tailwind, and accessible layouts",
markdown: "# React UI\nUse when building React components with hooks and Tailwind.",
sourceType: "local_path",
sourceLocator: null,
sourceRef: null,
trustLevel: "markdown_only",
compatibility: "compatible",
fileInventory: "[]",
metadata: null,
createdAt: now,
updatedAt: now,
});
db.assignSkillToAgent(styledEngineerId, skillId);
const taskId = db.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.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.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Registry Co",
description: "Tests capability registry prompt injection",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const skillId = randomUUID();
const now = new Date().toISOString();
db.upsertCompanySkill({
id: skillId,
companyId,
key: "frontend/react-ui",
slug: "react-ui",
name: "React UI",
description: "Use when building React interfaces",
markdown: "# React UI\nUse 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.listSnapshot();
const prompt = buildTaskPrompt({
agent: db.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 === "codex_local")
.map((connector) => ({ id: connector.id, label: connector.label, status: connector.status, enabledModels: ["gpt-5.4"] })),
companySkillCatalog: db.listCompanySkills(companyId).map((skill) => ({
name: skill.name,
slug: skill.slug,
description: skill.description,
})),
companyWorkspaces: snapshot.workspaces
.filter((workspace) => workspace.companyId === companyId)
.map((workspace) => ({ name: workspace.name, localPath: workspace.localPath })),
companyDocuments: [],
skillEntries: [
{
name: "React Standards",
whenToUse: "building React UI",
allowedTools: ["read", "edit"],
effort: "high",
contextMode: "inline",
},
],
skillNames: ["React Standards"],
});
expect(prompt).toContain("# Company Capability Registry");
expect(prompt).toContain("React UI (`react-ui`)");
expect(prompt).toContain("Primary:");
expect(prompt).toContain("Ready Connectors");
expect(prompt).toContain("Your domain skills:");
db.close();
});
});
@@ -0,0 +1,253 @@
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 { createThreadIntegration } from "../../src/main/thread-integration";
import { createCheckpointEngine } from "../../src/main/checkpoint-engine";
import { dispatchAgentMessage, HUMAN_SENDER_ID } from "../../src/main/message-dispatch";
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.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Thread Wait Co",
description: "Tests cofounder wait states and replies",
status: "active",
executionMode: "ai_cofounder",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: "/tmp/thread-wait",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const engineerId = db.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Lead Engineer",
status: "idle",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Ship features",
budgetMonthlyUsd: 180,
});
const taskId = db.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 };
}
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.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.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.saveAgent({
companyId,
name: "Marketer",
role: "CMO",
title: "Chief Marketing Officer",
status: "idle",
reportsTo: null,
connectorId: "codex_local",
workspaceId: db.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.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.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.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.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.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId);
expect(waitStates).toHaveLength(1);
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true);
db.decideApproval({
approvalId,
state: "approved",
decisionNote: "Approved by board",
});
threadIntegration.onApprovalResolved(approvalId, companyId, "approved");
waitStates = db.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId);
expect(waitStates).toHaveLength(0);
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(false);
db.close();
});
});
+249
View File
@@ -0,0 +1,249 @@
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 {
canAssignTask,
canControlAgent,
canManageGoal,
canManageOrganization,
canManageProject,
canRequestHire,
} from "../../src/main/agent-permissions";
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("canManageOrganization", () => {
it("allows executive leaders without direct reports to manage organization growth", async () => {
const profileDir = await createTempDir("agentcompany-agent-perms-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.saveCompany({
name: "Org Permissions Co",
description: "Tests recursive org authority",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 220,
department: "engineering",
});
const engineerId = db.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Software Engineer",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Build features",
budgetMonthlyUsd: 180,
department: "engineering",
});
expect(canManageOrganization(db, companyId, ceoId)).toBe(true);
expect(canManageOrganization(db, companyId, ctoId)).toBe(false);
expect(canRequestHire(db, companyId, ctoId)).toBe(true);
expect(canManageOrganization(db, companyId, engineerId)).toBe(false);
expect(canRequestHire(db, companyId, engineerId)).toBe(false);
db.close();
});
it("allows explicit organization_manage permission to grant recursive org authority", async () => {
const profileDir = await createTempDir("agentcompany-agent-perms-override-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.saveCompany({
name: "Org Permission Override Co",
description: "Tests permission-based org authority",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const agentId = db.saveAgent({
companyId,
name: "Operations Lead",
role: "Specialist",
title: "Operations Specialist",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Coordinate operations",
budgetMonthlyUsd: 180,
department: "operations",
});
expect(canManageOrganization(db, companyId, agentId)).toBe(false);
db.setAgentPermission(agentId, companyId, "organization_manage", true);
expect(canManageOrganization(db, companyId, agentId)).toBe(true);
expect(canRequestHire(db, companyId, agentId)).toBe(true);
db.close();
});
it("manager subtree growth does not become org-wide admin after the first hire", async () => {
const profileDir = await createTempDir("agentcompany-agent-perms-scope-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.saveCompany({
name: "Scoped Authority Co",
description: "Tests that branch growth does not unlock org-wide admin powers",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 220,
department: "engineering",
});
const cmoId = db.saveAgent({
companyId,
name: "CMO",
role: "CMO",
title: "Chief Marketing Officer",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead marketing",
budgetMonthlyUsd: 220,
department: "marketing",
});
const ctoReportId = db.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Frontend Engineer",
status: "active",
reportsTo: ctoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Build frontend features",
budgetMonthlyUsd: 180,
department: "engineering",
});
const cmoTaskId = db.saveTask({
companyId,
title: "Launch campaign",
description: "Owned by the marketing branch",
assigneeAgentId: cmoId,
workspaceId,
priority: "high",
status: "todo",
});
expect(canRequestHire(db, companyId, ctoId)).toBe(true);
expect(canManageOrganization(db, companyId, ctoId)).toBe(false);
expect(canControlAgent(db, companyId, ctoId, ctoReportId)).toBe(true);
expect(canControlAgent(db, companyId, ctoId, cmoId)).toBe(false);
expect(canAssignTask(db, companyId, ctoId, ctoReportId)).toBe(true);
expect(canAssignTask(db, companyId, ctoId, cmoId)).toBe(false);
expect(canManageGoal(db, companyId, ctoId, cmoId)).toBe(false);
expect(canManageProject(db, companyId, ctoId, cmoId)).toBe(false);
expect(canManageGoal(db, companyId, ceoId, cmoId)).toBe(true);
expect(canManageProject(db, companyId, ceoId, cmoId)).toBe(true);
expect(db.getTask(cmoTaskId).assigneeAgentId).toBe(cmoId);
db.close();
});
});
+131
View File
@@ -0,0 +1,131 @@
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 { handleApprovalResolved } from "../../src/main/automation/automation-approval-handler";
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("hire approval routing", () => {
it("approved subordinate hires wake the hire, requester, and CEO", async () => {
const profileDir = await createTempDir("agentcompany-hire-routing-");
const db = new AppDatabase(profileDir);
db.init();
db.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Hire Routing Co",
description: "Tests recursive hire wake routing",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 220,
department: "engineering",
});
const { agentId: hireId, approvalId } = db.createHireRequest({
companyId,
requestedByAgentId: ctoId,
name: "New Engineer",
role: "Engineer",
title: "Frontend Engineer",
department: "engineering",
reportsTo: ctoId,
connectorId: "codex_local",
workspaceId,
model: null,
metadataJson: "{}",
capabilities: "React, TypeScript, Tailwind",
budgetMonthlyUsd: 180,
payloadSummary: "Hire new engineer",
impactSummary: "Need another IC under the CTO",
payloadJson: "{}",
});
db.decideApproval({
approvalId,
state: "approved",
decisionNote: "Approved by board",
});
const wakeAgentIfPossible = vi.fn().mockReturnValue(null);
const dispatchAutomation = vi.fn();
const notify = vi.fn();
handleApprovalResolved({
db,
logger: { info: () => {}, warn: () => {}, error: () => {} },
publishDomainChanged: () => {},
notify,
wakeAgentIfPossible,
dispatchBrowserAction: async () => {},
isQuitting: () => false,
}, {
dispatchAutomation,
handleTaskStatusChange: vi.fn(),
runWorkflowPipeline: async () => {},
trackTimer: vi.fn(),
}, approvalId, companyId, "approved");
expect(db.getAgent(hireId).status).toBe("idle");
expect(dispatchAutomation).toHaveBeenCalledWith("approval_resolved", expect.objectContaining({ companyId, agentId: ctoId }));
expect(dispatchAutomation).toHaveBeenCalledWith("hire_approved", expect.objectContaining({ companyId, agentId: hireId }));
expect(wakeAgentIfPossible).toHaveBeenCalledWith(hireId, companyId, "assignment");
expect(wakeAgentIfPossible).toHaveBeenCalledWith(ceoId, companyId, "assignment");
expect(wakeAgentIfPossible).toHaveBeenCalledWith(ctoId, companyId, "approval_resolved");
expect(notify).toHaveBeenCalledWith(expect.objectContaining({
body: expect.stringContaining("New Engineer"),
}));
db.close();
});
});
@@ -0,0 +1,125 @@
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 { handleApprovalResolved } from "../../src/main/automation/automation-approval-handler";
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("deliverable review feedback writeback", () => {
it("persists high-importance knowledge when the board requests revisions on a deliverable", async () => {
const profileDir = await createTempDir("agentcompany-review-feedback-");
const db = new AppDatabase(profileDir);
db.init();
db.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Review Feedback Co",
description: "Tests review feedback knowledge writeback",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "Research Lead",
role: "Researcher",
title: "Research Lead",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Research and synthesis",
budgetMonthlyUsd: 180,
department: "research",
});
const taskId = db.saveTask({
companyId,
title: "Draft market report",
description: "Produce a market report with concrete evidence and business insight.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "in_review",
});
const documentId = db.saveDocument({
companyId,
type: "knowledge_article",
title: "Market report",
content: "Draft report body",
authorAgentId: agentId,
status: "in_review",
});
const approvalId = db.requestApproval({
companyId,
relatedTaskId: taskId,
requestedByAgentId: agentId,
type: "deliverable_review",
payloadSummary: "Review market report deliverable",
impactSummary: "Board review required before the report is finalized.",
payloadJson: JSON.stringify({ documentIds: [documentId] }),
});
db.decideApproval({
approvalId,
state: "revision_requested",
decisionNote: "Add stronger competitor evidence and a clearer recommendation.",
});
const wakeAgentIfPossible = vi.fn().mockReturnValue(null);
handleApprovalResolved({
db,
logger: { info: () => {}, warn: () => {}, error: () => {} },
publishDomainChanged: () => {},
notify: vi.fn(),
wakeAgentIfPossible,
dispatchBrowserAction: async () => {},
isQuitting: () => false,
}, {
dispatchAutomation: vi.fn(),
handleTaskStatusChange: vi.fn(),
runWorkflowPipeline: async () => {},
trackTimer: vi.fn(),
}, approvalId, companyId, "revision_requested");
const knowledgeEntries = db.listSnapshot().knowledgeBase.filter((entry) => entry.companyId === companyId);
expect(knowledgeEntries).toHaveLength(1);
expect(knowledgeEntries[0]?.category).toBe("lesson_learned");
expect(knowledgeEntries[0]?.importance).toBe("high");
expect(knowledgeEntries[0]?.referencedEntityType).toBe("task");
expect(knowledgeEntries[0]?.referencedEntityId).toBe(taskId);
expect(knowledgeEntries[0]?.content).toContain("revision_requested");
expect(knowledgeEntries[0]?.content).toContain("stronger competitor evidence");
expect(wakeAgentIfPossible).toHaveBeenCalledWith(agentId, companyId, "revision_needed");
expect(db.getTask(taskId).status).toBe("in_progress");
const document = db.listSnapshot().documents.find((entry) => entry.id === documentId);
expect(document?.status).toBe("draft");
db.close();
});
});
+3 -3
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { describe, it, expect, beforeEach } from "vitest";
/**
* Tests for budget incident lifecycle: creation, deduplication (D-08),
@@ -231,7 +231,7 @@ describe("autoCloseExpiredIncidents", () => {
});
it("closes incidents whose window_end is in the past", () => {
const policy = makePolicy();
const _policy = makePolicy();
// Manually create an expired incident
db.incidents.push({
id: "expired-1",
@@ -329,7 +329,7 @@ describe("checkAutoResolve (D-06)", () => {
// Spent is now 7500 (below warn threshold of 8000 = 10000*80/100)
const spent = 7500;
const warnAmount = Math.floor((policy.amount * policy.warnPercent) / 100);
const _warnAmount = Math.floor((policy.amount * policy.warnPercent) / 100);
if (spent < policy.amount) {
db.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
}
+44
View File
@@ -0,0 +1,44 @@
import { describe, expect, it } from "vitest";
import { buildCapabilityRegistrySection } from "@main/prompt-builder/capability-registry";
describe("buildCapabilityRegistrySection", () => {
it("renders a company-wide capability summary for delegation decisions", () => {
const section = buildCapabilityRegistrySection({
companySkillCatalog: [
{ name: "React Standards", slug: "react-standards", description: "Use when building React UI" },
],
companyWorkspaces: [
{ name: "Primary", localPath: "/tmp/workspace" },
],
allConnectors: [
{ id: "codex_local", label: "Codex", status: "ready", enabledModels: ["gpt-5.4"] },
],
companyDocuments: [
{
id: "doc-1",
companyId: "company-1",
title: "Architecture Decision",
type: "architecture_decision",
content: "",
status: "approved",
authorAgentId: null,
reviewerAgentId: null,
projectId: null,
goalId: null,
parentDocId: null,
version: 1,
tagsJson: "[]",
latestRevisionId: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
],
});
expect(section).toContain("# Company Capability Registry");
expect(section).toContain("React Standards (`react-standards`)");
expect(section).toContain("Primary: /tmp/workspace");
expect(section).toContain("Codex (`codex_local`) — models: gpt-5.4");
expect(section).toContain("architecture_decision");
});
});
+24
View File
@@ -0,0 +1,24 @@
import { describe, expect, it } from "vitest";
import { normalizeCommandValue } from "@agentcompany/adapter-utils/server-utils";
import { commandLooksLike } from "@agentcompany/adapter-utils/test-utils";
describe("command path normalization", () => {
it("removes surrounding quotes from absolute executable paths", () => {
expect(normalizeCommandValue('"C:\\Users\\First Last\\AppData\\Local\\bin\\claude.exe"')).toBe(
"C:\\Users\\First Last\\AppData\\Local\\bin\\claude.exe",
);
expect(normalizeCommandValue("'C:\\Users\\First Last\\AppData\\Local\\bin\\claude.exe'")).toBe(
"C:\\Users\\First Last\\AppData\\Local\\bin\\claude.exe",
);
});
it("keeps bare commands unchanged", () => {
expect(normalizeCommandValue("claude")).toBe("claude");
expect(normalizeCommandValue("codex")).toBe("codex");
});
it("matches quoted commands against connector executables", () => {
expect(commandLooksLike('"C:\\Users\\First Last\\AppData\\Local\\bin\\claude.exe"', "claude")).toBe(true);
expect(commandLooksLike("'C:\\Users\\First Last\\AppData\\Local\\bin\\codex.exe'", "codex")).toBe(true);
});
});
+3 -4
View File
@@ -4,8 +4,8 @@ import {
inferTaskType,
getConnectorStrengths,
TASK_TYPE_PREFERENCE,
CONNECTOR_STRENGTHS,
ROLE_DEPARTMENT_HINTS,
CONNECTOR_STRENGTHS as _CONNECTOR_STRENGTHS,
ROLE_DEPARTMENT_HINTS as _ROLE_DEPARTMENT_HINTS,
} from "../../src/main/connector-matching";
import type { ConnectorId, ConnectorStatus } from "@shared/types";
@@ -35,10 +35,9 @@ describe("connector-matching", () => {
expect(TASK_TYPE_PREFERENCE.general[0]).toBe("claude_local");
});
it("includes all three connectors for every task type", () => {
it("includes all three core connectors for every task type", () => {
for (const key of Object.keys(TASK_TYPE_PREFERENCE)) {
const prefs = TASK_TYPE_PREFERENCE[key as keyof typeof TASK_TYPE_PREFERENCE];
expect(prefs).toHaveLength(3);
expect(prefs).toContain("claude_local");
expect(prefs).toContain("codex_local");
expect(prefs).toContain("gemini_local");
+81
View File
@@ -0,0 +1,81 @@
import { describe, expect, it } from "vitest";
import { getConnectorModelPolicy, validateConnectorModelSelection } from "@main/connector-model-policy";
import { getConnectorDefinition } from "@main/connectors";
function createDeps(overrides: {
connectorModel?: string | null;
disabledModels?: Array<{ id: string; label: string }>;
} = {}) {
return {
getConnector: () => ({
id: "claude_compatible" as const,
label: "Claude Compatible",
model: overrides.connectorModel ?? "model-alpha,model-beta,model-gamma",
}),
getDisabledModels: () => overrides.disabledModels ?? [],
};
}
describe("connector model policy", () => {
it("derives enabled compat models from connector config minus disabled entries", () => {
const policy = getConnectorModelPolicy(
createDeps({ disabledModels: [{ id: "model-beta", label: "model-beta" }] }),
"claude_compatible",
);
expect(policy.enabledModels.map((model) => model.id)).toEqual(["model-alpha", "model-gamma"]);
});
it("accepts an explicit enabled model", () => {
const result = validateConnectorModelSelection(createDeps(), "claude_compatible", "model-alpha");
expect(result).toEqual({ ok: true, effectiveModel: "model-alpha" });
});
it("rejects an explicit disabled model", () => {
const result = validateConnectorModelSelection(
createDeps({ disabledModels: [{ id: "model-beta", label: "model-beta" }] }),
"claude_compatible",
"model-beta",
);
expect(result.ok).toBe(false);
if (result.ok) {
throw new Error("Expected explicit disabled model to be rejected.");
}
expect(result.error).toContain('does not allow model "model-beta"');
});
});
describe("native connector defaults", () => {
it("uses the persisted connector model as the native default when configured", () => {
const codexModels = getConnectorDefinition("codex_local").module.models ?? [];
const configuredDefault = codexModels[1]?.id;
if (!configuredDefault) {
throw new Error("codex_local requires at least two models for this default policy test.");
}
const policy = getConnectorModelPolicy({
getConnector: () => ({
id: "codex_local" as const,
label: "Codex",
model: configuredDefault,
}),
getDisabledModels: () => [],
}, "codex_local");
expect(policy.defaultModel).toBe(configuredDefault);
expect(validateConnectorModelSelection({
getConnector: () => ({
id: "codex_local" as const,
label: "Codex",
model: configuredDefault,
}),
getDisabledModels: () => [],
}, "codex_local", null)).toEqual({
ok: true,
effectiveModel: configuredDefault,
});
});
});
+55
View File
@@ -0,0 +1,55 @@
import { describe, expect, it } from "vitest";
import {
mergeConnectorModelEntries,
resolveSelectedConnectorModel,
} from "@shared/connector-models";
describe("connector model helpers", () => {
it("merges enabled and disabled models into one toggle list", () => {
const entries = mergeConnectorModelEntries(
[
{ id: "gpt-5.4", label: "GPT-5.4" },
{ id: "gpt-5.4-mini", label: "GPT-5.4 Mini" },
],
[{ id: "o4-mini", label: "o4-mini" }],
);
expect(entries).toEqual([
{ id: "gpt-5.4", label: "GPT-5.4", enabled: true },
{ id: "gpt-5.4-mini", label: "GPT-5.4 Mini", enabled: true },
{ id: "o4-mini", label: "o4-mini", enabled: false },
]);
});
it("keeps the current selection only if it remains enabled", () => {
const entries = mergeConnectorModelEntries(
[{ id: "gpt-5.4", label: "GPT-5.4" }],
[{ id: "gpt-5.4-mini", label: "GPT-5.4 Mini" }],
);
expect(resolveSelectedConnectorModel(entries, "gpt-5.4")).toBe("gpt-5.4");
expect(resolveSelectedConnectorModel(entries, "gpt-5.4-mini")).toBe("gpt-5.4");
expect(resolveSelectedConnectorModel(entries, "")).toBe("gpt-5.4");
});
it("dedupes duplicate ids and keeps enabled entries ahead of disabled ones", () => {
const entries = mergeConnectorModelEntries(
[{ id: "gpt-5.4", label: "GPT-5.4" }],
[
{ id: "gpt-5.4", label: "GPT-5.4" },
{ id: "gpt-4.1", label: "GPT-4.1" },
],
);
expect(entries).toEqual([
{ id: "gpt-5.4", label: "GPT-5.4", enabled: true },
{ id: "gpt-4.1", label: "GPT-4.1", enabled: false },
]);
});
it("returns an empty selection when all entries are disabled", () => {
const entries = mergeConnectorModelEntries([], [{ id: "gpt-5.4", label: "GPT-5.4" }]);
expect(resolveSelectedConnectorModel(entries, "gpt-5.4")).toBe("");
});
});
+8 -8
View File
@@ -11,13 +11,13 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
*/
interface MockStatement {
run: ReturnType<typeof vi.fn>;
get: ReturnType<typeof vi.fn>;
all: ReturnType<typeof vi.fn>;
run: (...args: unknown[]) => unknown;
get: (...args: unknown[]) => unknown;
all: (...args: unknown[]) => unknown[];
}
interface MockDatabase {
prepare: ReturnType<typeof vi.fn>;
prepare: (key: string) => MockStatement;
}
/**
@@ -25,7 +25,7 @@ interface MockDatabase {
*/
function simulateInterruptStaleRuns(
db: MockDatabase,
releaseAllWorkspaceLocks: ReturnType<typeof vi.fn>,
releaseAllWorkspaceLocks: () => void,
transactionFn: (fn: () => void) => void,
runningCount: number,
): number {
@@ -95,7 +95,7 @@ function simulateMarkInterruptedTaskFailed(
describe("Crash Recovery Detection", () => {
let db: MockDatabase;
let releaseAllWorkspaceLocks: ReturnType<typeof vi.fn>;
let releaseAllWorkspaceLocks: () => void;
beforeEach(() => {
db = {
@@ -104,8 +104,8 @@ describe("Crash Recovery Detection", () => {
get: vi.fn().mockReturnValue(undefined),
all: vi.fn().mockReturnValue([]),
}),
};
releaseAllWorkspaceLocks = vi.fn();
} as unknown as MockDatabase;
releaseAllWorkspaceLocks = vi.fn() as unknown as () => void;
});
describe("interruptStaleRuns", () => {
+3 -3
View File
@@ -7,9 +7,9 @@ import {
formatShortDate,
} from "../../src/renderer/components/dashboard/charts/chart-utils";
import type {
DailyRunData,
DailyCostData,
AgentWorkloadData,
DailyRunData as _DailyRunData,
DailyCostData as _DailyCostData,
AgentWorkloadData as _AgentWorkloadData,
} from "../../src/renderer/components/dashboard/charts/chart-utils";
// Helper to create a minimal RunRecord for testing
+2 -2
View File
@@ -195,7 +195,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
});
db.finishRun({
runId: run.id,
status: "completed",
status: "succeeded",
summary: "Completed initial implementation",
errorMessage: null,
exitCode: 0,
@@ -341,7 +341,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
});
db.finishRun({
runId: run.id,
status: "completed",
status: "succeeded",
summary: "Previous work done",
errorMessage: null,
exitCode: 0,
+11 -11
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { describe, it, expect, vi, beforeEach, afterEach, type MockInstance } from "vitest";
/**
* Tests for the retry-aware failure escalation pipeline.
@@ -15,19 +15,19 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
// and verifying the calls it makes to the database and notification system.
interface MockDb {
getTaskRetryCount: ReturnType<typeof vi.fn>;
incrementTaskRetryCount: ReturnType<typeof vi.fn>;
resetTaskRetryCount: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
getCeoForCompany: ReturnType<typeof vi.fn>;
advanceTaskStatus: ReturnType<typeof vi.fn>;
getTaskRetryCount: MockInstance & ((taskId: string) => number);
incrementTaskRetryCount: MockInstance & ((taskId: string) => void);
resetTaskRetryCount: MockInstance & ((taskId: string) => void);
getChainOfCommand: MockInstance & ((agentId: string) => { id: string; name: string }[]);
getCeoForCompany: MockInstance & ((companyId: string) => { id: string; name: string } | null);
advanceTaskStatus: MockInstance & ((taskId: string, status: string, note: string) => { previousStatus: string });
}
interface MockDeps {
db: MockDb;
wakeAgentIfPossible: ReturnType<typeof vi.fn>;
notify: ReturnType<typeof vi.fn>;
logger: { info: ReturnType<typeof vi.fn>; warn: ReturnType<typeof vi.fn>; error: ReturnType<typeof vi.fn> };
wakeAgentIfPossible: MockInstance & ((agentId: string, companyId: string, reason: string) => string);
notify: MockInstance & ((...args: unknown[]) => void);
logger: { info: MockInstance & ((...args: unknown[]) => void); warn: MockInstance & ((...args: unknown[]) => void); error: MockInstance & ((...args: unknown[]) => void) };
}
const MAX_RETRY_ATTEMPTS = 2;
@@ -146,7 +146,7 @@ function createMockDeps(): MockDeps {
warn: vi.fn(),
error: vi.fn(),
},
};
} as unknown as MockDeps;
}
const RUN = { taskId: "task-1", agentId: "agent-1", companyId: "company-1" };
+1 -1
View File
@@ -144,7 +144,7 @@ describe("Heartbeat task coalescing", () => {
const agentId = createTestAgent(db, companyId, wsId, "CEO");
// Create two done heartbeat tasks
const oldTaskId = createHeartbeatTask(db, companyId, agentId, "done", "CEO");
const _oldTaskId = createHeartbeatTask(db, companyId, agentId, "done", "CEO");
const newTaskId = createHeartbeatTask(db, companyId, agentId, "done", "CEO");
// Advance the newer one so its updated_at is more recent
+100
View File
@@ -0,0 +1,100 @@
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 { IPC_CHANNELS } from "@shared/contracts";
import { AppDatabase } from "../../src/main/database";
import { registerCoreAgentGraphHandlers } from "../../src/main/ipc-core-agent-graph-handlers";
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("ipc-core-agent-graph-handlers model enforcement", () => {
it("rejects saveAgent when the selected model is disabled for the connector", async () => {
const profileDir = await createTempDir("agentcompany-agent-handler-");
const db = new AppDatabase(profileDir);
db.init();
db.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
db.setDisabledModels("codex_local", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
const companyId = db.saveCompany({
name: "Handler Test Co",
description: "Tests saveAgent model validation",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const handlers = new Map<string, (...args: any[]) => any>();
registerCoreAgentGraphHandlers({
registerHandle: (channel, handler) => {
handlers.set(channel, handler);
},
db,
ensureCompanyExists: (id) => (db.hasCompany(id) ? null : { ok: false, error: { code: "COMPANY_NOT_FOUND", message: "missing" } }),
findInvalidReference: () => null,
publishDomainChanged: vi.fn(),
handleApprovalResolved: vi.fn(),
});
const saveAgent = handlers.get(IPC_CHANNELS.saveAgent);
if (!saveAgent) {
throw new Error("saveAgent handler was not registered.");
}
const result = await saveAgent(null, {
id: agentId,
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
department: "executive",
status: "idle",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: "gpt-5.4",
metadataJson: "{}",
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
}) as { ok: boolean; error?: { code: string; message: string } };
expect(result.ok).toBe(false);
expect(result.error?.code).toBe("CONNECTOR_MODEL_NOT_ALLOWED");
});
});
+1 -1
View File
@@ -144,7 +144,7 @@ describe("createDomainEventBatcher", () => {
const batcher = createDomainEventBatcher(onFull, onGranular, 50);
// These events are not recognized as granular -- per D-05, unknown triggers full refresh
batcher.push({ type: "run-log", runId: "r-1", stream: "stdout", text: "hi" } as DesktopEvent);
batcher.push({ type: "run-log", runId: "r-1", stream: "stdout", chunk: "hi" } as DesktopEvent);
vi.advanceTimersByTime(50);
expect(onFull).toHaveBeenCalledTimes(1);
@@ -0,0 +1,58 @@
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 { IPC_CHANNELS } from "@shared/contracts";
import { AppDatabase } from "../../src/main/database";
import { registerCoreAdminHandlers } from "../../src/main/ipc-core-admin-handlers";
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("ipc-core-admin-handlers connector saves", () => {
it("accepts compatible connector model catalogs stored as csv", async () => {
const profileDir = await createTempDir("agentcompany-admin-handler-");
const db = new AppDatabase(profileDir);
db.init();
const handlers = new Map<string, (...args: any[]) => any>();
registerCoreAdminHandlers({
registerHandle: (channel, handler) => {
handlers.set(channel, handler);
},
db,
ensureCompanyExists: () => null,
publishDomainChanged: vi.fn(),
checkConnector: vi.fn(),
openConnectorAuthTerminal: vi.fn(),
openGenericTerminal: vi.fn(),
});
const saveConnector = handlers.get(IPC_CHANNELS.saveConnector);
if (!saveConnector) {
throw new Error("saveConnector handler was not registered.");
}
const result = await saveConnector(null, {
id: "claude_compatible",
command: "claude",
model: "model-alpha,model-beta,model-gamma",
envBindingText: "ANTHROPIC_BASE_URL=https://example.com/anthropic",
notes: "Compat provider",
}) as { ok: boolean; error?: { code: string; message: string } };
expect(result.ok).toBe(true);
expect(db.getConnector("claude_compatible").model).toBe("model-alpha,model-beta,model-gamma");
db.close();
});
});
+59
View File
@@ -0,0 +1,59 @@
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";
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("knowledge provenance", () => {
it("preserves or refreshes sourceRunId when an existing knowledge entry is updated", async () => {
const profileDir = await createTempDir("agentcompany-knowledge-provenance-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.saveCompany({
name: "Knowledge Provenance Co",
description: "Tests sourceRunId updates on knowledge entry edits",
status: "active",
});
const knowledgeId = db.saveKnowledgeEntry({
companyId,
category: "lesson_learned",
topic: "Original lesson",
content: "Initial content",
importance: "medium",
sourceRunId: null,
});
db.saveKnowledgeEntry({
id: knowledgeId,
companyId,
category: "lesson_learned",
topic: "Original lesson",
content: "Updated content with better guidance",
importance: "high",
sourceRunId: "run-123",
});
const entry = db.queryAll<{ sourceRunId: string | null; importance: string }>(
"select source_run_id as sourceRunId, importance from knowledge_base where id = ? limit 1",
knowledgeId,
)[0];
expect(entry?.sourceRunId).toBe("run-123");
expect(entry?.importance).toBe("high");
db.close();
});
});
+251
View File
@@ -0,0 +1,251 @@
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 { dispatchAgentMessage, HUMAN_SENDER_ID } from "../../src/main/message-dispatch";
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 createScaffold(db: AppDatabase) {
db.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Message Dispatch Co",
description: "Tests board wake and wait-state resolution",
status: "active",
executionMode: "ai_cofounder",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: "/tmp/message-dispatch",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "idle",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 200,
department: "engineering",
});
const cmoId = db.saveAgent({
companyId,
name: "CMO",
role: "CMO",
title: "Chief Marketing Officer",
status: "idle",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead marketing",
budgetMonthlyUsd: 200,
department: "marketing",
});
const pendingId = db.saveAgent({
companyId,
name: "Pending Recruit",
role: "IC",
title: "Pending Hire",
status: "pending_approval",
reportsTo: ctoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Pending",
budgetMonthlyUsd: 100,
department: "engineering",
});
const terminatedId = db.saveAgent({
companyId,
name: "Former COO",
role: "COO",
title: "Former COO",
status: "terminated",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "No longer active",
budgetMonthlyUsd: 100,
department: "operations",
});
const taskId = db.saveTask({
companyId,
title: "Review architecture plan",
description: "Need board feedback in the thread.",
assigneeAgentId: ctoId,
workspaceId,
priority: "high",
status: "todo",
});
return { companyId, workspaceId, ceoId, ctoId, cmoId, pendingId, terminatedId, taskId };
}
describe("message dispatch", () => {
it("wakes top-level active agents for company messages and active agents for incident messages", async () => {
const profileDir = await createTempDir("agentcompany-message-dispatch-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, ceoId, ctoId, cmoId } = createScaffold(db);
const wakeAgentIfPossible = vi.fn().mockReturnValue(null);
const companyResult = dispatchAgentMessage({
db,
wakeAgentIfPossible,
}, {
companyId,
fromAgentId: HUMAN_SENDER_ID,
fromHumanUser: true,
channel: "company",
body: "Board broadcast",
});
expect(companyResult.ok).toBe(true);
if (!companyResult.ok) throw new Error("Expected company message dispatch to succeed.");
expect(companyResult.data.wakeTargets).toEqual([ceoId]);
const incidentResult = dispatchAgentMessage({
db,
wakeAgentIfPossible,
}, {
companyId,
fromAgentId: HUMAN_SENDER_ID,
fromHumanUser: true,
channel: "incident",
body: "Urgent issue",
});
expect(incidentResult.ok).toBe(true);
if (!incidentResult.ok) throw new Error("Expected incident message dispatch to succeed.");
expect(new Set(incidentResult.data.wakeTargets)).toEqual(new Set([ceoId, ctoId, cmoId]));
db.close();
});
it("resolves only user_reply wait states for company-wide human messages, but scoped replies resolve thread wait states", async () => {
const profileDir = await createTempDir("agentcompany-message-wait-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, ctoId, taskId } = createScaffold(db);
const threadIntegration = createThreadIntegration({
db,
logger: { info: () => {}, error: () => {} },
});
threadIntegration.onTaskCreated(taskId, companyId);
const thread = db.listThreads({
companyId,
type: "task",
relatedEntityType: "task",
relatedEntityId: taskId,
limit: 1,
})[0];
if (!thread) {
throw new Error("Expected task thread to exist.");
}
const replyWait = db.setThreadWaitState({
threadId: thread.id,
companyId,
waitingFor: "user_reply",
relatedApprovalId: null,
reason: "Reply needed",
});
const reviewWait = db.setThreadWaitState({
threadId: thread.id,
companyId,
waitingFor: "user_review",
relatedApprovalId: null,
reason: "Review needed",
});
const approvalWait = db.setThreadWaitState({
threadId: thread.id,
companyId,
waitingFor: "user_approval",
relatedApprovalId: "approval-1",
reason: "Approval needed",
});
dispatchAgentMessage({
db,
onHumanMessage: (currentCompanyId, toAgentId, threadId, waitStateId) =>
threadIntegration.onHumanMessage(currentCompanyId, toAgentId, threadId, waitStateId),
}, {
companyId,
fromAgentId: HUMAN_SENDER_ID,
fromHumanUser: true,
channel: "company",
body: "Board-wide update",
});
let unresolved = db.listThreadWaitStates(companyId);
expect(unresolved.some((entry) => entry.id === replyWait.id)).toBe(false);
expect(unresolved.some((entry) => entry.id === reviewWait.id)).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: ctoId,
threadId: thread.id,
body: "Specific reply for the thread",
});
unresolved = db.listThreadWaitStates(companyId);
expect(unresolved.some((entry) => entry.id === reviewWait.id)).toBe(false);
expect(unresolved.some((entry) => entry.id === approvalWait.id)).toBe(true);
db.close();
});
});
+6 -4
View File
@@ -1,4 +1,6 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import type { BrowserWindow } from "electron";
import type { DesktopEvent } from "@shared/types";
// Track all constructed notification instances
const constructedNotifications: Array<{
@@ -61,9 +63,9 @@ describe("notification-service", () => {
};
service = createNotificationService({
getMainWindow: mockGetMainWindow,
emitEvent: mockEmitEvent,
logger: mockLogger,
getMainWindow: mockGetMainWindow as unknown as () => BrowserWindow | null,
emitEvent: mockEmitEvent as unknown as (event: DesktopEvent) => void,
logger: mockLogger as { info: (...args: unknown[]) => void; error: (...args: unknown[]) => void },
});
});
@@ -203,7 +205,7 @@ describe("notification-service", () => {
// emitEvent should have been called with section but no entityId
const emitCall = mockEmitEvent.mock.calls.find(
([event]: [{ type: string }]) => event.type === "notification-click",
(args: unknown[]) => (args[0] as { type?: string })?.type === "notification-click",
);
expect(emitCall).toBeDefined();
expect(emitCall![0].section).toBe("orgchart");
@@ -0,0 +1,80 @@
import { describe, expect, it } from "vitest";
import { buildSystemPrompt } from "../../src/main/prompt-builder";
import type { PromptContext } from "../../src/main/prompt-builder/index";
function buildCtx(overrides: Partial<PromptContext> = {}): PromptContext {
return {
agent: {
id: "agent-1",
companyId: "company-1",
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
department: "engineering",
status: "active",
reportsTo: "ceo-1",
connectorId: "codex_local",
workspaceId: null,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 200,
spentMonthlyUsd: 0,
metadataJson: "{}",
heartbeatEnabled: true,
heartbeatIntervalSec: 120,
lastHeartbeatAt: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
apiUrl: "http://127.0.0.1:3210",
apiKey: "token",
runId: "run-1",
task: null,
companyName: "Prompt Co",
companyDescription: "Tests leadership prompt classification",
goals: [],
projects: [],
directReports: [],
chainOfCommand: [{
id: "ceo-1",
companyId: "company-1",
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
department: "executive",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId: null,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
spentMonthlyUsd: 0,
metadataJson: "{}",
heartbeatEnabled: true,
heartbeatIntervalSec: 120,
lastHeartbeatAt: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}],
assignedTasks: [],
allAgents: [],
pendingApprovals: [],
tasksAwaitingReview: [],
wakeReason: null,
allTasks: [],
recentComments: [],
socialAccounts: [],
...overrides,
};
}
describe("buildSystemPrompt leadership classification", () => {
it("treats first-layer executive leaders as managers even before they have direct reports", () => {
const prompt = buildSystemPrompt(buildCtx());
expect(prompt).toContain("Operating Instructions (Manager)");
expect(prompt).toContain("New hires for your team should report to you by default");
expect(prompt).not.toContain("Operating Instructions (Individual Contributor)");
});
});
+2
View File
@@ -106,6 +106,8 @@ describe("selective IPC event types", () => {
return "agent-updated";
case "budget-changed":
return "budget-changed";
case "thread-wait-state-updated":
return "thread-wait-state-updated";
default: {
const _exhaustive: never = event;
return _exhaustive;
+5 -5
View File
@@ -205,7 +205,7 @@ describe("task_sessions CRUD", () => {
const run1 = db.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "claude_local" });
db.finishRun({
runId: run1.id,
status: "completed",
status: "succeeded",
summary: "First run done",
errorMessage: null,
exitCode: 0,
@@ -237,7 +237,7 @@ describe("task_sessions CRUD", () => {
expect(runs[0].status).toBe("failed");
expect(runs[0].errorMessage).toBe("Timeout");
expect(runs[1].id).toBe(run1.id);
expect(runs[1].status).toBe("completed");
expect(runs[1].status).toBe("succeeded");
});
});
@@ -561,7 +561,7 @@ describe("session management service", () => {
const run = db.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "gemini_local" as ConnectorId });
db.finishRun({
runId: run.id,
status: "completed",
status: "succeeded",
summary: "Implemented initial logic",
errorMessage: null,
exitCode: 0,
@@ -582,7 +582,7 @@ describe("session management service", () => {
expect(handoff).toContain("# Session Handoff");
expect(handoff).toContain("Implement session rotation"); // task title
expect(handoff).toContain("## Recent Run History");
expect(handoff).toContain("completed");
expect(handoff).toContain("succeeded");
expect(handoff).toContain("15000 total tokens"); // 10000 + 5000
});
});
@@ -736,7 +736,7 @@ describe("wiring integration", () => {
function simulateRunFinishedSessionWiring(
sessionService: ReturnType<typeof createMockSessionService>,
db: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getActiveTaskSession: (...args: any[]) => any;
},
task: { id: string } | null,
+51
View File
@@ -0,0 +1,51 @@
import { describe, expect, it } from "vitest";
import { buildConnectorSection } from "@main/prompt-builder/skill-injection";
describe("buildConnectorSection", () => {
it("includes enabled model pools for ready connectors", () => {
const section = buildConnectorSection({
allConnectors: [
{
id: "codex_local",
label: "Codex",
status: "ready",
enabledModels: ["gpt-5.4", "gpt-5.4-mini"],
},
],
});
expect(section).toContain("Enabled models: gpt-5.4, gpt-5.4-mini.");
expect(section).toContain("choose from the enabled model pool");
});
it("does not render disabled models in the connector section", () => {
const section = buildConnectorSection({
allConnectors: [
{
id: "codex_local",
label: "Codex",
status: "ready",
enabledModels: ["gpt-5.4"],
},
],
});
expect(section).toContain("Enabled models: gpt-5.4.");
expect(section).not.toContain("gpt-5.4-mini");
});
it("omits ready connectors whose enabled pool is empty", () => {
const section = buildConnectorSection({
allConnectors: [
{
id: "codex_local",
label: "Codex",
status: "ready",
enabledModels: [],
},
],
});
expect(section).toBeNull();
});
});
+31 -6
View File
@@ -1,6 +1,7 @@
import { describe, it, expect } from "vitest";
import {
TOKEN_BUDGETS,
resolveTokenBudget,
} from "@main/skill-types";
import type {
SkillSourceType,
@@ -16,20 +17,36 @@ import type {
describe("skill-types", () => {
describe("TOKEN_BUDGETS", () => {
it("has entries for all five adapter types", () => {
it("has entries for all supported adapter types", () => {
expect(TOKEN_BUDGETS).toHaveProperty("claude_local");
expect(TOKEN_BUDGETS).toHaveProperty("codex_local");
expect(TOKEN_BUDGETS).toHaveProperty("gemini_local");
expect(TOKEN_BUDGETS).toHaveProperty("opencode_local");
expect(TOKEN_BUDGETS).toHaveProperty("kimi_local");
expect(TOKEN_BUDGETS).toHaveProperty("claude_compatible");
});
it("has numeric token caps for each adapter", () => {
expect(TOKEN_BUDGETS.claude_local).toBe(50000);
expect(TOKEN_BUDGETS.codex_local).toBe(30000);
expect(TOKEN_BUDGETS.gemini_local).toBe(50000);
expect(TOKEN_BUDGETS.opencode_local).toBe(30000);
expect(TOKEN_BUDGETS.kimi_local).toBe(20000);
expect(TOKEN_BUDGETS.claude_compatible).toBe(50000);
});
});
describe("resolveTokenBudget", () => {
it("returns exact match for known adapter types", () => {
expect(resolveTokenBudget("claude_local")).toBe(50000);
expect(resolveTokenBudget("codex_local")).toBe(30000);
expect(resolveTokenBudget("gemini_local")).toBe(50000);
});
it("resolves claude_compat_* variants to claude_compatible budget", () => {
expect(resolveTokenBudget("claude_compat_2")).toBe(50000);
expect(resolveTokenBudget("claude_compat_10")).toBe(50000);
expect(resolveTokenBudget("claude_compatible")).toBe(50000);
});
it("returns 30000 default for unknown adapter types", () => {
expect(resolveTokenBudget("unknown_adapter")).toBe(30000);
});
});
@@ -105,14 +122,22 @@ describe("skill-types", () => {
expect(result.isUpdate).toBe(false);
});
it("RuntimeSkillEntry has runtimeName, sourcePath, name, tokenEstimate", () => {
it("RuntimeSkillEntry has runtimeName, sourcePath, name, tokenEstimate and semantics", () => {
const entry: RuntimeSkillEntry = {
runtimeName: "claude",
sourcePath: "/path/to/skill",
name: "test-skill",
tokenEstimate: 1000,
allowedTools: ["read", "write"],
hooks: ["on_task_start"],
contextMode: "inline",
isAgent: false,
effort: "medium",
whenToUse: "when writing code",
};
expect(entry.tokenEstimate).toBe(1000);
expect(entry.allowedTools).toEqual(["read", "write"]);
expect(entry.contextMode).toBe("inline");
});
it("ManagedSkillRef has runtimeName and sourcePath only", () => {
+7 -2
View File
@@ -272,9 +272,12 @@ const fakeTask = (id: string, title = "Task"): TaskRecord => ({
sessionDisplayId: null,
costUsd: 0,
requiresUserReview: false,
startedAt: null,
completedAt: null,
cancelledAt: null,
createdAt: "2026-01-01",
updatedAt: "2026-01-01",
} as TaskRecord);
} as unknown as TaskRecord);
const fakeRun = (id: string): RunRecord => ({
id,
@@ -326,15 +329,17 @@ const fakeApproval = (id: string): ApprovalRecord => ({
id,
companyId: "company-1",
relatedTaskId: "t-1",
relatedAgentId: null,
requestedByAgentId: "ag-1",
type: "task_completion",
payloadSummary: "",
impactSummary: "",
payloadJson: null,
decisionNote: null,
state: "pending",
createdAt: "2026-01-01",
updatedAt: "2026-01-01",
} as ApprovalRecord);
} as unknown as ApprovalRecord);
describe("CompanySlice patch methods", () => {
beforeEach(() => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { describe, expect, it } from "vitest";
/**
* Tests for non-code task deliverable enforcement in automation-service.ts.
+249
View File
@@ -0,0 +1,249 @@
import { describe, expect, it, vi } from "vitest";
import {
REVIEW_TIMEOUT_MS,
sweepStaleReviews,
} from "../../src/main/task-quality-gates";
import type { SweepStaleReviewsDeps } from "../../src/main/task-quality-gates";
describe("sweepStaleReviews", () => {
const NOW = Date.now();
const THIRTY_FIVE_MIN_AGO = new Date(NOW - 35 * 60 * 1000).toISOString();
const TWENTY_FIVE_MIN_AGO = new Date(NOW - 25 * 60 * 1000).toISOString();
function makeSweepDeps(overrides: Partial<SweepStaleReviewsDeps> = {}): SweepStaleReviewsDeps {
return {
db: {
queryAll: vi.fn().mockReturnValue([]),
getManagerForAgent: vi.fn().mockReturnValue(null),
getChainOfCommand: vi.fn().mockReturnValue([]),
advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_review" }),
},
getCompanyExecutionMode: vi.fn().mockReturnValue("ai_ceo"),
dispatchAgentMessage: vi.fn().mockReturnValue({ ok: true }),
resolveApprovalDecision: vi.fn(),
notify: vi.fn(),
handleTaskStatusChange: vi.fn(),
handleApprovalResolved: vi.fn(),
emitEvent: vi.fn(),
publishDomainChanged: vi.fn(),
wakeAgentIfPossible: vi.fn(),
logger: { info: vi.fn(), warn: vi.fn() },
...overrides,
};
}
it("escalates to manager's manager when task in_review for 35 minutes", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
queryAll: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
};
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager One", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-manager-1", name: "Higher Manager" }]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(1);
expect(deps.dispatchAgentMessage).toHaveBeenCalledWith(
expect.objectContaining({ db: deps.db }),
expect.objectContaining({
companyId: "company-1",
fromAgentId: "manager-1",
toAgentId: "higher-manager-1",
channel: "direct",
priority: "urgent",
}),
"subtask_completed",
);
});
it("does not escalate when task in_review for only 25 minutes", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: TWENTY_FIVE_MIN_AGO, title: "Build login" },
]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.notify).not.toHaveBeenCalled();
});
it("notifies the human when the reviewer has no higher manager", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
queryAll: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
};
dbMock.queryAll
.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
])
.mockReturnValueOnce([]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "ceo-1", name: "CEO", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(1);
expect(deps.notify).toHaveBeenCalledWith(
expect.objectContaining({
title: "Review Timeout",
urgency: "critical",
batchKey: "review_escalation",
navigation: expect.objectContaining({ section: "tasks", entityId: "task-1" }),
}),
);
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
});
it("auto-approves pending approval linked to a stale review task", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
queryAll: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
};
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.queryAll.mockReturnValueOnce([{ id: "approval-1", type: "deliverable_review" }]);
sweepStaleReviews("company-1", deps, NOW);
expect(deps.resolveApprovalDecision).toHaveBeenCalledWith(
expect.objectContaining({
db: deps.db,
companyId: "company-1",
approvalId: "approval-1",
state: "approved",
decisionNote: "Auto-approved: review timeout exceeded 30 minutes.",
actor: "system",
}),
);
});
it("swallows resolveApprovalDecision races gracefully", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
queryAll: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
};
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.queryAll.mockReturnValueOnce([{ id: "approval-1", type: "deliverable_review" }]);
(deps.resolveApprovalDecision as ReturnType<typeof vi.fn>).mockImplementationOnce(() => {
throw new Error("Approval already decided");
});
expect(() => sweepStaleReviews("company-1", deps, NOW)).not.toThrow();
});
it("advances task status to done after escalation", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
queryAll: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
advanceTaskStatus: ReturnType<typeof vi.fn>;
};
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.queryAll.mockReturnValueOnce([]);
sweepStaleReviews("company-1", deps, NOW);
expect(dbMock.advanceTaskStatus).toHaveBeenCalledWith(
"task-1",
"done",
"Auto-advanced: review timeout exceeded 30 minutes.",
);
expect(deps.handleTaskStatusChange).toHaveBeenCalledWith("task-1", "company-1", "in_review", "done");
});
it("skips tasks without an assignee", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: null, updatedAt: THIRTY_FIVE_MIN_AGO, title: "Orphan task" },
]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.notify).not.toHaveBeenCalled();
});
it("exports REVIEW_TIMEOUT_MS as 30 minutes in milliseconds", () => {
expect(REVIEW_TIMEOUT_MS).toBe(30 * 60 * 1000);
});
it("does not remind before the cofounder review timeout threshold", () => {
const deps = makeSweepDeps({
getCompanyExecutionMode: vi.fn().mockReturnValue("ai_cofounder"),
});
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: TWENTY_FIVE_MIN_AGO, title: "Review me" },
]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
expect(deps.notify).not.toHaveBeenCalled();
expect(deps.resolveApprovalDecision).not.toHaveBeenCalled();
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.db.advanceTaskStatus).not.toHaveBeenCalled();
});
it("sends a critical cofounder review reminder without auto-approving", () => {
const deps = makeSweepDeps({
getCompanyExecutionMode: vi.fn().mockReturnValue("ai_cofounder"),
});
const dbMock = deps.db as unknown as {
queryAll: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
};
dbMock.queryAll
.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Review me" },
])
.mockReturnValueOnce([{ id: "approval-1", type: "deliverable_review" }]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
expect(deps.notify).toHaveBeenCalledWith(
expect.objectContaining({
title: "Your Review Required",
urgency: "critical",
batchKey: "cofounder_review_needed",
navigation: expect.objectContaining({ section: "approvals", entityId: "approval-1" }),
}),
);
expect(deps.resolveApprovalDecision).not.toHaveBeenCalled();
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.db.advanceTaskStatus).not.toHaveBeenCalled();
});
});
+5 -192
View File
@@ -1,16 +1,11 @@
import { describe, expect, it, vi } from "vitest";
import { describe, expect, it } from "vitest";
import {
validateTaskCreation,
checkAcceptanceCriteria,
checkCapabilityMatch,
ACCEPTANCE_CRITERIA_KEYWORDS,
sweepStaleReviews,
REVIEW_TIMEOUT_MS,
} from "../../src/main/task-quality-gates";
import type {
TaskValidationResult,
SweepStaleReviewsDeps,
} from "../../src/main/task-quality-gates";
import type { TaskValidationResult as _TaskValidationResult } from "../../src/main/task-quality-gates";
// --- TASK-01: validateTaskCreation tests ---
@@ -183,10 +178,11 @@ describe("checkCapabilityMatch", () => {
expect(result.warning).toContain("Data Analyst");
});
it("returns matched=true when agent has empty capabilities and no skills (graceful degradation)", () => {
it("returns matched=false with warning when agent has empty capabilities and no skills", () => {
const db = makeMockDb({ name: "New Agent", capabilities: "" });
const result = checkCapabilityMatch(db, "agent-1", "Build React login form", "company-1");
expect(result.matched).toBe(true);
expect(result.matched).toBe(false);
expect(result.warning).toContain("New Agent");
});
it("returns matched=true when agent has no capabilities but skill name overlaps with task", () => {
@@ -206,186 +202,3 @@ describe("checkCapabilityMatch", () => {
expect(result.matched).toBe(true);
});
});
// --- TASK-04: sweepStaleReviews tests ---
describe("sweepStaleReviews", () => {
const NOW = Date.now();
const THIRTY_FIVE_MIN_AGO = new Date(NOW - 35 * 60 * 1000).toISOString();
const TWENTY_FIVE_MIN_AGO = new Date(NOW - 25 * 60 * 1000).toISOString();
function makeSweepDeps(overrides: Partial<SweepStaleReviewsDeps> = {}): SweepStaleReviewsDeps {
return {
db: {
queryAll: vi.fn().mockReturnValue([]),
getManagerForAgent: vi.fn().mockReturnValue(null),
getChainOfCommand: vi.fn().mockReturnValue([]),
advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_review" }),
},
dispatchAgentMessage: vi.fn().mockReturnValue({ ok: true }),
resolveApprovalDecision: vi.fn(),
notify: vi.fn(),
handleTaskStatusChange: vi.fn(),
handleApprovalResolved: vi.fn(),
emitEvent: vi.fn(),
publishDomainChanged: vi.fn(),
wakeAgentIfPossible: vi.fn(),
logger: { info: vi.fn(), warn: vi.fn() },
...overrides,
};
}
it("escalates to manager's manager when task in_review for 35 minutes", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn>; getManagerForAgent: ReturnType<typeof vi.fn>; getChainOfCommand: ReturnType<typeof vi.fn> };
// Task in review for 35 min
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
// Direct manager of the assignee (the reviewer)
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager One", companyId: "company-1" });
// Chain of command for the reviewer — the reviewer's manager (escalation target)
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-manager-1", name: "Higher Manager" }]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(1);
expect(deps.dispatchAgentMessage).toHaveBeenCalledWith(
expect.objectContaining({ db: deps.db }),
expect.objectContaining({
companyId: "company-1",
fromAgentId: "manager-1",
toAgentId: "higher-manager-1",
channel: "direct",
priority: "urgent",
}),
"subtask_completed",
);
});
it("does not escalate when task in_review for only 25 minutes (under threshold)", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: TWENTY_FIVE_MIN_AGO, title: "Build login" },
]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.notify).not.toHaveBeenCalled();
});
it("notifies human when no higher manager (reviewer is CEO)", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn>; getManagerForAgent: ReturnType<typeof vi.fn>; getChainOfCommand: ReturnType<typeof vi.fn> };
dbMock.queryAll
.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
])
.mockReturnValueOnce([]); // no pending approvals
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "ceo-1", name: "CEO", companyId: "company-1" });
// Empty chain means reviewer is the top of hierarchy
dbMock.getChainOfCommand.mockReturnValueOnce([]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(1);
expect(deps.notify).toHaveBeenCalledWith(
expect.objectContaining({
title: "Review Timeout",
urgency: "critical",
batchKey: "review_escalation",
navigation: expect.objectContaining({ section: "tasks", entityId: "task-1" }),
}),
);
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
});
it("auto-approves pending approval linked to stale review task", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn>; getManagerForAgent: ReturnType<typeof vi.fn>; getChainOfCommand: ReturnType<typeof vi.fn> };
// First queryAll: stale task
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
// Second queryAll: pending approval
dbMock.queryAll.mockReturnValueOnce([{ id: "approval-1" }]);
sweepStaleReviews("company-1", deps, NOW);
expect(deps.resolveApprovalDecision).toHaveBeenCalledWith(
expect.objectContaining({
db: deps.db,
companyId: "company-1",
approvalId: "approval-1",
state: "approved",
decisionNote: "Auto-approved: review timeout exceeded 30 minutes.",
actor: "system",
}),
);
});
it("catches resolveApprovalDecision error gracefully (race condition)", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn>; getManagerForAgent: ReturnType<typeof vi.fn>; getChainOfCommand: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.queryAll.mockReturnValueOnce([{ id: "approval-1" }]);
// resolveApprovalDecision throws (already decided)
(deps.resolveApprovalDecision as ReturnType<typeof vi.fn>).mockImplementationOnce(() => {
throw new Error("Approval already decided");
});
// Should not throw
expect(() => sweepStaleReviews("company-1", deps, NOW)).not.toThrow();
});
it("advances task status to done after escalation", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn>; getManagerForAgent: ReturnType<typeof vi.fn>; getChainOfCommand: ReturnType<typeof vi.fn>; advanceTaskStatus: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.queryAll.mockReturnValueOnce([]); // no approvals
sweepStaleReviews("company-1", deps, NOW);
expect(dbMock.advanceTaskStatus).toHaveBeenCalledWith(
"task-1",
"done",
"Auto-advanced: review timeout exceeded 30 minutes.",
);
expect(deps.handleTaskStatusChange).toHaveBeenCalledWith("task-1", "company-1", "in_review", "done");
});
it("skips task with no assigneeAgentId", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: null, updatedAt: THIRTY_FIVE_MIN_AGO, title: "Orphan task" },
]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.notify).not.toHaveBeenCalled();
});
it("exports REVIEW_TIMEOUT_MS as 30 minutes in milliseconds", () => {
expect(REVIEW_TIMEOUT_MS).toBe(30 * 60 * 1000);
});
});