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

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

No files matched your search

-117
View File
@@ -1,117 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { AppDatabase } from "../../src/main/database";
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
type RawDb = {
db: {
prepare: (sql: string) => {
run: (...args: unknown[]) => void;
};
};
};
function createCompany(db: AppDatabase): string {
return db.core.company.saveCompany({
name: "Projection Co",
description: "Active run projection contract",
status: "active",
});
}
function createWorkspace(db: AppDatabase, companyId: string, localPath: string): string {
return db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
}
function createAgent(db: AppDatabase, companyId: string, workspaceId: string): string {
return db.core.agents.saveAgent({
companyId,
name: "Projection Agent",
role: "Engineer",
title: "Engineer",
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: "gpt-5.4",
capabilities: "Tests active run projections",
budgetMonthlyUsd: 100,
});
}
describe("active run projection", () => {
let tempDir: string;
let db: AppDatabase;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-active-run-projection-"));
db = new AppDatabase(tempDir);
db.init();
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("ignores terminal run leases when projecting activeRunId", () => {
const companyId = createCompany(db);
const workspaceId = createWorkspace(db, companyId, tempDir);
const agentId = createAgent(db, companyId, workspaceId);
const taskId = db.core.tasks.saveTask({
companyId,
title: "Recoverable task",
description: "A stale terminal lease must not hide this task from recovery.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId: null,
connectorId: "openai-codex",
});
db.core.runs.finishRun({
runId: run.id,
status: "succeeded",
summary: "Done",
errorMessage: null,
exitCode: 0,
signal: null,
model: null,
unattributedCost: true,
});
const now = new Date().toISOString();
(db as unknown as RawDb).db
.prepare(
`insert into task_leases (
task_id, company_id, run_id, agent_id, acquired_at, renewed_at, expires_at, created_at, updated_at
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(taskId, companyId, run.id, agentId, now, now, now, now, now);
expect(db.core.entity.getTask(taskId).activeRunId).toBeNull();
expect(db.core.tasks.listAgentTasks(agentId, companyId, ["todo"])[0]?.activeRunId).toBeNull();
expect(db.projection.snapshot.listSnapshot({ companyId }).tasks.find((task) => task.id === taskId)?.activeRunId).toBeNull();
});
});
-78
View File
@@ -1,78 +0,0 @@
import { createHmac } from "node:crypto";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
createAgentToken,
initializeAgentTokenSigningKey,
validateAgentToken,
} from "../../src/main/agent-auth";
import { FatalError } from "../../src/main/errors";
const tempDirs: string[] = [];
async function createTempDir() {
const dir = await mkdtemp(join(tmpdir(), "agentcompany-agent-auth-"));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
vi.restoreAllMocks();
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
async function createSignedRawPayloadToken(profileDir: string, rawPayload: string): Promise<string> {
const key = await readFile(join(profileDir, "agent-api-signing.key"));
const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
const body = Buffer.from(rawPayload).toString("base64url");
const signature = createHmac("sha256", key)
.update(`${header}.${body}`)
.digest("base64url");
return `${header}.${body}.${signature}`;
}
describe("agent token signing key", () => {
it("keeps existing run tokens valid across profile reinitialization", async () => {
const profileDir = await createTempDir();
initializeAgentTokenSigningKey(profileDir);
const token = createAgentToken("agent-1", "company-1", "run-1");
initializeAgentTokenSigningKey(profileDir);
expect(validateAgentToken(token)).toMatchObject({
agentId: "agent-1",
companyId: "company-1",
runId: "run-1",
});
});
it("treats a corrupt signing key as fatal state", async () => {
const profileDir = await createTempDir();
await writeFile(join(profileDir, "agent-api-signing.key"), Buffer.from("too-short"));
expect(() => initializeAgentTokenSigningKey(profileDir)).toThrow(FatalError);
});
it("logs malformed signed token context without token fingerprints", async () => {
const profileDir = await createTempDir();
initializeAgentTokenSigningKey(profileDir);
const rawPayload = "{malformed-token-body";
const token = await createSignedRawPayloadToken(profileDir, rawPayload);
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
expect(validateAgentToken(token, {
runId: "run-1",
companyId: "company-1",
requestId: "req-1",
})).toBeNull();
expect(warn).toHaveBeenCalledWith(
"[agent-auth] token shape invalid: reason=payload_json_parse_failed runId=run-1 companyId=company-1 requestId=req-1",
);
const logLine = String(warn.mock.calls[0]?.[0] ?? "");
expect(logLine).not.toContain(rawPayload);
expect(logLine).not.toContain(token.slice(0, 8));
});
});
-112
View File
@@ -1,112 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
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("agent budget policy synchronization", () => {
it("updates the canonical agent-scope policy instead of creating duplicate active policies", async () => {
const profileDir = await createTempDir("agentcompany-budget-sync-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.core.company.saveCompany({
name: "Budget Sync Co",
description: "Budget sync regression coverage",
status: "active",
});
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Backend Engineer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Build backend services",
budgetMonthlyUsd: 100,
department: "engineering",
});
const firstPolicyId = db.core.budget.saveBudgetPolicy({
companyId,
scopeType: "agent",
scopeId: agentId,
metric: "billed_cents",
windowKind: "calendar_month_utc",
amount: 10_000,
warnPercent: 80,
hardStopEnabled: true,
notifyEnabled: true,
isActive: true,
});
db.core.agents.saveAgent({
id: agentId,
companyId,
name: "Engineer",
role: "Engineer",
title: "Backend Engineer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Build backend services",
budgetMonthlyUsd: 250,
department: "engineering",
});
const secondPolicyId = db.core.budget.saveBudgetPolicy({
companyId,
scopeType: "agent",
scopeId: agentId,
metric: "billed_cents",
windowKind: "calendar_month_utc",
amount: 25_000,
warnPercent: 80,
hardStopEnabled: true,
notifyEnabled: true,
isActive: true,
});
const agentPolicies = db.core.budget.listBudgetPolicies(companyId).filter(
(policy) => policy.scopeType === "agent" && policy.scopeId === agentId,
);
expect(secondPolicyId).toBe(firstPolicyId);
expect(agentPolicies).toHaveLength(1);
expect(agentPolicies[0]?.amount).toBe(25_000);
expect(db.core.budget.getActivePoliciesForRun(companyId, agentId, null).map((policy) => policy.amount)).toEqual([25_000]);
db.close();
});
});
+158
View File
@@ -0,0 +1,158 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ChatService } from "../../src/main/services/chat-service";
import { dispatchAgentTool, type AgentToolContext } from "../../src/main/services/agent-tools";
function call(name: string, args: Record<string, unknown>) {
return { id: `call_${randomUUID()}`, name, arguments: args };
}
interface Harness {
connection: DatabaseConnection;
organization: OrganizationService;
chat: ChatService;
orgId: string;
employeeId: string;
ceoId: string;
danaId: string;
ctx: AgentToolContext;
}
function harness(options: { enableProvider: boolean }): Harness {
const connection = new DatabaseConnection(join(tmpdir(), `ac-dm-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let danaId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "OrbitOps", employeeName: "Yang", mission: "m", goal: "g" });
const providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
if (options.enableProvider) {
connection.db.prepare("update providers set enabled = 1 where id = ? and org_id = ?").run(providerId, orgId);
}
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null,
});
danaId = organization.createAgent({
orgId, name: "Dana", role: "Engineer", department: "Engineering",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 1000, managerAgentId: ceoId,
});
});
const employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
const chat = new ChatService(connection.db);
return {
connection,
organization,
chat,
orgId,
employeeId,
ceoId,
danaId,
ctx: {
orgId,
agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null,
taskGoalId: null,
workspace: null,
},
};
}
function directSpaces(h: Harness): Array<{ id: string }> {
return h.connection.db.prepare("select id from chat_spaces where org_id = ? and space_type = 'direct'")
.all(h.orgId) as Array<{ id: string }>;
}
describe("agent direct messages", () => {
test("a DM gets its own conversation and later messages reuse it", () => {
const h = harness({ enableProvider: false });
const deps = { db: h.connection.db, organization: h.organization, chat: h.chat };
const first = dispatchAgentTool(deps, h.ctx, call("send_message", { to: "Dana", body: "How is the booking API going?" }));
expect(first).toContain("direct message to Dana");
const threadId = /thread_id="([^"]+)"/.exec(first)?.[1];
expect(threadId).toBeTruthy();
dispatchAgentTool(deps, h.ctx, call("send_message", { to: "Dana", body: "Also, ping me when the spec lands.", thread_id: threadId }));
const spaces = directSpaces(h);
expect(spaces).toHaveLength(1);
const members = h.connection.db.prepare("select principal_type, principal_id from chat_memberships where space_id = ?")
.all(spaces[0].id) as Array<{ principal_type: string; principal_id: string }>;
expect(members).toHaveLength(2);
expect(members.map((m) => m.principal_id).sort()).toEqual([h.ceoId, h.danaId].sort());
const messages = h.connection.db.prepare("select space_id from chat_messages where org_id = ?").all(h.orgId) as Array<{ space_id: string }>;
expect(messages).toHaveLength(2);
expect(messages.every((m) => m.space_id === spaces[0].id)).toBe(true);
});
test("a direct message wakes the counterpart agent without an @mention", () => {
const h = harness({ enableProvider: true });
const deps = { db: h.connection.db, organization: h.organization, chat: h.chat };
dispatchAgentTool(deps, h.ctx, call("send_message", { to: "Dana", body: "Take a look at the failing deploy when you can." }));
const runs = h.connection.db.prepare(
"select count(*) as count from task_runs where org_id = ? and agent_id = ? and trigger = 'chat_mention'",
).get(h.orgId, h.danaId) as { count: number };
expect(runs.count).toBe(1);
});
test("an employee DM wakes the agent like any messenger would", () => {
const h = harness({ enableProvider: true });
const spaceId = h.chat.ensureDirectSpace(h.orgId, { type: "employee", id: h.employeeId }, { type: "agent", id: h.danaId });
h.chat.sendMessage({
orgId: h.orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: h.employeeId,
bodyMd: "Dana, what is the launch status?",
attachmentFileIds: [],
});
const runs = h.connection.db.prepare(
"select count(*) as count from task_runs where org_id = ? and agent_id = ? and trigger = 'chat_mention'",
).get(h.orgId, h.danaId) as { count: number };
expect(runs.count).toBe(1);
});
test("task assignments go to the manager's direct conversation with the assignee", () => {
const h = harness({ enableProvider: true });
const deps = { db: h.connection.db, organization: h.organization, chat: h.chat };
const result = dispatchAgentTool(deps, h.ctx, call("create_task", {
title: "Ship booking API",
description: "Design and ship the booking API v1.",
assignee_agent_name: "Dana",
}));
expect(result).toContain("assigned to Dana");
const spaces = directSpaces(h);
expect(spaces).toHaveLength(1);
const assignment = h.connection.db.prepare("select space_id, body_md from chat_messages where org_id = ?").get(h.orgId) as { space_id: string; body_md: string };
expect(assignment.space_id).toBe(spaces[0].id);
expect(assignment.body_md).toContain("take task_id=");
});
test("send_message requires exactly one destination", () => {
const h = harness({ enableProvider: false });
const deps = { db: h.connection.db, organization: h.organization, chat: h.chat };
expect(dispatchAgentTool(deps, h.ctx, call("send_message", { body: "hello" }))).toContain("ERROR");
expect(dispatchAgentTool(deps, h.ctx, call("send_message", { to: "Dana", channel: "general", body: "hello" }))).toContain("ERROR");
});
test("read_messages can read a direct conversation by teammate name", () => {
const h = harness({ enableProvider: false });
const deps = { db: h.connection.db, organization: h.organization, chat: h.chat };
dispatchAgentTool(deps, h.ctx, call("send_message", { to: "Dana", body: "Morning - priorities for today are in the doc." }));
const readBack = dispatchAgentTool(deps, h.ctx, call("read_messages", { with: "Dana" }));
expect(readBack).toContain("priorities for today");
expect(readBack).toContain("[CEO]");
});
});
-79
View File
@@ -1,79 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { AppDatabase } from "../../src/main/database";
beforeAll(() => {
process.env.AGENTCOMPANY_ALLOW_INSECURE_VAULT = "true";
});
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
describe("agent hierarchy cycle guard", () => {
let tempDir: string;
let db: AppDatabase;
let companyId: string;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-hierarchy-"));
db = new AppDatabase(tempDir);
db.init();
companyId = db.core.company.saveCompany({
name: "Hierarchy Guard Co",
description: "Tests reporting relationships.",
status: "active",
});
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("rejects direct self-reporting", () => {
expect(() => saveAgent("agent-self", "agent-self")).toThrow("Agent reporting relationship would create a cycle.");
});
it("rejects assigning a manager to one of its descendants", () => {
saveAgent("ceo", null);
saveAgent("cto", "ceo");
saveAgent("engineer", "cto");
expect(() => saveAgent("ceo", "engineer")).toThrow("Agent reporting relationship would create a cycle.");
});
it("allows acyclic hierarchy updates", () => {
saveAgent("ceo", null);
saveAgent("cto", "ceo");
saveAgent("engineer", "cto");
saveAgent("engineer", "ceo");
expect(db.core.entity.getAgent("engineer").reportsTo).toBe("ceo");
});
function saveAgent(id: string, reportsTo: string | null): string {
return db.core.agents.saveAgent({
id,
companyId,
name: id,
role: "Engineer",
title: id,
status: "active",
reportsTo,
connectorId: "openai-codex",
workspaceId: null,
model: null,
capabilities: "Maintains hierarchy.",
budgetMonthlyUsd: 100,
});
}
});
-98
View File
@@ -1,98 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { getHireRoutingValidationError, resolveHireReportsTo } 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("hire routing guards", () => {
it("defaults same-department hires to the requester and infers department leads for cross-department hires", async () => {
const profileDir = await createTempDir("agentcompany-hire-manager-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.core.company.saveCompany({
name: "Routing Co",
description: "Hire routing regression coverage",
status: "active",
});
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
department: "executive",
});
const hrId = db.core.agents.saveAgent({
companyId,
name: "HR Lead",
role: "HR Lead",
title: "Head of HR",
status: "active",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Run hiring operations",
budgetMonthlyUsd: 180,
department: "hr",
});
const ctoId = db.core.agents.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "active",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 220,
department: "engineering",
});
expect(resolveHireReportsTo(db, companyId, hrId, undefined, "hr")).toBe(hrId);
expect(resolveHireReportsTo(db, companyId, hrId, undefined, "engineering")).toBe(ctoId);
expect(getHireRoutingValidationError(db, companyId, hrId, hrId, "engineering")).toBe(
"Selected manager must belong to the same department as the new hire, unless they are a top-level executive.",
);
expect(getHireRoutingValidationError(db, companyId, ctoId, ctoId, "engineering")).toBeNull();
db.close();
});
});
-37
View File
@@ -1,37 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import { buildAgentHooks } from "../../src/main/agent-runtime/agent-hooks";
import type { WorkerRpcClient } from "../../src/main/worker/entry/rpc";
function createRpc(response: { ok: true; result: unknown } | { ok: false; error: string }) {
return {
invoke: vi.fn(async () => response),
} as unknown as WorkerRpcClient & { invoke: ReturnType<typeof vi.fn> };
}
describe("agent hook RPC policy", () => {
it("fails beforeToolCall closed when main cannot answer approval policy", async () => {
const rpc = createRpc({ ok: false, error: "RuntimeBridge unavailable" });
const hooks = buildAgentHooks({
runId: "11111111-1111-4111-8111-111111111111",
rpc,
});
await expect(
hooks.beforeToolCall({
toolCall: { name: "bash", id: "tool-call-1" },
args: { path: "report.md" },
} as unknown as Parameters<typeof hooks.beforeToolCall>[0]),
).rejects.toThrow("beforeToolCall RPC failed: RuntimeBridge unavailable");
});
it("installs no per-tool artifact projection hook", () => {
const rpc = createRpc({ ok: true, result: { block: false, reason: null } });
const hooks = buildAgentHooks({
runId: "11111111-1111-4111-8111-111111111111",
rpc,
});
expect(Object.keys(hooks).sort()).toEqual(["beforeToolCall", "transformContext"]);
expect("afterToolCall" in hooks).toBe(false);
});
});
+98
View File
@@ -0,0 +1,98 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ChatService } from "../../src/main/services/chat-service";
import { dispatchAgentTool, type AgentToolContext } from "../../src/main/services/agent-tools";
function call(name: string, args: Record<string, unknown>) {
return { id: `call_${randomUUID()}`, name, arguments: args };
}
describe("agent messaging", () => {
test("agents post to a department channel and reply in the same thread", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-msg-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let providerId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "OrbitOps", employeeName: "Yang", mission: "m", goal: "g" });
providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null,
});
});
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: null, workspace: null,
};
const first = dispatchAgentTool(deps, ctx, call("send_message", { channel: "engineering", body: "Assigning the booking API to Dana." }));
const threadId = /thread_id="([^"]+)"/.exec(first)?.[1];
expect(threadId).toBeTruthy();
const second = dispatchAgentTool(deps, ctx, call("send_message", { channel: "engineering", body: "Update: API shipped.", thread_id: threadId }));
expect(second).toContain("#engineering");
const engineering = connection.db.prepare("select id from chat_spaces where org_id = ? and name = 'engineering'").get(orgId) as { id: string };
const messages = connection.db.prepare("select space_id, thread_id, sender_type, sender_id from chat_messages where org_id = ?")
.all(orgId) as Array<{ space_id: string; thread_id: string; sender_type: string; sender_id: string }>;
expect(messages).toHaveLength(2);
expect(messages.every((m) => m.space_id === engineering.id)).toBe(true);
expect(messages.every((m) => m.sender_type === "agent" && m.sender_id === ceoId)).toBe(true);
// Both messages share the one thread — the reply joined the assignment thread.
expect(new Set(messages.map((m) => m.thread_id)).size).toBe(1);
// Addressing a teammate comes back as presence — who can reply — not a receipt id.
connection.transaction(() => organization.createAgent({
orgId, name: "Dana", role: "Engineer", department: "Engineering",
modelProviderId: null, modelId: null, budgetCents: 1000, managerAgentId: ceoId,
}));
const addressed = dispatchAgentTool(deps, ctx, call("send_message", { channel: "engineering", body: "@Dana can you take the booking API?" }));
expect(addressed).toContain("Dana was mentioned in this thread");
expect(addressed).not.toContain("Dana can see it and reply here");
expect(addressed).not.toContain("message_id=");
expect(connection.db.prepare("select count(*) as count from task_runs where org_id = ? and trigger = 'chat_mention'")
.get(orgId)).toEqual({ count: 0 });
connection.close();
});
test("reports reply readiness when a mention wakes a teammate run", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-msg-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let danaId = "";
let providerId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "OrbitOps", employeeName: "Yang", mission: "m", goal: "g" });
providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
connection.db.prepare("update providers set enabled = 1 where id = ? and org_id = ?").run(providerId, orgId);
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null,
});
danaId = organization.createAgent({
orgId, name: "Dana", role: "Engineer", department: "Engineering",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 1000, managerAgentId: ceoId,
});
});
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: null, workspace: null,
};
const result = dispatchAgentTool(deps, ctx, call("send_message", { channel: "engineering", body: "@Dana can you take this?" }));
expect(result).toContain("Dana can see it and reply here");
expect(connection.db.prepare("select count(*) as count from task_runs where org_id = ? and agent_id = ? and trigger = 'chat_mention'")
.get(orgId, danaId)).toEqual({ count: 1 });
connection.close();
});
});
@@ -1,159 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { canFileCeoStrategy, 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;
}
async function setupCompany(prefix: string) {
const profileDir = await createTempDir(prefix);
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.core.company.saveCompany({
name: "CEO Strategy Co",
description: "Tests canFileCeoStrategy orthogonality",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
return { db, companyId, workspaceId };
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("canFileCeoStrategy", () => {
it("returns true for a genuine CEO who reports to the board", async () => {
const { db, companyId, workspaceId } = await setupCompany("agentcompany-ceo-strategy-real-");
const ceoId = db.core.agents.saveAgent({
companyId,
name: "Ada",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
expect(canFileCeoStrategy(db, companyId, ceoId)).toBe(true);
db.close();
});
it("returns false for a CEO-titled agent with a virtual superior", async () => {
const { db, companyId, workspaceId } = await setupCompany("agentcompany-ceo-strategy-virtual-");
const boardId = db.core.agents.saveAgent({
companyId,
name: "Board Proxy",
role: "Board",
title: "Board Representative",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Hold CEO accountable",
budgetMonthlyUsd: 100,
});
const ceoId = db.core.agents.saveAgent({
companyId,
name: "Ada",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: boardId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
expect(canFileCeoStrategy(db, companyId, ceoId)).toBe(false);
db.close();
});
it("returns false for a VP with no manager (role is not CEO)", async () => {
const { db, companyId, workspaceId } = await setupCompany("agentcompany-ceo-strategy-vp-");
const vpId = db.core.agents.saveAgent({
companyId,
name: "Kai",
role: "VP Product",
title: "Vice President of Product",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Own product strategy",
budgetMonthlyUsd: 220,
department: "product",
});
expect(canFileCeoStrategy(db, companyId, vpId)).toBe(false);
expect(canRequestHire(db, companyId, vpId)).toBe(true);
db.close();
});
it("returns false for a plain IC", async () => {
const { db, companyId, workspaceId } = await setupCompany("agentcompany-ceo-strategy-ic-");
const ceoId = db.core.agents.saveAgent({
companyId,
name: "Ada",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const icId = db.core.agents.saveAgent({
companyId,
name: "Eve",
role: "Engineer",
title: "Software Engineer",
status: "active",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Build features",
budgetMonthlyUsd: 180,
department: "engineering",
});
expect(canFileCeoStrategy(db, companyId, icId)).toBe(false);
db.close();
});
});
-249
View File
@@ -1,249 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
import {
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.core.company.saveCompany({
name: "Org Permissions Co",
description: "Tests recursive org authority",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.core.agents.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "active",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 220,
department: "engineering",
});
const engineerId = db.core.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Software Engineer",
status: "active",
reportsTo: ceoId,
connectorId: "openai-codex",
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.core.company.saveCompany({
name: "Org Permission Override Co",
description: "Tests permission-based org authority",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Operations Lead",
role: "Specialist",
title: "Operations Specialist",
status: "active",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Coordinate operations",
budgetMonthlyUsd: 180,
department: "operations",
});
expect(canManageOrganization(db, companyId, agentId)).toBe(false);
db.core.agents.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.core.company.saveCompany({
name: "Scoped Authority Co",
description: "Tests that branch growth does not unlock org-wide admin powers",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.core.agents.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "active",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 220,
department: "engineering",
});
const cmoId = db.core.agents.saveAgent({
companyId,
name: "CMO",
role: "CMO",
title: "Chief Marketing Officer",
status: "active",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead marketing",
budgetMonthlyUsd: 220,
department: "marketing",
});
const ctoReportId = db.core.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Frontend Engineer",
status: "active",
reportsTo: ctoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Build frontend features",
budgetMonthlyUsd: 180,
department: "engineering",
});
const cmoTaskId = db.core.tasks.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.core.entity.getTask(cmoTaskId).assigneeAgentId).toBe(cmoId);
db.close();
});
});
+234
View File
@@ -0,0 +1,234 @@
import { existsSync, mkdirSync, readFileSync } from "node:fs";
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ChatService } from "../../src/main/services/chat-service";
import { runAgentLoop } from "../../src/main/services/agent-run-loop";
import { canonicalMentionHandle } from "../../src/shared/mention-handles";
import type { AgentToolContext } from "../../src/main/services/agent-tools";
import type { AgentTurnResult, ToolCall } from "../../src/main/services/provider-tool-turn";
interface ScriptedTurn {
text: string;
toolCalls: ToolCall[];
}
function fakeProvider(script: ScriptedTurn[]) {
let index = 0;
return {
runToolTurn: async (): Promise<AgentTurnResult & { requestId: string }> => {
const turn = script[index] ?? { text: "done", toolCalls: [] };
index += 1;
return { text: turn.text, toolCalls: turn.toolCalls, rawResponseJson: "{}", requestId: `req_${index}` };
},
};
}
function call(name: string, args: Record<string, unknown>): ToolCall {
return { id: `call_${randomUUID()}`, name, arguments: args };
}
describe("agent run loop (real tool dispatch)", () => {
test("the CEO hires, creates a task, writes a deliverable, then converges", async () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-loop-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
const workspaceRoot = join(tmpdir(), `ac-ws-${randomUUID()}`);
mkdirSync(workspaceRoot, { recursive: true });
let orgId = "";
let ceoId = "";
let providerId = "";
const workspaceId = "wsp_test";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "OrbitOps", employeeName: "Yang", mission: "Run an AI company.", goal: "Win customers.",
});
providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
connection.db.prepare("update providers set enabled = 1 where id = ?").run(providerId);
connection.db.prepare(`
insert into workspaces(id, org_id, name, local_path, repo_url, repo_ref, project_id, is_primary, created_at, updated_at)
values (?, ?, 'Default Workspace', ?, '', '', null, 1, datetime('now'), datetime('now'))
`).run(workspaceId, orgId, workspaceRoot);
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", title: "Chief Executive Officer", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null,
roleLevel: "c_suite", workspaceId,
});
});
const toolContext: AgentToolContext = {
orgId,
agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null,
taskGoalId: null,
workspace: { id: workspaceId, localPath: workspaceRoot },
};
const script: ScriptedTurn[] = [
{ text: "Staffing the team.", toolCalls: [
call("hire_agent", { name: "Alex Rivera", role: "Growth Marketer", department: "Marketing" }),
call("hire_agent", { name: "Sam Lee", role: "Operations Lead", department: "Operations" }),
] },
{ text: "Delegating.", toolCalls: [
call("create_task", { title: "Build the booking site", description: "Ship a landing + booking flow.", assignee_agent_name: "Alex Rivera" }),
] },
{ text: "Writing the plan.", toolCalls: [
call("write_deliverable", { path: "plan/operating-plan.md", content: "# Operating Plan\n\n1. Validate demand.\n2. Launch booking site.\n" }),
call("send_message", { channel: "general", body: "Team staffed and the operating plan is live. Booking site is next." }),
] },
{ text: "Team staffed, work delegated, plan written.", toolCalls: [] },
];
let toolResultNotifications = 0;
const result = await runAgentLoop(
{
providers: fakeProvider(script),
tools: { db: connection.db, organization, chat: new ChatService(connection.db) },
onToolResults: () => { toolResultNotifications += 1; },
},
{ orgId, providerId, workspaceId, instructions: "You are the CEO.", initialUserTurn: "Start the company.", toolContext },
);
expect(result.iterations).toBe(4);
expect(result.toolCallCount).toBe(5);
expect(result.finalText).toContain("Team staffed");
// The UI is told about new data after every tool-bearing iteration, not only at the end.
expect(toolResultNotifications).toBe(3);
const agentNames = (connection.db.prepare("select name from agents where org_id = ? order by name").all(orgId) as Array<{ name: string }>).map((r) => r.name);
expect(agentNames).toEqual(["Alex Rivera", "CEO", "Sam Lee"]);
const task = connection.db.prepare("select title, assignee_agent_id from tasks where org_id = ?").get(orgId) as { title: string; assignee_agent_id: string } | undefined;
expect(task?.title).toBe("Build the booking site");
const alexId = (connection.db.prepare("select id from agents where org_id = ? and name = 'Alex Rivera'").get(orgId) as { id: string }).id;
expect(task?.assignee_agent_id).toBe(alexId);
const deliverablePath = join(workspaceRoot, "plan", "operating-plan.md");
expect(existsSync(deliverablePath)).toBe(true);
expect(readFileSync(deliverablePath, "utf8")).toContain("Operating Plan");
// The assignment message and the CEO's coordination message both land in chat.
const messages = connection.db.prepare(
"select sender_type, sender_id, body_md, space_id from chat_messages where org_id = ?",
).all(orgId) as Array<{ sender_type: string; sender_id: string; body_md: string; space_id: string }>;
expect(messages).toHaveLength(2);
expect(messages).toContainEqual(expect.objectContaining({ sender_type: "agent", sender_id: ceoId, body_md: expect.stringContaining(`@${canonicalMentionHandle("Alex Rivera")}`) }));
expect(messages).toContainEqual(expect.objectContaining({ sender_type: "agent", sender_id: ceoId, body_md: expect.stringContaining("operating plan is live") }));
connection.close();
});
test("rejects deliverable paths that escape the workspace", async () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-loop2-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
const workspaceRoot = join(tmpdir(), `ac-ws2-${randomUUID()}`);
mkdirSync(workspaceRoot, { recursive: true });
let orgId = "";
let ceoId = "";
let providerId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "OrbitOps", employeeName: "Yang", mission: "m", goal: "g" });
providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
ceoId = organization.createAgent({ orgId, name: "CEO", role: "CEO", department: "Executive", modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null });
});
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null, taskGoalId: null, workspace: { id: "wsp", localPath: workspaceRoot },
};
const script: ScriptedTurn[] = [
{ text: "escape", toolCalls: [call("write_deliverable", { path: "../escape.txt", content: "x" })] },
{ text: "done", toolCalls: [] },
];
const result = await runAgentLoop(
{ providers: fakeProvider(script), tools: { db: connection.db, organization, chat: new ChatService(connection.db) } },
{ orgId, providerId, workspaceId: null, instructions: "i", initialUserTurn: "u", toolContext: ctx },
);
// The tool returns an error string to the model; nothing is written outside the root.
expect(existsSync(join(workspaceRoot, "..", "escape.txt"))).toBe(false);
expect(result.toolCallCount).toBe(1);
connection.close();
});
test("delegating a task opens an assignment thread for the assignee", async () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-loop-assign-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let providerId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "OrbitOps", employeeName: "Yang", mission: "m", goal: "g",
});
providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
connection.db.prepare("update providers set enabled = 1 where id = ?").run(providerId);
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null,
});
});
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null, taskGoalId: null, workspace: null,
};
const script: ScriptedTurn[] = [
{ text: "staff", toolCalls: [call("hire_agent", { name: "Alex Rivera", role: "Engineer", department: "Engineering" })] },
{ text: "delegate", toolCalls: [call("create_task", { title: "Build the booking API", description: "Ship the booking API.", assignee_agent_name: "Alex Rivera" })] },
{ text: "done", toolCalls: [] },
];
await runAgentLoop(
{ providers: fakeProvider(script), tools: { db: connection.db, organization, chat: new ChatService(connection.db) } },
{ orgId, providerId, workspaceId: null, instructions: "i", initialUserTurn: "u", toolContext: ctx },
);
const alex = connection.db.prepare("select id from agents where org_id = ? and name = 'Alex Rivera'").get(orgId) as { id: string };
const assignment = connection.db.prepare(`
select m.body_md, m.thread_id, s.id as space_id, s.space_type
from chat_messages m
join chat_spaces s on s.id = m.space_id
where m.org_id = ?
`).get(orgId) as { body_md: string; thread_id: string; space_id: string; space_type: string } | undefined;
// Assignments are 1:1 hand-offs, so they live in the manager's direct
// conversation with the assignee instead of a shared channel.
expect(assignment).toMatchObject({ space_type: "direct" });
const dmMembers = connection.db.prepare("select principal_id from chat_memberships where space_id = ?")
.all(assignment?.space_id ?? "") as Array<{ principal_id: string }>;
expect(dmMembers.map((m) => m.principal_id).sort()).toEqual([ceoId, alex.id].sort());
expect(assignment?.body_md).toContain(`@${canonicalMentionHandle("Alex Rivera")}`);
expect(assignment?.body_md).toContain("Build the booking API");
const wake = connection.db.prepare(`
select state, trigger, secret_scope_json
from task_runs
where org_id = ? and agent_id = ? and trigger = 'chat_mention'
`).get(orgId, alex.id) as { state: string; trigger: string; secret_scope_json: string } | undefined;
expect(wake).toMatchObject({ state: "queued", trigger: "chat_mention" });
expect(JSON.parse(wake?.secret_scope_json ?? "{}")).toMatchObject({ sourceThreadId: assignment?.thread_id });
connection.close();
});
test("a first turn with no text and no tool calls fails the run instead of completing a no-op", async () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-loop3-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let providerId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "OrbitOps", employeeName: "Yang", mission: "m", goal: "g" });
providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
ceoId = organization.createAgent({ orgId, name: "CEO", role: "CEO", department: "Executive", modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null });
});
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null, taskGoalId: null, workspace: null,
};
await expect(runAgentLoop(
{ providers: fakeProvider([{ text: "", toolCalls: [] }]), tools: { db: connection.db, organization, chat: new ChatService(connection.db) } },
{ orgId, providerId, workspaceId: null, instructions: "i", initialUserTurn: "u", toolContext: ctx },
)).rejects.toThrow(/empty turn/);
connection.close();
});
});
@@ -1,59 +0,0 @@
import { describe, expect, it } from "vitest";
import { hireRequestSchema } from "../../src/shared/contracts/schemas-agents";
import { getHireNameValidationError } from "../../src/main/agent-permissions";
const baseHireRequest = {
companyId: "00000000-0000-0000-0000-000000000000",
requestedByAgentId: null,
name: "Alice",
role: "Engineer",
title: "Backend Engineer",
department: "engineering" as const,
reportsTo: null,
connectorId: "openai-codex" as const,
workspaceId: null,
model: null,
metadataJson: "{}",
capabilities: "Build backend services",
budgetMonthlyUsd: 120,
};
describe("agent input schemas", () => {
it("rejects whitespace-only hire names", () => {
expect(() =>
hireRequestSchema.parse({
...baseHireRequest,
name: " ",
}),
).toThrow();
});
it("trims semantic hire fields before persistence", () => {
const parsed = hireRequestSchema.parse({
...baseHireRequest,
name: " Alice ",
role: " Engineer ",
title: " Backend Engineer ",
});
expect(parsed.name).toBe("Alice");
expect(parsed.role).toBe("Engineer");
expect(parsed.title).toBe("Backend Engineer");
});
it("rejects role-like names for non-executive hires", () => {
expect(getHireNameValidationError({
name: "Backend Engineer",
role: "Backend Engineer",
title: "Backend Engineer",
})).toBe("Hire name must be a real display name, not just the role.");
});
it("allows executive acronyms when they are the intended display name", () => {
expect(getHireNameValidationError({
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
})).toBeNull();
});
});
-20
View File
@@ -1,20 +0,0 @@
import { describe, expect, it } from "vitest";
import { decodeBodyToUtf8 } from "../../src/main/api-server/server-core";
describe("API request body encoding", () => {
it("decodes valid UTF-8 JSON", () => {
const raw = Buffer.from(JSON.stringify({ text: "中文" }), "utf8");
expect(decodeBodyToUtf8(raw)).toBe("{\"text\":\"中文\"}");
});
it("rejects GBK bytes instead of repairing them", () => {
const raw = Buffer.from([
0x7b, 0x22, 0x74, 0x65, 0x78, 0x74, 0x22, 0x3a, 0x22,
0xd6, 0xd0, 0xce, 0xc4,
0x22, 0x7d,
]);
expect(() => decodeBodyToUtf8(raw)).toThrow();
});
});
-30
View File
@@ -1,30 +0,0 @@
import { describe, expect, it } from "vitest";
import { createApiServer, type ApiServerEventCallbacks } from "../../src/main/api-server";
import type { AppDatabase } from "../../src/main/database";
import { createTestApiServerCallbacks } from "../support/api-server-callbacks";
describe("api server callback contract", () => {
it("fails during construction when the callback object is missing", () => {
expect(() => {
createApiServer({} as AppDatabase, undefined as unknown as ApiServerEventCallbacks);
}).toThrow("Missing AgentApiServer callbacks.");
});
it("fails during construction when a required callback is missing", () => {
const callbacks: Partial<ApiServerEventCallbacks> = createTestApiServerCallbacks();
delete callbacks.onTaskCreated;
expect(() => {
createApiServer({} as AppDatabase, callbacks as ApiServerEventCallbacks);
}).toThrow("Missing AgentApiServer callback: onTaskCreated");
});
it("constructs the server when every callback is explicit", () => {
const callbacks = createTestApiServerCallbacks();
const server = createApiServer({} as AppDatabase, callbacks);
expect(server.callbacks.onTaskCreated).toBe(callbacks.onTaskCreated);
expect(server.onDomainChanged).toBe(callbacks.onDomainChanged);
expect(server.onWakeAgent).toBe(callbacks.onWakeAgent);
});
});
-30
View File
@@ -1,30 +0,0 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { logAppError, setFatalErrorRouteForTesting } from "../../src/main/errors";
import type { AppError } from "../../src/main/errors";
describe("AppError fatal route", () => {
afterEach(() => {
setFatalErrorRouteForTesting(null);
});
it("routes fatal errors through the process-level handler", () => {
const logger = { error: vi.fn(), warn: vi.fn() };
const routed: AppError[] = [];
const sentinel = new Error("fatal route called");
setFatalErrorRouteForTesting((error) => {
routed.push(error);
throw sentinel;
});
expect(() => logAppError(logger, new Error("sqlite corrupt"), {
kind: "fatal",
code: "db.sqlite.corrupt",
message: "SQLite profile is corrupt",
context: { profile: "test" },
})).toThrow(sentinel);
expect(routed[0]?.kind).toBe("fatal");
expect(routed[0]?.code).toBe("db.sqlite.corrupt");
expect(logger.error).toHaveBeenCalled();
});
});
-131
View File
@@ -1,131 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { 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.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Hire Routing Co",
description: "Tests recursive hire wake routing",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.core.agents.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "active",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 220,
department: "engineering",
});
const { agentId: hireId, approvalId } = db.createHireRequest({
companyId,
requestedByAgentId: ctoId,
name: "New Engineer",
role: "Engineer",
title: "Frontend Engineer",
department: "engineering",
reportsTo: ctoId,
connectorId: "openai-codex",
workspaceId,
model: null,
metadataJson: "{}",
capabilities: "React, TypeScript, Tailwind",
budgetMonthlyUsd: 180,
payloadSummary: "Hire new engineer",
impactSummary: "Need another IC under the CTO",
payloadJson: "{}",
});
db.core.approvals.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,
queueBrowserActionExecution: () => {},
isQuitting: () => false,
}, {
dispatchAutomation,
handleTaskStatusChange: vi.fn(),
runWorkflowPipeline: async () => {},
trackTimer: vi.fn(),
}, approvalId, companyId, "approved");
expect(db.core.entity.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();
});
});
@@ -1,262 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { handleApprovalResolved } from "../../src/main/automation/automation-approval-handler";
import { createHeartbeat } from "../../src/main/orchestration/heartbeat";
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.each(["rejected", "revision_requested"] as const)(
"persists high-importance knowledge when the board returns %s on a deliverable",
async (decision) => {
const profileDir = await createTempDir("agentcompany-review-feedback-");
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Review Feedback Co",
description: "Tests review feedback knowledge writeback",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Research Lead",
role: "Researcher",
title: "Research Lead",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Research and synthesis",
budgetMonthlyUsd: 180,
department: "research",
});
const taskId = db.core.tasks.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.core.documents.saveDocument({
companyId,
type: "knowledge_article",
title: "Market report",
content: "Draft report body",
authorAgentId: agentId,
status: "in_review",
});
const approvalId = db.core.approvals.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.core.approvals.decideApproval({
approvalId,
state: decision,
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,
queueBrowserActionExecution: () => {},
isQuitting: () => false,
}, {
dispatchAutomation: vi.fn(),
handleTaskStatusChange: vi.fn(),
runWorkflowPipeline: async () => {},
trackTimer: vi.fn(),
}, approvalId, companyId, decision);
const knowledgeEntries = db.projection.snapshot.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(decision);
expect(knowledgeEntries[0]?.content).toContain("stronger competitor evidence");
expect(wakeAgentIfPossible).toHaveBeenCalledWith(agentId, companyId, "revision_needed");
expect(wakeAgentIfPossible).toHaveBeenCalledTimes(1);
expect(db.core.entity.getTask(taskId).status).toBe("todo");
const document = db.projection.snapshot.listSnapshot().documents.find((entry) => entry.id === documentId);
expect(document?.status).toBe("draft");
db.close();
},
);
it("makes a revision request immediately checkoutable by the assignee heartbeat", async () => {
const profileDir = await createTempDir("agentcompany-review-checkout-");
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.saveConnector({
id: "openai-codex",
model: "gpt-5.4",
envBindingText: "",
notes: "Test connector",
});
const companyId = db.core.company.saveCompany({
name: "Review Checkout Co",
description: "Tests review revision checkout",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Research Lead",
role: "Researcher",
title: "Research Lead",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: "gpt-5.4",
capabilities: "Research and synthesis",
budgetMonthlyUsd: 180,
department: "research",
});
const taskId = db.core.tasks.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.core.documents.saveDocument({
companyId,
type: "knowledge_article",
title: "Market report",
content: "Draft report body",
authorAgentId: agentId,
status: "in_review",
});
const approvalId = db.core.approvals.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.core.approvals.decideApproval({
approvalId,
state: "revision_requested",
decisionNote: "Add stronger competitor evidence and a clearer recommendation.",
});
const dispatchRunToWorker = vi.fn();
const heartbeat = createHeartbeat(
{
db,
logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
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: {
autoCloseExpiredIncidents: vi.fn(() => 0),
evaluateBudgetGate: vi.fn(() => ({ allowed: true, blockedBy: [] })),
} as never,
checkpointEngine: { shouldPauseHeartbeat: () => false },
},
() => ({
workspace: null,
lockMode: "none",
source: "none",
observationPath: profileDir,
}),
() => null,
dispatchRunToWorker,
(filter) => db.core.runs.hasActiveRun(filter),
);
handleApprovalResolved({
db,
logger: { info: () => {}, warn: () => {}, error: () => {} },
publishDomainChanged: () => {},
notify: vi.fn(),
wakeAgentIfPossible: heartbeat.wakeAgentIfPossible,
queueBrowserActionExecution: () => {},
isQuitting: () => false,
}, {
dispatchAutomation: vi.fn(),
handleTaskStatusChange: vi.fn(),
runWorkflowPipeline: async () => {},
trackTimer: vi.fn(),
}, approvalId, companyId, "revision_requested");
const task = db.core.entity.getTask(taskId);
expect(task.status).toBe("in_progress");
expect(db.core.runs.hasActiveRun({ companyId, taskId, agentId })).toBe(true);
expect(dispatchRunToWorker).toHaveBeenCalledTimes(1);
expect(dispatchRunToWorker).toHaveBeenCalledWith(expect.objectContaining({
companyId,
task: expect.objectContaining({ id: taskId }),
trigger: "revision_needed",
}));
db.close();
});
});
@@ -1,66 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { AppDatabase } from "../../src/main/database";
import {
captureBaselineIfRequired,
reconcileWorkspaceArtifacts,
type ArtifactProjectorRunContext,
} from "../../src/main/agent-runtime/artifact-projector";
const tempDirs: string[] = [];
async function createTempDir(prefix: string): Promise<string> {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
function createLogger() {
return {
warn: vi.fn(),
error: vi.fn(),
};
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("artifact projector error taxonomy", () => {
it("classifies unreadable workspace baseline capture failures", async () => {
const logger = createLogger();
const parent = await createTempDir("agentcompany-missing-workspace-parent-");
const workspacePath = join(parent, "missing-workspace");
const baseline = captureBaselineIfRequired(workspacePath, logger);
expect(baseline).toBeNull();
expect(logger.warn).toHaveBeenCalledWith(
"[artifact.fs.workspace_stat_failed] Failed to stat workspace before artifact baseline capture.",
{ workspacePath },
expect.stringContaining("cause="),
);
expect(logger.error).not.toHaveBeenCalled();
});
it("skips completion reconciliation when the run has no change source", async () => {
const workspacePath = await createTempDir("agentcompany-shared-artifacts-");
const ctx: ArtifactProjectorRunContext = {
runId: "run-shared",
agentId: "agent-shared",
companyId: "company-shared",
taskId: "task-shared",
workspaceId: null,
workspacePath,
workspaceMode: "none",
logger: createLogger(),
workspaceBaseline: null,
};
expect(reconcileWorkspaceArtifacts({} as AppDatabase, ctx)).toEqual([
{ kind: "skipped", reason: "no_baseline" },
]);
});
});
-595
View File
@@ -1,595 +0,0 @@
import { join } from "node:path";
import { tmpdir } from "node:os";
import { randomUUID } from "node:crypto";
import { mkdtemp, rm } from "node:fs/promises";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase, STALE_LOCK_TIMEOUT_MS } from "../../src/main/database";
type RawDb = {
db: {
prepare: (sql: string) => {
run: (...args: unknown[]) => void;
get: (...args: unknown[]) => unknown;
};
};
};
type TaskLeaseRow = {
task_id: string;
run_id: string;
expires_at: string;
};
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function createTestCompany(db: AppDatabase): string {
return db.core.company.saveCompany({
name: "Test Company",
description: "Atomic checkout tests",
status: "active",
});
}
function createTestConnector(db: AppDatabase): void {
db.core.connectors.saveConnector({
id: "openai-codex",
model: "gpt-5",
envBindingText: "",
notes: "Test connector",
});
}
function createTestWorkspace(db: AppDatabase, companyId: string): string {
return db.core.workspaces.saveWorkspace({
companyId,
name: "Test Workspace",
localPath: "/tmp/test-workspace",
repoUrl: "https://github.com/example/test",
repoRef: "main",
isPrimary: true,
});
}
function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string, name = "Agent"): string {
return db.core.agents.saveAgent({
companyId,
name,
role: "IC",
title: name,
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Executes tasks",
budgetMonthlyUsd: 100,
});
}
function createTestTask(db: AppDatabase, companyId: string, agentId: string | null, workspaceId: string): string {
return db.core.tasks.saveTask({
companyId,
title: "Test Task",
description: "A test task for checkout",
assigneeAgentId: agentId,
workspaceId,
priority: "medium",
status: "todo",
});
}
function getTaskLease(db: AppDatabase, taskId: string): TaskLeaseRow | null {
return (
((db as unknown as RawDb).db
.prepare("SELECT task_id, run_id, expires_at FROM task_leases WHERE task_id = ?")
.get(taskId) as TaskLeaseRow | undefined) ?? null
);
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("CAS checkout", () => {
let tempDir: string;
let db: AppDatabase;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-checkout-"));
db = new AppDatabase(tempDir);
db.init();
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("CAS checkout succeeds for first claimer", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const { id: runId } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
const result = db.core.tasks.checkoutTask(taskId, runId);
expect(result).toBe(true);
const task = db.core.entity.getTask(taskId);
expect(task.status).toBe("in_progress");
});
it("createRun does not claim a task before checkout", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
const task = db.core.entity.getTask(taskId);
expect(task.activeRunId).toBeNull();
expect(task.status).toBe("todo");
});
it("checkout rejects a missing run without mutating task state", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const missingRunId = randomUUID();
expect(() => db.core.tasks.checkoutTask(taskId, missingRunId)).toThrow(/Run not found/);
const task = db.core.entity.getTask(taskId);
expect(task.activeRunId).toBeNull();
expect(task.status).toBe("todo");
expect(getTaskLease(db, taskId)).toBeNull();
});
it("failed checkout keeps a taskless run unbound", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
(db as unknown as RawDb).db.prepare("UPDATE tasks SET status = 'in_progress' WHERE id = ?").run(taskId);
const { id: runId } = db.core.runs.createRun({
companyId,
taskId: null,
agentId,
workspaceId,
connectorId: "openai-codex",
});
expect(db.core.tasks.checkoutTask(taskId, runId)).toBe(false);
expect(db.core.runs.getRun(runId).taskId).toBeNull();
expect(getTaskLease(db, taskId)).toBeNull();
expect(db.core.runs.hasActiveRun({ companyId, taskId })).toBe(false);
});
it("createTaskRun atomically creates the run and claims the task row lock", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const run = db.core.runs.createTaskRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
expect(run).not.toBeNull();
const task = db.core.entity.getTask(taskId);
expect(task.status).toBe("in_progress");
expect(task.activeRunId).toBe(run?.id);
expect(getTaskLease(db, taskId)?.run_id).toBe(run?.id);
});
it("createTaskRun rolls back the run row when checkout loses the race", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const firstRun = db.core.runs.createTaskRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
expect(firstRun).not.toBeNull();
const losingRunId = randomUUID();
const secondRun = db.core.runs.createTaskRun({
id: losingRunId,
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
expect(secondRun).toBeNull();
const stored = (db as unknown as RawDb).db
.prepare("SELECT id FROM runs WHERE id = ?")
.get(losingRunId);
expect(stored).toBeUndefined();
});
it("CAS checkout fails for second claimer", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const { id: runId1 } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
const { id: runId2 } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
expect(db.core.tasks.checkoutTask(taskId, runId1)).toBe(true);
expect(db.core.tasks.checkoutTask(taskId, runId2)).toBe(false);
});
it("CAS checkout rejects already-in-progress tasks", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
// Manually set task to in_progress
(db as unknown as RawDb).db.prepare("UPDATE tasks SET status = 'in_progress' WHERE id = ?").run(taskId);
const { id: runId } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
expect(db.core.tasks.checkoutTask(taskId, runId)).toBe(false);
});
it("lock release in finishRun clears the task lease", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const { id: runId } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
db.core.tasks.checkoutTask(taskId, runId);
db.core.runs.markRunStatus(runId, "running", "");
db.core.runs.finishRun({
runId,
status: "succeeded",
summary: "Done",
errorMessage: null,
exitCode: 0,
signal: null,
model: null,
unattributedCost: false,
});
expect(getTaskLease(db, taskId)).toBeNull();
});
it("lock release in cancelRunForDeletion clears the task lease", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const { id: runId } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
db.core.tasks.checkoutTask(taskId, runId);
db.core.runs.markRunStatus(runId, "running", "");
// Deleting the agent triggers cancelRunForDeletion for active runs
db.core.agents.deleteAgent(agentId, companyId);
expect(getTaskLease(db, taskId)).toBeNull();
});
it("stale lock recovery resets tasks older than 15 minutes", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const { id: runId } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
db.core.tasks.checkoutTask(taskId, runId);
// Backdate the lease expiry to 20 minutes ago.
const backdated = new Date(Date.now() - 20 * 60 * 1000).toISOString();
(db as unknown as RawDb).db
.prepare("UPDATE task_leases SET expires_at = ? WHERE task_id = ?")
.run(backdated, taskId);
const recovered = db.recoverStaleLocks();
expect(recovered.length).toBe(1);
expect(recovered[0].taskId).toBe(taskId);
expect(recovered[0].companyId).toBe(companyId);
const task = db.core.entity.getTask(taskId);
expect(task.status).toBe("todo");
expect(getTaskLease(db, taskId)).toBeNull();
});
it("stale lock recovery ignores tasks within 15 minute window", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const { id: runId } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
db.core.tasks.checkoutTask(taskId, runId);
const recovered = db.recoverStaleLocks();
expect(recovered.length).toBe(0);
const task = db.core.entity.getTask(taskId);
expect(task.status).toBe("in_progress");
});
it("STALE_LOCK_TIMEOUT_MS is 15 minutes", () => {
expect(STALE_LOCK_TIMEOUT_MS).toBe(15 * 60 * 1000);
});
it("failed dispatch (run never started) rolls task back to 'todo' so heartbeat can retry", () => {
// Regression: workspace-lock contention calls finishRun('failed') on a
// run that never reached 'running'. The lease must clear and the task
// status must return to 'todo' so the heartbeat retry path can reclaim it.
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const { id: runId } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
db.core.tasks.checkoutTask(taskId, runId);
expect(db.core.entity.getTask(taskId).status).toBe("in_progress");
// Never call markRunStatus('running', ...) — this simulates the
// "workspace already locked" bail-out inside dispatchRunToWorker.
db.core.runs.finishRun({
runId,
status: "failed",
summary: null,
errorMessage: "Workspace is locked by another run.",
exitCode: null,
signal: null,
model: null,
unattributedCost: true,
});
expect(db.core.entity.getTask(taskId).status).toBe("todo");
// And the next CAS checkout must succeed — proving the heartbeat
// retry path is unblocked.
const { id: retryRunId } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
expect(db.core.tasks.checkoutTask(taskId, retryRunId)).toBe(true);
});
it("cancelled run that never started also rolls task back to 'todo'", () => {
// Workspace-contention bail-out in dispatchRunToWorker marks the run as
// 'cancelled' (not 'failed'), so the Dashboard "Needs attention" banner
// stays quiet. The rollback path must handle cancelled too, or the task
// would stick in 'in_progress' just like the original 'failed' bug.
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const { id: runId } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
db.core.tasks.checkoutTask(taskId, runId);
db.core.runs.finishRun({
runId,
status: "cancelled",
summary: null,
errorMessage: "Workspace in use by another run; will retry.",
exitCode: null,
signal: null,
model: null,
unattributedCost: true,
});
expect(db.core.entity.getTask(taskId).status).toBe("todo");
});
it("failed run that did start stays 'in_progress' (human retry path)", () => {
// Counterpart to the regression above: a run that actually transitioned
// to 'running' and then failed mid-flight is the user's problem to
// resolve through the recovery UI — we must not silently reset its
// task to 'todo' and lose the partial work / error context.
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const { id: runId } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
db.core.tasks.checkoutTask(taskId, runId);
db.core.runs.markRunStatus(runId, "running", "");
db.core.runs.finishRun({
runId,
status: "failed",
summary: null,
errorMessage: "Worker crashed",
exitCode: 1,
signal: null,
model: null,
unattributedCost: false,
});
expect(db.core.entity.getTask(taskId).status).toBe("in_progress");
});
it("advanceTaskStatus('in_review') rejects a CEO self-review that has no reviewer", () => {
// Regression guard for the CEO dead-letter case: a task whose
// assignee is a top-level agent (reports_to IS NULL) and that is
// not flagged for user review has no reachable reviewer. Previous
// versions silently resurrected these tasks at every DB init; the
// root fix rejects the transition at source so callers either
// escalate to the human or leave the task in a reclaimable state.
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const ceoId = createTestAgent(db, companyId, workspaceId, "CEO");
const taskId = createTestTask(db, companyId, ceoId, workspaceId);
expect(() => db.core.tasks.advanceTaskStatus(taskId, "in_review")).toThrow(/no reviewer/);
// Task status must remain unchanged after the rejected transition.
expect(db.core.entity.getTask(taskId).status).toBe("todo");
});
it("advanceTaskStatus('in_review') accepts a CEO assignee flagged requires_user_review=1", () => {
// The same task becomes legitimate when the human is the reviewer.
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const ceoId = createTestAgent(db, companyId, workspaceId, "CEO");
const taskId = createTestTask(db, companyId, ceoId, workspaceId);
db.core.tasks.setTaskRequiresUserReview(taskId, true);
db.core.tasks.advanceTaskStatus(taskId, "in_review");
expect(db.core.entity.getTask(taskId).status).toBe("in_review");
});
it("interruptStaleRuns surfaces shutdown-era running runs without startup task rewrites", () => {
// `interruptStaleRuns` is the ONLY mutation the startup path may
// perform: any run at 'running' on boot means a crash, so the run
// flips to 'interrupted' and the task surfaces as 'blocked' for
// operator attention. No other reconciliation runs at init; the
// task-status machine (see guardInReviewHasReviewer) and the
// runtime `finishRun` guards are now responsible for every other
// data-repair concern.
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentId = createTestAgent(db, companyId, workspaceId);
const taskId = createTestTask(db, companyId, null, workspaceId);
const { id: liveRunId } = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
db.core.tasks.checkoutTask(taskId, liveRunId);
db.core.runs.markRunStatus(liveRunId, "running", "");
db.close();
db = new AppDatabase(tempDir);
db.init();
expect(db.lastInterruptedCount).toBe(1);
expect(db.core.entity.getTask(taskId).status).toBe("blocked");
});
});
+89
View File
@@ -0,0 +1,89 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { AuditService } from "../../src/main/services/audit-service";
import { ChatService } from "../../src/main/services/chat-service";
import { OrganizationService } from "../../src/main/services/organization-service";
import { SnapshotService } from "../../src/main/services/snapshot-service";
function createConnection(): DatabaseConnection {
return new DatabaseConnection(join(tmpdir(), `agentcompany-new-audit-${randomUUID()}.db`));
}
describe("audit service", () => {
test("keeps core audit events immutable", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
let auditId = "";
connection.transaction(() => {
const orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
auditId = (connection.db.prepare("select id from audit_events where org_id = ? limit 1").get(orgId) as { id: string }).id;
});
expect(() => connection.db.prepare("update audit_events set action = ? where id = ?")
.run("changed", auditId)).toThrow("Audit events are immutable.");
expect(() => connection.db.prepare("delete from audit_events where id = ?")
.run(auditId)).toThrow("Audit events are immutable.");
connection.close();
});
test("creates thread scoped replay runs from immutable audit events", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
const audit = new AuditService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let employeeId = "";
let threadId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
const spaceId = chat.createSpace({
orgId,
spaceType: "project",
name: "release",
topic: "Release coordination",
visibility: "workspace",
});
const messageId = chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/escalate review release plan",
attachmentFileIds: [],
});
threadId = (connection.db.prepare("select thread_id from chat_messages where id = ?").get(messageId) as { thread_id: string }).thread_id;
audit.createThreadReplay({ orgId, threadId, createdByEmployeeId: employeeId });
});
const state = snapshot.load();
const run = state.auditReplayRuns[0];
if (!run) throw new Error("Audit replay run was not created.");
const replay = JSON.parse(run.replayJson) as { events: Array<{ entityType: string; action: string }> };
expect(run.threadId).toBe(threadId);
expect(run.eventDigest).toHaveLength(64);
expect(run.eventCount).toBe(replay.events.length);
expect(replay.events).toContainEqual(expect.objectContaining({ entityType: "chat_message", action: "sent" }));
expect(replay.events).toContainEqual(expect.objectContaining({ entityType: "approval", action: "requested" }));
expect(replay.events.some((event) => event.entityType === "audit_replay_run")).toBe(false);
expect(state.auditEvents.some((event) => event.entityType === "audit_replay_run" && event.action === "created")).toBe(true);
connection.close();
});
});
@@ -1,48 +0,0 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { AUTOMATION_ACTION_HANDLERS } from "../../src/main/automation/automation-actions";
import {
ACTION_SCHEMAS,
AUTOMATION_ACTIONS,
WORKFLOW_ACTIONS,
isAutomationAction,
parseActionConfig,
} from "../../src/main/automation/automation-action-schemas";
import { normalizeWorkflowSteps } from "../../src/main/automation/automation-workflow";
describe("automation action registry", () => {
it("drives schemas, workflow validation, and execution handlers from one action set", () => {
const actionNames = [...AUTOMATION_ACTIONS].sort();
expect([...WORKFLOW_ACTIONS].sort()).toEqual(actionNames);
expect(Object.keys(ACTION_SCHEMAS).sort()).toEqual(actionNames);
expect(Object.keys(AUTOMATION_ACTION_HANDLERS).sort()).toEqual(actionNames);
for (const action of AUTOMATION_ACTIONS) {
expect(isAutomationAction(action)).toBe(true);
expect(AUTOMATION_ACTION_HANDLERS[action]).toEqual(expect.any(Function));
expect(() => parseActionConfig(action, "{}")).not.toThrow();
expect(normalizeWorkflowSteps([{ action, configJson: "{}" }])[0]).toMatchObject({
action,
status: "pending",
});
}
});
it("rejects unsupported workflow actions before execution", () => {
expect(isAutomationAction("unknown_action")).toBe(false);
expect(() => normalizeWorkflowSteps([{ action: "unknown_action" }])).toThrow(
'Workflow step 1 has unsupported action "unknown_action".',
);
});
it("keeps automation execution table-driven", () => {
const dispatchSource = readFileSync(join(process.cwd(), "src/main/automation/automation-actions.ts"), "utf8");
const handlerSource = readFileSync(join(process.cwd(), "src/main/automation/automation-action-handlers.ts"), "utf8");
expect(dispatchSource).not.toContain("switch (action)");
expect(handlerSource).not.toContain("switch (action)");
expect(dispatchSource).toContain("AUTOMATION_ACTION_HANDLERS");
expect(handlerSource).toContain("satisfies Record<AutomationAction, AutomationActionHandler>");
});
});
-133
View File
@@ -1,133 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { createBrowserActionService } from "../../src/main/browser-action-service";
import { recoverScheduledPosts } from "../../src/main/bootstrap/recover-scheduled-posts";
import type { BrowserManager } from "../../src/main/browser-manager";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
async function nextTick(): Promise<void> {
await new Promise<void>((resolve) => setImmediate(resolve));
}
async function delay(ms: number): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, ms));
}
function seedBrowserAction(db: AppDatabase, profileDir: string, input?: {
payloadJson?: string;
approvalId?: string | null;
}): { companyId: string; actionId: string } {
const companyId = db.core.company.saveCompany({
name: "Browser Queue Co",
description: "Queue contract test",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const socialAccountId = db.core.social.saveSocialAccount({
companyId,
platform: "twitter",
accountName: "agentcompany",
status: "ready",
requireApproval: false,
});
const actionId = db.core.social.saveBrowserAction({
companyId,
socialAccountId,
agentId,
actionType: "post",
payloadJson: input?.payloadJson ?? JSON.stringify({ content: "hello" }),
status: "queued",
approvalId: input?.approvalId ?? null,
});
return { companyId, actionId };
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("browser action queue", () => {
it("marks queued actions failed and records activity when dispatch fails before execution", async () => {
const profileDir = await createTempDir("agentcompany-browser-queue-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, actionId } = seedBrowserAction(db, profileDir, {
approvalId: "missing-approval",
});
let published = 0;
const service = createBrowserActionService({
db,
browserManagerRef: { current: null as BrowserManager | null },
publishDomainChanged: () => { published += 1; },
});
service.queueBrowserActionExecution(actionId, companyId);
await nextTick();
const action = db.core.social.getBrowserAction(actionId, companyId);
expect(action?.status).toBe("failed");
expect(action?.errorMessage).toContain("missing-approval");
const activity = db.projection.snapshot.listSnapshot({ companyId }).activity;
expect(activity.some((entry) => entry.action === "browser.action_dispatch_failed" && entry.entityId === actionId)).toBe(true);
expect(published).toBeGreaterThan(0);
db.close();
});
it("startup recovery schedules queued browser actions through the queue wrapper", async () => {
const profileDir = await createTempDir("agentcompany-browser-recovery-");
const db = new AppDatabase(profileDir);
db.init();
const scheduledAt = new Date(Date.now() + 40).toISOString();
const { companyId, actionId } = seedBrowserAction(db, profileDir, {
payloadJson: JSON.stringify({ content: "scheduled", scheduledAt }),
});
const calls: Array<{ actionId: string; companyId: string }> = [];
const timers = recoverScheduledPosts({
db,
logger: { info: () => {}, warn: () => {} },
isQuittingRef: { current: false },
getBrowserActionService: () => ({
queueBrowserActionExecution: (queuedActionId, queuedCompanyId) => {
calls.push({ actionId: queuedActionId, companyId: queuedCompanyId });
},
}),
});
await delay(80);
for (const timer of timers) clearTimeout(timer);
expect(calls).toEqual([{ actionId, companyId }]);
db.close();
});
});
-92
View File
@@ -1,92 +0,0 @@
import type { Session } from "electron";
import { describe, expect, it } from "vitest";
import {
assertAuthenticatedPage,
assertAuthenticatedSession,
} from "../../src/main/browser/browser-action-auth";
import { shouldCaptureSuccessScreenshot } from "../../src/main/browser/browser-screenshot-policy";
import { getPlatformSelectors } from "../../src/main/browser/platform-selectors";
function fakeSession(cookiesByOrigin: Record<string, unknown[]>): Pick<Session, "cookies"> {
return {
cookies: {
get: async ({ url }) => cookiesByOrigin[url ?? ""] ?? [],
},
} as Pick<Session, "cookies">;
}
describe("browser action runtime guards", () => {
it("fails social actions before execution when the account session has no cookies", async () => {
await expect(
assertAuthenticatedSession(fakeSession({}), "twitter", "post"),
).rejects.toMatchObject({
code: "browser.not_logged_in",
});
});
it("rejects guest cookies for platforms with explicit auth cookies", async () => {
await expect(
assertAuthenticatedSession(
fakeSession({ "https://x.com": [{ name: "guest_id", value: "present" }] }),
"twitter",
"post",
),
).rejects.toMatchObject({
code: "browser.not_logged_in",
});
});
it("accepts a persisted platform cookie before executing actions", async () => {
await expect(
assertAuthenticatedSession(
fakeSession({ "https://x.com": [{ name: "auth_token", value: "present" }] }),
"twitter",
"post",
),
).resolves.toBeUndefined();
});
it("treats login redirects as an unauthenticated page state", async () => {
await expect(
assertAuthenticatedPage(() => "https://x.com/i/flow/login", "twitter", "post"),
).rejects.toMatchObject({
code: "browser.not_logged_in",
});
});
it("keeps platform selectors versioned outside the executor", () => {
const twitter = getPlatformSelectors("twitter");
const linkedin = getPlatformSelectors("linkedin");
expect(twitter.editor).toContain("[data-testid='tweetTextarea_0']");
expect(twitter.submit).toContain("[data-testid='tweetButton']");
expect(linkedin.composerOpen).toContain("button.share-box-feed-entry__trigger");
expect(linkedin.editor).toContain(".ql-editor[contenteditable='true']");
});
it("captures successful screenshots only for explicit screenshot requests", () => {
expect(shouldCaptureSuccessScreenshot({
id: "post-1",
socialAccountId: "social-1",
platform: "twitter",
actionType: "post",
payload: { content: "hello" },
})).toBe(false);
expect(shouldCaptureSuccessScreenshot({
id: "screenshot-1",
socialAccountId: "social-1",
platform: "twitter",
actionType: "screenshot",
payload: {},
})).toBe(true);
expect(shouldCaptureSuccessScreenshot({
id: "post-2",
socialAccountId: "social-1",
platform: "twitter",
actionType: "post",
payload: { content: "hello", captureSuccessScreenshot: true },
})).toBe(true);
});
});
-134
View File
@@ -1,134 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { BrowserAction, BrowserActionResult } from "../../src/main/browser-manager";
import { BrowserManager } from "../../src/main/browser-manager";
const mocks = vi.hoisted(() => ({
runAction: vi.fn(),
fromPartition: vi.fn(),
logWarn: vi.fn(),
}));
vi.mock("electron", () => ({
BrowserWindow: vi.fn(),
session: {
fromPartition: mocks.fromPartition,
},
}));
vi.mock("electron-log/main.js", () => ({
default: {
info: vi.fn(),
warn: mocks.logWarn,
error: vi.fn(),
},
}));
vi.mock("../../src/main/browser/browser-action-executor", () => ({
assertAllowedUrl: vi.fn(),
attachWindowSecurity: vi.fn(),
getPlatformRules: vi.fn(() => ({
loginUrl: "https://x.com/i/flow/login",
cookieOrigins: [],
authenticatedCookieNames: [],
})),
waitForLoad: vi.fn(),
runAction: mocks.runAction,
}));
const ACTION_TIMEOUT_MS = 5 * 60 * 1000;
const tempDirs: string[] = [];
function createSession() {
return {
setPermissionCheckHandler: vi.fn(),
setPermissionRequestHandler: vi.fn(),
clearStorageData: vi.fn(),
clearCache: vi.fn(),
clearAuthCache: vi.fn(),
cookies: {
get: vi.fn(async () => []),
},
};
}
function createAction(id: string): BrowserAction {
return {
id,
socialAccountId: "social-1",
platform: "twitter",
actionType: "post",
payload: { content: "hello" },
};
}
function createActionWindow() {
const win = {
destroyed: false,
close: vi.fn(() => {
win.destroyed = true;
}),
isDestroyed: vi.fn(() => win.destroyed),
};
return win;
}
async function createProfileDir() {
const dir = await mkdtemp(join(tmpdir(), "agentcompany-browser-timeout-"));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
vi.useRealTimers();
mocks.runAction.mockReset();
mocks.fromPartition.mockReset();
mocks.logWarn.mockReset();
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("browser manager action timeout", () => {
it("settles a hung action and starts the next queued action", async () => {
const profileDir = await createProfileDir();
const firstWindow = createActionWindow();
const secondResult: BrowserActionResult = {
success: true,
cancelled: false,
summary: "second action completed",
screenshotPath: null,
error: null,
};
mocks.fromPartition.mockReturnValue(createSession());
mocks.runAction
.mockImplementationOnce((action, _screenshotDir, _session, _cancelledActionIds, activeActionWindows) => {
activeActionWindows.set(action.id, firstWindow);
return new Promise(() => {});
})
.mockResolvedValueOnce(secondResult);
vi.useFakeTimers();
const manager = new BrowserManager(profileDir);
const first = manager.executeAction(createAction("action-1"));
await Promise.resolve();
const second = manager.executeAction(createAction("action-2"));
expect(mocks.runAction).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(ACTION_TIMEOUT_MS);
await expect(first).resolves.toMatchObject({
success: false,
cancelled: true,
summary: "Action timed out and was cancelled.",
screenshotPath: null,
error: "Action timed out.",
});
await expect(second).resolves.toEqual(secondResult);
expect(firstWindow.close).toHaveBeenCalledTimes(1);
expect(mocks.runAction).toHaveBeenCalledTimes(2);
});
});
-113
View File
@@ -1,113 +0,0 @@
import { describe, it, expect, vi } from "vitest";
import { checkBudgetThreshold } from "@main/automation/automation-run-completion";
/**
* Tests for budget alert notification logic in automation-run-completion.ts.
*
* Budget alerts fire at two thresholds:
* 1. >= 80% utilization -> warning notification with agent name and percentage
* 2. >= 100% utilization -> exhaustion notification (both fire)
* 3. budgetMonthlyUsd <= 0 (unlimited) -> no notification
*/
/** Identity localizer: returns the English string, as in the production default path. */
function en(enStr: string, _zh: string): string {
return enStr;
}
describe("Budget Alert Notifications", () => {
it("fires warning notification at 80% budget utilization", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Alice", budgetMonthlyUsd: 100, spentMonthlyUsd: 85 },
notify,
en,
);
expect(notify).toHaveBeenCalledTimes(1);
const call = notify.mock.calls[0]![0] as { title: string; body: string };
expect(call.title).toContain("Budget");
expect(call.body).toContain("Alice");
expect(call.body).toContain("85%");
});
it("fires both notifications at >= 100% utilization", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Bob", budgetMonthlyUsd: 50, spentMonthlyUsd: 55 },
notify,
en,
);
expect(notify).toHaveBeenCalledTimes(2);
const bodies = notify.mock.calls.map((c) => (c[0] as { body: string }).body);
// Second call is the exhaustion notification
expect(bodies[1]).toContain("100%");
});
it("correctly rounds percentage as Math.round(utilization * 100)", () => {
const notify = vi.fn();
// 83.333...% should round to 83%
checkBudgetThreshold(
{ name: "Charlie", budgetMonthlyUsd: 60, spentMonthlyUsd: 50 },
notify,
en,
);
const body = (notify.mock.calls[0]![0] as { body: string }).body;
expect(body).toContain("83%");
});
it("does not fire notification when budgetMonthlyUsd is 0 (unlimited)", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Diana", budgetMonthlyUsd: 0, spentMonthlyUsd: 500 },
notify,
en,
);
expect(notify).not.toHaveBeenCalled();
});
it("does not fire notification when utilization is below 80%", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Eve", budgetMonthlyUsd: 100, spentMonthlyUsd: 50 },
notify,
en,
);
expect(notify).not.toHaveBeenCalled();
});
it("fires both notifications at exactly 100%", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Frank", budgetMonthlyUsd: 100, spentMonthlyUsd: 100 },
notify,
en,
);
expect(notify).toHaveBeenCalledTimes(2);
const first = notify.mock.calls[0]![0] as { title: string; body: string };
const second = notify.mock.calls[1]![0] as { title: string; body: string };
expect(first.body).toContain("100%");
expect(second.title).toContain("Exhausted");
});
it("does not fire notification when budgetMonthlyUsd is negative", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Grace", budgetMonthlyUsd: -10, spentMonthlyUsd: 500 },
notify,
en,
);
expect(notify).not.toHaveBeenCalled();
});
it("fires only the warning (not exhaustion) at exactly 80%", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Hank", budgetMonthlyUsd: 100, spentMonthlyUsd: 80 },
notify,
en,
);
expect(notify).toHaveBeenCalledTimes(1);
const body = (notify.mock.calls[0]![0] as { body: string }).body;
expect(body).toContain("80%");
});
});
-376
View File
@@ -1,376 +0,0 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
/**
* Tests for budget policy evaluation and cost aggregation logic.
*
* Tests pure functions extracted from the budget enforcement service:
* - getSpentInWindow: aggregates cost_entries by scope in cents
* - getActivePoliciesForRun: finds matching policies for company+agent+project
* - saveBudgetPolicy / deleteBudgetPolicy CRUD
* - evaluateBudgetGate: returns blocked when ANY scope is exhausted (strictest-wins D-01)
*/
import type { BudgetPolicyRecord } from "../../src/shared/types";
// ---------------------------------------------------------------------------
// Helper: simulate getSpentInWindow (SUM(amount_usd) * 100 rounded to cents)
// ---------------------------------------------------------------------------
function computeSpentCents(costEntries: Array<{ amountUsd: number | null }>): number {
const total = costEntries.reduce((sum, c) => sum + (c.amountUsd ?? 0), 0);
return Math.round(total * 100);
}
// ---------------------------------------------------------------------------
// Helper: simulate getCalendarMonthWindow
// ---------------------------------------------------------------------------
function getCalendarMonthWindow(now: Date = new Date()): { start: string; end: string } {
const year = now.getUTCFullYear();
const month = now.getUTCMonth();
const start = new Date(Date.UTC(year, month, 1)).toISOString();
const end = new Date(Date.UTC(year, month + 1, 1)).toISOString();
return { start, end };
}
// ---------------------------------------------------------------------------
// Helper: simulate evaluateBudgetGate (strictest-wins per D-01)
// ---------------------------------------------------------------------------
interface BudgetGateResult {
allowed: boolean;
blockedBy: Array<{
policyId: string;
scopeType: string;
scopeId: string;
spent: number;
limit: number;
}>;
}
function evaluateBudgetGate(
policies: BudgetPolicyRecord[],
getSpent: (policy: BudgetPolicyRecord) => number,
): BudgetGateResult {
if (policies.length === 0) {
return { allowed: true, blockedBy: [] };
}
const blockedBy: BudgetGateResult["blockedBy"] = [];
for (const policy of policies) {
if (!policy.hardStopEnabled) continue;
const spent = getSpent(policy);
if (spent >= policy.amount) {
blockedBy.push({
policyId: policy.id,
scopeType: policy.scopeType,
scopeId: policy.scopeId,
spent,
limit: policy.amount,
});
}
}
return { allowed: blockedBy.length === 0, blockedBy };
}
// ---------------------------------------------------------------------------
// Mock database for CRUD testing
// ---------------------------------------------------------------------------
function createMockDb() {
const policies: BudgetPolicyRecord[] = [];
const costEntries: Array<{
companyId: string;
agentId: string | null;
projectId: string | null;
amountUsd: number;
createdAt: string;
}> = [];
const budget = {
saveBudgetPolicy(input: Partial<BudgetPolicyRecord> & { companyId: string; scopeType: string; scopeId: string; amount: number }) {
const id = input.id ?? crypto.randomUUID();
const existing = policies.findIndex((p) => p.id === id);
const ts = new Date().toISOString();
const policy: BudgetPolicyRecord = {
id,
companyId: input.companyId,
scopeType: input.scopeType as "company" | "agent" | "project",
scopeId: input.scopeId,
metric: input.metric ?? "billed_cents",
windowKind: input.windowKind ?? "calendar_month_utc",
amount: input.amount,
warnPercent: input.warnPercent ?? 80,
hardStopEnabled: input.hardStopEnabled ?? true,
notifyEnabled: input.notifyEnabled ?? true,
isActive: input.isActive ?? true,
createdAt: existing >= 0 ? policies[existing]!.createdAt : ts,
updatedAt: ts,
};
if (existing >= 0) {
policies[existing] = policy;
} else {
policies.push(policy);
}
return id;
},
deleteBudgetPolicy(companyId: string, policyId: string) {
const idx = policies.findIndex((p) => p.id === policyId && p.companyId === companyId);
if (idx >= 0) policies.splice(idx, 1);
},
listBudgetPolicies(companyId: string) {
return policies
.filter((p) => p.companyId === companyId)
.sort((a, b) => a.scopeType.localeCompare(b.scopeType) || a.createdAt.localeCompare(b.createdAt));
},
getActivePoliciesForRun(companyId: string, agentId: string, projectId: string | null) {
return policies.filter(
(p) =>
p.companyId === companyId &&
p.isActive &&
((p.scopeType === "company" && p.scopeId === companyId) ||
(p.scopeType === "agent" && p.scopeId === agentId) ||
(p.scopeType === "project" && projectId !== null && p.scopeId === projectId)),
);
},
getSpentInWindow(policy: BudgetPolicyRecord): number {
const window = getCalendarMonthWindow();
let filtered = costEntries.filter(
(c) => c.companyId === policy.companyId && c.createdAt >= window.start && c.createdAt < window.end,
);
if (policy.scopeType === "agent") {
filtered = filtered.filter((c) => c.agentId === policy.scopeId);
} else if (policy.scopeType === "project") {
filtered = filtered.filter((c) => c.projectId === policy.scopeId);
}
return computeSpentCents(filtered);
},
};
return { policies, costEntries, core: { budget } };
}
// ======================================================================
// Tests
// ======================================================================
describe("Budget Policy CRUD", () => {
let db: ReturnType<typeof createMockDb>;
beforeEach(() => {
db = createMockDb();
});
it("saveBudgetPolicy creates new policy and returns its ID", () => {
const id = db.core.budget.saveBudgetPolicy({
companyId: "c1",
scopeType: "company",
scopeId: "c1",
amount: 10000,
});
expect(id).toBeTruthy();
expect(db.policies).toHaveLength(1);
expect(db.policies[0]!.amount).toBe(10000);
expect(db.policies[0]!.scopeType).toBe("company");
});
it("saveBudgetPolicy updates existing policy when ID provided", () => {
const id = db.core.budget.saveBudgetPolicy({
companyId: "c1",
scopeType: "company",
scopeId: "c1",
amount: 10000,
});
db.core.budget.saveBudgetPolicy({
id,
companyId: "c1",
scopeType: "company",
scopeId: "c1",
amount: 20000,
});
expect(db.policies).toHaveLength(1);
expect(db.policies[0]!.amount).toBe(20000);
});
it("deleteBudgetPolicy removes policy by ID", () => {
const id = db.core.budget.saveBudgetPolicy({
companyId: "c1",
scopeType: "company",
scopeId: "c1",
amount: 10000,
});
expect(db.policies).toHaveLength(1);
db.core.budget.deleteBudgetPolicy("c1", id);
expect(db.policies).toHaveLength(0);
});
});
describe("getActivePoliciesForRun", () => {
let db: ReturnType<typeof createMockDb>;
beforeEach(() => {
db = createMockDb();
});
it("returns all active policies matching company+agent+project scopes", () => {
db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 50000 });
db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "agent", scopeId: "a1", amount: 10000 });
db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "project", scopeId: "p1", amount: 20000 });
// Different company — should not match
db.core.budget.saveBudgetPolicy({ companyId: "c2", scopeType: "company", scopeId: "c2", amount: 99999 });
const result = db.core.budget.getActivePoliciesForRun("c1", "a1", "p1");
expect(result).toHaveLength(3);
expect(result.map((p) => p.scopeType).sort()).toEqual(["agent", "company", "project"]);
});
it("returns empty array when no policies exist (D-02: no policy = unlimited)", () => {
const result = db.core.budget.getActivePoliciesForRun("c1", "a1", null);
expect(result).toEqual([]);
});
it("excludes project scope when projectId is null", () => {
db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 50000 });
db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "project", scopeId: "p1", amount: 20000 });
const result = db.core.budget.getActivePoliciesForRun("c1", "a1", null);
expect(result).toHaveLength(1);
expect(result[0]!.scopeType).toBe("company");
});
});
describe("getSpentInWindow", () => {
let db: ReturnType<typeof createMockDb>;
beforeEach(() => {
db = createMockDb();
});
it("returns SUM(amount_usd)*100 rounded to integer cents for company scope", () => {
const now = new Date();
db.costEntries.push(
{ companyId: "c1", agentId: "a1", projectId: null, amountUsd: 1.50, createdAt: now.toISOString() },
{ companyId: "c1", agentId: "a2", projectId: null, amountUsd: 2.30, createdAt: now.toISOString() },
);
const policy: BudgetPolicyRecord = {
id: "pol1", companyId: "c1", scopeType: "company", scopeId: "c1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
isActive: true, createdAt: now.toISOString(), updatedAt: now.toISOString(),
};
const spent = db.core.budget.getSpentInWindow(policy);
expect(spent).toBe(380); // (1.50 + 2.30) * 100 = 380 cents
});
it("filters by agent_id for agent scope", () => {
const now = new Date();
db.costEntries.push(
{ companyId: "c1", agentId: "a1", projectId: null, amountUsd: 5.00, createdAt: now.toISOString() },
{ companyId: "c1", agentId: "a2", projectId: null, amountUsd: 3.00, createdAt: now.toISOString() },
);
const policy: BudgetPolicyRecord = {
id: "pol1", companyId: "c1", scopeType: "agent", scopeId: "a1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
isActive: true, createdAt: now.toISOString(), updatedAt: now.toISOString(),
};
const spent = db.core.budget.getSpentInWindow(policy);
expect(spent).toBe(500); // Only a1's $5.00 = 500 cents
});
it("filters by project_id for project scope", () => {
const now = new Date();
db.costEntries.push(
{ companyId: "c1", agentId: "a1", projectId: "p1", amountUsd: 7.50, createdAt: now.toISOString() },
{ companyId: "c1", agentId: "a1", projectId: "p2", amountUsd: 2.00, createdAt: now.toISOString() },
);
const policy: BudgetPolicyRecord = {
id: "pol1", companyId: "c1", scopeType: "project", scopeId: "p1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
isActive: true, createdAt: now.toISOString(), updatedAt: now.toISOString(),
};
const spent = db.core.budget.getSpentInWindow(policy);
expect(spent).toBe(750); // Only p1's $7.50 = 750 cents
});
});
describe("evaluateBudgetGate", () => {
it("returns allowed:true with empty blockedBy when no policies exist (D-02)", () => {
const result = evaluateBudgetGate([], vi.fn());
expect(result).toEqual({ allowed: true, blockedBy: [] });
});
it("returns allowed:true when no policies are exhausted", () => {
const policy: BudgetPolicyRecord = {
id: "pol1", companyId: "c1", scopeType: "company", scopeId: "c1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 10000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
isActive: true, createdAt: "", updatedAt: "",
};
const getSpent = vi.fn().mockReturnValue(5000); // 50% spent
const result = evaluateBudgetGate([policy], getSpent);
expect(result.allowed).toBe(true);
expect(result.blockedBy).toHaveLength(0);
});
it("returns blocked when ANY scope is exhausted (strictest-wins D-01)", () => {
const companyPolicy: BudgetPolicyRecord = {
id: "pol1", companyId: "c1", scopeType: "company", scopeId: "c1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
isActive: true, createdAt: "", updatedAt: "",
};
const agentPolicy: BudgetPolicyRecord = {
id: "pol2", companyId: "c1", scopeType: "agent", scopeId: "a1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 5000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
isActive: true, createdAt: "", updatedAt: "",
};
const getSpent = vi.fn((policy: BudgetPolicyRecord) => {
if (policy.scopeType === "company") return 50000; // 50% of company
return 5500; // 110% of agent
});
const result = evaluateBudgetGate([companyPolicy, agentPolicy], getSpent);
expect(result.allowed).toBe(false);
expect(result.blockedBy).toHaveLength(1);
expect(result.blockedBy[0]!.policyId).toBe("pol2");
expect(result.blockedBy[0]!.scopeType).toBe("agent");
});
it("skips policies where hardStopEnabled is false", () => {
const policy: BudgetPolicyRecord = {
id: "pol1", companyId: "c1", scopeType: "company", scopeId: "c1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 1000, warnPercent: 80, hardStopEnabled: false, notifyEnabled: true,
isActive: true, createdAt: "", updatedAt: "",
};
const getSpent = vi.fn().mockReturnValue(5000); // Way over limit
const result = evaluateBudgetGate([policy], getSpent);
expect(result.allowed).toBe(true);
expect(result.blockedBy).toHaveLength(0);
});
});
describe("getCalendarMonthWindow", () => {
it("returns start of current month and start of next month in UTC", () => {
const testDate = new Date("2026-03-15T10:30:00Z");
const window = getCalendarMonthWindow(testDate);
expect(window.start).toBe("2026-03-01T00:00:00.000Z");
expect(window.end).toBe("2026-04-01T00:00:00.000Z");
});
it("handles December rollover to January next year", () => {
const testDate = new Date("2026-12-25T10:30:00Z");
const window = getCalendarMonthWindow(testDate);
expect(window.start).toBe("2026-12-01T00:00:00.000Z");
expect(window.end).toBe("2027-01-01T00:00:00.000Z");
});
});
-339
View File
@@ -1,339 +0,0 @@
import { describe, it, expect, beforeEach } from "vitest";
/**
* Tests for budget incident lifecycle: creation, deduplication (D-08),
* resolution, and auto-close of expired incidents (D-06).
*/
import type { BudgetPolicyRecord, BudgetIncidentRecord } from "../../src/shared/types";
// ---------------------------------------------------------------------------
// Mock incident store simulating upsertBudgetIncident, resolveIncidentsForPolicy,
// autoCloseExpiredIncidents behaviors from database.ts
// ---------------------------------------------------------------------------
function createMockIncidentDb() {
const incidents: BudgetIncidentRecord[] = [];
let idCounter = 0;
function getCalendarMonthWindow(now: Date = new Date()): { start: string; end: string } {
const year = now.getUTCFullYear();
const month = now.getUTCMonth();
const start = new Date(Date.UTC(year, month, 1)).toISOString();
const end = new Date(Date.UTC(year, month + 1, 1)).toISOString();
return { start, end };
}
const budget = {
getCalendarMonthWindow,
upsertBudgetIncident(
policy: BudgetPolicyRecord,
thresholdType: "warn_threshold" | "hard_stop",
amountObserved: number,
): string {
const window = getCalendarMonthWindow();
const existing = incidents.find(
(i) =>
i.policyId === policy.id &&
i.windowStart === window.start &&
i.thresholdType === thresholdType &&
i.status === "open",
);
if (existing) {
existing.amountObserved = amountObserved;
existing.updatedAt = new Date().toISOString();
return existing.id;
}
const id = `inc-${++idCounter}`;
const ts = new Date().toISOString();
incidents.push({
id,
companyId: policy.companyId,
policyId: policy.id,
scopeType: policy.scopeType,
scopeId: policy.scopeId,
metric: policy.metric,
windowKind: policy.windowKind,
windowStart: window.start,
windowEnd: window.end,
thresholdType,
amountLimit: policy.amount,
amountObserved,
status: "open",
resolvedAt: null,
createdAt: ts,
updatedAt: ts,
});
return id;
},
resolveIncidentsForPolicy(policyId: string, resolutionStatus: string): number {
let count = 0;
const ts = new Date().toISOString();
for (const incident of incidents) {
if (incident.policyId === policyId && incident.status === "open") {
incident.status = resolutionStatus as BudgetIncidentRecord["status"];
incident.resolvedAt = ts;
incident.updatedAt = ts;
count++;
}
}
return count;
},
resolveWarnIncidentsForPolicy(policyId: string, resolutionStatus: string): number {
let count = 0;
const ts = new Date().toISOString();
for (const incident of incidents) {
if (incident.policyId === policyId && incident.status === "open" && incident.thresholdType === "warn_threshold") {
incident.status = resolutionStatus as BudgetIncidentRecord["status"];
incident.resolvedAt = ts;
incident.updatedAt = ts;
count++;
}
}
return count;
},
autoCloseExpiredIncidents(companyId: string): number {
let count = 0;
const now = new Date().toISOString();
for (const incident of incidents) {
if (
incident.companyId === companyId &&
incident.status === "open" &&
incident.windowEnd <= now
) {
incident.status = "auto_closed";
incident.resolvedAt = now;
incident.updatedAt = now;
count++;
}
}
return count;
},
listBudgetIncidents(companyId: string, statusFilter: string = "all"): BudgetIncidentRecord[] {
return incidents
.filter((i) => i.companyId === companyId && (statusFilter === "all" || i.status === statusFilter))
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
},
resolveBudgetIncident(incidentId: string, companyId: string): boolean {
const incident = incidents.find((i) => i.id === incidentId && i.companyId === companyId && i.status === "open");
if (!incident) return false;
const ts = new Date().toISOString();
incident.status = "resolved";
incident.resolvedAt = ts;
incident.updatedAt = ts;
return true;
},
};
return { incidents, core: { budget } };
}
function makePolicy(overrides: Partial<BudgetPolicyRecord> = {}): BudgetPolicyRecord {
return {
id: "pol-1",
companyId: "c1",
scopeType: "agent",
scopeId: "a1",
metric: "billed_cents",
windowKind: "calendar_month_utc",
amount: 10000,
warnPercent: 80,
hardStopEnabled: true,
notifyEnabled: true,
isActive: true,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
...overrides,
};
}
// ======================================================================
// Tests
// ======================================================================
describe("upsertBudgetIncident", () => {
let db: ReturnType<typeof createMockIncidentDb>;
beforeEach(() => {
db = createMockIncidentDb();
});
it("creates new incident when none exists for policy+window+threshold_type", () => {
const policy = makePolicy();
const id = db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
expect(id).toBeTruthy();
expect(db.incidents).toHaveLength(1);
expect(db.incidents[0]!.thresholdType).toBe("warn_threshold");
expect(db.incidents[0]!.amountObserved).toBe(8500);
expect(db.incidents[0]!.status).toBe("open");
});
it("updates amount_observed when open incident already exists (D-08 dedup)", () => {
const policy = makePolicy();
const id1 = db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
const id2 = db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 9200);
expect(id1).toBe(id2); // Same incident, not a new one
expect(db.incidents).toHaveLength(1); // Still just one
expect(db.incidents[0]!.amountObserved).toBe(9200); // Updated
});
it("creates separate incidents for different threshold types", () => {
const policy = makePolicy();
db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
db.core.budget.upsertBudgetIncident(policy, "hard_stop", 10500);
expect(db.incidents).toHaveLength(2);
expect(db.incidents[0]!.thresholdType).toBe("warn_threshold");
expect(db.incidents[1]!.thresholdType).toBe("hard_stop");
});
});
describe("resolveIncidentsForPolicy", () => {
let db: ReturnType<typeof createMockIncidentDb>;
beforeEach(() => {
db = createMockIncidentDb();
});
it("sets status to provided resolution reason for all open incidents", () => {
const policy = makePolicy();
db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
db.core.budget.upsertBudgetIncident(policy, "hard_stop", 10500);
const count = db.core.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
expect(count).toBe(2);
expect(db.incidents[0]!.status).toBe("auto_resolved_limit_increase");
expect(db.incidents[1]!.status).toBe("auto_resolved_limit_increase");
expect(db.incidents[0]!.resolvedAt).toBeTruthy();
});
it("does not affect already-resolved incidents", () => {
const policy = makePolicy();
db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
db.core.budget.resolveIncidentsForPolicy("pol-1", "resolved");
// Now try to resolve again
const count = db.core.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
expect(count).toBe(0);
expect(db.incidents[0]!.status).toBe("resolved"); // Unchanged
});
});
describe("autoCloseExpiredIncidents", () => {
let db: ReturnType<typeof createMockIncidentDb>;
beforeEach(() => {
db = createMockIncidentDb();
});
it("closes incidents whose window_end is in the past", () => {
const _policy = makePolicy();
// Manually create an expired incident
db.incidents.push({
id: "expired-1",
companyId: "c1",
policyId: "pol-1",
scopeType: "agent",
scopeId: "a1",
metric: "billed_cents",
windowKind: "calendar_month_utc",
windowStart: "2025-01-01T00:00:00.000Z",
windowEnd: "2025-02-01T00:00:00.000Z", // In the past
thresholdType: "warn_threshold",
amountLimit: 10000,
amountObserved: 8500,
status: "open",
resolvedAt: null,
createdAt: "2025-01-15T00:00:00.000Z",
updatedAt: "2025-01-15T00:00:00.000Z",
});
const count = db.core.budget.autoCloseExpiredIncidents("c1");
expect(count).toBe(1);
expect(db.incidents[0]!.status).toBe("auto_closed");
expect(db.incidents[0]!.resolvedAt).toBeTruthy();
});
it("does not close incidents whose window_end is in the future", () => {
const policy = makePolicy();
db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
// The auto-created incident has window_end in the future (end of current month)
const count = db.core.budget.autoCloseExpiredIncidents("c1");
expect(count).toBe(0);
expect(db.incidents[0]!.status).toBe("open");
});
});
describe("listBudgetIncidents", () => {
let db: ReturnType<typeof createMockIncidentDb>;
beforeEach(() => {
db = createMockIncidentDb();
});
it("returns all incidents for a company when status is 'all'", () => {
const policy = makePolicy();
db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
db.core.budget.upsertBudgetIncident(policy, "hard_stop", 10500);
db.core.budget.resolveIncidentsForPolicy("pol-1", "resolved");
const all = db.core.budget.listBudgetIncidents("c1", "all");
expect(all).toHaveLength(2);
});
it("filters by status when specified", () => {
const policy = makePolicy();
db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
db.core.budget.upsertBudgetIncident(policy, "hard_stop", 10500);
// Resolve only warn
db.core.budget.resolveWarnIncidentsForPolicy("pol-1", "resolved");
const open = db.core.budget.listBudgetIncidents("c1", "open");
expect(open).toHaveLength(1);
expect(open[0]!.thresholdType).toBe("hard_stop");
});
});
describe("checkAutoResolve (D-06)", () => {
let db: ReturnType<typeof createMockIncidentDb>;
beforeEach(() => {
db = createMockIncidentDb();
});
it("auto-resolves incidents when policy limit increases above observed amount", () => {
const policy = makePolicy({ amount: 10000 });
db.core.budget.upsertBudgetIncident(policy, "hard_stop", 10500);
expect(db.incidents[0]!.status).toBe("open");
// Simulate limit increase: now checking if spent < new limit
// If spent (10500) < new policy.amount (20000), resolve
const spent = 10500;
const newAmount = 20000;
if (spent < newAmount) {
db.core.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
}
expect(db.incidents[0]!.status).toBe("auto_resolved_limit_increase");
});
it("auto-resolves only warn incidents when below warn threshold but above hard stop", () => {
const policy = makePolicy({ amount: 10000, warnPercent: 80 });
db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
db.core.budget.upsertBudgetIncident(policy, "hard_stop", 10500);
// Spent is now 7500 (below warn threshold of 8000 = 10000*80/100)
const spent = 7500;
const _warnAmount = Math.floor((policy.amount * policy.warnPercent) / 100);
if (spent < policy.amount) {
db.core.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
}
// Both resolved since spent < policy.amount
expect(db.incidents.every((i) => i.status === "auto_resolved_limit_increase")).toBe(true);
});
});
-41
View File
@@ -1,41 +0,0 @@
import { describe, expect, it } from "vitest";
import { buildBudgetPolicyInput, getBudgetPolicyAmountDollars } from "../../src/renderer/lib/budget-policy-input";
describe("budget-policy-input", () => {
it("preserves the existing policy id on edit", () => {
const payload = buildBudgetPolicyInput({
policyId: "policy-123",
companyId: "00000000-0000-0000-0000-000000000001",
scopeType: "agent",
scopeId: "00000000-0000-0000-0000-000000000002",
amountDollars: "250.00",
warnPercent: "85",
hardStopEnabled: true,
notifyEnabled: true,
});
expect(payload?.id).toBe("policy-123");
expect(payload?.amount).toBe(25000);
expect(payload?.warnPercent).toBe(85);
});
it("formats stored cents back to dollars for edit forms", () => {
expect(
getBudgetPolicyAmountDollars({
id: "policy-123",
companyId: "company-1",
scopeType: "agent",
scopeId: "agent-1",
metric: "billed_cents",
windowKind: "calendar_month_utc",
amount: 12550,
warnPercent: 80,
hardStopEnabled: true,
notifyEnabled: true,
isActive: true,
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
}),
).toBe("125.50");
});
});
-44
View File
@@ -1,44 +0,0 @@
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: [
{ id: "workspace-1", name: "Primary", localPath: "/tmp/workspace" },
],
allConnectors: [
{ id: "openai-codex", 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 (`workspace-1`): /tmp/workspace");
expect(section).toContain("Codex (`openai-codex`) — models: gpt-5.4");
expect(section).toContain("architecture_decision");
});
});
+30
View File
@@ -0,0 +1,30 @@
import { describe, expect, test } from "vitest";
import { pointInTriangle } from "../../src/renderer/src/shared/CascadeMenu";
// The safe triangle: apex at the cursor's prior position, base on the open
// submenu's near (left) edge. A submenu sitting to the right spanning y∈[-20,40]
// at x=100, with the cursor having just been at the origin.
const apex = { x: 0, y: 0 };
const top = { x: 100, y: -20 };
const bottom = { x: 100, y: 40 };
describe("safe triangle (cursor → submenu)", () => {
test("a cursor sweeping diagonally toward the submenu stays inside", () => {
// Midway to the submenu, within the widening cone — switching is deferred.
expect(pointInTriangle({ x: 50, y: 5 }, apex, top, bottom)).toBe(true);
expect(pointInTriangle({ x: 90, y: 30 }, apex, top, bottom)).toBe(true);
});
test("a cursor that drifts off-course toward a sibling row falls outside", () => {
// At x=50 the cone only spans y∈[-10,20]; y=30 is below it — let the sibling win.
expect(pointInTriangle({ x: 50, y: 30 }, apex, top, bottom)).toBe(false);
// Straight down from the apex, away from the submenu entirely.
expect(pointInTriangle({ x: 5, y: 35 }, apex, top, bottom)).toBe(false);
});
test("the apex and the submenu corners are on the triangle", () => {
expect(pointInTriangle(apex, apex, top, bottom)).toBe(true);
expect(pointInTriangle(top, apex, top, bottom)).toBe(true);
expect(pointInTriangle(bottom, apex, top, bottom)).toBe(true);
});
});
-79
View File
@@ -1,79 +0,0 @@
import { describe, expect, it } from "vitest";
import type { AgentMessageRecord, AgentRecord } from "../../src/shared/types";
import { deriveChannels, getChannelMessages } from "../../src/renderer/components/communication/channel-utils";
function agent(id: string, name: string): AgentRecord {
return {
id,
companyId: "company-1",
name,
role: name,
title: name,
department: null,
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId: null,
model: null,
capabilities: "",
budgetMonthlyUsd: 0,
spentMonthlyUsd: 0,
metadataJson: "{}",
heartbeatEnabled: false,
heartbeatIntervalSec: 300,
lastHeartbeatAt: null,
createdAt: "2026-04-22T00:00:00.000Z",
updatedAt: "2026-04-22T00:00:00.000Z",
};
}
function message(input: Partial<AgentMessageRecord>): AgentMessageRecord {
return {
id: input.id ?? "msg-1",
companyId: "company-1",
fromAgentId: input.fromAgentId ?? "agent-ceo",
toAgentId: input.toAgentId ?? "agent-cto",
senderKind: input.senderKind ?? "agent",
channel: input.channel ?? "direct",
channelTargetId: input.channelTargetId ?? null,
subject: input.subject ?? "Subject",
body: input.body ?? "Body",
priority: input.priority ?? "normal",
parentMessageId: input.parentMessageId ?? null,
attachmentsJson: input.attachmentsJson ?? "[]",
createdAt: input.createdAt ?? "2026-04-22T00:00:00.000Z",
readAt: input.readAt ?? null,
};
}
describe("communication channel utilities", () => {
it("drops legacy direct messages whose sender and recipient are the same agent", () => {
const ceo = agent("agent-ceo", "CEO");
const selfMessage = message({
id: "self-direct",
fromAgentId: ceo.id,
toAgentId: ceo.id,
});
const channels = deriveChannels([selfMessage], [ceo]);
expect(channels.map((channel) => channel.label)).not.toContain("CEO ↔ CEO");
expect(channels.some((channel) => channel.kind === "message" && channel.channel === "direct")).toBe(false);
});
it("keeps normal direct conversations visible", () => {
const ceo = agent("agent-ceo", "CEO");
const cto = agent("agent-cto", "CTO");
const direct = message({
id: "ceo-cto",
fromAgentId: ceo.id,
toAgentId: cto.id,
});
const channels = deriveChannels([direct], [ceo, cto]);
const dm = channels.find((channel) => channel.kind === "message" && channel.channel === "direct");
expect(dm?.label).toBe("CEO ↔ CTO");
expect(dm ? getChannelMessages([direct], dm).map((entry) => entry.id) : []).toEqual(["ceo-cto"]);
});
});
-74
View File
@@ -1,74 +0,0 @@
import { describe, it, expect } from "vitest";
import {
formatDateLabel,
formatPlainLabel,
shouldRenderLabel,
} from "../../src/renderer/components/dashboard/charts/ChartTooltip";
// The <ChartTooltip /> component itself is a thin shell around three pure
// helpers. Testing the helpers covers every branch of the rendered output:
//
// formatDateLabel → "date" mode default formatter
// formatPlainLabel → "plain" mode default formatter
// shouldRenderLabel → whether the top label row is rendered at all
//
// Rendering JSX here would require adding @testing-library/react + jsdom just
// to re-check what the helpers already decide. The vitest config also only
// picks up *.test.ts (not .tsx), so keeping the test purely functional avoids
// expanding the harness for one component.
describe("formatDateLabel (date mode)", () => {
it("formats a valid ISO date string into Mon DD, YYYY", () => {
expect(formatDateLabel("2026-03-15")).toBe("Mar 15, 2026");
});
it("falls back to the original string on an Invalid Date", () => {
// "not-a-date" cannot be parsed; formatter must return the input verbatim
// rather than "Invalid Date" leaking to the UI.
expect(formatDateLabel("not-a-date")).toBe("not-a-date");
});
it("stringifies non-string labels (numbers) instead of trying to parse them", () => {
expect(formatDateLabel(42)).toBe("42");
});
});
describe("formatPlainLabel (plain mode)", () => {
it("passes a regular string through unchanged", () => {
expect(formatPlainLabel("Alice")).toBe("Alice");
});
it("stringifies numbers", () => {
expect(formatPlainLabel(7)).toBe("7");
});
it("returns empty string for null/undefined instead of 'null'/'undefined'", () => {
expect(formatPlainLabel(undefined)).toBe("");
// @ts-expect-error — runtime behavior for null is the contract we care about
expect(formatPlainLabel(null)).toBe("");
});
});
describe("shouldRenderLabel (empty label suppression)", () => {
it("returns false for undefined", () => {
expect(shouldRenderLabel(undefined)).toBe(false);
});
it("returns false for null", () => {
// @ts-expect-error — recharts can hand us null at runtime
expect(shouldRenderLabel(null)).toBe(false);
});
it("returns false for empty string", () => {
expect(shouldRenderLabel("")).toBe(false);
});
it("returns true for a non-empty string", () => {
expect(shouldRenderLabel("2026-03-15")).toBe(true);
});
it("returns true for a number (including 0)", () => {
expect(shouldRenderLabel(0)).toBe(true);
expect(shouldRenderLabel(42)).toBe(true);
});
});
+874
View File
@@ -0,0 +1,874 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { ApprovalService } from "../../src/main/services/approval-service";
import { ChatService } from "../../src/main/services/chat-service";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ProviderService } from "../../src/main/services/provider-service";
import { providerAdapters } from "../../src/main/services/provider-adapters";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { RISK_LEVEL } from "../../src/shared/domain";
function createConnection(): DatabaseConnection {
return new DatabaseConnection(join(tmpdir(), `agentcompany-new-chat-${randomUUID()}.db`));
}
const testVault = {
encryptSecret: (secret: string) => Buffer.from(secret, "utf8"),
decryptSecret: (ciphertext: Buffer) => ciphertext.toString("utf8"),
};
describe("chat governance", () => {
test("persists memberships, retention policy, and structured delivery metadata", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let employeeId = "";
let agentId = "";
let spaceId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
agentId = organization.createAgent({
orgId,
name: "Ada Lovelace",
role: "Release reviewer",
department: "Engineering",
modelProviderId: null,
modelId: null,
budgetCents: 10000,
managerAgentId: null,
});
spaceId = chat.createSpace({
orgId,
spaceType: "project",
name: "release-review",
topic: "Release review",
visibility: "restricted",
memberEmployeeIds: [employeeId],
memberAgentIds: [agentId],
});
chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/escalate review @Ada-Lovelace release gate",
attachmentFileIds: [],
});
});
const state = snapshot.load();
const message = state.chatMessages[0];
const structure = JSON.parse(message!.bodyStructuredJson) as {
slashInvocation: { command: string };
mentions: Array<{ targetType: string; targetId: string }>;
};
expect(state.chatSpaces.find((space) => space.id === spaceId)?.retentionPolicy).toBe("restricted_365d");
expect(state.chatMemberships
.filter((membership) => membership.spaceId === spaceId)
.map((membership) => `${membership.principalType}:${membership.principalId}`)
.sort())
.toEqual([`agent:${agentId}`, `employee:${employeeId}`]);
expect(message).toMatchObject({ deliveryState: "sent" });
expect(structure.slashInvocation.command).toBe("escalate");
expect(structure.mentions[0]).toMatchObject({ targetType: "agent", targetId: agentId });
connection.close();
});
test("audits chat message edits and deletes", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let employeeId = "";
let agentId = "";
let spaceId = "";
let messageId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
agentId = organization.createAgent({
orgId,
name: "Ada Lovelace",
role: "Reviewer",
department: "Engineering",
modelProviderId: null,
modelId: null,
budgetCents: 10000,
managerAgentId: null,
});
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
messageId = chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "Initial launch note @Ada-Lovelace",
attachmentFileIds: [],
});
chat.editMessage({
orgId,
messageId,
actorType: "employee",
actorId: employeeId,
bodyMd: `Updated launch note @${agentId}`,
});
chat.deleteMessage({ orgId, messageId, actorType: "employee", actorId: employeeId });
});
const state = snapshot.load();
const message = state.chatMessages.find((item) => item.id === messageId);
expect(message).toMatchObject({ bodyMd: `Updated launch note @${agentId}` });
expect(message?.editedAt).toBeTruthy();
expect(message?.deletedAt).toBeTruthy();
expect(state.chatMentions).toEqual([expect.objectContaining({ messageId, targetType: "agent", targetId: agentId })]);
expect(state.auditEvents).toEqual(expect.arrayContaining([
expect.objectContaining({ entityType: "chat_message", entityId: messageId, action: "edited" }),
expect.objectContaining({ entityType: "chat_message", entityId: messageId, action: "deleted" }),
]));
connection.close();
});
test("decides approvals from chat slash commands", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const approvals = new ApprovalService(connection.db);
const chat = new ChatService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let employeeId = "";
let spaceId = "";
let approvalId = "";
let declinedApprovalId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
approvalId = approvals.create({
orgId,
kind: "release_gate",
title: "Release gate",
details: "Review the release window.",
risk: RISK_LEVEL.HIGH,
requestedByType: "employee",
requestedById: employeeId,
});
declinedApprovalId = approvals.create({
orgId,
kind: "release_gate",
title: "Rollback gate",
details: "Review rollback window.",
risk: RISK_LEVEL.HIGH,
requestedByType: "employee",
requestedById: employeeId,
});
chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: `/approve ${approvalId} release window cleared`,
attachmentFileIds: [],
});
chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: `/deny ${declinedApprovalId} rollback window blocked`,
attachmentFileIds: [],
});
});
const state = snapshot.load();
expect(state.approvals.find((item) => item.id === approvalId)?.state).toBe("approved");
expect(state.approvals.find((item) => item.id === declinedApprovalId)?.state).toBe("declined");
expect(state.chatSlashInvocations).toEqual(expect.arrayContaining([
expect.objectContaining({ command: "approve", resultRef: approvalId }),
expect.objectContaining({ command: "deny", resultRef: declinedApprovalId }),
]));
expect(state.auditEvents).toEqual(expect.arrayContaining([
expect.objectContaining({ entityType: "approval", entityId: approvalId, action: "approved" }),
expect.objectContaining({ entityType: "approval", entityId: declinedApprovalId, action: "declined" }),
]));
connection.close();
});
test("routes collaboration slash commands to concrete domain targets", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const providers = new ProviderService(connection.db, providerAdapters, testVault);
const chat = new ChatService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let employeeId = "";
let agentId = "";
let providerId = "";
let spaceId = "";
let skillId = "";
let fileId = "";
let agentMessageId = "";
let skillMessageId = "";
let fileMessageId = "";
let runbookMessageId = "";
let handoffMessageId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'")
.get(orgId) as { id: string }).id;
providers.saveCredential({ orgId, providerId, secret: "sk-live-provider", model: "gpt-5.4" });
agentId = organization.createAgent({
orgId,
name: "Ada",
role: "Release reviewer",
department: "Engineering",
modelProviderId: providerId,
modelId: "gpt-5.4",
budgetCents: 10000,
managerAgentId: null,
});
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
skillId = "skd_release_gate";
fileId = "fil_release_notes";
const now = new Date().toISOString();
connection.db.prepare(`
insert into skill_definitions(id, org_id, slug, name, owner_type, status, created_at)
values (?, ?, ?, ?, ?, ?, ?)
`).run(skillId, orgId, "release-gate", "Release Gate", "system", "active", now);
connection.db.prepare(`
insert into file_objects(id, org_id, name, kind, size_bytes, sha256, storage_path, owner_type, owner_id, created_at)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(fileId, orgId, "release-notes.md", "md", 24, "8f3a", "artifacts/release-notes.md", "employee", employeeId, now);
agentMessageId = chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/agent @Ada review the gate",
attachmentFileIds: [],
});
skillMessageId = chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/skill release-gate apply release checks",
attachmentFileIds: [],
});
fileMessageId = chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/file release-notes.md",
attachmentFileIds: [],
});
runbookMessageId = chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/runbook release rollback steps",
attachmentFileIds: [],
});
handoffMessageId = chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/handoff @Ada release notes",
attachmentFileIds: [fileId],
});
});
const state = snapshot.load();
const slashByMessage = new Map(state.chatSlashInvocations.map((item) => [item.messageId, item]));
expect(slashByMessage.get(agentMessageId)).toMatchObject({ command: "agent", resultRef: agentId });
expect(slashByMessage.get(skillMessageId)).toMatchObject({ command: "skill", resultRef: skillId });
expect(slashByMessage.get(fileMessageId)).toMatchObject({ command: "file", resultRef: fileId });
expect(slashByMessage.get(runbookMessageId)).toMatchObject({ command: "runbook", resultRef: runbookMessageId });
expect(slashByMessage.get(handoffMessageId)).toMatchObject({ command: "handoff", resultRef: fileId });
const agentTask = state.tasks.find((item) => item.assigneeAgentId === agentId && item.title === "Chat mention: review the gate");
expect(agentTask).toMatchObject({
orgId,
assigneeAgentId: agentId,
status: "backlog",
risk: "low",
priority: "high",
requiresFilesystem: false,
});
expect(agentTask?.description).toContain(`Source message: ${agentMessageId}`);
const agentRun = state.taskRuns.find((item) => item.taskId === agentTask?.id);
expect(agentRun).toMatchObject({
orgId,
agentId,
providerId,
trigger: "chat_mention",
state: "queued",
startedAt: null,
completedAt: null,
errorMessage: null,
});
expect(agentRun?.executionContextId).toMatch(/^ctx_/);
expect(agentRun?.serviceIdentityId).toMatch(/^sid_/);
expect(JSON.parse(agentRun?.secretScopeJson ?? "{}")).toMatchObject({
serviceIdentityId: agentRun?.serviceIdentityId,
providerIds: [providerId],
scope: "chat_mention",
credentialAccess: "provider_runtime_only",
sourceMessageId: agentMessageId,
});
expect(state.chatPresence).toContainEqual(expect.objectContaining({ principalType: "agent", principalId: agentId, status: "thinking" }));
expect(state.fileHandoffRecords).toContainEqual(expect.objectContaining({
messageId: handoffMessageId,
fileId,
targetPrincipalType: "agent",
targetPrincipalId: agentId,
policyDecision: "approved_for_handoff",
}));
expect(state.auditEvents.filter((event) => event.action === "slash_routed")).toHaveLength(5);
connection.close();
});
test("bounds autonomous agent-to-agent wake loops until a human re-engages", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const providers = new ProviderService(connection.db, providerAdapters, testVault);
const chat = new ChatService(connection.db);
let orgId = "";
let employeeId = "";
let speakerId = "";
let targetId = "";
let spaceId = "";
let threadId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
const providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'")
.get(orgId) as { id: string }).id;
providers.saveCredential({ orgId, providerId, secret: "sk-live-provider", model: "gpt-5.4" });
speakerId = organization.createAgent({ orgId, name: "Alice", role: "Engineer", department: "Engineering", modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 10000, managerAgentId: null });
targetId = organization.createAgent({ orgId, name: "Bob", role: "Engineer", department: "Engineering", modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 10000, managerAgentId: null });
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "engineering", topic: "Eng", visibility: "workspace" });
});
const wakeRunCount = (): number => (connection.db.prepare(
"select count(*) as n from task_runs where org_id = ? and agent_id = ? and trigger = 'chat_mention'",
).get(orgId, targetId) as { n: number }).n;
// Alice (an agent) keeps @mentioning Bob in one thread. Each post is an
// autonomous turn; only the first batch wakes Bob, then the budget is spent.
connection.transaction(() => {
const first = chat.sendMessage({ orgId, spaceId, threadId: null, senderType: "agent", senderId: speakerId, bodyMd: "@Bob turn 1", attachmentFileIds: [] });
threadId = (connection.db.prepare("select thread_id from chat_messages where id = ?").get(first) as { thread_id: string }).thread_id;
for (let turn = 2; turn <= 40; turn += 1) {
chat.sendMessage({ orgId, spaceId, threadId, senderType: "agent", senderId: speakerId, bodyMd: `@Bob turn ${turn}`, attachmentFileIds: [] });
}
});
const capped = wakeRunCount();
// A real exchange happened, but it is finite — far below the 40 posts.
expect(capped).toBeGreaterThanOrEqual(10);
expect(capped).toBeLessThanOrEqual(16);
// A human re-enters the thread: their message wakes Bob and grants the
// agents a fresh budget, so the next agent turn wakes him again.
connection.transaction(() => {
chat.sendMessage({ orgId, spaceId, threadId, senderType: "employee", senderId: employeeId, bodyMd: "Keep going @Bob", attachmentFileIds: [] });
chat.sendMessage({ orgId, spaceId, threadId, senderType: "agent", senderId: speakerId, bodyMd: "@Bob resuming", attachmentFileIds: [] });
});
expect(wakeRunCount()).toBeGreaterThan(capped);
connection.close();
});
test("records board interventions and enforces thread control", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let employeeId = "";
let agentId = "";
let spaceId = "";
let threadId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
agentId = organization.createAgent({
orgId,
name: "Ada",
role: "Release reviewer",
department: "Engineering",
modelProviderId: null,
modelId: null,
budgetCents: 10000,
managerAgentId: null,
});
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "Release thread",
attachmentFileIds: [],
});
threadId = (connection.db.prepare("select id from chat_threads where space_id = ?").get(spaceId) as { id: string }).id;
chat.sendMessage({
orgId,
spaceId,
threadId,
senderType: "board",
senderId: employeeId,
bodyMd: "/board freeze hold the release",
attachmentFileIds: [],
});
});
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId,
threadId,
senderType: "employee",
senderId: employeeId,
bodyMd: "Proceeding",
attachmentFileIds: [],
}))).toThrow("Board-frozen thread requires board sender.");
connection.transaction(() => {
chat.sendMessage({
orgId,
spaceId,
threadId,
senderType: "board",
senderId: employeeId,
bodyMd: "/board rollback reopen release coordination",
attachmentFileIds: [],
});
chat.sendMessage({
orgId,
spaceId,
threadId,
senderType: "board",
senderId: employeeId,
bodyMd: `/board takeover @${agentId} Ada owns release checks`,
attachmentFileIds: [],
});
});
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId,
threadId,
senderType: "employee",
senderId: employeeId,
bodyMd: "Taking back control",
attachmentFileIds: [],
}))).toThrow("Board takeover controls this thread.");
connection.transaction(() => {
chat.sendMessage({
orgId,
spaceId,
threadId,
senderType: "agent",
senderId: agentId,
bodyMd: "Release checks are in progress.",
attachmentFileIds: [],
});
chat.sendMessage({
orgId,
spaceId,
threadId,
senderType: "board",
senderId: employeeId,
bodyMd: "/board force-approval approve gated release window",
attachmentFileIds: [],
});
});
const state = snapshot.load();
const thread = state.chatThreads.find((item) => item.id === threadId);
expect(thread).toMatchObject({ boardState: "takeover", controlledByType: "agent", controlledById: agentId });
expect(state.boardInterventions.map((item) => item.action)).toEqual(["freeze", "rollback", "takeover", "force_approval"]);
const forceApproval = state.approvals.find((item) => item.kind === "board_force_approval");
if (!forceApproval) throw new Error("Board force approval was not created.");
const forceIntervention = state.boardInterventions.find((item) => item.action === "force_approval");
if (!forceIntervention) throw new Error("Board force approval intervention was not created.");
expect(forceIntervention.approvalId).toBe(forceApproval.id);
expect(state.chatSlashInvocations.some((item) => item.resultRef === forceIntervention.id)).toBe(true);
expect(state.auditEvents.some((event) => event.entityType === "board_intervention" && event.action === "recorded")).toBe(true);
connection.close();
});
test("rejects chat writes outside membership and prevents partial invalid messages", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
let orgId = "";
let employeeId = "";
let agentId = "";
let restrictedSpaceId = "";
let workspaceSpaceId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
agentId = organization.createAgent({
orgId,
name: "Ada",
role: "Release reviewer",
department: "Engineering",
modelProviderId: null,
modelId: null,
budgetCents: 10000,
managerAgentId: null,
});
restrictedSpaceId = chat.createSpace({
orgId,
spaceType: "project",
name: "board-review",
topic: "Restricted release review",
visibility: "restricted",
memberEmployeeIds: [employeeId],
});
workspaceSpaceId = chat.createSpace({
orgId,
spaceType: "project",
name: "release",
topic: "Release coordination",
visibility: "workspace",
});
});
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId: restrictedSpaceId,
threadId: null,
senderType: "agent",
senderId: agentId,
bodyMd: "I should not be here.",
attachmentFileIds: [],
}))).toThrow("Chat membership is required.");
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId: restrictedSpaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "Restricted ping @Ada",
attachmentFileIds: [],
}))).toThrow("Mention @Ada could not be resolved.");
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId: workspaceSpaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/unknown command",
attachmentFileIds: [],
}))).toThrow("Slash command /unknown is unavailable.");
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId: workspaceSpaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "Missing file",
attachmentFileIds: ["file_missing"],
}))).toThrow("Attachment file was not found.");
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId: workspaceSpaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/escalate",
attachmentFileIds: [],
}))).toThrow("Slash command /escalate requires details.");
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId: workspaceSpaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/approve",
attachmentFileIds: [],
}))).toThrow("Slash command /approve requires an approval id.");
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId: workspaceSpaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/agent wake release checks",
attachmentFileIds: [],
}))).toThrow("Slash command /agent requires an agent mention.");
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId: workspaceSpaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/handoff @Ada release notes",
attachmentFileIds: [],
}))).toThrow("Slash command /handoff requires a file attachment.");
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId: workspaceSpaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/file missing.md",
attachmentFileIds: [],
}))).toThrow("Slash command /file target was not found.");
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId: workspaceSpaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/skill missing-skill",
attachmentFileIds: [],
}))).toThrow("Slash command /skill target was not found.");
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId: workspaceSpaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "Unknown @Nobody",
attachmentFileIds: [],
}))).toThrow("Mention @Nobody could not be resolved.");
const count = connection.db.prepare("select count(*) as count from chat_messages").get() as { count: number };
expect(count.count).toBe(0);
connection.close();
});
test("rejects reserved mention handles for employees and agents", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
let orgId = "";
expect(() => connection.transaction(() => organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Board",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
}))).toThrow("Mention handle is reserved.");
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
});
expect(() => connection.transaction(() => organization.createAgent({
orgId,
name: "On Call",
role: "Escalation lead",
department: "Operations",
modelProviderId: null,
modelId: null,
budgetCents: 10000,
managerAgentId: null,
}))).toThrow("Mention handle is reserved.");
connection.close();
});
test("rejects ambiguous chat mentions", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
let orgId = "";
let employeeId = "";
let spaceId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
organization.createAgent({ orgId, name: "Ada", role: "Reviewer", department: "Engineering", modelProviderId: null, modelId: null, budgetCents: 10000, managerAgentId: null });
organization.createAgent({ orgId, name: "Ada", role: "Release lead", department: "Engineering", modelProviderId: null, modelId: null, budgetCents: 10000, managerAgentId: null });
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
});
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "Please review @Ada",
attachmentFileIds: [],
}))).toThrow("Mention @Ada is ambiguous.");
const count = connection.db.prepare("select count(*) as count from chat_messages").get() as { count: number };
expect(count.count).toBe(0);
connection.close();
});
test("resolves chat mentions by display handle and principal id", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
let orgId = "";
let employeeId = "";
let agentId = "";
let spaceId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
agentId = organization.createAgent({
orgId,
name: "Ada Lovelace",
role: "Reviewer",
department: "Engineering",
modelProviderId: null,
modelId: null,
budgetCents: 10000,
managerAgentId: null,
});
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
});
connection.transaction(() => {
chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: `Review @Ada-Lovelace and @${agentId}`,
attachmentFileIds: [],
});
});
const mentions = connection.db.prepare(`
select target_type, target_id
from chat_mentions
order by offset_start asc
`).all() as Array<{ target_type: string; target_id: string }>;
expect(mentions).toEqual([
{ target_type: "agent", target_id: agentId },
{ target_type: "agent", target_id: agentId },
]);
connection.close();
});
test("rejects replies to threads from another chat space", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
let orgId = "";
let employeeId = "";
let firstSpaceId = "";
let secondSpaceId = "";
let threadId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
firstSpaceId = chat.createSpace({ orgId, spaceType: "project", name: "release-a", topic: "A", visibility: "workspace" });
secondSpaceId = chat.createSpace({ orgId, spaceType: "project", name: "release-b", topic: "B", visibility: "workspace" });
chat.sendMessage({
orgId,
spaceId: firstSpaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "Root",
attachmentFileIds: [],
});
threadId = (connection.db.prepare("select id from chat_threads where space_id = ?").get(firstSpaceId) as { id: string }).id;
});
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId: secondSpaceId,
threadId,
senderType: "employee",
senderId: employeeId,
bodyMd: "Wrong thread",
attachmentFileIds: [],
}))).toThrow("Chat thread was not found in this space.");
const count = connection.db.prepare("select count(*) as count from chat_messages").get() as { count: number };
expect(count.count).toBe(1);
connection.close();
});
});
+49
View File
@@ -0,0 +1,49 @@
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { coalesce } from "../../src/main/app/coalesce";
describe("coalesce", () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
test("collapses a burst into one trailing call", () => {
const calls: number[] = [];
const notify = coalesce(() => calls.push(Date.now()), 200);
notify();
notify();
notify();
expect(calls).toHaveLength(0);
vi.advanceTimersByTime(199);
expect(calls).toHaveLength(0);
vi.advanceTimersByTime(1);
expect(calls).toHaveLength(1);
});
test("fires again for events after the window closes", () => {
let count = 0;
const notify = coalesce(() => { count += 1; }, 200);
notify();
vi.advanceTimersByTime(200);
expect(count).toBe(1);
notify();
notify();
vi.advanceTimersByTime(200);
expect(count).toBe(2);
});
test("an event during the pending window is covered by the trailing call", () => {
let count = 0;
const notify = coalesce(() => { count += 1; }, 200);
notify();
vi.advanceTimersByTime(100);
notify();
vi.advanceTimersByTime(100);
expect(count).toBe(1);
vi.advanceTimersByTime(200);
expect(count).toBe(1);
});
});
-180
View File
@@ -1,180 +0,0 @@
import { join } from "node:path";
import { tmpdir } from "node:os";
import { mkdtemp, rm } from "node:fs/promises";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase } from "../../src/main/database";
import {
handleBeforeToolCall,
type ActiveRunContext,
} from "../../src/main/agent-runtime/hook-handlers";
function seedRun(db: AppDatabase, executionMode: "ai_ceo" | "ai_cofounder" = "ai_cofounder") {
const companyId = db.core.company.saveCompany({
name: "Gate Co",
description: "Cofounder runtime gate test",
status: "active",
executionMode,
});
db.core.connectors.saveConnector({
id: "openai-codex",
model: "gpt-5",
envBindingText: "",
notes: "fixture",
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Researcher",
role: "Research Lead",
title: "Research Lead",
department: "product",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId: null,
model: null,
capabilities: "Research",
budgetMonthlyUsd: 100,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Draft market plan",
description: "Prepare the market plan after approval.",
assigneeAgentId: agentId,
priority: "high",
status: "in_progress",
requiresFilesystem: false,
});
const runContext: ActiveRunContext = {
runId: "run-1",
agentId,
companyId,
taskId,
connectorId: "openai-codex",
modelId: "gpt-5",
taskSessionId: "session-1",
workspaceId: null,
workspacePath: null,
workspaceMode: "none",
workspaceBaseline: null,
state: "agent_active",
};
return { companyId, agentId, taskId, runContext };
}
describe("cofounder approval runtime gate", () => {
let tempDir: string;
let db: AppDatabase;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "cofounder-gate-"));
db = new AppDatabase(tempDir);
db.init();
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("blocks destructive tools while a cofounder plan review is pending", () => {
const { companyId, agentId, taskId, runContext } = seedRun(db, "ai_cofounder");
const approvalId = db.core.approvals.requestApproval({
companyId,
relatedTaskId: taskId,
requestedByAgentId: agentId,
type: "plan_review",
payloadSummary: "Review plan",
impactSummary: "Human approval is required before execution.",
payloadJson: "{}",
});
const blocking = db.core.approvals.getBlockingApproval(companyId, agentId, taskId);
expect(blocking?.id).toBe(approvalId);
const result = handleBeforeToolCall(
{
db,
logger: { info: () => {}, warn: () => {}, error: () => {} },
},
runContext,
{
toolName: "bash",
toolCallId: "call-1",
args: { command: "touch blocked.txt" },
},
);
expect(result.block).toBe(true);
expect(result.reason).toContain("plan_review");
});
it("lets CEO-mode execution proceed past plan review policy", () => {
const { companyId, agentId, taskId, runContext } = seedRun(db, "ai_ceo");
db.core.approvals.requestApproval({
companyId,
relatedTaskId: taskId,
requestedByAgentId: agentId,
type: "plan_review",
payloadSummary: "Review plan",
impactSummary: "Human review requested for planning.",
payloadJson: "{}",
});
expect(db.core.approvals.getBlockingApproval(companyId, agentId, taskId)).toBeNull();
const result = handleBeforeToolCall(
{
db,
logger: { info: () => {}, warn: () => {}, error: () => {} },
},
runContext,
{
toolName: "bash",
toolCallId: "call-1",
args: { command: "touch allowed.txt" },
},
);
expect(result.block).toBe(false);
});
it("blocks operational approvals in CEO mode", () => {
const { companyId, agentId, taskId, runContext } = seedRun(db, "ai_ceo");
const approvalId = db.core.approvals.requestApproval({
companyId,
relatedTaskId: taskId,
requestedByAgentId: agentId,
type: "dangerous_command",
payloadSummary: "Approve destructive command",
impactSummary: "Host-level mutation requires explicit approval.",
payloadJson: "{}",
});
const blocking = db.core.approvals.getBlockingApproval(companyId, agentId, taskId);
expect(blocking?.id).toBe(approvalId);
const result = handleBeforeToolCall(
{
db,
logger: { info: () => {}, warn: () => {}, error: () => {} },
},
runContext,
{
toolName: "bash",
toolCallId: "call-1",
args: { command: "rm -rf dist" },
},
);
expect(result.block).toBe(true);
expect(result.reason).toContain("dangerous_command");
});
});
-179
View File
@@ -1,179 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { IpcMainInvokeEvent } from "electron";
import type { DesktopResult } from "@shared/contracts";
import { IPC_CHANNELS } from "@shared/contracts";
import { AppDatabase } from "../../src/main/database";
import { registerCoreAdminHandlers } from "../../src/main/ipc-core-admin-handlers";
import { createThreadIntegration } from "../../src/main/thread-integration";
type IpcHandler = (
event: IpcMainInvokeEvent,
...args: unknown[]
) => Promise<DesktopResult<unknown>> | DesktopResult<unknown>;
type RawDb = {
db: {
prepare: (sql: string) => {
run: (...args: unknown[]) => void;
};
};
};
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("company mode switching", () => {
it("clears cofounder review artifacts when switching back to AI CEO", async () => {
const profileDir = await createTempDir("agentcompany-mode-switch-");
const db = new AppDatabase(profileDir);
db.init();
db.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Mode Switch Co",
description: "Execution mode cleanup",
status: "active",
executionMode: "ai_cofounder",
autoApproveHires: false,
reviewDeliverables: true,
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const engineerId = db.core.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Lead Engineer",
status: "active",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Ship features",
budgetMonthlyUsd: 180,
department: "engineering",
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Ship feature",
description: "Waits for cofounder review.",
assigneeAgentId: engineerId,
workspaceId,
priority: "high",
status: "in_progress",
});
const threadIntegration = createThreadIntegration({
db,
logger: { info: () => {}, warn: () => {}, error: () => {} },
});
threadIntegration.onTaskCreated(taskId, companyId);
threadIntegration.onRunFinished("run-1", taskId, engineerId, companyId, "succeeded", null);
db.core.tasks.setTaskRequiresUserReview(taskId, true);
const approvalId = db.core.approvals.requestApproval({
companyId,
relatedTaskId: taskId,
requestedByAgentId: engineerId,
type: "deliverable_review",
payloadSummary: "Review deliverable",
impactSummary: "Needs review",
payloadJson: "{}",
});
threadIntegration.onApprovalCreated(approvalId, companyId, taskId, "deliverable_review", "Review deliverable");
(db as unknown as RawDb).db
.prepare("update approval_policies set blocks_execution = 1, required_mode = 'ai_cofounder', updated_at = ? where approval_type = 'incident_escalation'")
.run(new Date().toISOString());
const policyDrivenApprovalId = db.core.approvals.requestApproval({
companyId,
relatedTaskId: taskId,
requestedByAgentId: engineerId,
type: "incident_escalation",
payloadSummary: "Review incident policy",
impactSummary: "Data-driven cofounder review policy.",
payloadJson: "{}",
});
expect(db.core.threads.listThreadWaitStates(companyId).length).toBeGreaterThan(0);
expect(db.core.entity.getTask(taskId).requiresUserReview).toBe(true);
expect(db.core.approvals.getApprovalRecord(approvalId).state).toBe("pending");
expect(db.core.approvals.getApprovalRecord(policyDrivenApprovalId).state).toBe("pending");
const handlers = new Map<string, IpcHandler>();
registerCoreAdminHandlers({
registerHandle: (channel, handler) => {
handlers.set(channel, handler);
},
db,
ensureCompanyExists: (id) => (db.core.settings.hasCompany(id) ? null : { ok: false, error: { code: "COMPANY_NOT_FOUND", message: "missing" } }),
publishDomainChanged: vi.fn(),
handleApprovalResolved: (resolvedApprovalId, resolvedCompanyId, decision) => {
threadIntegration.onApprovalResolved(resolvedApprovalId, resolvedCompanyId, decision);
},
checkConnector: vi.fn(),
openGenericTerminal: vi.fn(),
});
const saveCompany = handlers.get(IPC_CHANNELS.saveCompany);
if (!saveCompany) {
throw new Error("saveCompany handler was not registered.");
}
const result = await saveCompany(null as unknown as IpcMainInvokeEvent, {
id: companyId,
name: "Mode Switch Co",
description: "Execution mode cleanup",
status: "active",
autoApproveHires: true,
reviewDeliverables: false,
executionMode: "ai_ceo",
}) as { ok: boolean };
expect(result.ok).toBe(true);
expect(db.core.company.getCompanyExecutionMode(companyId)).toBe("ai_ceo");
expect(db.core.threads.listThreadWaitStates(companyId)).toHaveLength(0);
expect(db.core.entity.getTask(taskId).requiresUserReview).toBe(false);
expect(db.core.approvals.getApprovalRecord(approvalId).state).toBe("approved");
expect(db.core.approvals.getApprovalRecord(policyDrivenApprovalId).state).toBe("approved");
db.close();
});
});
@@ -1,36 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import { IPC_CHANNELS } from "../../src/shared/contracts";
import { registerCompanyOperationsHandlers } from "../../src/main/ipc-company-operations";
import type { CompanyOperationHandlerDependencies } from "../../src/main/ipc-company-operations/types";
describe("company operation IPC handlers", () => {
it("registers only company-operation channels through the company-operation registry", () => {
const registeredChannels = new Set<string>();
registerCompanyOperationsHandlers({
registerHandle: (channel) => {
registeredChannels.add(channel);
},
db: {} as CompanyOperationHandlerDependencies["db"],
ensureCompanyExists: () => null,
findInvalidReference: () => null,
publishDomainChanged: vi.fn(),
dispatchAutomation: vi.fn(),
threadIntegration: {} as CompanyOperationHandlerDependencies["threadIntegration"],
checkpointEngine: {} as CompanyOperationHandlerDependencies["checkpointEngine"],
lifecycleProcessor: {} as CompanyOperationHandlerDependencies["lifecycleProcessor"],
logger: { warn: vi.fn(), error: vi.fn() },
});
expect([...registeredChannels].sort()).toEqual([
IPC_CHANNELS.deleteDocument,
IPC_CHANNELS.deleteKnowledgeEntry,
IPC_CHANNELS.deleteMeeting,
IPC_CHANNELS.deleteSprint,
IPC_CHANNELS.saveDocument,
IPC_CHANNELS.saveKnowledgeEntry,
IPC_CHANNELS.saveMeeting,
IPC_CHANNELS.saveSprint,
].sort());
});
});
-256
View File
@@ -1,256 +0,0 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { assembleProfileSnapshot } from "@renderer/state/snapshot-sections";
import { useAppStore } from "@renderer/state/store";
import type { AgentRecord, ApprovalRecord, ProfileSnapshot, RunRecord, TaskRecord } from "@shared/types";
function makeSnapshot(overrides: Partial<ProfileSnapshot> = {}): ProfileSnapshot {
return {
currentCompanyId: "company-1",
profilePath: "/test/path",
updatesEnabled: true,
theme: "system",
locale: "en",
backend: { secretBackend: "safe_storage" },
companies: [],
connectors: [],
workspaces: [],
agents: [],
goals: [],
projects: [],
tasks: [],
approvals: [],
runs: [],
costs: [],
activity: [],
secrets: [],
comments: [],
socialAccounts: [],
meetings: [],
documents: [],
workProducts: [],
knowledgeBase: [],
sprints: [],
automationRules: [],
agentMessages: [],
workflows: [],
...overrides,
};
}
function expectSnapshotSectionsMatchSnapshot() {
const { snapshot, snapshotSections } = useAppStore.getState();
expect(snapshot).toEqual(snapshotSections ? assembleProfileSnapshot(snapshotSections) : null);
}
const fakeTask = (id: string, title = "Task"): TaskRecord => ({
id,
companyId: "company-1",
projectId: null,
goalId: null,
parentId: null,
title,
description: "",
assigneeAgentId: null,
workspaceId: null,
status: "open",
priority: "medium",
activeRunId: null,
sessionJson: null,
requiresUserReview: false,
startedAt: null,
completedAt: null,
cancelledAt: null,
createdAt: "2026-01-01",
updatedAt: "2026-01-01",
} as unknown as TaskRecord);
const fakeRun = (id: string): RunRecord => ({
id,
companyId: "company-1",
taskId: "t-1",
agentId: "ag-1",
workspaceId: null,
connectorId: "claude-pro",
status: "queued",
summary: "",
errorMessage: null,
exitCode: null,
signal: null,
model: null,
unattributedCost: false,
startedAt: null,
finishedAt: null,
logPath: null,
createdAt: "2026-01-01",
updatedAt: "2026-01-01",
} as RunRecord);
const fakeAgent = (id: string, name = "Agent"): AgentRecord => ({
id,
companyId: "company-1",
name,
role: "engineer",
title: "",
department: "engineering",
status: "idle",
reportsTo: null,
connectorId: "claude-pro",
workspaceId: null,
model: null,
capabilities: "",
budgetMonthlyUsd: 100,
spentMonthlyUsd: 0,
metadataJson: "{}",
heartbeatEnabled: false,
heartbeatIntervalSec: 0,
lastHeartbeatAt: null,
createdAt: "2026-01-01",
updatedAt: "2026-01-01",
} as AgentRecord);
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 unknown as ApprovalRecord);
describe("CompanySlice patch methods", () => {
beforeEach(() => {
useAppStore.setState(useAppStore.getInitialState());
vi.clearAllMocks();
});
it("patchTask inserts new task at beginning when task ID is missing", () => {
useAppStore.getState().setSnapshot(makeSnapshot({ tasks: [fakeTask("t-existing")] }));
useAppStore.getState().patchTask(fakeTask("t-new", "New Task"));
const tasks = useAppStore.getState().snapshot!.tasks;
expect(tasks.map((task) => task.id)).toEqual(["t-new", "t-existing"]);
expect(useAppStore.getState().snapshotSections!.company.tasks.map((task) => task.id)).toEqual([
"t-new",
"t-existing",
]);
expectSnapshotSectionsMatchSnapshot();
});
it("patchTask updates existing task in both section and compatibility snapshot", () => {
useAppStore.getState().setSnapshot(makeSnapshot({ tasks: [fakeTask("t-1", "Original")] }));
useAppStore.getState().patchTask(fakeTask("t-1", "Updated"));
expect(useAppStore.getState().snapshot!.tasks[0]!.title).toBe("Updated");
expect(useAppStore.getState().snapshotSections!.company.tasks[0]!.title).toBe("Updated");
expectSnapshotSectionsMatchSnapshot();
});
it("removeTask filters task by ID in both section and compatibility snapshot", () => {
useAppStore.getState().setSnapshot(makeSnapshot({ tasks: [fakeTask("t-1"), fakeTask("t-2")] }));
useAppStore.getState().removeTask("t-1");
expect(useAppStore.getState().snapshot!.tasks.map((task) => task.id)).toEqual(["t-2"]);
expect(useAppStore.getState().snapshotSections!.company.tasks.map((task) => task.id)).toEqual(["t-2"]);
expectSnapshotSectionsMatchSnapshot();
});
it("patchRun inserts and updates run records through snapshot sections", () => {
useAppStore.getState().setSnapshot(makeSnapshot({ runs: [fakeRun("r-existing")] }));
useAppStore.getState().patchRun(fakeRun("r-new"));
useAppStore.getState().patchRun({ ...fakeRun("r-new"), status: "running" });
expect(useAppStore.getState().snapshot!.runs.map((run) => `${run.id}:${run.status}`)).toEqual([
"r-new:running",
"r-existing:queued",
]);
expect(useAppStore.getState().snapshotSections!.company.runs[0]!.status).toBe("running");
expectSnapshotSectionsMatchSnapshot();
});
it("removeRun filters run by ID in both section and compatibility snapshot", () => {
useAppStore.getState().setSnapshot(makeSnapshot({ runs: [fakeRun("r-1"), fakeRun("r-2")] }));
useAppStore.getState().removeRun("r-1");
expect(useAppStore.getState().snapshot!.runs.map((run) => run.id)).toEqual(["r-2"]);
expect(useAppStore.getState().snapshotSections!.company.runs.map((run) => run.id)).toEqual(["r-2"]);
expectSnapshotSectionsMatchSnapshot();
});
it("patchAgent inserts and updates agent records through snapshot sections", () => {
useAppStore.getState().setSnapshot(makeSnapshot({ agents: [fakeAgent("ag-existing")] }));
useAppStore.getState().patchAgent(fakeAgent("ag-new", "New Agent"));
useAppStore.getState().patchAgent(fakeAgent("ag-new", "Renamed Agent"));
expect(useAppStore.getState().snapshot!.agents.map((agent) => `${agent.id}:${agent.name}`)).toEqual([
"ag-new:Renamed Agent",
"ag-existing:Agent",
]);
expect(useAppStore.getState().snapshotSections!.company.agents[0]!.name).toBe("Renamed Agent");
expectSnapshotSectionsMatchSnapshot();
});
it("removeAgent filters agent by ID in both section and compatibility snapshot", () => {
useAppStore.getState().setSnapshot(makeSnapshot({ agents: [fakeAgent("ag-1"), fakeAgent("ag-2")] }));
useAppStore.getState().removeAgent("ag-1");
expect(useAppStore.getState().snapshot!.agents.map((agent) => agent.id)).toEqual(["ag-2"]);
expect(useAppStore.getState().snapshotSections!.company.agents.map((agent) => agent.id)).toEqual(["ag-2"]);
expectSnapshotSectionsMatchSnapshot();
});
it("patchApproval inserts and updates approval records through snapshot sections", () => {
useAppStore.getState().setSnapshot(makeSnapshot({ approvals: [fakeApproval("ap-existing")] }));
useAppStore.getState().patchApproval(fakeApproval("ap-new"));
useAppStore.getState().patchApproval({ ...fakeApproval("ap-new"), state: "approved" });
expect(useAppStore.getState().snapshot!.approvals.map((approval) => `${approval.id}:${approval.state}`)).toEqual([
"ap-new:approved",
"ap-existing:pending",
]);
expect(useAppStore.getState().snapshotSections!.company.approvals[0]!.state).toBe("approved");
expectSnapshotSectionsMatchSnapshot();
});
it("removeApproval filters approval by ID in both section and compatibility snapshot", () => {
useAppStore.getState().setSnapshot(makeSnapshot({ approvals: [fakeApproval("ap-1"), fakeApproval("ap-2")] }));
useAppStore.getState().removeApproval("ap-1");
expect(useAppStore.getState().snapshot!.approvals.map((approval) => approval.id)).toEqual(["ap-2"]);
expect(useAppStore.getState().snapshotSections!.company.approvals.map((approval) => approval.id)).toEqual([
"ap-2",
]);
expectSnapshotSectionsMatchSnapshot();
});
it("patch methods leave null snapshot state unchanged", () => {
expect(useAppStore.getState().snapshot).toBeNull();
useAppStore.getState().patchTask(fakeTask("t-1"));
useAppStore.getState().removeTask("t-1");
useAppStore.getState().patchRun(fakeRun("r-1"));
useAppStore.getState().removeRun("r-1");
useAppStore.getState().patchAgent(fakeAgent("ag-1"));
useAppStore.getState().removeAgent("ag-1");
useAppStore.getState().patchApproval(fakeApproval("ap-1"));
useAppStore.getState().removeApproval("ap-1");
expect(useAppStore.getState().snapshot).toBeNull();
expect(useAppStore.getState().snapshotSections).toBeNull();
});
});
+304
View File
@@ -0,0 +1,304 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { ApprovalService } from "../../src/main/services/approval-service";
import { ChatService } from "../../src/main/services/chat-service";
import { ComplianceService } from "../../src/main/services/compliance-service";
import { OrganizationService } from "../../src/main/services/organization-service";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { SocialService } from "../../src/main/services/social-service";
import { APPROVAL_STATE, SOCIAL_PLATFORM } from "../../src/shared/domain";
function createConnection(): DatabaseConnection {
return new DatabaseConnection(join(tmpdir(), `agentcompany-new-compliance-${randomUUID()}.db`));
}
describe("compliance service", () => {
test("creates GDPR export payloads with audit coverage", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
const compliance = new ComplianceService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let employeeId = "";
let spaceId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "Exportable message",
attachmentFileIds: [],
});
compliance.createExport({
orgId,
subjectType: "employee",
subjectId: employeeId,
exportType: "gdpr_export",
requestedByEmployeeId: employeeId,
});
});
const state = snapshot.load();
const record = state.complianceExports[0];
if (!record) throw new Error("Compliance export was not created.");
const payload = JSON.parse(record.payloadJson) as { profile: { display_name: string }; chatMessages: unknown[] };
expect(record).toMatchObject({ subjectType: "employee", subjectId: employeeId, exportType: "gdpr_export", state: "completed" });
expect(payload.profile.display_name).toBe("Dana Lee");
expect(payload.chatMessages).toHaveLength(1);
expect(state.auditEvents.some((event) => event.entityType === "compliance_export" && event.action === "created")).toBe(true);
connection.close();
});
test("blocks GDPR delete under holds and redacts after release", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const approvals = new ApprovalService(connection.db);
const chat = new ChatService(connection.db);
const compliance = new ComplianceService(connection.db);
const social = new SocialService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let employeeId = "";
let spaceId = "";
let holdId = "";
let approvalId = "";
let socialAccountId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
chat.sendMessage({ orgId, spaceId, threadId: null, senderType: "employee", senderId: employeeId, bodyMd: "Delete me", attachmentFileIds: [] });
approvalId = approvals.create({
orgId,
kind: "security_review",
title: "Security review",
details: "Employee requested and decided.",
risk: "low",
requestedByType: "employee",
requestedById: employeeId,
});
approvals.decide({ orgId, approvalId, state: APPROVAL_STATE.APPROVED, decidedByEmployeeId: employeeId });
socialAccountId = social.createAccount({
orgId,
platform: SOCIAL_PLATFORM.X,
handle: "@acme",
displayName: "Acme",
createdByEmployeeId: employeeId,
authorizedEmployeeId: employeeId,
});
const providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'github-copilot'")
.get(orgId) as { id: string }).id;
connection.db.prepare(`
insert into provider_auth_sessions(
id, org_id, provider_id, provider_key, employee_id, state, nonce, code_verifier_sha256,
redirect_uri, sign_in_url, partition, created_at, expires_at, completed_at
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, null)
`).run(
"auths-test",
orgId,
providerId,
"github-copilot",
employeeId,
"state",
"nonce",
"a".repeat(64),
`agentcompany://provider-auth/${providerId}`,
"https://github.com/login",
`persist:agentcompany-provider-${providerId}`,
new Date().toISOString(),
new Date(Date.now() + 60000).toISOString(),
);
holdId = compliance.createLegalHold({
orgId,
entityType: "employee",
entityId: employeeId,
reason: "Discovery hold",
createdByEmployeeId: employeeId,
});
});
expect(() => connection.transaction(() => compliance.createExport({
orgId,
subjectType: "employee",
subjectId: employeeId,
exportType: "gdpr_delete",
requestedByEmployeeId: employeeId,
}))).toThrow("Active compliance hold prevents GDPR delete.");
connection.transaction(() => {
compliance.setLegalHoldState({ orgId, legalHoldId: holdId, active: false, employeeId });
compliance.createExport({
orgId,
subjectType: "employee",
subjectId: employeeId,
exportType: "gdpr_delete",
requestedByEmployeeId: employeeId,
});
});
const state = snapshot.load();
const redactedApproval = state.approvals.find((approval) => approval.id === approvalId);
const redactedSocialAccount = state.socialAccounts.find((account) => account.id === socialAccountId);
expect(state.employees[0]?.displayName).toBe("Deleted employee");
expect(state.chatMessages[0]?.senderId).toBeNull();
expect(redactedApproval?.requestedById).toBeNull();
expect(redactedApproval?.decidedByEmployeeId).toBeNull();
expect(redactedSocialAccount?.authorizedEmployeeId).toBeNull();
expect(state.socialAccountGrants).toHaveLength(0);
expect(state.providerAuthSessions).toHaveLength(0);
expect(state.complianceExports[0]?.exportType).toBe("gdpr_delete");
expect(state.complianceExports[0]?.requestedByEmployeeId).toBeNull();
expect(state.auditEvents.some((event) => event.entityType === "compliance_export" && event.action === "gdpr_deleted")).toBe(true);
connection.close();
});
test("blocks GDPR delete under organization retention freeze", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const compliance = new ComplianceService(connection.db);
let orgId = "";
let employeeId = "";
let agentId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
agentId = organization.createAgent({
orgId,
name: "Ada",
role: "Release reviewer",
department: "Engineering",
modelProviderId: null,
modelId: null,
budgetCents: 10000,
managerAgentId: null,
});
compliance.createRetentionFreeze({
orgId,
scopeType: "organization",
scopeId: orgId,
reason: "Retention freeze",
createdByEmployeeId: employeeId,
});
});
expect(() => connection.transaction(() => compliance.createExport({
orgId,
subjectType: "agent",
subjectId: agentId,
exportType: "gdpr_delete",
requestedByEmployeeId: employeeId,
}))).toThrow("Active compliance hold prevents GDPR delete.");
connection.close();
});
test("blocks GDPR delete under related chat and file holds", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
const compliance = new ComplianceService(connection.db);
let orgId = "";
let employeeId = "";
let spaceId = "";
let threadId = "";
let fileId = "";
let holdId = "";
let freezeId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
chat.sendMessage({ orgId, spaceId, threadId: null, senderType: "employee", senderId: employeeId, bodyMd: "Held chat", attachmentFileIds: [] });
threadId = (connection.db.prepare("select thread_id from chat_messages where org_id = ?").get(orgId) as { thread_id: string }).thread_id;
fileId = "file-held";
connection.db.prepare(`
insert into file_objects(id, org_id, name, kind, size_bytes, sha256, storage_path, owner_type, owner_id, created_at)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(fileId, orgId, "held.txt", "text/plain", 4, "a".repeat(64), "artifacts/held.txt", "employee", employeeId, new Date().toISOString());
holdId = compliance.createLegalHold({
orgId,
entityType: "chat_thread",
entityId: threadId,
reason: "Thread discovery",
createdByEmployeeId: employeeId,
});
});
expect(() => connection.transaction(() => compliance.createExport({
orgId,
subjectType: "employee",
subjectId: employeeId,
exportType: "gdpr_delete",
requestedByEmployeeId: employeeId,
}))).toThrow("Active compliance hold prevents GDPR delete.");
connection.transaction(() => {
compliance.setLegalHoldState({ orgId, legalHoldId: holdId, active: false, employeeId });
freezeId = compliance.createRetentionFreeze({
orgId,
scopeType: "chat_space",
scopeId: spaceId,
reason: "Space retention",
createdByEmployeeId: employeeId,
});
});
expect(() => connection.transaction(() => compliance.createExport({
orgId,
subjectType: "employee",
subjectId: employeeId,
exportType: "gdpr_delete",
requestedByEmployeeId: employeeId,
}))).toThrow("Active compliance hold prevents GDPR delete.");
connection.transaction(() => {
compliance.setRetentionFreezeState({ orgId, retentionFreezeId: freezeId, active: false, employeeId });
compliance.createLegalHold({
orgId,
entityType: "file_object",
entityId: fileId,
reason: "File discovery",
createdByEmployeeId: employeeId,
});
});
expect(() => connection.transaction(() => compliance.createExport({
orgId,
subjectType: "employee",
subjectId: employeeId,
exportType: "gdpr_delete",
requestedByEmployeeId: employeeId,
}))).toThrow("Active compliance hold prevents GDPR delete.");
connection.close();
});
});
-82
View File
@@ -1,82 +0,0 @@
import { describe, expect, it } from "vitest";
import { CONNECTORS } from "../../src/main/connector-definitions";
import { findConnectorDefinition, listFeaturedOnboardingConnectors } from "../../src/main/connector-definitions-lookup";
/** Verifies the static pi-mono connector catalog is shape-correct and that
* required fields hold the values downstream code (worker, resolver,
* Settings panel) depends on. */
describe("CONNECTORS catalog", () => {
it("contains exactly 28 connectors (5 OAuth + 18 API key + 3 Cloud + 2 Local)", () => {
expect(CONNECTORS.length).toBe(28);
const oauth = CONNECTORS.filter((c) => c.auth?.type === "oauth");
const apiKey = CONNECTORS.filter((c) => c.auth?.type === "api_key");
const cloud = CONNECTORS.filter((c) => c.auth ? ["aws", "azure", "gcp_adc"].includes(c.auth.type) : false);
const local = CONNECTORS.filter((c) => c.auth?.type === "local");
expect(oauth.length).toBe(5);
expect(apiKey.length).toBe(18);
expect(cloud.length).toBe(3);
expect(local.length).toBe(2);
});
it("uses unique connector ids", () => {
const ids = CONNECTORS.map((c) => c.id);
expect(new Set(ids).size).toBe(ids.length);
});
it("every entry has a non-empty label and description", () => {
for (const connector of CONNECTORS) {
expect(connector.label.length).toBeGreaterThan(0);
expect(connector.description.length).toBeGreaterThan(0);
expect((connector.defaultModel ?? "").length).toBeGreaterThan(0);
}
});
it("every entry references a documented pi `KnownProvider` id (or a local model-resolver provider)", () => {
// pi-ai's KnownProvider matrix (catalog covered by getModel/getModels).
const knownPiProviders = new Set([
"amazon-bedrock", "anthropic", "google", "google-gemini-cli", "google-antigravity", "google-vertex",
"openai", "azure-openai-responses", "openai-codex", "github-copilot",
"xai", "groq", "cerebras", "openrouter", "vercel-ai-gateway", "zai", "mistral",
"minimax", "minimax-cn", "huggingface", "opencode", "opencode-go", "kimi-coding",
]);
// Providers wired through src/main/model-resolver.ts instead of pi's catalog.
const localProviders = new Set(["fireworks", "fireworks-firepass", "ollama", "lm-studio"]);
for (const connector of CONNECTORS) {
const provider = connector.provider ?? "";
const recognised = knownPiProviders.has(provider) || localProviders.has(provider);
expect(recognised).toBe(true);
}
});
it("OAuth connectors carry one of the five pi OAuth provider ids", () => {
const validOauthIds = new Set(["anthropic", "openai-codex", "github-copilot", "google-gemini-cli", "google-antigravity"]);
const oauthConnectors = CONNECTORS.filter((c) => c.auth?.type === "oauth");
for (const connector of oauthConnectors) {
if (connector.auth?.type !== "oauth") throw new Error("unreachable");
expect(validOauthIds.has(connector.auth.oauthId ?? "")).toBe(true);
}
});
it("API-key connectors carry an envVar name", () => {
const apiKeyConnectors = CONNECTORS.filter((c) => c.auth?.type === "api_key");
for (const connector of apiKeyConnectors) {
if (connector.auth?.type !== "api_key") throw new Error("unreachable");
expect((connector.auth.envKey ?? "").length).toBeGreaterThan(0);
}
});
it("findConnectorDefinition resolves known ids and returns undefined for unknown ones", () => {
expect(findConnectorDefinition("anthropic")?.label).toBe("Anthropic API");
expect(findConnectorDefinition("amazon-bedrock")?.auth?.type).toBe("aws");
expect(findConnectorDefinition("google-vertex")?.auth?.type).toBe("gcp_adc");
expect(findConnectorDefinition("definitely-not-a-connector")).toBeUndefined();
});
it("featured onboarding rail returns the curated 3-provider subset", () => {
const featured = listFeaturedOnboardingConnectors();
expect(featured.length).toBeGreaterThanOrEqual(2);
expect(featured.length).toBeLessThanOrEqual(5);
expect(featured.find((c) => (c.id as string) === "claude-pro")).toBeDefined();
});
});
+113
View File
@@ -0,0 +1,113 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ChatService } from "../../src/main/services/chat-service";
import { ConnectorGrantsService } from "../../src/main/services/composio/connector-grants";
import { dispatchAgentTool, type AgentToolContext } from "../../src/main/services/agent-tools";
function call(name: string, args: Record<string, unknown>) {
return { id: `call_${randomUUID()}`, name, arguments: args };
}
interface Harness {
connection: DatabaseConnection;
orgId: string;
ceoId: string;
danaId: string;
ceoCtx: AgentToolContext;
danaCtx: AgentToolContext;
deps: { db: DatabaseConnection["db"]; organization: OrganizationService; chat: ChatService };
}
function harness(): Harness {
const connection = new DatabaseConnection(join(tmpdir(), `ac-connector-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let danaId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "OrbitOps", employeeName: "Yang", mission: "m", goal: "g" });
const providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null, roleLevel: "c_suite",
});
danaId = organization.createAgent({
orgId, name: "Dana", role: "Engineer", department: "Engineering",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 1000, managerAgentId: ceoId,
});
});
const context = (agentId: string): AgentToolContext => ({
orgId,
agent: { id: agentId, modelProviderId: null, modelId: null },
taskProjectId: null,
taskGoalId: null,
workspace: null,
});
return {
connection,
orgId,
ceoId,
danaId,
ceoCtx: context(ceoId),
danaCtx: context(danaId),
deps: { db: connection.db, organization, chat: new ChatService(connection.db) },
};
}
function connectToolkit(h: Harness, slug: string, name: string): void {
const now = new Date().toISOString();
h.connection.db.prepare(`
insert into connector_accounts(id, org_id, toolkit_slug, toolkit_name, auth_config_id, status, created_at, updated_at)
values (?, ?, ?, ?, ?, 'ACTIVE', ?, ?)
`).run(`ca_${slug}`, h.orgId, slug, name, `ac_${slug}`, now, now);
}
describe("connector grants", () => {
test("granting requires an actively connected toolkit", () => {
const h = harness();
const grants = new ConnectorGrantsService(h.connection.db);
expect(() => grants.grant({
orgId: h.orgId, agentId: h.danaId, toolkitSlug: "github", grantedByType: "employee", grantedById: null,
})).toThrow(/No active connection/);
});
test("the CEO grants and revokes toolkits for teammates", () => {
const h = harness();
connectToolkit(h, "github", "GitHub");
const granted = dispatchAgentTool(h.deps, h.ceoCtx, call("grant_connector", { agent_name: "Dana", toolkit: "github" }));
expect(granted).toContain("Dana now has access to the github connector");
const grants = new ConnectorGrantsService(h.connection.db);
expect(grants.toolkitsForAgent(h.orgId, h.danaId)).toEqual(["github"]);
expect(grants.toolkitsForAgent(h.orgId, h.ceoId)).toEqual([]);
const listing = dispatchAgentTool(h.deps, h.danaCtx, call("list_connectors", {}));
expect(listing).toContain("GitHub (github): granted to Dana");
const revoked = dispatchAgentTool(h.deps, h.ceoCtx, call("revoke_connector", { agent_name: "Dana", toolkit: "github" }));
expect(revoked).toContain("no longer has access");
expect(grants.toolkitsForAgent(h.orgId, h.danaId)).toEqual([]);
});
test("staff cannot hand out connector access", () => {
const h = harness();
connectToolkit(h, "github", "GitHub");
const denied = dispatchAgentTool(h.deps, h.danaCtx, call("grant_connector", { agent_name: "Dana", toolkit: "github" }));
expect(denied).toContain("ERROR");
expect(new ConnectorGrantsService(h.connection.db).toolkitsForAgent(h.orgId, h.danaId)).toEqual([]);
});
test("a disconnected toolkit stops counting for its grants", () => {
const h = harness();
connectToolkit(h, "slack", "Slack");
const grants = new ConnectorGrantsService(h.connection.db);
grants.grant({ orgId: h.orgId, agentId: h.danaId, toolkitSlug: "slack", grantedByType: "employee", grantedById: null });
expect(grants.toolkitsForAgent(h.orgId, h.danaId)).toEqual(["slack"]);
h.connection.db.prepare("update connector_accounts set status = 'EXPIRED' where org_id = ? and toolkit_slug = 'slack'").run(h.orgId);
expect(grants.toolkitsForAgent(h.orgId, h.danaId)).toEqual([]);
});
});
-92
View File
@@ -1,92 +0,0 @@
import { describe, expect, it } from "vitest";
import {
inferTaskType,
getConnectorStrengths,
} from "../../src/main/connector-matching";
describe("connector-matching", () => {
describe("inferTaskType", () => {
it("returns code for engineering department", () => {
expect(inferTaskType("engineering")).toBe("code");
});
it("returns research for research department", () => {
expect(inferTaskType("research")).toBe("research");
});
it("returns content for marketing department", () => {
expect(inferTaskType("marketing")).toBe("content");
});
it("returns content for product department", () => {
expect(inferTaskType("product")).toBe("content");
});
it("returns management for hr department", () => {
expect(inferTaskType("hr")).toBe("management");
});
it("returns management for finance department", () => {
expect(inferTaskType("finance")).toBe("management");
});
it("returns management for executive department", () => {
expect(inferTaskType("executive")).toBe("management");
});
it("returns content for design department", () => {
expect(inferTaskType("design")).toBe("content");
});
it("returns content for customer_support department", () => {
expect(inferTaskType("customer_support")).toBe("content");
});
it("returns management for operations department", () => {
expect(inferTaskType("operations")).toBe("management");
});
it("returns content for sales department", () => {
expect(inferTaskType("sales")).toBe("content");
});
it("returns management for legal department", () => {
expect(inferTaskType("legal")).toBe("management");
});
it("returns general for null department", () => {
expect(inferTaskType(null)).toBe("general");
});
it("returns general for undefined department", () => {
expect(inferTaskType(undefined)).toBe("general");
});
it("returns general for unknown department", () => {
expect(inferTaskType("unknown_dept")).toBe("general");
});
});
describe("getConnectorStrengths", () => {
it("returns non-empty string for claude-pro", () => {
const result = getConnectorStrengths("claude-pro");
expect(result).toBeTruthy();
expect(typeof result).toBe("string");
expect(result.length).toBeGreaterThan(10);
});
it("returns non-empty string for openai-codex", () => {
const result = getConnectorStrengths("openai-codex");
expect(result).toBeTruthy();
expect(typeof result).toBe("string");
expect(result.length).toBeGreaterThan(10);
});
it("returns non-empty string for gemini-cli", () => {
const result = getConnectorStrengths("gemini-cli");
expect(result).toBeTruthy();
expect(typeof result).toBe("string");
expect(result.length).toBeGreaterThan(10);
});
});
});
-92
View File
@@ -1,92 +0,0 @@
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: "anthropic" as const,
label: "Anthropic",
model: overrides.connectorModel ?? null,
}),
getDisabledModels: (_c: string, _k: string) => overrides.disabledModels ?? [],
};
}
describe("connector model policy", () => {
it("excludes disabled models from the enabled set", () => {
const policy = getConnectorModelPolicy(createDeps(), "test-co", "anthropic");
const allIds = policy.enabledModels.map((model) => model.id);
if (allIds.length === 0) {
throw new Error("anthropic provider should expose at least one model from pi-ai.");
}
const firstModel = allIds[0];
const filteredPolicy = getConnectorModelPolicy(
createDeps({ disabledModels: [{ id: firstModel, label: firstModel }] }), "test-co", "anthropic",
);
expect(filteredPolicy.enabledModels.some((model) => model.id === firstModel)).toBe(false);
});
it("accepts an explicit enabled model", () => {
const policy = getConnectorModelPolicy(createDeps(), "test-co", "anthropic");
const enabledId = policy.enabledModels[0]?.id;
if (!enabledId) {
throw new Error("anthropic provider should expose at least one model from pi-ai.");
}
const result = validateConnectorModelSelection(createDeps(), "test-co", "anthropic", enabledId);
expect(result).toEqual({ ok: true, effectiveModel: enabledId });
});
it("rejects an explicit disabled model", () => {
const policy = getConnectorModelPolicy(createDeps(), "test-co", "anthropic");
const enabledId = policy.enabledModels[0]?.id;
if (!enabledId) {
throw new Error("anthropic provider should expose at least one model from pi-ai.");
}
const result = validateConnectorModelSelection(
createDeps({ disabledModels: [{ id: enabledId, label: enabledId }] }), "test-co", "anthropic",
enabledId,
);
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 "${enabledId}"`);
});
});
describe("native connector defaults", () => {
it("falls back to definition defaultModel when connector model is empty", () => {
const definition = getConnectorDefinition("openai-codex");
const configuredDefault = definition.defaultModel;
const policy = getConnectorModelPolicy({
getConnector: () => ({
id: "openai-codex" as const,
label: "Codex",
model: null,
}),
getDisabledModels: (_c: string, _k: string) => [],
}, "test-co", "openai-codex");
expect(policy.defaultModel).toBe(configuredDefault);
expect(validateConnectorModelSelection({
getConnector: () => ({
id: "openai-codex" as const,
label: "Codex",
model: null,
}),
getDisabledModels: (_c: string, _k: string) => [],
}, "test-co", "openai-codex", null)).toEqual({
ok: true,
effectiveModel: configuredDefault,
});
});
});
-38
View File
@@ -1,38 +0,0 @@
import { describe, expect, it } from "vitest";
import {
mergeConnectorModelEntries,
} 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("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 },
]);
});
});
@@ -1,68 +0,0 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { getProviders } from "@mariozechner/pi-ai";
import { describe, expect, it, vi } from "vitest";
import {
CONNECTORS,
getConnectorKnownProvider,
getConnectorProvider,
isLocalModelResolverProvider,
isPiKnownProvider,
} from "../../src/main/connectors";
import { FIREWORKS_FIREPASS_MODEL_ID, fetchLocalConnectorModels, resolveModel, resolveModels } from "../../src/main/model-resolver";
describe("connector provider guard", () => {
it("classifies catalog providers before they enter pi-ai APIs", () => {
const piProviders = new Set(getProviders());
for (const connector of CONNECTORS) {
const provider = getConnectorProvider(connector.id);
expect(provider).toBe(connector.provider);
if (isLocalModelResolverProvider(provider)) {
expect(getConnectorKnownProvider(connector.id)).toBeNull();
} else {
expect(isPiKnownProvider(provider)).toBe(true);
expect(piProviders.has(provider)).toBe(true);
expect(getConnectorKnownProvider(connector.id)).toBe(provider);
}
}
});
it("uses the provider guard in model discovery", () => {
const anthropicModels = resolveModels("anthropic");
const firstAnthropicModel = anthropicModels[0];
if (!firstAnthropicModel) throw new Error("Anthropic model catalog is empty.");
expect(resolveModel("anthropic", firstAnthropicModel.id)?.id).toBe(firstAnthropicModel.id);
expect(resolveModels("not-a-pi-provider")).toEqual([]);
expect(resolveModel("not-a-pi-provider", "model-id")).toBeNull();
expect(resolveModel("fireworks-firepass", "ignored")?.id).toBe(FIREWORKS_FIREPASS_MODEL_ID);
expect(resolveModel("ollama", "llama3.2", { baseUrl: "http://127.0.0.1:11434/v1" })?.baseUrl).toBe("http://127.0.0.1:11434/v1");
expect(resolveModel("ollama", "llama3.2", { baseUrl: "https://example.com/v1" })).toBeNull();
});
it("rejects remote URLs before local model catalog fetch", async () => {
const fetchSpy = vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("fetch should stay unused"));
try {
await expect(fetchLocalConnectorModels("https://example.com/v1")).resolves.toEqual([]);
expect(fetchSpy).not.toHaveBeenCalled();
} finally {
fetchSpy.mockRestore();
}
});
it("keeps provider calls free of erased never casts", () => {
const files = [
"src/main/model-resolver.ts",
"src/main/connector-health-service.ts",
"src/main/credentials/credential-resolver.ts",
];
for (const file of files) {
const source = readFileSync(join(process.cwd(), file), "utf8");
expect(source).not.toContain("provider as never");
expect(source).not.toContain("definition.provider as never");
}
});
});
-39
View File
@@ -1,39 +0,0 @@
import { describe, expect, it } from "vitest";
import { buildDefaultConnectorRows, parseEnvBindingText } from "../../src/main/connectors";
describe("parseEnvBindingText", () => {
it("ignores comments and blank lines while preserving values with equals", () => {
expect(
parseEnvBindingText(`
# comment
OPENAI_API_KEY=vault-openai
CUSTOM=value=with=equals
`),
).toEqual({
OPENAI_API_KEY: "vault-openai",
CUSTOM: "value=with=equals",
});
});
});
describe("buildDefaultConnectorRows", () => {
it("preserves previously saved connector overrides", () => {
const rows = buildDefaultConnectorRows([
{
id: "openai-codex",
status: "ready",
notes: "custom note",
},
]);
const codex = rows.find((row) => row.id === "openai-codex");
if (!codex) {
throw new Error("openai-codex connector not found in default rows.");
}
expect(codex.id).toBe("openai-codex");
expect(codex.status).toBe("ready");
expect(codex.notes).toBe("custom note");
expect(codex.capabilityMatrix.structuredTranscript).toBe(true);
});
});
@@ -1,66 +0,0 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import { describe, expect, it, vi } from "vitest";
import { buildAgentHooks } from "../../src/main/agent-runtime/agent-hooks";
import type { WorkerRpcClient } from "../../src/main/worker/entry/rpc";
function createRpc() {
return {
invoke: vi.fn(async () => ({ ok: true, result: undefined })),
} as unknown as WorkerRpcClient & { invoke: ReturnType<typeof vi.fn> };
}
function largeTranscript(): AgentMessage[] {
const messages: AgentMessage[] = [];
for (let i = 0; i < 46; i += 1) {
const role = i % 2 === 0 ? "user" : "assistant";
messages.push({
role,
content: [{
type: "text",
text: `${role} turn ${i}\n${"context ".repeat(6_000)}`,
}],
timestamp: i,
} as AgentMessage);
}
return messages;
}
describe("worker-side context compaction", () => {
it("compacts inside the worker without transformContext RPC", async () => {
const rpc = createRpc();
const onContextCompaction = vi.fn();
const hooks = buildAgentHooks({
runId: "8b3f07b2-841a-4e16-9d1c-9d2a27c3c2ad",
rpc,
onContextCompaction,
});
const messages = largeTranscript();
const transformed = await hooks.transformContext(messages);
expect(rpc.invoke).not.toHaveBeenCalledWith(
expect.any(String),
"transformContext",
expect.anything(),
);
expect(transformed.length).toBeLessThan(messages.length);
expect(transformed[0]).toMatchObject({ role: "user" });
expect(onContextCompaction).toHaveBeenCalledWith(expect.objectContaining({
runId: "8b3f07b2-841a-4e16-9d1c-9d2a27c3c2ad",
originalMessageCount: messages.length,
compactedMessageCount: transformed.length,
}));
});
it("propagates compaction failures instead of returning the original transcript", async () => {
const rpc = createRpc();
const hooks = buildAgentHooks({
runId: "b9f6aa93-0db7-4f39-8f28-2d87742ef414",
rpc,
});
await expect(hooks.transformContext(null as unknown as AgentMessage[])).rejects.toThrow(
"transformContext compaction failed",
);
});
});
@@ -1,119 +0,0 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import { describe, expect, it } from "vitest";
import { compactMessagesIfNeeded } from "../../src/main/agent-runtime/context-compactor";
function textOf(message: AgentMessage): string {
const content = (message as { content?: unknown }).content;
if (!Array.isArray(content)) return "";
const first = content[0] as { text?: string } | undefined;
return first?.text ?? "";
}
function makeLongTranscript(olderFacts: AgentMessage[]): AgentMessage[] {
const messages: AgentMessage[] = [
{
role: "user",
content: [{ type: "text", text: `Ship the approval workflow.\n${"context ".repeat(12_000)}` }],
timestamp: 1,
} as AgentMessage,
...olderFacts,
];
for (let i = 0; i < 42; i += 1) {
messages.push({
role: i % 2 === 0 ? "user" : "assistant",
content: [{ type: "text", text: `recent turn ${i}\n${"tail ".repeat(4_000)}` }],
timestamp: 100 + i,
} as AgentMessage);
}
return messages;
}
describe("context compactor tool result facts", () => {
it("uses tokenizer counts so long CJK transcripts compact before provider overflow", () => {
const messages: AgentMessage[] = [
{
role: "user",
content: [{ type: "text", text: "Start the Chinese market launch review." }],
timestamp: 1,
} as AgentMessage,
{
role: "assistant",
content: [{ type: "text", text: "I will keep the plan concise." }],
timestamp: 2,
} as AgentMessage,
{
role: "user",
content: [{ type: "text", text: "请完成市场复盘,保留所有客户反馈与行动项。 ".repeat(8_000) }],
timestamp: 3,
} as AgentMessage,
];
for (let i = 0; i < 42; i += 1) {
messages.push({
role: i % 2 === 0 ? "user" : "assistant",
content: [{ type: "text", text: `recent ${i}` }],
timestamp: 100 + i,
} as AgentMessage);
}
const outcome = compactMessagesIfNeeded(messages);
expect(outcome.changed).toBe(true);
expect(outcome.telemetry?.estimatedTokens).toBeGreaterThanOrEqual(120_000);
});
it("preserves task, approval, message, status, decision, and error facts from tool results", () => {
const messages = makeLongTranscript([
{
role: "assistant",
content: [
{ type: "text", text: "I will request approval for the deliverable." },
{
type: "toolCall",
id: "call-approval",
name: "file_approval",
args: { taskId: "task_123" },
},
],
timestamp: 2,
} as AgentMessage,
{
role: "toolResult",
toolCallId: "call-approval",
content: [{
type: "text",
text: JSON.stringify({
ok: false,
taskId: "task_123",
approvalId: "approval_456",
messageId: "message_789",
status: "blocked",
decision: "requires_human_review",
error: "missing reviewer",
}),
}],
details: {
taskId: "task_123",
approvalId: "approval_456",
messageId: "message_789",
status: "blocked",
decision: "requires_human_review",
error: "missing reviewer",
},
timestamp: 3,
} as AgentMessage,
]);
const outcome = compactMessagesIfNeeded(messages);
expect(outcome.changed).toBe(true);
const summary = textOf(outcome.messages?.[0] as AgentMessage);
expect(summary).toContain("file_approval");
expect(summary).toContain("taskId=task_123");
expect(summary).toContain("approvalId=approval_456");
expect(summary).toContain("messageId=message_789");
expect(summary).toContain("status=blocked");
expect(summary).toContain("decision=requires_human_review");
expect(summary).toContain("error=missing reviewer");
});
});
-122
View File
@@ -1,122 +0,0 @@
import { describe, expect, it } from "vitest";
import {
connectorInputSchema,
documentInputSchema,
knowledgeEntryInputSchema,
meetingInputSchema,
runLogSchema,
secretInputSchema,
startRunSchema,
workspaceInputSchema,
} from "../../src/shared/contracts";
describe("shared contracts", () => {
it("fills connector defaults for optional fields", () => {
const parsed = connectorInputSchema.parse({
id: "openai-codex",
});
expect(parsed.model).toBeUndefined();
expect(parsed.envBindingText).toBe("");
expect(parsed.notes).toBe("");
});
it("validates workspace and run payloads", () => {
expect(() =>
workspaceInputSchema.parse({
companyId: "bad-id",
name: "Primary Workspace",
localPath: "/tmp/workspace",
}),
).toThrow();
expect(() =>
startRunSchema.parse({
taskId: "bad-id",
}),
).toThrow();
expect(() =>
runLogSchema.parse({
runId: "bad-id",
}),
).toThrow();
});
it("accepts bounded secret payloads", () => {
const parsed = secretInputSchema.parse({
companyId: "00000000-0000-0000-0000-000000000000",
name: "vault-openai",
description: "OpenAI API token",
value: "sk-test-value",
});
expect(parsed.companyId).toBe("00000000-0000-0000-0000-000000000000");
expect(parsed.name).toBe("vault-openai");
expect(parsed.value).toContain("sk-");
});
it("normalizes empty optional ids to null", () => {
const parsed = workspaceInputSchema.parse({
companyId: "00000000-0000-0000-0000-000000000000",
name: "Primary Workspace",
localPath: "/tmp/workspace",
projectId: "",
});
expect(parsed.projectId).toBeNull();
});
it("canonicalizes structured JSON string fields", () => {
const companyId = "00000000-0000-0000-0000-000000000000";
const document = documentInputSchema.parse({
companyId,
type: "technical_spec",
title: "JSON contract",
tagsJson: "[ \"security\", \"audit\" ]",
});
expect(document.tagsJson).toBe("[\"security\",\"audit\"]");
const knowledge = knowledgeEntryInputSchema.parse({
companyId,
category: "technical",
topic: "JSON fields",
content: "Canonicalize JSON before persistence.",
tagsJson: "[ \"runtime\" ]",
});
expect(knowledge.tagsJson).toBe("[\"runtime\"]");
const meeting = meetingInputSchema.parse({
companyId,
type: "department_sync",
title: "JSON field sync",
scheduledAt: "2026-05-15T10:00:00.000Z",
participantAgentIds: "[ \"11111111-1111-1111-1111-111111111111\" ]",
agendaJson: "[ \"Plan\" ]",
actionItemsJson: "[ { \"title\": \"Ship\", \"description\": \"Finish\" } ]",
});
expect(meeting.participantAgentIds).toBe("[\"11111111-1111-1111-1111-111111111111\"]");
expect(meeting.agendaJson).toBe("[\"Plan\"]");
expect(meeting.actionItemsJson).toBe("[{\"title\":\"Ship\",\"description\":\"Finish\"}]");
});
it("rejects malformed structured JSON string fields", () => {
const companyId = "00000000-0000-0000-0000-000000000000";
expect(() => documentInputSchema.parse({
companyId,
type: "technical_spec",
title: "Broken tags",
tagsJson: "{",
})).toThrow();
expect(() => meetingInputSchema.parse({
companyId,
type: "department_sync",
title: "Broken participants",
scheduledAt: "2026-05-15T10:00:00.000Z",
participantAgentIds: "[1]",
})).toThrow();
});
});
-185
View File
@@ -1,185 +0,0 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import {
interruptStaleRunsLogic,
retryInterruptedTaskLogic,
markInterruptedTaskFailedLogic,
type StaleRunDb,
type RetryTaskDb,
type FailTaskDb,
} from "@main/database/crash-recovery-helpers";
/**
* Tests for crash recovery detection and user notification.
*
* Covers:
* - interruptStaleRunsLogic returns correct count and marks tasks as blocked
* - retryInterruptedTaskLogic resets task to 'todo' and clears retry_count
* - markInterruptedTaskFailedLogic sets task to 'cancelled'
*
* getInterruptedRunsForRecovery is a pure SQL query with a well-defined JOIN
* shape; its contract is tested via the return-type assertions below.
*/
function makeStaleRunDb(runningCount: number): StaleRunDb & { calls: string[] } {
const calls: string[] = [];
return {
calls,
interruptRunningRuns: vi.fn((_ts: string) => {
calls.push("interruptRunningRuns");
return runningCount;
}) as unknown as StaleRunDb["interruptRunningRuns"],
blockInterruptedTasks: vi.fn((_ts: string) => {
calls.push("blockInterruptedTasks");
}) as unknown as StaleRunDb["blockInterruptedTasks"],
clearInterruptedTaskLeases: vi.fn(() => {
calls.push("clearInterruptedTaskLeases");
}) as unknown as StaleRunDb["clearInterruptedTaskLeases"],
resetAgentsToIdle: vi.fn((_ts: string) => {
calls.push("resetAgentsToIdle");
}) as unknown as StaleRunDb["resetAgentsToIdle"],
releaseAllWorkspaceLocks: vi.fn(() => {
calls.push("releaseAllWorkspaceLocks");
}) as unknown as StaleRunDb["releaseAllWorkspaceLocks"],
};
}
describe("Crash Recovery Detection", () => {
const transaction = (fn: () => void) => fn();
const nowIso = () => new Date().toISOString();
describe("interruptStaleRunsLogic", () => {
it("returns 0 when no running runs exist", () => {
const db = makeStaleRunDb(0);
const count = interruptStaleRunsLogic(db, transaction, nowIso);
expect(count).toBe(0);
});
it("returns correct count when running runs exist", () => {
const db = makeStaleRunDb(3);
const count = interruptStaleRunsLogic(db, transaction, nowIso);
expect(count).toBe(3);
});
it("marks associated tasks as blocked when interrupted runs exist", () => {
const db = makeStaleRunDb(2);
interruptStaleRunsLogic(db, transaction, nowIso);
expect(db.calls).toContain("blockInterruptedTasks");
});
it("leaves task blocking step idle when no runs were interrupted", () => {
const db = makeStaleRunDb(0);
interruptStaleRunsLogic(db, transaction, nowIso);
expect(db.calls).not.toContain("blockInterruptedTasks");
});
it("always clears interrupted task leases and resets agents to idle", () => {
const db = makeStaleRunDb(0);
interruptStaleRunsLogic(db, transaction, nowIso);
expect(db.calls).toContain("clearInterruptedTaskLeases");
expect(db.calls).toContain("resetAgentsToIdle");
});
it("releases all workspace locks", () => {
const db = makeStaleRunDb(1);
interruptStaleRunsLogic(db, transaction, nowIso);
expect(db.calls).toContain("releaseAllWorkspaceLocks");
});
it("executes all steps inside the transaction", () => {
const db = makeStaleRunDb(1);
const txCalls: string[] = [];
const trackingTransaction = (fn: () => void) => {
txCalls.push("begin");
fn();
txCalls.push("end");
};
interruptStaleRunsLogic(db, trackingTransaction, nowIso);
expect(txCalls[0]).toBe("begin");
expect(txCalls[txCalls.length - 1]).toBe("end");
expect(db.calls.length).toBeGreaterThan(0);
});
});
describe("getInterruptedRunsForRecovery (shape contract)", () => {
it("result entries have required fields", () => {
// This function is a pure SQL JOIN; we verify its return shape here
// by asserting the TypeScript type it must conform to.
const mockResult: Array<{
runId: string;
taskId: string;
taskTitle: string;
agentId: string;
agentName: string;
connectorId: string;
interruptedAt: string;
}> = [
{
runId: "run-1",
taskId: "task-1",
taskTitle: "Build feature",
agentId: "agent-1",
agentName: "Developer",
connectorId: "claude-code",
interruptedAt: "2026-03-22T07:00:00Z",
},
];
expect(mockResult[0]).toEqual(
expect.objectContaining({
runId: expect.any(String),
taskId: expect.any(String),
taskTitle: expect.any(String),
agentName: expect.any(String),
interruptedAt: expect.any(String),
}),
);
});
it("returns empty array when no interrupted runs with blocked tasks exist", () => {
const result: Array<unknown> = [];
expect(result).toHaveLength(0);
});
});
describe("retryInterruptedTaskLogic", () => {
let db: RetryTaskDb;
beforeEach(() => {
db = {
setTaskTodo: vi.fn(),
resetTaskRetryFields: vi.fn(),
};
});
it("sets task to todo and resets retry fields", () => {
retryInterruptedTaskLogic(db, "task-42", nowIso);
expect(db.setTaskTodo).toHaveBeenCalledWith(expect.any(String), "task-42");
expect(db.resetTaskRetryFields).toHaveBeenCalledWith(expect.any(String), "task-42");
});
it("calls both statements with the same timestamp", () => {
const fixedTs = "2026-01-01T00:00:00.000Z";
retryInterruptedTaskLogic(db, "task-42", () => fixedTs);
expect(db.setTaskTodo).toHaveBeenCalledWith(fixedTs, "task-42");
expect(db.resetTaskRetryFields).toHaveBeenCalledWith(fixedTs, "task-42");
});
});
describe("markInterruptedTaskFailedLogic", () => {
let db: FailTaskDb;
beforeEach(() => {
db = { setTaskCancelled: vi.fn() };
});
it("sets task to cancelled", () => {
markInterruptedTaskFailedLogic(db, "task-99", nowIso);
expect(db.setTaskCancelled).toHaveBeenCalledWith(expect.any(String), "task-99");
});
it("passes the taskId correctly", () => {
const fixedTs = "2026-01-01T00:00:00.000Z";
markInterruptedTaskFailedLogic(db, "task-99", () => fixedTs);
expect(db.setTaskCancelled).toHaveBeenCalledWith(fixedTs, "task-99");
});
});
});
@@ -1,106 +0,0 @@
import { describe, expect, it } from "vitest";
import { InMemoryCredentialConcurrencyGate } from "../../src/main/worker/runtime/credential-concurrency-gate";
function makeGate(limit: number | null) {
return new InMemoryCredentialConcurrencyGate(() => limit);
}
describe("InMemoryCredentialConcurrencyGate", () => {
it("returns a no-op release when the provider is uncapped", async () => {
const gate = makeGate(null);
const release = await gate.acquire("anthropic", "key-x");
expect(typeof release).toBe("function");
release();
release();
});
it("hands out slots up to the limit immediately", async () => {
const gate = makeGate(2);
const r1 = await gate.acquire("fireworks-firepass", "shared");
const r2 = await gate.acquire("fireworks-firepass", "shared");
expect(r1).not.toBe(r2);
r1();
r2();
});
it("queues the third caller until a slot is released", async () => {
const gate = makeGate(1);
const release = await gate.acquire("fireworks-firepass", "shared");
const order: string[] = [];
const second = (async () => {
const r = await gate.acquire("fireworks-firepass", "shared");
order.push("second");
r();
})();
const third = (async () => {
const r = await gate.acquire("fireworks-firepass", "shared");
order.push("third");
r();
})();
await new Promise((resolve) => setTimeout(resolve, 10));
expect(order).toEqual([]);
release();
await second;
expect(order).toEqual(["second"]);
await third;
expect(order).toEqual(["second", "third"]);
});
it("scopes pools by (provider, apiKey) so different keys do not block each other", async () => {
const gate = makeGate(1);
const r1 = await gate.acquire("fireworks-firepass", "key-a");
// Different apiKey -> new pool, no waiting.
const r2 = await gate.acquire("fireworks-firepass", "key-b");
expect(typeof r1).toBe("function");
expect(typeof r2).toBe("function");
r1();
r2();
});
it("releases the pool when no callers remain", async () => {
const gate = makeGate(1);
const release = await gate.acquire("fireworks-firepass", "shared");
release();
// Re-acquire should not be queued.
const next = await gate.acquire("fireworks-firepass", "shared");
next();
});
it("rejects when the abort signal fires while waiting", async () => {
const gate = makeGate(1);
const held = await gate.acquire("fireworks-firepass", "shared");
const controller = new AbortController();
const pending = gate.acquire("fireworks-firepass", "shared", controller.signal);
controller.abort();
await expect(pending).rejects.toMatchObject({ name: "AbortError" });
// Slot must remain available after the aborted waiter is removed.
held();
const next = await gate.acquire("fireworks-firepass", "shared");
next();
});
it("rejects immediately when the abort signal is already aborted", async () => {
const gate = makeGate(1);
const controller = new AbortController();
controller.abort();
await expect(
gate.acquire("fireworks-firepass", "shared", controller.signal),
).rejects.toMatchObject({ name: "AbortError" });
});
it("treats double-release as a no-op", async () => {
const gate = makeGate(1);
const release = await gate.acquire("fireworks-firepass", "shared");
release();
release();
const next = await gate.acquire("fireworks-firepass", "shared");
next();
});
});
-210
View File
@@ -1,210 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { CredentialStore } from "../../src/main/credentials/credential-store";
import { CredentialStoreReadError } from "../../src/main/credentials/credential-store";
import { CredentialResolver, CredentialError } from "../../src/main/credentials/credential-resolver";
import { AppError } from "../../src/main/errors";
beforeAll(() => {
process.env.AGENTCOMPANY_ALLOW_INSECURE_VAULT = "true";
});
describe("CredentialResolver", () => {
let tempDir: string;
let db: AppDatabase;
let store: CredentialStore;
let resolver: CredentialResolver;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-credential-resolver-"));
db = new AppDatabase(tempDir);
db.init();
store = new CredentialStore({ db });
resolver = new CredentialResolver(store);
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("returns the literal apiKey for an api_key connector", async () => {
store.save("anthropic", {
kind: "api_key",
value: { source: "literal", value: "sk-ant-test-1234567890abcd" },
});
const resolved = await resolver.resolve("anthropic");
expect(resolved.apiKey).toBe("sk-ant-test-1234567890abcd");
expect(resolved.extraEnv).toBeUndefined();
});
it("resolves an api_key connector backed by an env variable", async () => {
process.env.OPENAI_API_KEY = "sk-from-env-1234";
store.save("openai", {
kind: "api_key",
value: { source: "env", value: "OPENAI_API_KEY" },
});
try {
const resolved = await resolver.resolve("openai");
expect(resolved.apiKey).toBe("sk-from-env-1234");
} finally {
delete process.env.OPENAI_API_KEY;
}
});
it("throws CREDENTIAL_MISSING when the env-backed key is unset", async () => {
delete process.env.XAI_API_KEY;
store.save("xai", { kind: "api_key", value: { source: "env", value: "XAI_API_KEY" } });
let thrown: unknown;
try {
await resolver.resolve("xai");
} catch (error) {
thrown = error;
}
expect(thrown).toBeInstanceOf(CredentialError);
expect(thrown).toBeInstanceOf(AppError);
expect(thrown).toMatchObject({
kind: "upstream",
code: "CREDENTIAL_MISSING",
connectorId: "xai",
context: { connectorId: "xai" },
});
});
it("assembles AWS extraEnv for IAM auth mode", async () => {
store.save("amazon-bedrock", {
kind: "aws",
value: {
authMode: "iam",
region: "us-west-2",
accessKeyId: "AKIA-TEST-FAKE-1234",
secretAccessKey: "secret-fake-secret",
sessionToken: "session-fake-token",
},
});
const resolved = await resolver.resolve("amazon-bedrock");
expect(resolved.apiKey).toBe("");
expect(resolved.extraEnv).toEqual({
AWS_REGION: "us-west-2",
AWS_ACCESS_KEY_ID: "AKIA-TEST-FAKE-1234",
AWS_SECRET_ACCESS_KEY: "secret-fake-secret",
AWS_SESSION_TOKEN: "session-fake-token",
});
});
it("assembles AWS extraEnv for profile auth mode plus advanced flags", async () => {
store.save("amazon-bedrock", {
kind: "aws",
value: {
authMode: "profile",
region: "eu-west-1",
awsProfile: "production",
forceCache: true,
endpointUrl: "https://bedrock-runtime.eu-west-1.amazonaws.com",
skipAuth: true,
forceHttp1: true,
},
});
const resolved = await resolver.resolve("amazon-bedrock");
expect(resolved.extraEnv).toEqual({
AWS_REGION: "eu-west-1",
AWS_PROFILE: "production",
AWS_BEDROCK_FORCE_CACHE: "1",
AWS_ENDPOINT_URL_BEDROCK_RUNTIME: "https://bedrock-runtime.eu-west-1.amazonaws.com",
AWS_BEDROCK_SKIP_AUTH: "1",
AWS_BEDROCK_FORCE_HTTP1: "1",
});
});
it("rejects unsafe AWS endpointUrl values from stored credentials", async () => {
store.save("amazon-bedrock", {
kind: "aws",
value: {
authMode: "profile",
region: "us-east-1",
awsProfile: "default",
endpointUrl: "https://example.com",
},
});
let thrown: unknown;
try {
await resolver.resolve("amazon-bedrock");
} catch (error) {
thrown = error;
}
expect(thrown).toBeInstanceOf(CredentialStoreReadError);
expect(thrown).toMatchObject({
kind: "upstream",
code: "CREDENTIAL_INVALID",
connectorId: "amazon-bedrock",
context: { connectorId: "amazon-bedrock" },
});
});
it("assembles Azure extraEnv with deploymentMap", async () => {
store.save("azure-openai", {
kind: "azure",
value: {
apiKey: "azure-secret-key",
baseUrl: "https://my-resource.openai.azure.com/",
resourceName: "my-resource",
apiVersion: "2024-08-01-preview",
deploymentMap: { "gpt-4o": "gpt4-deploy", "gpt-4o-mini": "gpt4mini-deploy" },
},
});
const resolved = await resolver.resolve("azure-openai");
expect(resolved.apiKey).toBe("azure-secret-key");
expect(resolved.extraEnv).toMatchObject({
AZURE_OPENAI_API_KEY: "azure-secret-key",
AZURE_OPENAI_BASE_URL: "https://my-resource.openai.azure.com/",
AZURE_OPENAI_RESOURCE_NAME: "my-resource",
AZURE_OPENAI_API_VERSION: "2024-08-01-preview",
});
expect(resolved.extraEnv?.AZURE_OPENAI_DEPLOYMENT_NAME_MAP).toContain("gpt-4o=gpt4-deploy");
});
it("assembles Vertex extraEnv from project + location + credentials path", async () => {
store.save("google-vertex", {
kind: "gcp_adc",
value: {
project: "my-gcp-project",
location: "us-central1",
credentialsPath: "/etc/gcp/service-account.json",
},
});
const resolved = await resolver.resolve("google-vertex");
expect(resolved.apiKey).toBe("");
expect(resolved.extraEnv).toEqual({
GOOGLE_CLOUD_PROJECT: "my-gcp-project",
GOOGLE_CLOUD_LOCATION: "us-central1",
GOOGLE_APPLICATION_CREDENTIALS: "/etc/gcp/service-account.json",
});
});
it("rejects remote baseUrl values for local connectors", async () => {
store.save("ollama", {
kind: "local",
value: { baseUrl: "https://example.com/v1" },
});
let thrown: unknown;
try {
await resolver.resolve("ollama");
} catch (error) {
thrown = error;
}
expect(thrown).toBeInstanceOf(CredentialStoreReadError);
expect(thrown).toMatchObject({
kind: "upstream",
code: "CREDENTIAL_INVALID",
connectorId: "ollama",
context: { connectorId: "ollama" },
});
});
});
-152
View File
@@ -1,152 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { CredentialStore, CredentialStoreReadError } from "../../src/main/credentials/credential-store";
beforeAll(() => {
// SecretVault throws unless safeStorage is available or this gate is set.
// safeStorage is unavailable inside vitest, so we use the test-only path.
process.env.AGENTCOMPANY_ALLOW_INSECURE_VAULT = "true";
});
describe("CredentialStore", () => {
let tempDir: string;
let db: AppDatabase;
let store: CredentialStore;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-credential-store-"));
db = new AppDatabase(tempDir);
db.init();
store = new CredentialStore({ db });
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("round-trips an api_key literal credential", () => {
const summary = store.save("anthropic", { kind: "api_key", value: { source: "literal", value: "sk-ant-test1234567890abcd" } });
expect(summary.kind).toBe("api_key");
expect(summary.status).toBe("active");
expect(summary.displayHint).toContain("...");
expect(summary.displayHint).not.toContain("test1234567890");
const decrypted = store.read("anthropic");
expect(decrypted).not.toBeNull();
expect(decrypted!.kind).toBe("api_key");
if (decrypted!.kind === "api_key") {
expect(decrypted!.value.source).toBe("literal");
expect(decrypted!.value.value).toBe("sk-ant-test1234567890abcd");
}
});
it("round-trips an aws credential with bearer mode", () => {
store.save("amazon-bedrock", {
kind: "aws",
value: {
authMode: "bearer",
region: "us-east-2",
bearerToken: "bedrock-bearer-xyz",
},
});
const decrypted = store.read("amazon-bedrock");
expect(decrypted?.kind).toBe("aws");
if (decrypted?.kind === "aws") {
expect(decrypted.value.authMode).toBe("bearer");
expect(decrypted.value.region).toBe("us-east-2");
expect(decrypted.value.bearerToken).toBe("bedrock-bearer-xyz");
}
});
it("round-trips an oauth credential through the read schema", () => {
store.save("claude-pro", {
kind: "oauth",
value: {
refresh: "refresh-token",
access: "access-token",
expires: Date.now() + 60_000,
domain: "anthropic.com",
},
});
const decrypted = store.read("claude-pro");
expect(decrypted?.kind).toBe("oauth");
if (decrypted?.kind === "oauth") {
expect(decrypted.value.refresh).toBe("refresh-token");
expect(decrypted.value.access).toBe("access-token");
expect(decrypted.value.domain).toBe("anthropic.com");
}
});
it("rejects stored credentials whose decrypted payload does not match the row kind", () => {
const encrypted = db.vault.encrypt(JSON.stringify({ source: "literal", value: "sk-test-abcdefghij" }));
db.core.credentials.upsert({
connectorId: "amazon-bedrock",
kind: "aws",
backend: encrypted.backend,
ciphertext: encrypted.payload,
displayHint: "bad",
status: "active",
});
let thrown: unknown;
try {
store.read("amazon-bedrock");
} catch (error) {
thrown = error;
}
expect(thrown).toBeInstanceOf(CredentialStoreReadError);
expect(thrown).toMatchObject({
code: "CREDENTIAL_INVALID",
context: { connectorId: "amazon-bedrock" },
});
expect(thrown).toHaveProperty("message", expect.stringContaining("failed schema validation"));
});
it("rejects stored credentials whose ciphertext is not valid JSON", () => {
const encrypted = db.vault.encrypt("{not-json");
db.core.credentials.upsert({
connectorId: "anthropic",
kind: "api_key",
backend: encrypted.backend,
ciphertext: encrypted.payload,
displayHint: "bad",
status: "active",
});
let thrown: unknown;
try {
store.read("anthropic");
} catch (error) {
thrown = error;
}
expect(thrown).toBeInstanceOf(CredentialStoreReadError);
expect(thrown).toMatchObject({
code: "CREDENTIAL_INVALID",
context: { connectorId: "anthropic" },
});
expect(thrown).toHaveProperty("message", "Stored credential payload is not valid JSON.");
});
it("removes a credential", () => {
store.save("openai", { kind: "api_key", value: { source: "literal", value: "sk-test-abcdefghij" } });
expect(store.list().some((row) => row.connectorId === "openai")).toBe(true);
store.remove("openai");
expect(store.list().some((row) => row.connectorId === "openai")).toBe(false);
expect(store.read("openai")).toBeNull();
});
it("setStatus updates the status row without losing ciphertext", () => {
store.save("groq", { kind: "api_key", value: { source: "env", value: "GROQ_API_KEY" } });
store.setStatus("groq", "expired", "refresh failed");
const row = store.list().find((entry) => entry.connectorId === "groq");
expect(row?.status).toBe("expired");
expect(row?.lastError).toBe("refresh failed");
});
});
-219
View File
@@ -1,219 +0,0 @@
import { describe, expect, it } from "vitest";
import {
apiKeyValueSchema,
awsCredentialSchema,
azureCredentialSchema,
credentialPayloadSchema,
gcpAdcCredentialSchema,
isAwsBedrockEndpointUrl,
isLocalConnectorBaseUrl,
localCredentialSchema,
removeCredentialInputSchema,
saveCredentialInputSchema,
testCredentialInputSchema,
} from "../../src/shared/contracts/schemas-credentials";
import {
oauthCancelInputSchema,
oauthManualCodeResponseSchema,
oauthPromptResponseSchema,
oauthStartInputSchema,
} from "../../src/shared/contracts/schemas-oauth";
/** Verifies the IPC payload schemas reject malformed input and accept valid
* shapes. These are the only contract between renderer ↔ main for the new
* credential / OAuth channels — incorrect validation would let bad data
* reach pi-mono primitives. */
describe("credentials Zod schemas", () => {
it("apiKeyValueSchema rejects empty value", () => {
const result = apiKeyValueSchema.safeParse({ source: "literal", value: "" });
expect(result.success).toBe(false);
});
it("apiKeyValueSchema accepts both source kinds", () => {
expect(apiKeyValueSchema.safeParse({ source: "literal", value: "sk-x" }).success).toBe(true);
expect(apiKeyValueSchema.safeParse({ source: "env", value: "MY_KEY" }).success).toBe(true);
});
it("apiKeyValueSchema rejects unknown source", () => {
const result = apiKeyValueSchema.safeParse({ source: "shell", value: "$(echo x)" });
expect(result.success).toBe(false);
});
it("awsCredentialSchema requires authMode", () => {
expect(awsCredentialSchema.safeParse({ region: "us-east-1" }).success).toBe(false);
expect(awsCredentialSchema.safeParse({ authMode: "profile", region: "us-east-1", awsProfile: "default" }).success).toBe(true);
});
it("awsCredentialSchema accepts all three auth modes", () => {
for (const authMode of ["profile", "iam", "bearer"] as const) {
const result = awsCredentialSchema.safeParse({ authMode, region: "us-east-1" });
expect(result.success).toBe(true);
}
});
it("awsCredentialSchema accepts AWS Bedrock and private HTTPS endpointUrl values", () => {
for (const endpointUrl of [
"https://bedrock-runtime.us-east-1.amazonaws.com",
"https://bedrock-runtime.us-east-1.amazonaws.com/",
"https://bedrock-runtime-fips.us-gov-west-1.amazonaws.com",
"https://bedrock-runtime.cn-north-1.amazonaws.com.cn",
"https://vpce-123abc.bedrock-runtime.us-east-1.vpce.amazonaws.com",
"https://vpce-123abc-us-east-1a.bedrock-runtime.us-east-1.vpce.amazonaws.com",
"https://10.0.0.20",
"https://192.168.1.20",
"https://[fd12::1]",
]) {
const result = awsCredentialSchema.safeParse({ authMode: "profile", region: "us-east-1", awsProfile: "default", endpointUrl });
expect(result.success).toBe(true);
expect(isAwsBedrockEndpointUrl(endpointUrl)).toBe(true);
}
});
it("awsCredentialSchema rejects unsafe endpointUrl values", () => {
for (const endpointUrl of [
"http://bedrock-runtime.us-east-1.amazonaws.com",
"https://api.openai.com/v1",
"https://bedrock-runtime.example.com",
"https://user:pass@bedrock-runtime.us-east-1.amazonaws.com",
"https://bedrock-runtime.us-east-1.amazonaws.com/model/test",
"https://bedrock-runtime.us-east-1.amazonaws.com?proxy=1",
"ftp://bedrock-runtime.us-east-1.amazonaws.com",
"https://bedrock-runtime.us-east-1.amazonaws.com?",
"https://bedrock-runtime.us-east-1.amazonaws.com#",
"https://bedrock-runtime.us-east-1.amazonaws.com/%2e%2e",
"https://bedrock-runtime.us-east-1.amazonaws.com/a/..",
"https://bedrock-runtime.us-east-1.vpce.amazonaws.com",
"https://foo.bar.bedrock-runtime.us-east-1.vpce.amazonaws.com",
]) {
const result = awsCredentialSchema.safeParse({ authMode: "profile", region: "us-east-1", awsProfile: "default", endpointUrl });
expect(result.success).toBe(false);
expect(isAwsBedrockEndpointUrl(endpointUrl)).toBe(false);
}
});
it("azureCredentialSchema requires apiKey", () => {
expect(azureCredentialSchema.safeParse({}).success).toBe(false);
expect(azureCredentialSchema.safeParse({ apiKey: "azure-secret" }).success).toBe(true);
});
it("gcpAdcCredentialSchema requires project + location", () => {
expect(gcpAdcCredentialSchema.safeParse({ project: "" }).success).toBe(false);
expect(gcpAdcCredentialSchema.safeParse({ project: "p", location: "us-central1" }).success).toBe(true);
});
it("localCredentialSchema accepts loopback and private-network baseUrl values", () => {
for (const baseUrl of [
"http://localhost:11434/v1",
"http://127.0.0.1:11434/v1",
"http://10.0.0.20:11434/v1",
"https://172.16.0.10:11434/v1",
"http://172.31.255.255:11434/v1",
"http://192.168.1.20:11434/v1",
"http://169.254.1.20:11434/v1",
"http://[::1]:11434/v1",
"http://[fc00::1]:11434/v1",
"http://[fd12::1]:11434/v1",
"http://[fe80::1]:11434/v1",
]) {
expect(localCredentialSchema.safeParse({ baseUrl }).success).toBe(true);
expect(isLocalConnectorBaseUrl(baseUrl)).toBe(true);
}
});
it("localCredentialSchema rejects remote OpenAI-compatible endpoints", () => {
for (const baseUrl of [
"https://api.openai.com/v1",
"https://example.com/v1",
"http://172.32.0.1:11434/v1",
"http://100.64.0.1:11434/v1",
"ftp://localhost:11434/v1",
"http://user:pass@127.0.0.1:11434/v1",
]) {
expect(localCredentialSchema.safeParse({ baseUrl }).success).toBe(false);
expect(isLocalConnectorBaseUrl(baseUrl)).toBe(false);
}
});
it("credentialPayloadSchema discriminates on kind", () => {
expect(
credentialPayloadSchema.safeParse({
kind: "api_key",
value: { source: "literal", value: "sk-x" },
}).success,
).toBe(true);
expect(
credentialPayloadSchema.safeParse({
kind: "azure",
value: { source: "literal", value: "wrong-shape" },
}).success,
).toBe(false);
});
it("saveCredentialInputSchema requires connectorId + payload", () => {
expect(
saveCredentialInputSchema.safeParse({
connectorId: "anthropic",
payload: { kind: "api_key", value: { source: "literal", value: "sk-x" } },
}).success,
).toBe(true);
expect(saveCredentialInputSchema.safeParse({ connectorId: "" }).success).toBe(false);
expect(saveCredentialInputSchema.safeParse({ payload: {} }).success).toBe(false);
});
it("removeCredentialInputSchema rejects empty connectorId", () => {
expect(removeCredentialInputSchema.safeParse({ connectorId: "openai" }).success).toBe(true);
expect(removeCredentialInputSchema.safeParse({ connectorId: "" }).success).toBe(false);
expect(removeCredentialInputSchema.safeParse({}).success).toBe(false);
});
it("testCredentialInputSchema rejects empty connectorId", () => {
expect(testCredentialInputSchema.safeParse({ connectorId: "openai" }).success).toBe(true);
expect(testCredentialInputSchema.safeParse({ connectorId: "" }).success).toBe(false);
});
});
describe("oauth Zod schemas", () => {
it("oauthStartInputSchema rejects empty connectorId", () => {
expect(oauthStartInputSchema.safeParse({ connectorId: "claude-pro" }).success).toBe(true);
expect(oauthStartInputSchema.safeParse({ connectorId: "" }).success).toBe(false);
});
it("oauthCancelInputSchema rejects missing connectorId", () => {
expect(oauthCancelInputSchema.safeParse({ connectorId: "claude-pro" }).success).toBe(true);
expect(oauthCancelInputSchema.safeParse({}).success).toBe(false);
});
it("oauthPromptResponseSchema requires connectorId, requestId and a (possibly empty) value", () => {
expect(
oauthPromptResponseSchema.safeParse({
connectorId: "github-copilot",
requestId: "req-1",
value: "github.com",
}).success,
).toBe(true);
expect(
oauthPromptResponseSchema.safeParse({ connectorId: "github-copilot", requestId: "" }).success,
).toBe(false);
expect(
oauthPromptResponseSchema.safeParse({ connectorId: "github-copilot", value: "x" }).success,
).toBe(false);
});
it("oauthManualCodeResponseSchema requires non-empty code", () => {
expect(
oauthManualCodeResponseSchema.safeParse({
connectorId: "anthropic",
requestId: "req-1",
code: "abcd",
}).success,
).toBe(true);
expect(
oauthManualCodeResponseSchema.safeParse({
connectorId: "anthropic",
requestId: "req-1",
code: "",
}).success,
).toBe(false);
});
});
-136
View File
@@ -1,136 +0,0 @@
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
interface D1MigrationModule {
applyD1Migrations(input: {
landingDir: string;
run: (
command: string,
args: string[],
options: { cwd: string; input: string },
) => Promise<unknown>;
}): Promise<{ databaseName: string | null; applied: number }>;
parseD1DatabaseName(source: string): string;
validateD1MigrationFileName(fileName: string): string;
}
const tempDirs: string[] = [];
async function loadModule(): Promise<D1MigrationModule> {
const moduleUrl = pathToFileURL(
join(process.cwd(), "scripts/release/lib/d1-migrations.mjs"),
).href;
return await import(moduleUrl) as D1MigrationModule;
}
async function createLandingFixture() {
const landingDir = await mkdtemp(join(tmpdir(), "agentcompany-d1-test-"));
tempDirs.push(landingDir);
await mkdir(join(landingDir, "migrations"), { recursive: true });
await writeFile(
join(landingDir, "wrangler.toml"),
[
"name = \"agentcompany\"",
"",
"[[d1_databases]]",
"binding = \"DOWNLOAD_COUNTER_DB\"",
"database_name = \"agentcompany-landing-downloads\"",
"database_id = \"dfa3f0f3-009b-4436-b519-5b50ae6b1cf9\"",
"",
].join("\n"),
"utf8",
);
return landingDir;
}
afterEach(async () => {
await Promise.all(
tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })),
);
});
describe("Cloudflare D1 migration release helper", () => {
it("parses the D1 database name from the wrangler D1 table", async () => {
const { parseD1DatabaseName } = await loadModule();
expect(parseD1DatabaseName([
"[vars]",
"DOWNLOAD_COUNTER_SALT = \"salt\"",
"",
"[[d1_databases]]",
"binding = \"DOWNLOAD_COUNTER_DB\"",
"database_name = \"agentcompany-landing-downloads\" # production",
].join("\n"))).toBe("agentcompany-landing-downloads");
});
it("rejects shell-shaped database names", async () => {
const { parseD1DatabaseName } = await loadModule();
expect(() => parseD1DatabaseName([
"[[d1_databases]]",
"database_name = \"prod; rm -rf /\"",
].join("\n"))).toThrow("Invalid Cloudflare D1 database_name");
});
it("rejects path-shaped migration file names", async () => {
const { validateD1MigrationFileName } = await loadModule();
expect(() => validateD1MigrationFileName("../0001.sql")).toThrow(
"Invalid D1 migration file name",
);
expect(() => validateD1MigrationFileName("0001;touch.sql")).toThrow(
"Invalid D1 migration file name",
);
});
it("executes wrangler through argv and confirms through stdin", async () => {
const { applyD1Migrations } = await loadModule();
const landingDir = await createLandingFixture();
await writeFile(
join(landingDir, "migrations", "0001_download_counter.sql"),
"SELECT 1;",
"utf8",
);
await writeFile(
join(landingDir, "migrations", "notes.txt"),
"ignored",
"utf8",
);
const calls: Array<{
command: string;
args: string[];
options: { cwd: string; input: string };
}> = [];
const result = await applyD1Migrations({
landingDir,
run: async (command, args, options) => {
calls.push({ command, args, options });
return {};
},
});
expect(result).toEqual({
databaseName: "agentcompany-landing-downloads",
applied: 1,
});
expect(calls).toEqual([
{
command: "npx",
args: [
"wrangler",
"d1",
"execute",
"agentcompany-landing-downloads",
"--remote",
"--file",
"migrations/0001_download_counter.sql",
],
options: { cwd: landingDir, input: "y\n" },
},
]);
});
});
-267
View File
@@ -1,267 +0,0 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import type { RunRecord, CostRecord, AgentRecord } from "../../src/shared/types";
import {
aggregateRunsByDay,
aggregateCostsByDay,
aggregateAgentWorkload,
formatShortDate,
} from "../../src/renderer/components/dashboard/charts/chart-utils";
import type {
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
function makeRun(overrides: Partial<RunRecord> = {}): RunRecord {
return {
id: "run-1",
companyId: "co-1",
taskId: "task-1",
agentId: "agent-1",
workspaceId: null,
connectorId: "claude-pro",
status: "succeeded",
summary: "",
errorMessage: null,
exitCode: null,
signal: null,
model: null,
costUsd: null,
unattributedCost: false,
usageStatus: "ok",
startedAt: null,
finishedAt: null,
logPath: null,
createdAt: "2026-03-20T10:00:00Z",
updatedAt: "2026-03-20T10:00:00Z",
...overrides,
};
}
// Helper to create a minimal CostRecord for testing
function makeCost(overrides: Partial<CostRecord> = {}): CostRecord {
return {
id: "cost-1",
companyId: "co-1",
runId: "run-1",
taskId: null,
agentId: null,
connectorId: "claude-pro",
amountUsd: 0.5,
unattributed: false,
createdAt: "2026-03-20T10:00:00Z",
...overrides,
};
}
// Helper to create a minimal AgentRecord for testing
function makeAgent(overrides: Partial<AgentRecord> = {}): AgentRecord {
return {
id: "agent-1",
companyId: "co-1",
name: "Alice",
role: "engineer",
title: "Software Engineer",
roleLevel: "ic",
department: "engineering",
status: "active",
reportsTo: null,
connectorId: "claude-pro",
workspaceId: null,
model: null,
capabilities: "[]",
budgetMonthlyUsd: 100,
spentMonthlyUsd: 0,
metadataJson: "{}",
heartbeatEnabled: false,
heartbeatIntervalSec: 300,
lastHeartbeatAt: null,
createdAt: "2026-03-01T00:00:00Z",
updatedAt: "2026-03-01T00:00:00Z",
...overrides,
};
}
// Fix "today" to a known date so bucket generation is deterministic
const FIXED_NOW = new Date("2026-03-27T12:00:00Z");
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(FIXED_NOW);
});
afterEach(() => {
vi.useRealTimers();
});
describe("aggregateRunsByDay", () => {
it("returns 14 entries all zeroed when given empty runs array", () => {
const result = aggregateRunsByDay([], 14);
expect(result).toHaveLength(14);
for (const entry of result) {
expect(entry.total).toBe(0);
expect(entry.succeeded).toBe(0);
expect(entry.failed).toBe(0);
}
});
it("counts 3 runs on same day: 2 succeeded, 1 failed", () => {
const runs = [
makeRun({ id: "r1", status: "succeeded", createdAt: "2026-03-25T08:00:00Z" }),
makeRun({ id: "r2", status: "succeeded", createdAt: "2026-03-25T15:00:00Z" }),
makeRun({ id: "r3", status: "failed", createdAt: "2026-03-25T20:00:00Z" }),
];
const result = aggregateRunsByDay(runs, 14);
const day25 = result.find((d) => d.date === "2026-03-25");
expect(day25).toBeDefined();
expect(day25!.total).toBe(3);
expect(day25!.succeeded).toBe(2);
expect(day25!.failed).toBe(1);
});
it("returns dates sorted ascending (oldest first)", () => {
const result = aggregateRunsByDay([], 14);
for (let i = 1; i < result.length; i++) {
expect(result[i].date > result[i - 1].date).toBe(true);
}
});
it("excludes runs outside the 14-day window", () => {
const oldRun = makeRun({
id: "old",
status: "succeeded",
createdAt: "2026-03-01T10:00:00Z", // well before 14 days ago
});
const result = aggregateRunsByDay([oldRun], 14);
const total = result.reduce((sum, d) => sum + d.total, 0);
expect(total).toBe(0);
});
it("counts timed_out and interrupted as failed", () => {
const runs = [
makeRun({ id: "r1", status: "timed_out", createdAt: "2026-03-25T10:00:00Z" }),
makeRun({ id: "r2", status: "interrupted", createdAt: "2026-03-25T11:00:00Z" }),
];
const result = aggregateRunsByDay(runs, 14);
const day25 = result.find((d) => d.date === "2026-03-25");
expect(day25!.total).toBe(2);
expect(day25!.failed).toBe(2);
expect(day25!.succeeded).toBe(0);
});
});
describe("aggregateCostsByDay", () => {
it("returns 14 entries all zeroed when given empty costs array", () => {
const result = aggregateCostsByDay([], 14);
expect(result).toHaveLength(14);
for (const entry of result) {
expect(entry.total).toBe(0);
}
});
it("sums costs per day correctly", () => {
const costs = [
makeCost({ id: "c1", amountUsd: 1.5, createdAt: "2026-03-25T08:00:00Z" }),
makeCost({ id: "c2", amountUsd: 2.25, createdAt: "2026-03-25T12:00:00Z" }),
makeCost({ id: "c3", amountUsd: 0.75, createdAt: "2026-03-26T10:00:00Z" }),
];
const result = aggregateCostsByDay(costs, 14);
const day25 = result.find((d) => d.date === "2026-03-25");
const day26 = result.find((d) => d.date === "2026-03-26");
expect(day25!.total).toBeCloseTo(3.75);
expect(day26!.total).toBeCloseTo(0.75);
});
it("handles null amountUsd as 0", () => {
const costs = [
makeCost({ id: "c1", amountUsd: null, createdAt: "2026-03-25T10:00:00Z" }),
makeCost({ id: "c2", amountUsd: 1.0, createdAt: "2026-03-25T11:00:00Z" }),
];
const result = aggregateCostsByDay(costs, 14);
const day25 = result.find((d) => d.date === "2026-03-25");
expect(day25!.total).toBeCloseTo(1.0);
});
});
describe("aggregateAgentWorkload", () => {
it("returns entries sorted by run count descending for 3 agents", () => {
const agents = [
makeAgent({ id: "a1", name: "Alice" }),
makeAgent({ id: "a2", name: "Bob" }),
makeAgent({ id: "a3", name: "Charlie" }),
];
const runs = [
makeRun({ id: "r1", agentId: "a1" }),
makeRun({ id: "r2", agentId: "a1" }),
makeRun({ id: "r3", agentId: "a1" }),
makeRun({ id: "r4", agentId: "a2" }),
makeRun({ id: "r5", agentId: "a2" }),
makeRun({ id: "r6", agentId: "a3" }),
];
const result = aggregateAgentWorkload(runs, agents);
expect(result).toHaveLength(3);
expect(result[0].name).toBe("Alice");
expect(result[0].runs).toBe(3);
expect(result[1].name).toBe("Bob");
expect(result[1].runs).toBe(2);
expect(result[2].name).toBe("Charlie");
expect(result[2].runs).toBe(1);
});
it("aggregates into top 8 + Others when 10 agents exist", () => {
const agents: AgentRecord[] = [];
const runs: RunRecord[] = [];
for (let i = 1; i <= 10; i++) {
agents.push(makeAgent({ id: `a${i}`, name: `Agent${i}` }));
// Each agent gets (11 - i) runs so agent1 has most
for (let j = 0; j < 11 - i; j++) {
runs.push(makeRun({ id: `r${i}-${j}`, agentId: `a${i}` }));
}
}
const result = aggregateAgentWorkload(runs, agents);
expect(result).toHaveLength(9); // 8 top + Others
expect(result[result.length - 1].name).toBe("Others");
// Others should aggregate agent9 (2 runs) + agent10 (1 run) = 3 runs
expect(result[result.length - 1].runs).toBe(3);
});
it("resolves agentId to agent name", () => {
const agents = [makeAgent({ id: "a1", name: "DeepResearcher" })];
const runs = [makeRun({ id: "r1", agentId: "a1" })];
const result = aggregateAgentWorkload(runs, agents);
expect(result[0].name).toBe("DeepResear...");
});
it("truncates agent names longer than 10 characters with ellipsis", () => {
const agents = [makeAgent({ id: "a1", name: "VeryLongAgentName" })];
const runs = [makeRun({ id: "r1", agentId: "a1" })];
const result = aggregateAgentWorkload(runs, agents);
expect(result[0].name).toBe("VeryLongAg...");
expect(result[0].name.length).toBe(13); // 10 chars + "..."
});
it("does not truncate short names", () => {
const agents = [makeAgent({ id: "a1", name: "ShortName" })];
const runs = [makeRun({ id: "r1", agentId: "a1" })];
const result = aggregateAgentWorkload(runs, agents);
expect(result[0].name).toBe("ShortName");
});
});
describe("formatShortDate", () => {
it('formats "2026-03-15" as "Mar 15"', () => {
const result = formatShortDate("2026-03-15");
expect(result).toBe("Mar 15");
});
it('formats "2026-01-01" as "Jan 1"', () => {
const result = formatShortDate("2026-01-01");
expect(result).toBe("Jan 1");
});
it('formats "2026-12-25" as "Dec 25"', () => {
const result = formatShortDate("2026-12-25");
expect(result).toBe("Dec 25");
});
});
-78
View File
@@ -1,78 +0,0 @@
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
const FLAT_MODULE_KEYS = [
"threads",
"company",
"agents",
"tasks",
"messages",
"runs",
"snap",
"settings",
"connectorsCrud",
"workspacesMod",
"approvalsMod",
"social",
"activityMod",
"skills",
"budget",
"entityCrud",
"documentsMod",
"automationMod",
"knowledgeMod",
"wakeup",
"backupMod",
"sessionEntries",
"inboxProjections",
"documentLifecycleEvents",
"credentialsTable",
"modelCapabilities",
];
describe("AppDatabase module groups", () => {
it("exposes domain modules through grouped namespaces", () => {
const dir = mkdtempSync(join(tmpdir(), "agentcompany-db-groups-"));
const db = new AppDatabase(dir);
try {
expect(Object.keys(db.core).sort()).toEqual([
"activity",
"agents",
"approvals",
"backup",
"budget",
"company",
"connectors",
"credentials",
"documents",
"entity",
"knowledge",
"messages",
"modelCapabilities",
"runs",
"settings",
"skills",
"social",
"tasks",
"threads",
"workspaces",
]);
expect(Object.keys(db.automation).sort()).toEqual(["rules", "wakeup"]);
expect(Object.keys(db.projection).sort()).toEqual([
"documentLifecycleEvents",
"inbox",
"sessionEntries",
"snapshot",
]);
for (const key of FLAT_MODULE_KEYS) {
expect(Object.prototype.hasOwnProperty.call(db, key)).toBe(false);
}
} finally {
db.close();
rmSync(dir, { recursive: true, force: true });
}
});
});
-62
View File
@@ -1,62 +0,0 @@
import { join } from "node:path";
import { tmpdir } from "node:os";
import { mkdtemp, rm } from "node:fs/promises";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
beforeAll(() => {
process.env.AGENTCOMPANY_ALLOW_INSECURE_VAULT = "true";
});
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase } from "../../src/main/database";
describe("AppDatabase transactions", () => {
let tempDir: string;
let db: AppDatabase;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-db-tx-"));
db = new AppDatabase(tempDir);
db.init();
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("rolls back nested transactions to their savepoint", () => {
const outerCompanyId = db.withTransaction(() => {
const companyId = db.core.company.saveCompany({
name: "Nested Transaction Co",
description: "Outer transaction should survive inner rollback.",
status: "active",
});
expect(() => {
db.withTransaction(() => {
db.core.company.saveCompany({
name: "Nested Transaction Rolled Back",
description: "Inner transaction should roll back.",
status: "active",
});
throw new Error("inner write failed");
});
}).toThrow("inner write failed");
return companyId;
});
const companies = db.queryAll<{ id: string; name: string }>("select id, name from companies order by created_at asc");
expect(companies.map((company) => company.id)).toContain(outerCompanyId);
expect(companies.some((company) => company.name === "Nested Transaction Rolled Back")).toBe(false);
});
});
-764
View File
@@ -1,764 +0,0 @@
import { join } from "node:path";
import { tmpdir } from "node:os";
import { mkdtemp, rm } from "node:fs/promises";
import { DatabaseSync } from "node:sqlite";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { approvalTypeEnum } from "../../src/shared/types";
beforeAll(() => {
process.env.AGENTCOMPANY_ALLOW_INSECURE_VAULT = "true";
});
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase } from "../../src/main/database";
import { installBaselineSchema } from "../../src/main/database/migrations/baseline-v56";
import { markBaselineSchema } from "../../src/main/database/migrations/legacy-to-v56";
describe("AppDatabase", () => {
let tempDir: string;
let db: AppDatabase;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-db-"));
db = new AppDatabase(tempDir);
db.init();
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("keeps task runtime ownership in task_leases", () => {
const leaseTables = db.queryAll<{ name: string }>(
"select name from sqlite_master where type = 'table' and name = 'task_leases'",
);
const removedTaskColumns = new Set([
"phase",
"active_run_id",
"checkout_run_id",
"execution_locked_at",
"active_session_id",
]);
const taskColumns = db.queryAll<{ name: string }>("pragma table_info(tasks)")
.filter((column) => removedTaskColumns.has(column.name));
const phaseTriggers = db.queryAll<{ name: string }>(
"select name from sqlite_master where type = 'trigger' and name like 'trg_tasks_phase_from_status_%'",
);
expect(leaseTables).toEqual([{ name: "task_leases" }]);
expect(taskColumns).toEqual([]);
expect(phaseTriggers).toEqual([]);
});
it("persists company-scoped entities and resolves secret env bindings", () => {
const companyId = db.core.company.saveCompany({
name: "Northline Systems",
description: "Operator company",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary Workspace",
localPath: "/tmp/northline",
repoUrl: "https://github.com/example/northline",
repoRef: "main",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Chief Operator",
role: "CEO",
title: "Lead operator",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: "gpt-5",
capabilities: "Directs execution",
budgetMonthlyUsd: 250,
});
const goalId = db.core.entity.saveGoal({
companyId,
title: "Ship the desktop product",
description: "Stabilize the local operator console.",
ownerAgentId: agentId,
status: "active",
});
const projectId = db.core.entity.saveProject({
companyId,
goalId,
name: "Desktop Rebuild",
description: "Main delivery lane",
leadAgentId: agentId,
status: "in_progress",
targetDate: null,
});
const taskId = db.core.tasks.saveTask({
companyId,
projectId,
goalId,
title: "Validate connector health",
description: "Run a local connector check and capture diagnostics.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
db.core.entity.saveSecret({
companyId,
name: "vault-openai",
description: "OpenAI API token",
value: "sk-test-secret",
});
db.core.connectors.saveConnector({
id: "openai-codex",
model: "gpt-5",
envBindingText: "OPENAI_API_KEY=vault-openai",
notes: "Primary coding connector",
});
const snapshot = db.projection.snapshot.listSnapshot();
expect(snapshot.currentCompanyId).toBe(companyId);
expect(snapshot.workspaces[0]?.id).toBe(workspaceId);
expect(snapshot.agents[0]?.id).toBe(agentId);
expect(snapshot.goals[0]?.id).toBe(goalId);
expect(snapshot.projects[0]?.id).toBe(projectId);
expect(snapshot.tasks[0]?.id).toBe(taskId);
expect(db.core.entity.resolveSecretEnv("OPENAI_API_KEY=vault-openai", companyId)).toEqual({
OPENAI_API_KEY: "sk-test-secret",
});
});
it("backs up and restores the profile", async () => {
const companyId = db.core.company.saveCompany({
name: "Backup Test Co",
description: "Backup validation company",
status: "active",
});
const backupDir = await mkdtemp(join(tmpdir(), "agentcompany-backup-"));
await db.core.backup.exportBackup(backupDir);
db.core.company.saveCompany({
id: companyId,
name: "Mutated Company",
description: "Changed after backup",
status: "paused",
});
await db.core.backup.restoreFromBackup(backupDir);
const snapshot = db.projection.snapshot.listSnapshot();
expect(snapshot.companies[0]?.name).toBe("Backup Test Co");
await rm(backupDir, { recursive: true, force: true });
});
it("scopes secret resolution to the owning company", () => {
const alphaCompanyId = db.core.company.saveCompany({
name: "Alpha Company",
description: "Alpha",
status: "active",
});
const betaCompanyId = db.core.company.saveCompany({
name: "Beta Company",
description: "Beta",
status: "active",
});
db.core.entity.saveSecret({
companyId: alphaCompanyId,
name: "shared-secret-name",
description: "Alpha secret",
value: "alpha-secret",
});
expect(db.core.entity.resolveSecretEnv("API_KEY=shared-secret-name", alphaCompanyId)).toEqual({
API_KEY: "alpha-secret",
});
// Beta company has no secret named "shared-secret-name", so resolveSecretEnv
// falls back to using the raw value literally (allows direct env bindings).
expect(db.core.entity.resolveSecretEnv("API_KEY=shared-secret-name", betaCompanyId)).toEqual({
API_KEY: "shared-secret-name",
});
});
it("atomically checks out unclaimed work via CAS with run ID", () => {
const companyId = db.core.company.saveCompany({
name: "Checkout Company",
description: "Atomic checkout validation",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Execution Workspace",
localPath: "/tmp/checkout",
repoUrl: "https://github.com/example/checkout",
repoRef: "main",
isPrimary: true,
});
const leadId = db.core.agents.saveAgent({
companyId,
name: "Lead",
role: "CEO",
title: "Lead",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Leads",
budgetMonthlyUsd: 100,
});
db.core.connectors.saveConnector({
id: "openai-codex",
model: "gpt-5",
envBindingText: "",
notes: "Test connector",
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Claim me",
description: "First agent wins the checkout.",
assigneeAgentId: null,
workspaceId,
priority: "medium",
status: "todo",
});
const { id: runId1 } = db.core.runs.createRun({
companyId,
taskId,
agentId: leadId,
workspaceId,
connectorId: "openai-codex",
});
const { id: runId2 } = db.core.runs.createRun({
companyId,
taskId,
agentId: leadId,
workspaceId,
connectorId: "openai-codex",
});
expect(db.core.tasks.checkoutTask(taskId, runId1)).toBe(true);
expect(db.core.tasks.checkoutTask(taskId, runId2)).toBe(false);
const task = db.core.entity.getTask(taskId);
expect(task.status).toBe("in_progress");
});
it("auto-pauses an agent when a run exhausts the monthly budget", () => {
const companyId = db.core.company.saveCompany({
name: "Budget Company",
description: "Budget hard stop validation",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Budget Workspace",
localPath: "/tmp/budget",
repoUrl: "https://github.com/example/budget",
repoRef: "main",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Budget Owner",
role: "CTO",
title: "Budget Owner",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Ships work",
budgetMonthlyUsd: 10,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Spend budget",
description: "One run should exhaust the allowance.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "openai-codex",
});
// Simulate a per-message cost entry (written by CostWriter in prod).
db.core.runs.recordCostEntry({
runId: run.id,
companyId,
agentId,
taskId,
connectorId: "openai-codex",
amountUsd: 12,
unattributed: false,
});
const outcome = db.core.runs.finishRun({
runId: run.id,
status: "succeeded",
summary: "Budget consumed",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gpt-5",
unattributedCost: false,
});
expect(outcome.budgetHardStopped).toBe(true);
expect(db.core.entity.getAgent(agentId).status).toBe("paused");
expect(db.core.entity.getAgent(agentId).spentMonthlyUsd).toBe(12);
});
it("persists explicit run usage status", () => {
const companyId = db.core.company.saveCompany({
name: "Usage Status Company",
description: "Tracks run usage status",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Usage Workspace",
localPath: "/tmp/usage-status",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Usage Agent",
role: "Engineer",
title: "Engineer",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: "gpt-5",
capabilities: "Run tasks",
budgetMonthlyUsd: 100,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Usage task",
description: "Exercise explicit usage status.",
assigneeAgentId: agentId,
workspaceId,
priority: "medium",
status: "in_progress",
});
const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex" });
db.core.runs.finishRun({
runId: run.id,
status: "failed",
summary: null,
errorMessage: "stream setup failed",
exitCode: null,
signal: null,
model: "gpt-5",
unattributedCost: true,
usageStatus: "error_stop",
});
expect(db.core.runs.getRun(run.id).usageStatus).toBe("error_stop");
expect(db.projection.snapshot.listSnapshot().runs.find((record) => record.id === run.id)?.usageStatus).toBe("error_stop");
});
it("activates pending hires when the corresponding approval is granted", () => {
const companyId = db.core.company.saveCompany({
name: "Hiring Company",
description: "Approval-driven hiring",
status: "active",
});
const managerId = db.core.agents.saveAgent({
companyId,
name: "Manager",
role: "CEO",
title: "Manager",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId: null,
model: null,
capabilities: "Manages",
budgetMonthlyUsd: 100,
});
const pendingHireId = db.core.agents.saveAgent({
companyId,
name: "Pending Hire",
role: "IC",
title: "Pending Hire",
status: "pending_approval",
reportsTo: managerId,
connectorId: "openai-codex",
workspaceId: null,
model: null,
capabilities: "Will execute after approval",
budgetMonthlyUsd: 50,
});
const approvalId = db.core.approvals.requestApproval({
companyId,
relatedTaskId: null,
requestedByAgentId: managerId,
type: "hire_agent",
payloadSummary: "Approve pending hire",
impactSummary: "Activate the pending hire so they can begin receiving assignments.",
payloadJson: JSON.stringify({ agentId: pendingHireId }),
});
db.core.approvals.decideApproval({
approvalId,
state: "approved",
decisionNote: "Approved",
});
expect(db.core.entity.getAgent(pendingHireId).status).toBe("idle");
});
it("detects pending-approval agents via isAgentPendingApproval", () => {
const companyId = db.core.company.saveCompany({
name: "Gate Test Co",
description: "Approval gate validation",
status: "active",
});
const activeAgentId = db.core.agents.saveAgent({
companyId,
name: "Active Agent",
role: "CTO",
title: "Active",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId: null,
model: null,
capabilities: "Ships",
budgetMonthlyUsd: 100,
});
const pendingAgentId = db.core.agents.saveAgent({
companyId,
name: "Pending Agent",
role: "IC",
title: "Pending",
status: "pending_approval",
reportsTo: activeAgentId,
connectorId: "openai-codex",
workspaceId: null,
model: null,
capabilities: "Waiting",
budgetMonthlyUsd: 50,
});
expect(db.core.agents.isAgentPendingApproval(activeAgentId)).toBe(false);
expect(db.core.agents.isAgentPendingApproval(pendingAgentId)).toBe(true);
});
it("installs the v56 baseline and v57 task leases once on a fresh profile", () => {
const migrations = db.queryAll<{ version: number; name: string }>(
"select version, name from schema_migrations order by version",
);
expect(migrations).toEqual([
{ version: 56, name: "baseline_v56" },
{ version: 57, name: "task_leases_runtime_ownership" },
{ version: 58, name: "approval_policy_table" },
{ version: 59, name: "social_company_scope" },
{ version: 60, name: "filesystem_task_workspaces" },
]);
db.close();
db = new AppDatabase(tempDir);
db.init();
const migrationsAfterReinit = db.queryAll<{ version: number; name: string }>(
"select version, name from schema_migrations order by version",
);
expect(migrationsAfterReinit).toEqual([
{ version: 56, name: "baseline_v56" },
{ version: 57, name: "task_leases_runtime_ownership" },
{ version: 58, name: "approval_policy_table" },
{ version: 59, name: "social_company_scope" },
{ version: 60, name: "filesystem_task_workspaces" },
]);
});
it("migrates v56 task ownership columns into task_leases", async () => {
const legacyDir = await mkdtemp(join(tmpdir(), "agentcompany-v56-lease-"));
const rawDb = new DatabaseSync(join(legacyDir, "profile.sqlite"));
const ts = "2026-04-24T00:00:00.000Z";
try {
installBaselineSchema(rawDb);
markBaselineSchema(rawDb);
rawDb
.prepare("insert into companies (id, name, description, status, created_at, updated_at) values (?, ?, ?, ?, ?, ?)")
.run("company-1", "Lease Co", "", "active", ts, ts);
rawDb
.prepare("insert into agents (id, company_id, name, role, title, status, reports_to, connector_id, workspace_id, capabilities, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("agent-1", "company-1", "Worker", "IC", "Worker", "active", null, "openai-codex", null, "Executes", ts, ts);
rawDb
.prepare("insert into tasks (id, company_id, title, description, assignee_agent_id, status, priority, active_run_id, checkout_run_id, execution_locked_at, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("task-1", "company-1", "Lease task", "", "agent-1", "in_progress", "medium", "run-1", "run-1", ts, ts, ts);
rawDb
.prepare("insert into runs (id, company_id, task_id, agent_id, connector_id, status, summary, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("run-1", "company-1", "task-1", "agent-1", "openai-codex", "queued", "", ts, ts);
} finally {
rawDb.close();
}
const legacyAppDb = new AppDatabase(legacyDir);
try {
legacyAppDb.init();
expect(
legacyAppDb.queryAll<{ taskId: string; runId: string; agentId: string }>(
"select task_id as taskId, run_id as runId, agent_id as agentId from task_leases",
),
).toEqual([{ taskId: "task-1", runId: "run-1", agentId: "agent-1" }]);
const columns = legacyAppDb.queryAll<{ name: string }>("pragma table_info(tasks)").map((column) => column.name);
expect(columns).not.toContain("active_run_id");
expect(columns).not.toContain("checkout_run_id");
expect(columns).not.toContain("execution_locked_at");
} finally {
legacyAppDb.close();
await rm(legacyDir, { recursive: true, force: true });
}
});
it("seeds approval policies for every approval type", () => {
const rows = db.queryAll<{
approvalType: string;
scopeType: string;
blocksExecution: number;
requiredMode: string;
autoApprovable: number;
}>(
"select approval_type as approvalType, scope_type as scopeType, blocks_execution as blocksExecution, required_mode as requiredMode, auto_approvable as autoApprovable from approval_policies order by approval_type",
);
expect(rows.map((row) => row.approvalType).sort()).toEqual([...approvalTypeEnum.options].sort());
expect(rows.find((row) => row.approvalType === "plan_review")).toMatchObject({
scopeType: "requester_or_task",
blocksExecution: 1,
requiredMode: "ai_cofounder",
autoApprovable: 0,
});
expect(rows.find((row) => row.approvalType === "dangerous_command")).toMatchObject({
scopeType: "requester_or_task",
blocksExecution: 1,
requiredMode: "any",
});
expect(rows.find((row) => row.approvalType === "hire_agent")).toMatchObject({
scopeType: "related_agent",
blocksExecution: 1,
requiredMode: "any",
autoApprovable: 1,
});
});
it("acquires and releases workspace locks", () => {
const companyId = db.core.company.saveCompany({
name: "Lock Test Co",
description: "Workspace lock test",
status: "active",
});
const wsId = db.core.workspaces.saveWorkspace({
companyId,
name: "Lockable WS",
localPath: "/tmp/lock-test",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
// Acquire lock for run-1
expect(db.core.workspaces.acquireWorkspaceLock(wsId, "run-1")).toBe(true);
let ws = db.core.workspaces.getWorkspace(wsId);
expect(ws?.state).toBe("busy");
// Same run can re-acquire (idempotent)
expect(db.core.workspaces.acquireWorkspaceLock(wsId, "run-1")).toBe(true);
// Different run cannot acquire while run-1 holds the lock
expect(db.core.workspaces.acquireWorkspaceLock(wsId, "run-2")).toBe(false);
// Release lock
db.core.workspaces.releaseWorkspaceLock(wsId, "run-1");
ws = db.core.workspaces.getWorkspace(wsId);
expect(ws?.state).toBe("ready");
// Now run-2 can acquire
expect(db.core.workspaces.acquireWorkspaceLock(wsId, "run-2")).toBe(true);
db.core.workspaces.releaseWorkspaceLock(wsId, "run-2");
});
it("releases all workspace locks on crash recovery", () => {
const companyId = db.core.company.saveCompany({
name: "Recovery Co",
description: "Lock recovery test",
status: "active",
});
const wsId = db.core.workspaces.saveWorkspace({
companyId,
name: "Recovery WS",
localPath: "/tmp/recovery-test",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
db.core.workspaces.acquireWorkspaceLock(wsId, "stale-run");
expect(db.core.workspaces.getWorkspace(wsId)?.state).toBe("busy");
db.core.workspaces.releaseAllWorkspaceLocks();
expect(db.core.workspaces.getWorkspace(wsId)?.state).toBe("ready");
});
it("purges expired run logs based on retention setting", async () => {
const companyId = db.core.company.saveCompany({
name: "Purge Co",
description: "Log purge test",
status: "active",
});
const wsId = db.core.workspaces.saveWorkspace({
companyId,
name: "PurgeWS",
localPath: "/tmp/purge",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Purge Agent",
role: "IC",
title: "Test",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId: wsId,
model: null,
capabilities: "test",
budgetMonthlyUsd: 100,
});
const taskId = db.core.tasks.saveTask({
companyId,
title: "Purge task",
description: "test",
assigneeAgentId: agentId,
workspaceId: wsId,
priority: "medium",
status: "todo",
});
const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId: wsId, connectorId: "openai-codex" });
await db.core.runs.appendRunLog(run.id, "stdout", "test log line\n");
// Finish the run with a very old timestamp
db.core.runs.finishRun({
runId: run.id,
status: "succeeded",
summary: "done",
errorMessage: null,
exitCode: 0,
signal: null,
model: null,
unattributedCost: true,
});
// Log should exist before purge
expect(db.core.runs.readRunLog(run.id)).toContain("test log line");
// Set retention to 1 day and manually backdate the run's finished_at
db.core.settings.updateSettings({ logRetentionDays: 1 });
// Backdate finished_at to 10 days ago
const tenDaysAgo = new Date(Date.now() - 10 * 86_400_000).toISOString();
// Direct SQL update for test only
(db as unknown as { db: { prepare: (sql: string) => { run: (...args: unknown[]) => void } } }).db
.prepare("update runs set finished_at = ? where id = ?")
.run(tenDaysAgo, run.id);
const purged = db.core.settings.purgeExpiredRunLogs();
expect(purged).toBe(1);
// Log should be gone after purge
expect(db.core.runs.readRunLog(run.id)).toBe("");
});
it("populates related_agent_id on hire approvals", () => {
const companyId = db.core.company.saveCompany({
name: "Agent Approval Co",
description: "test",
status: "active",
});
const managerId = db.core.agents.saveAgent({
companyId,
name: "Manager",
role: "CEO",
title: "CEO",
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId: null,
model: null,
capabilities: "manage",
budgetMonthlyUsd: 100,
});
const hireId = db.core.agents.saveAgent({
companyId,
name: "New Hire",
role: "IC",
title: "IC",
status: "pending_approval",
reportsTo: managerId,
connectorId: "openai-codex",
workspaceId: null,
model: null,
capabilities: "code",
budgetMonthlyUsd: 50,
});
const approvalId = db.core.approvals.requestApproval({
companyId,
requestedByAgentId: managerId,
type: "hire_agent",
payloadSummary: "Hire New Hire",
impactSummary: "test",
payloadJson: JSON.stringify({ agentId: hireId }),
});
const approval = db.core.approvals.getApprovalRecord(approvalId);
// related_agent_id should be populated from payloadJson extraction
expect(approval.relatedAgentId).toBe(hireId);
// Explicit relatedAgentId param should also work
const approvalId2 = db.core.approvals.requestApproval({
companyId,
requestedByAgentId: managerId,
relatedAgentId: hireId,
type: "hire_agent",
payloadSummary: "Hire New Hire v2",
impactSummary: "test",
});
const approval2 = db.core.approvals.getApprovalRecord(approvalId2);
expect(approval2.relatedAgentId).toBe(hireId);
});
});
-43
View File
@@ -1,43 +0,0 @@
import { beforeEach, describe, expect, it } from "vitest";
import {
incrementDeprecationCall,
readDeprecationCounters,
resetDeprecationCounters,
} from "../../src/main/api-server/deprecation-counter";
/**
* C4-followup #3: the in-process deprecation counter backs the
* "has legacy traffic hit zero?" decision. Keep these tests tight —
* the module has one job.
*/
describe("deprecation counter", () => {
beforeEach(() => {
resetDeprecationCounters();
});
it("increments and reads back the count for a single route", () => {
incrementDeprecationCall("POST /legacy/hire");
incrementDeprecationCall("POST /legacy/hire");
expect(readDeprecationCounters()).toEqual({ "POST /legacy/hire": 2 });
});
it("tracks multiple route labels independently", () => {
incrementDeprecationCall("POST /legacy/hire");
incrementDeprecationCall("POST /legacy/approvals");
incrementDeprecationCall("POST /legacy/hire");
expect(readDeprecationCounters()).toEqual({
"POST /legacy/hire": 2,
"POST /legacy/approvals": 1,
});
});
it("resets all counters back to empty", () => {
incrementDeprecationCall("POST /legacy/hire");
resetDeprecationCounters();
expect(readDeprecationCounters()).toEqual({});
});
it("throws on an empty routeLabel instead of silently recording", () => {
expect(() => incrementDeprecationCall("")).toThrow(/non-empty routeLabel/);
});
});
-108
View File
@@ -1,108 +0,0 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import type { DesktopResult } from "@shared/contracts";
import { describe, expect, it } from "vitest";
import {
createAppErrorNotice,
describeDesktopError,
DesktopError,
formatDesktopResultError,
formatDesktopResultNotice,
normalizeAppErrorNotice,
unwrap,
} from "../../src/renderer/lib/desktop";
describe("renderer desktop error notices", () => {
it("keeps DesktopResult error codes when unwrap throws", () => {
const result: DesktopResult<string> = {
ok: false,
error: {
code: "CONNECTOR_MODEL_NOT_ALLOWED",
message: "The assigned agent model is disabled.",
},
};
expect(() => unwrap(result)).toThrow(DesktopError);
try {
unwrap(result);
throw new Error("unwrap should have thrown");
} catch (error) {
expect(error).toBeInstanceOf(DesktopError);
expect((error as DesktopError).code).toBe("CONNECTOR_MODEL_NOT_ALLOWED");
expect((error as DesktopError).message).toBe("The assigned agent model is disabled.");
}
});
it("turns IPC error envelopes into actionable notices", () => {
const notice = formatDesktopResultError({
code: "CONNECTOR_MODEL_NOT_ALLOWED",
message: "The assigned agent model is disabled or invalid for its connector.",
});
expect(notice).toEqual({
code: "CONNECTOR_MODEL_NOT_ALLOWED",
message: "The assigned agent model is disabled or invalid for its connector.",
actionHint: "Open Connectors, enable an allowed model, then retry the run.",
});
});
it("keeps checkout races actionable for retry", () => {
const notice = formatDesktopResultNotice({
code: "CHECKOUT_RACE_LOST",
message: "Another run claimed this task first.",
});
expect(notice).toEqual({
code: "CHECKOUT_RACE_LOST",
message: "Another run claimed this task first.",
actionHint: "Refresh tasks, review the current assignee, then try again.",
});
});
it("suppresses shutdown noise from DesktopResult notices", () => {
expect(formatDesktopResultNotice({
code: "APP_SHUTTING_DOWN",
message: "The app is shutting down.",
})).toBeNull();
});
it("keeps App cancelRun on the DesktopResult envelope path", () => {
const source = readFileSync(join(process.cwd(), "src/renderer/App.tsx"), "utf8");
expect(source).not.toContain("unwrap(await window.agentCompany.cancelRun");
expect(source).toContain("formatDesktopResultNotice(result.error)");
});
it("describes thrown DesktopError instances with their original code", () => {
const notice = describeDesktopError(new DesktopError("TASK_UNASSIGNED", "Assign an agent before starting a run."));
expect(notice).toEqual({
code: "TASK_UNASSIGNED",
message: "Assign an agent before starting a run.",
actionHint: "Assign an agent to the task, then start the run.",
});
});
it("normalizes local string errors into action-required notices", () => {
expect(normalizeAppErrorNotice("Select a company before starting a run.")).toEqual({
code: "ACTION_REQUIRED",
message: "Select a company before starting a run.",
actionHint: "Complete the required selection, then retry the action.",
});
});
it("preserves explicit local action hints", () => {
expect(
createAppErrorNotice(
"Select a company before starting a run.",
"COMPANY_REQUIRED",
"Select a company in the sidebar, then start the run.",
),
).toEqual({
code: "COMPANY_REQUIRED",
message: "Select a company before starting a run.",
actionHint: "Select a company in the sidebar, then start the run.",
});
});
});
+752
View File
@@ -0,0 +1,752 @@
import { mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { createHash, randomUUID } from "node:crypto";
import { DatabaseSync } from "node:sqlite";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { FileService } from "../../src/main/services/file-service";
import { OrganizationService } from "../../src/main/services/organization-service";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { SkillService } from "../../src/main/services/skill-service";
import { ChatService } from "../../src/main/services/chat-service";
import { MigrationService } from "../../src/main/services/migration-service";
import { OnboardingService } from "../../src/main/services/onboarding-service";
import { RISK_LEVEL, TASK_STATUS } from "../../src/shared/domain";
import { createId } from "../../src/shared/ids";
function createConnection(): DatabaseConnection {
return new DatabaseConnection(join(tmpdir(), `agentcompany-new-${randomUUID()}.db`));
}
describe("domain services", () => {
test("records every applied schema migration version", () => {
const connection = createConnection();
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45]);
connection.close();
});
test("creates an organization with providers, chat spaces, employee, and audit", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const snapshot = new SnapshotService(connection.db);
connection.transaction(() => organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
}));
const state = snapshot.load();
expect(state.organizations).toHaveLength(1);
expect(state.employees[0]?.displayName).toBe("Dana Lee");
expect(state.providers.length).toBeGreaterThanOrEqual(6);
expect(state.employeeIdentities).toHaveLength(1);
expect(state.roleGrants.map((grant) => grant.roleKey).sort()).toEqual(["approval_decider", "hiring_manager", "org_admin"]);
expect(state.orgDepartments.map((department) => department.name)).toContain("Engineering");
expect(state.hiringPolicies.some((policy) => policy.departmentName === "Engineering")).toBe(true);
expect(state.approvalRoutingRules[0]).toMatchObject({ kind: "*", minimumRisk: "low", routeToType: "employee" });
expect(state.featureFlags.map((flag) => flag.key)).toContain("chaos_drills");
expect(state.sloTargets.map((target) => target.signalKey)).toContain("provider_request_success");
expect(state.chaosDrills.map((drill) => drill.scenarioKey)).toContain("approval_backlog_gate");
expect(state.sdlcReviewAgents.map((agent) => agent.key)).toContain("code_reviewer");
expect(state.evalSuites.map((suite) => suite.key)).toContain("contract");
expect(state.prBotGates.map((gate) => gate.key)).toContain("static_checks");
expect(state.chatSpaces.map((space) => space.name)).toContain("board");
expect(state.policyRules[0]?.action).toBe("task_create");
expect(state.auditEvents[0]?.action).toBe("created");
connection.close();
});
test("high risk work creates a pending approval", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
executionMode: "ai_cofounder",
});
organization.createTask({
orgId,
title: "Production deploy",
description: "Deploy a high-risk change.",
status: TASK_STATUS.BACKLOG,
assigneeAgentId: null,
risk: RISK_LEVEL.HIGH,
projectId: null,
goalId: null,
workspaceId: null,
priority: "normal",
requiresFilesystem: false,
});
});
const state = snapshot.load();
expect(state.tasks).toHaveLength(1);
expect(state.approvals).toHaveLength(1);
expect(state.approvalAssignments).toHaveLength(1);
expect(state.approvals[0]?.state).toBe("pending");
expect(state.approvals[0]).toMatchObject({ sourceEntityType: "task", sourceEntityId: state.tasks[0]?.id });
connection.close();
});
test("ai ceo mode auto-approves high risk work without operator review", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Autonomy Inc",
employeeName: "Robin Park",
mission: "Run the company autonomously.",
goal: "Ship without manual gates.",
executionMode: "ai_ceo",
});
organization.createTask({
orgId,
title: "Production deploy",
description: "Deploy a high-risk change.",
status: TASK_STATUS.BACKLOG,
assigneeAgentId: null,
risk: RISK_LEVEL.HIGH,
projectId: null,
goalId: null,
workspaceId: null,
priority: "normal",
requiresFilesystem: false,
});
});
const state = snapshot.load();
const employeeId = state.employees.find((employee) => employee.orgId === orgId)?.id;
expect(state.approvals).toHaveLength(1);
expect(state.approvals[0]?.state).toBe("approved");
expect(state.approvals[0]?.decidedByEmployeeId).toBe(employeeId);
expect(state.approvalAssignments).toHaveLength(0);
connection.close();
});
test("imports a versioned skill revision from a local directory", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const skills = new SkillService(connection.db, join(tmpdir(), `agentcompany-skill-artifacts-${randomUUID()}`));
const snapshot = new SnapshotService(connection.db);
const skillDir = join(tmpdir(), `agentcompany-skill-${randomUUID()}`);
mkdirSync(skillDir, { recursive: true });
mkdirSync(join(skillDir, "tests"), { recursive: true });
writeFileSync(join(skillDir, "SKILL.md"), "# Review Routes\n");
writeFileSync(join(skillDir, "tests", "happy_path.yaml"), "input: review\n");
writeFileSync(join(skillDir, "skill.manifest.json"), JSON.stringify({
name: "review-routes",
version: "1.0.0",
entrypoint: "SKILL.md",
capabilities: ["architecture-review"],
required_tools: ["file_search"],
compatible_models: ["openai/*", "anthropic/*"],
risk_level: "medium",
inputs: { diff: "markdown" },
outputs: { recommendation: "markdown" },
tests: ["tests/happy_path.yaml"],
}));
let orgId = "";
let agentId = "";
let revisionId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
agentId = organization.createAgent({
orgId,
name: "Ada",
role: "Architecture reviewer",
department: "Engineering",
modelProviderId: null,
modelId: null,
budgetCents: 10000,
managerAgentId: null,
});
revisionId = skills.importDirectory({ orgId, directoryPath: skillDir, versionSemver: "1.0.0" });
const skill = connection.db.prepare("select id from skill_definitions where org_id = ? and slug = ?")
.get(orgId, "review-routes") as { id: string };
skills.bind({ orgId, agentId, skillId: skill.id, revisionId });
rmSync(skillDir, { recursive: true, force: true });
skills.runTests({ orgId, revisionId });
skills.resolveRuntimeSnapshot({ orgId, agentId, runId: "run-review-1" });
});
const state = snapshot.load();
expect(state.skillDefinitions[0]?.slug).toBe("review-routes");
expect(state.skillRevisions[0]?.versionSemver).toBe("1.0.0");
expect(state.skillRevisions[0]?.immutableChecksum).toHaveLength(64);
expect(state.skillRevisions[0]?.manifestJson).toContain("architecture-review");
expect(state.skillTests[0]?.lastResult).toContain("\"status\":\"passed\"");
expect(state.skillTests[0]?.lastRunAt).toBeTruthy();
expect(state.skillRuntimeSnapshots[0]?.sourceRevisionIds).toContain(revisionId);
expect(state.skillRuntimeSnapshots[0]?.compiledPrompt).toContain("# Review Routes");
expect(state.auditEvents.some((event) => event.entityType === "skill_revision" && event.action === "tests_run")).toBe(true);
connection.close();
});
test("rejects cross-organization skill binding and duplicate runtime snapshots", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const skills = new SkillService(connection.db, join(tmpdir(), `agentcompany-skill-artifacts-${randomUUID()}`));
const skillDir = join(tmpdir(), `agentcompany-skill-${randomUUID()}`);
mkdirSync(skillDir, { recursive: true });
writeFileSync(join(skillDir, "SKILL.md"), "# Review Routes\n");
writeFileSync(join(skillDir, "skill.manifest.json"), JSON.stringify({
name: "review-routes",
version: "1.0.0",
entrypoint: "SKILL.md",
capabilities: [],
required_tools: [],
compatible_models: ["openai/*"],
risk_level: "low",
inputs: {},
outputs: {},
tests: [],
}));
let orgA = "";
let orgB = "";
let agentA = "";
let agentB = "";
let skillId = "";
let revisionId = "";
connection.transaction(() => {
orgA = organization.createOrganization({ organizationName: "Acme A", employeeName: "Dana Lee", mission: "A", goal: "A" });
orgB = organization.createOrganization({ organizationName: "Acme B", employeeName: "Riley Chen", mission: "B", goal: "B" });
agentA = organization.createAgent({ orgId: orgA, name: "Ada", role: "Reviewer", department: "Engineering", modelProviderId: null, modelId: null, budgetCents: 10000, managerAgentId: null });
agentB = organization.createAgent({ orgId: orgB, name: "Ben", role: "Reviewer", department: "Engineering", modelProviderId: null, modelId: null, budgetCents: 10000, managerAgentId: null });
revisionId = skills.importDirectory({ orgId: orgA, directoryPath: skillDir, versionSemver: "1.0.0" });
skillId = (connection.db.prepare("select id from skill_definitions where org_id = ? and slug = ?")
.get(orgA, "review-routes") as { id: string }).id;
expect(() => skills.bind({ orgId: orgA, agentId: agentB, skillId, revisionId })).toThrow("Agent was not found.");
skills.bind({ orgId: orgA, agentId: agentA, skillId, revisionId });
skills.resolveRuntimeSnapshot({ orgId: orgA, agentId: agentA, runId: "run-review-1" });
expect(() => skills.resolveRuntimeSnapshot({ orgId: orgA, agentId: agentA, runId: "run-review-1" }))
.toThrow("Skill runtime snapshot already exists for this run.");
});
expect(agentA).not.toBe(agentB);
connection.close();
});
test("records collaborative chat semantics for threads, commands, mentions, and files", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let fileId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
const spaceId = chat.createSpace({
orgId,
spaceType: "project",
name: "release",
topic: "Release coordination",
visibility: "workspace",
});
const employee = connection.db.prepare("select id from employees where org_id = ?")
.get(orgId) as { id: string };
fileId = createId("file");
connection.db.prepare(`
insert into file_objects(id, org_id, name, kind, size_bytes, sha256, storage_path, owner_type, owner_id, created_at)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(fileId, orgId, "runbook.md", "text/markdown", 16, "8f3a", "artifacts/runbook.md", "employee", employee.id, new Date().toISOString());
chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employee.id,
bodyMd: "/handoff @Board review the deployment runbook",
attachmentFileIds: [fileId],
});
});
const state = snapshot.load();
expect(state.chatSpaces.map((space) => space.name)).toContain("release");
expect(state.chatMessages).toHaveLength(1);
expect(state.chatThreads).toHaveLength(1);
expect(state.chatAttachments).toHaveLength(1);
expect(state.files[0]?.scanState).toBe("approved");
expect(state.fileAccessGrants[0]).toMatchObject({ grantedToType: "board", grantedToId: orgId, accessLevel: "read" });
expect(state.fileHandoffRecords[0]).toMatchObject({
fileId,
targetPrincipalType: "board",
targetPrincipalId: orgId,
checksum: "8f3a",
scanState: "approved",
policyDecision: "approved_for_handoff",
objectRef: "artifacts/runbook.md",
});
expect(state.chatMentions[0]).toMatchObject({ targetType: "board", targetId: orgId });
expect(state.chatSlashInvocations[0]).toMatchObject({ command: "handoff" });
expect(state.chatPresence).toHaveLength(1);
const slash = connection.db.prepare("select command from chat_slash_invocations").get() as { command: string };
const mention = connection.db.prepare("select target_type, target_id from chat_mentions").get() as { target_type: string; target_id: string };
expect(slash.command).toBe("handoff");
expect(mention.target_type).toBe("board");
expect(mention.target_id).toBe(orgId);
connection.close();
});
test("enforces scanned file grants for agent handoff", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let spaceId = "";
let adaId = "";
let benId = "";
let approvedFileId = "";
let blockedFileId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
adaId = organization.createAgent({
orgId,
name: "Ada",
role: "Release reviewer",
department: "Engineering",
modelProviderId: null,
modelId: null,
budgetCents: 10000,
managerAgentId: null,
});
benId = organization.createAgent({
orgId,
name: "Ben",
role: "Runtime owner",
department: "Engineering",
modelProviderId: null,
modelId: null,
budgetCents: 10000,
managerAgentId: null,
});
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
approvedFileId = createId("fil");
blockedFileId = createId("fil");
const now = new Date().toISOString();
connection.db.prepare(`
insert into file_objects(
id, org_id, name, kind, size_bytes, sha256, storage_path,
owner_type, owner_id, space_id, pinned, scan_state, scan_labels_json, scanned_at, created_at, updated_at
)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(approvedFileId, orgId, "release-notes.md", "md", 24, "sha-approved", "artifacts/release-notes.md", "agent", adaId, null, 0, "approved", "[]", now, now, now);
connection.db.prepare(`
insert into file_objects(
id, org_id, name, kind, size_bytes, sha256, storage_path,
owner_type, owner_id, space_id, pinned, scan_state, scan_labels_json, scanned_at, created_at, updated_at
)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(blockedFileId, orgId, "quarantine.bin", "file", 8, "sha-blocked", "artifacts/quarantine.bin", "agent", adaId, null, 0, "blocked", "[\"signature\"]", now, now, now);
});
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "agent",
senderId: benId,
bodyMd: "Attach release notes",
attachmentFileIds: [approvedFileId],
}))).toThrow("Attachment file access is required.");
connection.transaction(() => chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "agent",
senderId: adaId,
bodyMd: "/handoff @Ben release notes",
attachmentFileIds: [approvedFileId],
}));
expect(() => connection.transaction(() => chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "agent",
senderId: adaId,
bodyMd: "/handoff @Ben blocked payload",
attachmentFileIds: [blockedFileId],
}))).toThrow("Attachment file is not approved for handoff.");
connection.transaction(() => chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "agent",
senderId: benId,
bodyMd: "Received release notes",
attachmentFileIds: [approvedFileId],
}));
const state = snapshot.load();
const benGrant = state.fileAccessGrants.find((grant) => grant.fileId === approvedFileId && grant.grantedToId === benId);
if (!benGrant) throw new Error("Agent handoff grant was not created.");
const benHandoff = state.fileHandoffRecords.find((record) => (
record.fileId === approvedFileId && record.sourcePrincipalId === adaId && record.targetPrincipalId === benId
));
if (!benHandoff) throw new Error("Agent handoff record was not created.");
expect(benGrant).toMatchObject({ grantedToType: "agent", accessLevel: "read" });
expect(benHandoff).toMatchObject({
sourcePrincipalType: "agent",
sourcePrincipalId: adaId,
targetPrincipalType: "agent",
checksum: "sha-approved",
objectRef: "artifacts/release-notes.md",
scanState: "approved",
policyDecision: "approved_for_handoff",
});
expect(state.chatMessages).toHaveLength(2);
expect(state.auditEvents.some((event) => event.entityType === "file_object" && event.action === "access_granted")).toBe(true);
connection.close();
});
test("imports files with checksum, scan labels, object refs, and workspace grants", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const snapshot = new SnapshotService(connection.db);
const artifactRoot = join(tmpdir(), `agentcompany-file-artifacts-${randomUUID()}`);
const fileService = new FileService(connection.db, artifactRoot);
const sourceDir = join(tmpdir(), `agentcompany-file-sources-${randomUUID()}`);
mkdirSync(sourceDir, { recursive: true });
const cleanBody = "Release checklist for dana@example.com\n";
const secretBody = "-----BEGIN OPENSSH PRIVATE KEY-----\nabc123\n-----END OPENSSH PRIVATE KEY-----\n";
const cleanPath = join(sourceDir, "release-notes.md");
const secretPath = join(sourceDir, "deploy.env");
writeFileSync(cleanPath, cleanBody);
writeFileSync(secretPath, secretBody);
let orgId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
});
const cleanFileId = fileService.importPath(orgId, cleanPath);
const blockedFileId = fileService.importPath(orgId, secretPath);
const state = snapshot.load();
const cleanFile = state.files.find((file) => file.id === cleanFileId);
const blockedFile = state.files.find((file) => file.id === blockedFileId);
if (!cleanFile || !blockedFile) throw new Error("Imported files were not recorded.");
expect(cleanFile.sha256).toBe(createHash("sha256").update(cleanBody).digest("hex"));
expect(cleanFile.storagePath).toContain(cleanFileId);
expect(cleanFile.scanState).toBe("approved");
expect(JSON.parse(cleanFile.scanLabelsJson)).toEqual(expect.arrayContaining(["classification:confidential", "dlp:email", "kind:md"]));
expect(blockedFile.scanState).toBe("blocked");
expect(JSON.parse(blockedFile.scanLabelsJson)).toEqual(expect.arrayContaining(["classification:restricted", "dlp:private-key", "kind:env"]));
expect(state.fileAccessGrants).toContainEqual(expect.objectContaining({
fileId: cleanFileId,
grantedToType: "org_group",
grantedToId: `${orgId}:workspace`,
accessLevel: "manage",
}));
expect(state.fileAccessGrants).toContainEqual(expect.objectContaining({
fileId: blockedFileId,
grantedToType: "org_group",
grantedToId: `${orgId}:workspace`,
accessLevel: "manage",
}));
const importAudit = state.auditEvents.find((event) => event.entityId === blockedFileId && event.action === "imported");
if (!importAudit) throw new Error("File import audit event was not recorded.");
expect(importAudit.payloadJson).toContain("\"scanState\":\"blocked\"");
expect(importAudit.payloadJson).toContain("dlp:private-key");
expect(importAudit.payloadJson).toContain("objectRef");
rmSync(sourceDir, { recursive: true, force: true });
rmSync(artifactRoot, { recursive: true, force: true });
connection.close();
});
test("creates approval and waiting presence from chat escalation", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const chat = new ChatService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let employeeId = "";
let spaceId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
spaceId = chat.createSpace({
orgId,
spaceType: "project",
name: "release",
topic: "Release coordination",
visibility: "workspace",
});
chat.sendMessage({
orgId,
spaceId,
threadId: null,
senderType: "employee",
senderId: employeeId,
bodyMd: "/escalate @Board freeze deployment",
attachmentFileIds: [],
});
});
const state = snapshot.load();
const approval = state.approvals.find((item) => item.kind === "chat_escalation");
if (!approval) throw new Error("Chat escalation approval was not created.");
expect(approval.title).toBe("Escalate release");
expect(approval.details).toBe("@Board freeze deployment");
expect(approval.risk).toBe("high");
expect(state.chatSlashInvocations[0]?.resultRef).toBe(approval.id);
expect(state.chatPresence[0]).toMatchObject({ principalId: employeeId, status: "waiting_approval" });
const audit = state.auditEvents.find((event) => event.entityId === approval.id && event.action === "requested");
if (!audit) throw new Error("Chat escalation audit event was not created.");
expect(audit.payloadJson).toContain(state.chatMessages[0]!.id);
connection.close();
});
test("creates a read-only migration report from a legacy database", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const migrations = new MigrationService(connection.db);
const snapshot = new SnapshotService(connection.db);
const legacyPath = join(tmpdir(), `agentcompany-legacy-${randomUUID()}.db`);
const legacy = new DatabaseSync(legacyPath);
legacy.exec("create table agents(id text primary key); insert into agents(id) values ('agent-1');");
legacy.close();
let orgId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
migrations.createReport({ orgId, sourceDatabasePath: legacyPath });
});
const state = snapshot.load();
expect(state.migrationReports).toHaveLength(1);
expect(state.migrationReports[0]?.reportJson).toContain("\"agents\":1");
const report = JSON.parse(state.migrationReports[0]!.reportJson) as {
tableChecksums: Record<string, string>;
idMaps: Record<string, string[]>;
failures: Array<{ table: string; reason: string }>;
validationSummary: { missingTableCount: number; passed: boolean };
rollbackPack: { sourceDatabasePath: string };
};
expect(report.tableChecksums.agents).toHaveLength(64);
expect(report.idMaps.agents).toEqual(["agent-1"]);
expect(report.failures.some((failure) => failure.reason === "missing_table")).toBe(true);
expect(report.validationSummary.missingTableCount).toBeGreaterThan(0);
expect(report.validationSummary.passed).toBe(false);
expect(report.rollbackPack.sourceDatabasePath).toBe(realpathSync.native(legacyPath));
connection.close();
});
test("bootstraps the original onboarding chain with company, CEO, goal, project, task, and run", () => {
const connection = createConnection();
const onboarding = new OnboardingService({
db: connection.db,
saveProviderCredential: ({ orgId, providerId, model }) => {
connection.db.prepare("update providers set credential_id = ?, model = ?, enabled = 1 where id = ? and org_id = ?")
.run("cred_test", model, providerId, orgId);
},
});
const snapshot = new SnapshotService(connection.db);
connection.transaction(() => onboarding.bootstrap({
companyName: "Acme Operations",
companyDescription: "Coordinate agents with auditable approvals.",
employeeName: "Dana Lee",
workspacePath: join(tmpdir(), `agentcompany-workspace-${randomUUID()}`),
executionMode: "ai_ceo",
autoApproveHires: true,
reviewDeliverables: false,
goalTitle: "Ship a verified control plane",
goalDescription: "Bring the desktop operating loop online.",
providerKey: "openai",
providerSecret: "test-secret",
providerModel: "gpt-5.1",
}));
const state = snapshot.load();
expect(state.organizations[0]?.executionMode).toBe("ai_ceo");
expect(state.agents[0]?.roleLevel).toBe("c_suite");
expect(state.agents[0]?.modelStrategy).toBe("explicit");
expect(state.workspaces).toHaveLength(1);
expect(state.goals[0]?.status).toBe("active");
expect(state.projects[0]?.leadAgentId).toBe(state.agents[0]?.id);
const projectRoom = state.chatSpaces.find((space) => space.spaceType === "project" && space.topic === state.projects[0]?.name);
expect(projectRoom).toMatchObject({ name: "execution-plan-ship-a-verified-control-plane", visibility: "workspace" });
expect(state.chatMemberships).toContainEqual(expect.objectContaining({ spaceId: projectRoom?.id, principalType: "employee" }));
expect(state.chatMemberships).toContainEqual(expect.objectContaining({ spaceId: projectRoom?.id, principalType: "agent", principalId: state.agents[0]?.id }));
expect(state.tasks[0]?.assigneeAgentId).toBe(state.agents[0]?.id);
expect(state.taskRuns[0]?.trigger).toBe("bootstrap");
expect(state.taskRuns[0]?.executionContextId).toMatch(/^ctx_/);
expect(state.taskRuns[0]?.serviceIdentityId).toMatch(/^sid_/);
connection.close();
});
test("creating a goal kicks off an operationalize task for its owner or the CEO", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
let orgId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
});
// Without any agent there is nobody to operationalize the goal yet.
let waitingGoalId = "";
connection.transaction(() => {
waitingGoalId = organization.createGoal({ orgId, title: "Open the Berlin market", description: "Win the first ten customers.", ownerAgentId: null });
});
expect(connection.db.prepare("select count(*) as n from tasks where org_id = ?").get(orgId)).toMatchObject({ n: 0 });
let ceoId = "";
let ownerId = "";
connection.transaction(() => {
ceoId = organization.createAgent({
orgId,
name: "CEO",
role: "CEO",
department: "Executive",
modelProviderId: null,
modelId: null,
budgetCents: 25000,
managerAgentId: null,
roleLevel: "c_suite",
});
ownerId = organization.createAgent({
orgId,
name: "Mara Quinn",
role: "Growth Marketer",
department: "Marketing",
modelProviderId: null,
modelId: null,
budgetCents: 25000,
managerAgentId: ceoId,
});
});
// The first hire operationalizes the goal that was waiting, exactly once.
expect(connection.db.prepare("select count(*) as n from tasks where goal_id = ?").get(waitingGoalId)).toMatchObject({ n: 1 });
const revived = connection.db.prepare("select status, assignee_agent_id from tasks where goal_id = ?")
.get(waitingGoalId) as { status: string; assignee_agent_id: string };
expect(revived).toMatchObject({ status: TASK_STATUS.BACKLOG, assignee_agent_id: ceoId });
// Ownerless goals land with the top-level agent; the kickoff re-enables its heartbeat.
let ceoGoalId = "";
connection.transaction(() => {
ceoGoalId = organization.createGoal({ orgId, title: "Launch the loyalty program", description: "Ship the first reward tier.", ownerAgentId: null });
});
const ceoKickoff = connection.db.prepare("select title, status, assignee_agent_id from tasks where goal_id = ?")
.get(ceoGoalId) as { title: string; status: string; assignee_agent_id: string };
expect(ceoKickoff).toMatchObject({
title: "Operationalize goal: Launch the loyalty program",
status: TASK_STATUS.BACKLOG,
assignee_agent_id: ceoId,
});
const ceo = connection.db.prepare("select heartbeat_enabled, heartbeat_interval_sec from agents where id = ?")
.get(ceoId) as { heartbeat_enabled: number; heartbeat_interval_sec: number };
expect(ceo).toMatchObject({ heartbeat_enabled: 1, heartbeat_interval_sec: 120 });
// Goals with an explicit owner land with that owner.
let ownedGoalId = "";
connection.transaction(() => {
ownedGoalId = organization.createGoal({ orgId, title: "Win the spring campaign", description: "Double qualified leads.", ownerAgentId: ownerId });
});
const ownedKickoff = connection.db.prepare("select assignee_agent_id from tasks where goal_id = ?")
.get(ownedGoalId) as { assignee_agent_id: string };
expect(ownedKickoff.assignee_agent_id).toBe(ownerId);
connection.close();
});
test("hiring normalizes department case and rejects unknown departments with guidance", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
let orgId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
});
let agentId = "";
connection.transaction(() => {
agentId = organization.createAgent({
orgId,
name: "Zhang Hao",
role: "Algorithm Lead",
department: "engineering",
modelProviderId: null,
modelId: null,
budgetCents: 25000,
managerAgentId: null,
});
});
const hired = connection.db.prepare("select department from agents where id = ?").get(agentId) as { department: string };
expect(hired.department).toBe("Engineering");
expect(() => connection.transaction(() => organization.createAgent({
orgId,
name: "Chen Wei",
role: "Research Lead",
department: "general",
modelProviderId: null,
modelId: null,
budgetCents: 25000,
managerAgentId: null,
}))).toThrow(/Unknown department "general"\. Departments: .*Engineering.*Executive/);
connection.close();
});
});
-449
View File
@@ -1,449 +0,0 @@
import { join } from "node:path";
import { tmpdir } from "node:os";
import { mkdtemp, rm } from "node:fs/promises";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase } from "../../src/main/database";
import { createBudgetEnforcementService } from "../../src/main/budget-enforcement-service";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function scaffold(db: AppDatabase) {
const companyId = db.core.company.saveCompany({
name: "Budget Edge Case Co",
description: "Budget exhaustion mid-run edge case tests (D-05.2)",
status: "active",
});
db.core.connectors.saveConnector({
id: "gemini-cli",
model: "gemini-pro",
envBindingText: "",
notes: "Test connector",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: "/tmp/budget-edge-test",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Worker Agent",
role: "developer",
title: "Developer",
status: "active",
reportsTo: null,
connectorId: "gemini-cli",
workspaceId,
model: null,
capabilities: "Executes tasks",
budgetMonthlyUsd: 100,
});
const goalId = db.core.entity.saveGoal({
companyId,
title: "Ship feature",
description: "Build budget edge case test",
ownerAgentId: agentId,
status: "active",
});
const projectId = db.core.entity.saveProject({
companyId,
goalId,
name: "Budget Project",
description: "Testing budget exhaustion",
leadAgentId: agentId,
status: "in_progress",
targetDate: null,
});
const taskId = db.core.tasks.saveTask({
companyId,
projectId,
goalId,
title: "Implement feature",
description: "Build something to test budget edge cases.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
return { companyId, workspaceId, agentId, goalId, projectId, taskId };
}
// ---------------------------------------------------------------------------
// Tests — Edge Case: Budget Exhaustion Mid-Run (D-05.2)
// ---------------------------------------------------------------------------
describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
let tempDir: string;
let db: AppDatabase;
const mockLogger = { info: vi.fn(), warn: vi.fn(), error: vi.fn() };
const mockNotify = vi.fn();
const mockPublish = vi.fn();
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-budget-edge-"));
db = new AppDatabase(tempDir);
db.init();
mockLogger.info.mockClear();
mockLogger.warn.mockClear();
mockLogger.error.mockClear();
mockNotify.mockClear();
mockPublish.mockClear();
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("in-flight run completes normally when budget is exhausted during execution", () => {
const { companyId, workspaceId, agentId, projectId, taskId } = scaffold(db);
const budgetService = createBudgetEnforcementService({
db,
logger: mockLogger,
notify: mockNotify,
publishDomainChanged: mockPublish,
});
// Create company budget policy: 500 cents ($5), hard stop enabled
db.core.budget.saveBudgetPolicy({
companyId,
scopeType: "company",
scopeId: companyId,
amount: 500,
hardStopEnabled: true,
});
// Start Run-1 (the in-flight run)
const run1 = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "gemini-cli",
});
db.core.tasks.checkoutTask(taskId, run1.id);
db.core.runs.markRunStatus(run1.id, "running", "");
// Create a second task and run that exhausts budget WHILE Run-1 is in-flight
const taskId2 = db.core.tasks.saveTask({
companyId,
title: "Other Task",
description: "Another task that uses up budget",
assigneeAgentId: agentId,
workspaceId,
priority: "medium",
status: "todo",
});
const run0 = db.core.runs.createRun({
companyId,
taskId: taskId2,
agentId,
workspaceId,
connectorId: "gemini-cli",
});
db.core.tasks.checkoutTask(taskId2, run0.id);
db.core.runs.markRunStatus(run0.id, "running", "");
// Complete Run-0 with $6 cost (exceeds $5 budget)
db.core.runs.recordCostEntry({ runId: run0.id, companyId, agentId, taskId: taskId2, connectorId: "gemini-cli", amountUsd: 6.0, unattributed: false });
db.core.runs.finishRun({
runId: run0.id,
status: "succeeded",
summary: "Expensive run",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-pro",
unattributedCost: false,
});
// Now complete Run-1 (the in-flight run) -- it should succeed normally
// Per D-04/D-05: in-flight runs are unaffected by budget exhaustion
db.core.runs.recordCostEntry({ runId: run1.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 0.5, unattributed: false });
db.core.runs.finishRun({
runId: run1.id,
status: "succeeded",
summary: "In-flight run completed",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-pro",
unattributedCost: false,
});
// Verify Run-1 completed successfully
const finishedRun = db.core.runs.getRun(run1.id);
expect(finishedRun.status).toBe("succeeded");
// Verify Run-1's cost was recorded (total spend now $6.50)
const gate = budgetService.evaluateBudgetGate(companyId, agentId, projectId);
expect(gate.allowed).toBe(false);
// Spent should be at least 650 cents ($6.50 = $6 + $0.50)
expect(gate.blockedBy[0].spent).toBeGreaterThanOrEqual(650);
});
it("budget gate blocks next run after exhaustion", () => {
const { companyId, workspaceId, agentId, projectId, taskId } = scaffold(db);
const budgetService = createBudgetEnforcementService({
db,
logger: mockLogger,
notify: mockNotify,
publishDomainChanged: mockPublish,
});
// Create company budget policy: 500 cents ($5)
db.core.budget.saveBudgetPolicy({
companyId,
scopeType: "company",
scopeId: companyId,
amount: 500,
hardStopEnabled: true,
});
// Complete a run that exceeds budget
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "gemini-cli",
});
db.core.tasks.checkoutTask(taskId, run.id);
db.core.runs.markRunStatus(run.id, "running", "");
db.core.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 6.0, unattributed: false });
db.core.runs.finishRun({
runId: run.id,
status: "succeeded",
summary: "Done",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-pro",
unattributedCost: false,
});
// Budget gate should now block
const result = budgetService.evaluateBudgetGate(companyId, agentId, projectId);
expect(result.allowed).toBe(false);
expect(result.blockedBy.length).toBeGreaterThanOrEqual(1);
expect(result.blockedBy[0].spent).toBeGreaterThanOrEqual(500);
expect(result.blockedBy[0].limit).toBe(500);
});
it("budget gate returns detailed blockedBy information", () => {
const { companyId, workspaceId, agentId, projectId, taskId } = scaffold(db);
const budgetService = createBudgetEnforcementService({
db,
logger: mockLogger,
notify: mockNotify,
publishDomainChanged: mockPublish,
});
// Create company budget policy with known ID
const policyId = db.core.budget.saveBudgetPolicy({
companyId,
scopeType: "company",
scopeId: companyId,
amount: 500,
hardStopEnabled: true,
});
// Exhaust budget
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "gemini-cli",
});
db.core.tasks.checkoutTask(taskId, run.id);
db.core.runs.markRunStatus(run.id, "running", "");
db.core.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 6.0, unattributed: false });
db.core.runs.finishRun({
runId: run.id,
status: "succeeded",
summary: "Done",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-pro",
unattributedCost: false,
});
const result = budgetService.evaluateBudgetGate(companyId, agentId, projectId);
expect(result.allowed).toBe(false);
// Verify blockedBy contains correct details
expect(result.blockedBy[0].policyId).toBe(policyId);
expect(result.blockedBy[0].scopeType).toBe("company");
expect(result.blockedBy[0].scopeId).toBe(companyId);
});
it("agent-scope budget exhaustion blocks even when company budget has room", () => {
const { companyId, workspaceId, agentId, projectId, taskId } = scaffold(db);
const budgetService = createBudgetEnforcementService({
db,
logger: mockLogger,
notify: mockNotify,
publishDomainChanged: mockPublish,
});
// Company-level: plenty of room (10000 cents = $100)
db.core.budget.saveBudgetPolicy({
companyId,
scopeType: "company",
scopeId: companyId,
amount: 10000,
hardStopEnabled: true,
});
// Agent-level: tight budget (100 cents = $1)
db.core.budget.saveBudgetPolicy({
companyId,
scopeType: "agent",
scopeId: agentId,
amount: 100,
hardStopEnabled: true,
});
// Complete a run costing $1.50 (150 cents > 100 agent limit)
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "gemini-cli",
});
db.core.tasks.checkoutTask(taskId, run.id);
db.core.runs.markRunStatus(run.id, "running", "");
db.core.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 1.5, unattributed: false });
db.core.runs.finishRun({
runId: run.id,
status: "succeeded",
summary: "Done",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-pro",
unattributedCost: false,
});
const result = budgetService.evaluateBudgetGate(companyId, agentId, projectId);
expect(result.allowed).toBe(false);
// Should be blocked by agent scope, not company scope
expect(result.blockedBy.length).toBe(1);
expect(result.blockedBy[0].scopeType).toBe("agent");
expect(result.blockedBy[0].scopeId).toBe(agentId);
});
it("budget gate transitions from allowed to blocked after spending", () => {
const { companyId, workspaceId, agentId, projectId, taskId } = scaffold(db);
const budgetService = createBudgetEnforcementService({
db,
logger: mockLogger,
notify: mockNotify,
publishDomainChanged: mockPublish,
});
// Create policy with 300 cents ($3) budget
db.core.budget.saveBudgetPolicy({
companyId,
scopeType: "company",
scopeId: companyId,
amount: 300,
hardStopEnabled: true,
});
// Budget gate initially allows (nothing spent yet)
const before = budgetService.evaluateBudgetGate(companyId, agentId, projectId);
expect(before.allowed).toBe(true);
// Complete a run costing $4 (400 cents > 300 limit)
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "gemini-cli",
});
db.core.tasks.checkoutTask(taskId, run.id);
db.core.runs.markRunStatus(run.id, "running", "");
db.core.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 4.0, unattributed: false });
db.core.runs.finishRun({
runId: run.id,
status: "succeeded",
summary: "Done",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-pro",
unattributedCost: false,
});
// Budget gate now blocks
const after = budgetService.evaluateBudgetGate(companyId, agentId, projectId);
expect(after.allowed).toBe(false);
expect(after.blockedBy.length).toBeGreaterThanOrEqual(1);
});
it("in-flight run cost entry is recorded even when budget would be exceeded", () => {
const { companyId, workspaceId, agentId, projectId, taskId } = scaffold(db);
const budgetService = createBudgetEnforcementService({
db,
logger: mockLogger,
notify: mockNotify,
publishDomainChanged: mockPublish,
});
// Create tight budget
db.core.budget.saveBudgetPolicy({
companyId,
scopeType: "company",
scopeId: companyId,
amount: 100,
hardStopEnabled: true,
});
// Run that will exceed budget on completion
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "gemini-cli",
});
db.core.tasks.checkoutTask(taskId, run.id);
db.core.runs.markRunStatus(run.id, "running", "");
// Complete with cost that exceeds budget -- cost entry is still recorded
db.core.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 5.0, unattributed: false });
db.core.runs.finishRun({
runId: run.id,
status: "succeeded",
summary: "Done",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-pro",
unattributedCost: false,
});
// Verify cost was recorded by checking budget gate sees the spend
const result = budgetService.evaluateBudgetGate(companyId, agentId, projectId);
expect(result.allowed).toBe(false);
expect(result.blockedBy[0].spent).toBe(500); // 500 cents = $5
expect(result.blockedBy[0].limit).toBe(100);
});
});
-344
View File
@@ -1,344 +0,0 @@
import { join } from "node:path";
import { tmpdir } from "node:os";
import { mkdtemp, rm } from "node:fs/promises";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase, STALE_LOCK_TIMEOUT_MS } from "../../src/main/database";
type RawDb = {
db: {
prepare: (sql: string) => {
run: (...args: unknown[]) => void;
get: (...args: unknown[]) => unknown;
};
};
};
type TaskLeaseRow = {
task_id: string;
run_id: string;
expires_at: string;
};
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function createTestCompany(db: AppDatabase): string {
return db.core.company.saveCompany({
name: "Edge Case Company",
description: "Concurrent checkout edge case tests (D-05.1)",
status: "active",
});
}
function createTestConnector(db: AppDatabase): void {
db.core.connectors.saveConnector({
id: "gemini-cli",
model: "gemini-pro",
envBindingText: "",
notes: "Test connector for concurrent checkout",
});
}
function createTestWorkspace(db: AppDatabase, companyId: string): string {
return db.core.workspaces.saveWorkspace({
companyId,
name: "Test Workspace",
localPath: "/tmp/concurrent-test",
repoUrl: "https://github.com/example/concurrent",
repoRef: "main",
isPrimary: true,
});
}
function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string, name = "Agent"): string {
return db.core.agents.saveAgent({
companyId,
name,
role: "IC",
title: name,
status: "active",
reportsTo: null,
connectorId: "gemini-cli",
workspaceId,
model: null,
capabilities: "Executes tasks",
budgetMonthlyUsd: 100,
});
}
function createTestTask(db: AppDatabase, companyId: string, workspaceId: string): string {
return db.core.tasks.saveTask({
companyId,
title: "Concurrent Test Task",
description: "A task for testing concurrent checkout edge cases",
assigneeAgentId: null,
workspaceId,
priority: "medium",
status: "todo",
});
}
function getTaskLease(db: AppDatabase, taskId: string): TaskLeaseRow | null {
return (
((db as unknown as RawDb).db
.prepare("SELECT task_id, run_id, expires_at FROM task_leases WHERE task_id = ?")
.get(taskId) as TaskLeaseRow | undefined) ?? null
);
}
// ---------------------------------------------------------------------------
// Tests — Edge Case: Concurrent Task Checkout (D-05.1)
// ---------------------------------------------------------------------------
describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => {
let tempDir: string;
let db: AppDatabase;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-concurrent-"));
db = new AppDatabase(tempDir);
db.init();
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("exactly one agent wins when two attempt simultaneous checkout", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentA = createTestAgent(db, companyId, workspaceId, "Agent-A");
const agentB = createTestAgent(db, companyId, workspaceId, "Agent-B");
const taskId = createTestTask(db, companyId, workspaceId);
const { id: runIdA } = db.core.runs.createRun({
companyId,
taskId,
agentId: agentA,
workspaceId,
connectorId: "gemini-cli",
});
const { id: runIdB } = db.core.runs.createRun({
companyId,
taskId,
agentId: agentB,
workspaceId,
connectorId: "gemini-cli",
});
// Agent-A claims first
const resultA = db.core.tasks.checkoutTask(taskId, runIdA);
expect(resultA).toBe(true);
// Agent-B tries to claim the same task
const resultB = db.core.tasks.checkoutTask(taskId, runIdB);
expect(resultB).toBe(false);
// Task status must be in_progress (set by first successful checkout)
const task = db.core.entity.getTask(taskId);
expect(task.status).toBe("in_progress");
});
it("losing agent can checkout a different task", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentA = createTestAgent(db, companyId, workspaceId, "Agent-A");
const agentB = createTestAgent(db, companyId, workspaceId, "Agent-B");
const taskId1 = createTestTask(db, companyId, workspaceId);
const { id: runIdA } = db.core.runs.createRun({
companyId,
taskId: taskId1,
agentId: agentA,
workspaceId,
connectorId: "gemini-cli",
});
const { id: runIdB1 } = db.core.runs.createRun({
companyId,
taskId: taskId1,
agentId: agentB,
workspaceId,
connectorId: "gemini-cli",
});
// Agent-A wins first task
expect(db.core.tasks.checkoutTask(taskId1, runIdA)).toBe(true);
// Agent-B loses
expect(db.core.tasks.checkoutTask(taskId1, runIdB1)).toBe(false);
// Agent-B can still checkout a DIFFERENT task
const taskId2 = createTestTask(db, companyId, workspaceId);
const { id: runIdB2 } = db.core.runs.createRun({
companyId,
taskId: taskId2,
agentId: agentB,
workspaceId,
connectorId: "gemini-cli",
});
const resultB2 = db.core.tasks.checkoutTask(taskId2, runIdB2);
expect(resultB2).toBe(true);
// Verify second task is now in_progress
const task2 = db.core.entity.getTask(taskId2);
expect(task2.status).toBe("in_progress");
});
it("checkout of in_progress task is rejected", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentA = createTestAgent(db, companyId, workspaceId, "Agent-A");
const agentB = createTestAgent(db, companyId, workspaceId, "Agent-B");
const taskId = createTestTask(db, companyId, workspaceId);
// Agent-A checks out successfully
const { id: runIdA } = db.core.runs.createRun({
companyId,
taskId,
agentId: agentA,
workspaceId,
connectorId: "gemini-cli",
});
expect(db.core.tasks.checkoutTask(taskId, runIdA)).toBe(true);
// Task is now in_progress. A new run from Agent-B tries to checkout
const { id: runIdB } = db.core.runs.createRun({
companyId,
taskId,
agentId: agentB,
workspaceId,
connectorId: "gemini-cli",
});
const result = db.core.tasks.checkoutTask(taskId, runIdB);
expect(result).toBe(false);
// Confirm status is still in_progress
expect(db.core.entity.getTask(taskId).status).toBe("in_progress");
});
it("after lock release, CAS guard still checks task status", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentA = createTestAgent(db, companyId, workspaceId, "Agent-A");
const agentB = createTestAgent(db, companyId, workspaceId, "Agent-B");
const taskId = createTestTask(db, companyId, workspaceId);
// Agent-A checks out task
const { id: runIdA } = db.core.runs.createRun({
companyId,
taskId,
agentId: agentA,
workspaceId,
connectorId: "gemini-cli",
});
expect(db.core.tasks.checkoutTask(taskId, runIdA)).toBe(true);
// Release the lease while leaving the task lifecycle unchanged.
db.core.tasks.releaseTaskLock(taskId, runIdA);
expect(getTaskLease(db, taskId)).toBeNull();
// Task is still in_progress though, so a new checkout fails
// (CAS checks status IN ('todo', 'backlog'))
const { id: runIdB } = db.core.runs.createRun({
companyId,
taskId,
agentId: agentB,
workspaceId,
connectorId: "gemini-cli",
});
const result = db.core.tasks.checkoutTask(taskId, runIdB);
expect(result).toBe(false);
});
it("stale lock recovery frees locks older than timeout", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentA = createTestAgent(db, companyId, workspaceId, "Agent-A");
const taskId = createTestTask(db, companyId, workspaceId);
const { id: runIdA } = db.core.runs.createRun({
companyId,
taskId,
agentId: agentA,
workspaceId,
connectorId: "gemini-cli",
});
expect(db.core.tasks.checkoutTask(taskId, runIdA)).toBe(true);
// Backdate the lease expiry past the stale timeout.
(db as unknown as RawDb).db
.prepare("UPDATE task_leases SET expires_at = '2020-01-01T00:00:00Z' WHERE task_id = ?")
.run(taskId);
const recovered = db.recoverStaleLocks();
expect(recovered.length).toBe(1);
expect(recovered[0].taskId).toBe(taskId);
expect(recovered[0].companyId).toBe(companyId);
expect(getTaskLease(db, taskId)).toBeNull();
});
it("after stale recovery, different agent can checkout the recovered task", () => {
const companyId = createTestCompany(db);
createTestConnector(db);
const workspaceId = createTestWorkspace(db, companyId);
const agentA = createTestAgent(db, companyId, workspaceId, "Agent-A");
const agentB = createTestAgent(db, companyId, workspaceId, "Agent-B");
const taskId = createTestTask(db, companyId, workspaceId);
// Agent-A checks out, then lock goes stale
const { id: runIdA } = db.core.runs.createRun({
companyId,
taskId,
agentId: agentA,
workspaceId,
connectorId: "gemini-cli",
});
expect(db.core.tasks.checkoutTask(taskId, runIdA)).toBe(true);
// Backdate the lease to make it stale.
(db as unknown as RawDb).db
.prepare("UPDATE task_leases SET expires_at = '2020-01-01T00:00:00Z' WHERE task_id = ?")
.run(taskId);
// Recover stale locks (sets status back to "todo")
const recovered = db.recoverStaleLocks();
expect(recovered.length).toBe(1);
// Verify task is back to "todo"
expect(db.core.entity.getTask(taskId).status).toBe("todo");
// Agent-B can now checkout the recovered task
const { id: runIdB } = db.core.runs.createRun({
companyId,
taskId,
agentId: agentB,
workspaceId,
connectorId: "gemini-cli",
});
const result = db.core.tasks.checkoutTask(taskId, runIdB);
expect(result).toBe(true);
expect(db.core.entity.getTask(taskId).status).toBe("in_progress");
});
it("STALE_LOCK_TIMEOUT_MS matches expected 15 minute value", () => {
expect(STALE_LOCK_TIMEOUT_MS).toBe(15 * 60 * 1000);
});
});
-336
View File
@@ -1,336 +0,0 @@
import { join } from "node:path";
import { tmpdir } from "node:os";
import { mkdtemp, rm } from "node:fs/promises";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase } from "../../src/main/database";
import { createSessionManagementService } from "../../src/main/session-management-service";
import type { ConnectorId } from "@shared/types";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function scaffold(db: AppDatabase, connectorId: ConnectorId = "gemini-cli") {
const companyId = db.core.company.saveCompany({
name: "Session Edge Case Co",
description: "Session rotation edge case tests (D-05.3)",
status: "active",
});
// Always save gemini-cli (needed for most tests)
db.core.connectors.saveConnector({
id: "gemini-cli",
model: "gemini-pro",
envBindingText: "",
notes: "Test connector - no native context management",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: "/tmp/session-edge-test",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Worker Agent",
role: "developer",
title: "Developer",
status: "active",
reportsTo: null,
connectorId,
workspaceId,
model: null,
capabilities: "Executes tasks",
budgetMonthlyUsd: 100,
});
const goalId = db.core.entity.saveGoal({
companyId,
title: "Ship feature",
description: "Build session rotation edge case test",
ownerAgentId: agentId,
status: "active",
});
const projectId = db.core.entity.saveProject({
companyId,
goalId,
name: "Session Project",
description: "Testing session rotation during execution",
leadAgentId: agentId,
status: "in_progress",
targetDate: null,
});
const taskId = db.core.tasks.saveTask({
companyId,
projectId,
goalId,
title: "Implement session rotation feature",
description: "Build the session rotation logic for task execution edge cases.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "in_progress",
});
return { companyId, workspaceId, agentId, goalId, projectId, taskId };
}
// ---------------------------------------------------------------------------
// Tests -- Edge Case: Session Rotation During Execution (D-05.3)
// ---------------------------------------------------------------------------
describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
let tempDir: string;
let db: AppDatabase;
const mockLogger = { info: vi.fn(), warn: vi.fn(), error: vi.fn() };
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-session-edge-"));
db = new AppDatabase(tempDir);
db.init();
mockLogger.info.mockClear();
mockLogger.warn.mockClear();
mockLogger.error.mockClear();
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("rotation triggered when run count exceeds maxRuns threshold", () => {
const { companyId, agentId } = scaffold(db);
const sessionService = createSessionManagementService({ db, logger: mockLogger });
// Create a session
const sessionId = db.core.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
// Simulate runs until we reach the maxRuns threshold (20)
for (let i = 0; i < 20; i++) {
db.core.tasks.incrementSessionUsage(sessionId, 1000, 500, `run-${i}`);
}
// Get the updated session from DB
const session = db.core.tasks.getSessionById(sessionId);
expect(session).not.toBeNull();
expect(session!.runCount).toBe(20); // matches maxRuns threshold in session-management-service
// Evaluate rotation -- should trigger
const result = sessionService.evaluateRotation(session!);
expect(result.shouldRotate).toBe(true);
expect(result.reason).toBe("max_runs");
});
it("rotation triggered when token count exceeds maxTokens threshold", () => {
const { companyId, agentId } = scaffold(db);
const sessionService = createSessionManagementService({ db, logger: mockLogger });
const sessionId = db.core.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
// Accumulate tokens past the 500_000 maxTokens threshold in one big call
db.core.tasks.incrementSessionUsage(sessionId, 300000, 250000, "big-run");
const session = db.core.tasks.getSessionById(sessionId);
expect(session).not.toBeNull();
const totalTokens = session!.totalInputTokens + session!.totalOutputTokens;
expect(totalTokens).toBe(550000);
const result = sessionService.evaluateRotation(session!);
expect(result.shouldRotate).toBe(true);
expect(result.reason).toBe("max_tokens");
});
it("handoff markdown generated before next run starts", () => {
const { companyId, agentId, taskId, workspaceId } = scaffold(db);
const sessionService = createSessionManagementService({ db, logger: mockLogger });
const sessionId = db.core.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
// Accumulate enough to trigger rotation
for (let i = 0; i < 20; i++) {
db.core.tasks.incrementSessionUsage(sessionId, 1000, 500, `run-${i}`);
}
// Create a completed run for history (so generateHandoff has run data)
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "gemini-cli" as ConnectorId,
});
db.core.runs.finishRun({
runId: run.id,
status: "succeeded",
summary: "Completed initial implementation",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-pro",
unattributedCost: false,
});
const session = db.core.tasks.getSessionById(sessionId)!;
// generateHandoff is agent-scoped: it walks the agent's runs across
// all tasks + taskless heartbeats, not a single task.
const handoff = sessionService.generateHandoff(session);
// Verify handoff markdown content
expect(handoff).toContain("# Session Handoff");
expect(handoff).toContain("## Recent Run History");
// Archive the old session with handoff
db.core.tasks.archiveSession(sessionId, "max_runs", handoff);
// Verify archived session has handoff stored
const archivedRows = db.queryAll<{ handoffMarkdown: string | null }>(
"SELECT handoff_markdown as handoffMarkdown FROM task_sessions WHERE id = ?",
sessionId,
);
expect(archivedRows[0].handoffMarkdown).toContain("# Session Handoff");
});
it("new session created after rotation with clean counters", () => {
const { companyId, agentId } = scaffold(db);
// Create initial session
const oldSessionId = db.core.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
// Add usage to the old session
db.core.tasks.incrementSessionUsage(oldSessionId, 300000, 250000, "run-final");
// Archive the old session
db.core.tasks.archiveSession(oldSessionId, "max_tokens", "# Session Handoff\nPrevious work summary.");
// Create new session
const newSessionId = db.core.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
// Get the new active session
const newSession = db.core.tasks.getSessionById(newSessionId);
expect(newSession).not.toBeNull();
expect(newSession!.id).toBe(newSessionId);
expect(newSession!.id).not.toBe(oldSessionId);
// Verify clean counters
expect(newSession!.runCount).toBe(0);
expect(newSession!.totalInputTokens).toBe(0);
expect(newSession!.totalOutputTokens).toBe(0);
});
it("claude connector triggers rotation under pi-mono (no native exemption)", () => {
const { companyId, agentId } = scaffold(db, "gemini-cli");
const sessionService = createSessionManagementService({ db, logger: mockLogger });
const sessionId = db.core.tasks.createNewSession({
companyId,
agentId,
adapterType: "claude-pro",
});
// Accumulate way past all thresholds
for (let i = 0; i < 25; i++) {
db.core.tasks.incrementSessionUsage(sessionId, 30000, 20000, `run-${i}`);
}
const session = db.core.tasks.getSessionById(sessionId)!;
// Pi-mono delegates compaction to pi-agent-core; AgentCompany still applies rotation uniformly.
const result = sessionService.evaluateRotation(session);
expect(result.shouldRotate).toBe(true);
expect(result.reason).not.toBeNull();
});
it("codex connector triggers rotation under pi-mono (no native exemption)", () => {
const { companyId, agentId } = scaffold(db, "gemini-cli");
const sessionService = createSessionManagementService({ db, logger: mockLogger });
const sessionId = db.core.tasks.createNewSession({
companyId,
agentId,
adapterType: "openai-codex",
});
// Accumulate way past all thresholds
for (let i = 0; i < 25; i++) {
db.core.tasks.incrementSessionUsage(sessionId, 30000, 20000, `run-${i}`);
}
const session = db.core.tasks.getSessionById(sessionId)!;
// Pi-mono delegates compaction to pi-agent-core; AgentCompany still applies rotation uniformly.
const result = sessionService.evaluateRotation(session);
expect(result.shouldRotate).toBe(true);
expect(result.reason).not.toBeNull();
});
it("handoff markdown available for next run context after rotation", () => {
const { companyId, agentId, taskId, workspaceId } = scaffold(db);
const sessionService = createSessionManagementService({ db, logger: mockLogger });
// Create session and add usage
const sessionId = db.core.tasks.createNewSession({
companyId,
agentId,
adapterType: "gemini-cli",
});
db.core.tasks.incrementSessionUsage(sessionId, 300000, 200000, "run-1");
// Create a completed run for run history
const run = db.core.runs.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "gemini-cli" as ConnectorId,
});
db.core.runs.finishRun({
runId: run.id,
status: "succeeded",
summary: "Previous work done",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gemini-pro",
unattributedCost: false,
});
// Rotate the session (archives old, creates new)
const oldSession = db.core.tasks.getSessionById(sessionId)!;
sessionService.rotateSession(oldSession);
// After rotation, the handoff should be retrievable for the next run
const handoff = sessionService.getHandoffContext(taskId);
expect(handoff).not.toBeNull();
expect(handoff).toContain("# Session Handoff");
expect(handoff).toContain("Implement session rotation feature"); // task title from scaffold
});
});
-19
View File
@@ -1,19 +0,0 @@
import { describe, expect, it } from "vitest";
import { ErrorBoundary } from "../../src/renderer/components/ui";
describe("ErrorBoundary", () => {
it("resets a captured page error when the section changes", () => {
const error = new Error("boom");
const failedState = {
hasError: true,
error,
resetKey: "runs",
};
expect(ErrorBoundary.getDerivedStateFromProps({ children: null, resetKey: "tasks" }, failedState)).toEqual({
hasError: false,
error: null,
resetKey: "tasks",
});
});
});
-42
View File
@@ -1,42 +0,0 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("electron-log/main.js", () => ({
default: {
warn: vi.fn(),
},
}));
import log from "electron-log/main.js";
import { classifyUnknownError } from "../../src/main/errors/taxonomy";
const warn = vi.mocked(log.warn);
describe("error taxonomy", () => {
beforeEach(() => {
warn.mockClear();
});
it("warns once when raw thrown values cannot be JSON stringified", () => {
const raw: { self?: unknown } = {};
raw.self = raw;
const first = classifyUnknownError(raw, {
kind: "recoverable",
code: "test.raw.cyclic",
message: "Cyclic raw value.",
});
const second = classifyUnknownError(raw, {
kind: "recoverable",
code: "test.raw.cyclic",
message: "Cyclic raw value.",
});
expect(first.context?.raw).toBe("[object Object]");
expect(second.context?.raw).toBe("[object Object]");
expect(warn).toHaveBeenCalledTimes(1);
expect(warn).toHaveBeenCalledWith(
"[errors] stringifyRaw failed; using String(value) for raw thrown value.",
expect.stringContaining("circular"),
);
});
});
+116
View File
@@ -0,0 +1,116 @@
import { randomUUID } from "node:crypto";
import { mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ChatService } from "../../src/main/services/chat-service";
import { dispatchAgentTool, type AgentToolContext } from "../../src/main/services/agent-tools";
function call(name: string, args: Record<string, unknown>) {
return { id: `call_${randomUUID()}`, name, arguments: args };
}
interface Harness {
connection: DatabaseConnection;
orgId: string;
ceoId: string;
ctx: AgentToolContext;
deps: { db: DatabaseConnection["db"]; organization: OrganizationService; chat: ChatService };
}
function harness(mode: "ai_ceo" | "ai_cofounder"): Harness {
const connection = new DatabaseConnection(join(tmpdir(), `ac-mode-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
const workspaceRoot = join(tmpdir(), `ac-mode-ws-${randomUUID()}`);
mkdirSync(workspaceRoot, { recursive: true });
let orgId = "";
let ceoId = "";
let workspaceId = "";
connection.transaction(() => {
orgId = organization.createOrganization({
organizationName: "OrbitOps",
employeeName: "Yang",
mission: "m",
goal: "g",
executionMode: mode,
autoApproveHires: mode === "ai_ceo",
reviewDeliverables: mode === "ai_cofounder",
});
const providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null, roleLevel: "c_suite",
});
workspaceId = organization.createWorkspace({ orgId, name: "HQ", localPath: workspaceRoot, projectId: null });
});
return {
connection,
orgId,
ceoId,
ctx: {
orgId,
agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null,
taskGoalId: null,
workspace: { id: workspaceId, localPath: workspaceRoot },
},
deps: { db: connection.db, organization, chat: new ChatService(connection.db) },
};
}
function pendingApprovals(h: Harness, kind: string): Array<{ state: string; source_entity_type: string | null }> {
return h.connection.db.prepare(
"select state, source_entity_type from approvals where org_id = ? and kind = ?",
).all(h.orgId, kind) as Array<{ state: string; source_entity_type: string | null }>;
}
describe("execution modes", () => {
test("AI Co-Founder mode queues every deliverable and document for board review", () => {
const h = harness("ai_cofounder");
const wrote = dispatchAgentTool(h.deps, h.ctx, call("write_deliverable", { path: "plan/business-plan.md", content: "# Plan" }));
expect(wrote).toContain("queued for board review");
const doc = dispatchAgentTool(h.deps, h.ctx, call("create_document", { title: "Launch memo", doc_type: "memo", content: "Ship it." }));
expect(doc).toContain("queued for board review");
const reviews = pendingApprovals(h, "deliverable_review");
expect(reviews).toHaveLength(2);
expect(reviews.every((review) => review.state === "pending")).toBe(true);
expect(reviews.map((review) => review.source_entity_type).sort()).toEqual(["document", "file"]);
});
test("AI CEO mode ships deliverables without review", () => {
const h = harness("ai_ceo");
const wrote = dispatchAgentTool(h.deps, h.ctx, call("write_deliverable", { path: "plan/business-plan.md", content: "# Plan" }));
expect(wrote).not.toContain("board review");
dispatchAgentTool(h.deps, h.ctx, call("create_document", { title: "Launch memo", doc_type: "memo", content: "Ship it." }));
expect(pendingApprovals(h, "deliverable_review")).toHaveLength(0);
});
test("AI Co-Founder mode holds above-threshold hires; AI CEO mode clears them", () => {
// Hiring policies require review above $5,000/month; the mode decides
// whether that review waits for the operator or clears automatically.
const hireAva = (h: Harness) => {
const providerId = (h.connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(h.orgId) as { id: string }).id;
h.connection.transaction(() => h.deps.organization.createAgent({
orgId: h.orgId, name: "Ava", role: "Designer", department: "Product",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 600000, managerAgentId: h.ceoId,
}));
};
const cofounder = harness("ai_cofounder");
hireAva(cofounder);
const held = cofounder.connection.db.prepare(
"select state from approvals where org_id = ? and kind = 'agent_hire' and title like '%Ava%'",
).get(cofounder.orgId) as { state: string };
expect(held.state).toBe("pending");
const ceoMode = harness("ai_ceo");
hireAva(ceoMode);
const cleared = ceoMode.connection.db.prepare(
"select state from approvals where org_id = ? and kind = 'agent_hire' and title like '%Ava%'",
).get(ceoMode.orgId) as { state: string };
expect(cleared.state).toBe("approved");
});
});
-23
View File
@@ -1,23 +0,0 @@
import { describe, expect, it } from "vitest";
import {
classifyExternalUrl,
normalizeExternalHttpUrl,
} from "../../src/main/external-url-policy";
describe("external URL policy", () => {
it("normalizes http and https URLs", () => {
expect(normalizeExternalHttpUrl(" https://github.com/agentcompany ")).toBe("https://github.com/agentcompany");
expect(normalizeExternalHttpUrl("http://example.com/path?q=1")).toBe("http://example.com/path?q=1");
});
it("blocks custom schemes and credential-bearing URLs", () => {
expect(classifyExternalUrl("vscode://file/etc/passwd")).toMatchObject({ kind: "blocked" });
expect(classifyExternalUrl("https://user:pass@example.com/report")).toMatchObject({ kind: "blocked" });
});
it("separates trusted hosts from confirmation hosts", () => {
expect(classifyExternalUrl("https://github.com/acme/repo")).toMatchObject({ kind: "trusted", host: "github.com" });
expect(classifyExternalUrl("http://github.com/acme/repo")).toMatchObject({ kind: "confirm", host: "github.com" });
expect(classifyExternalUrl("https://example.com/report")).toMatchObject({ kind: "confirm", host: "example.com" });
});
});
+14
View File
@@ -0,0 +1,14 @@
import { describe, expect, test } from "vitest";
import { normalizeExternalBrowserUrl } from "../../src/main/app/external-url";
describe("external browser URL validation", () => {
test("normalizes HTTPS URLs and preserves query strings", () => {
expect(normalizeExternalBrowserUrl("https://example.com/docs?q=agent")).toBe("https://example.com/docs?q=agent");
});
test("rejects non-HTTPS and credential-bearing URLs", () => {
expect(() => normalizeExternalBrowserUrl("http://example.com")).toThrow("External URLs must use HTTPS.");
expect(() => normalizeExternalBrowserUrl("mailto:security@example.com")).toThrow("External URLs must use HTTPS.");
expect(() => normalizeExternalBrowserUrl("https://user:token@example.com")).toThrow("External URLs must not include credentials.");
});
});
-241
View File
@@ -1,241 +0,0 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
handleFailureEscalation,
handleSuccessReset,
type EscalationDeps,
} from "@main/worker/observability/failure-escalation";
/**
* Tests for the retry-aware failure escalation pipeline.
*
* The escalation state machine:
* 1. Task fails with retry_count < MAX_RETRY_ATTEMPTS (2) -> retry same agent after cooldown
* 2. Task fails with retry_count >= MAX_RETRY_ATTEMPTS -> escalate to manager chain
* 3. No manager in chain -> fall through to CEO
* 4. No CEO available -> notify user
* 5. Task succeeds -> reset retry count
*/
const MAX_RETRY_ATTEMPTS = 2;
const RETRY_COOLDOWN_MS = 30_000;
function createMockDeps(): EscalationDeps {
return {
db: {
core: {
tasks: {
getTaskRetryCount: vi.fn().mockReturnValue(0),
incrementTaskRetryCount: vi.fn(),
resetTaskRetryCount: vi.fn(),
advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_progress" }),
},
agents: {
getChainOfCommand: vi.fn().mockReturnValue([]),
getCeoForCompany: vi.fn().mockReturnValue(null),
},
},
},
wakeAgentIfPossible: vi.fn().mockReturnValue("wake-id"),
notify: vi.fn(),
logger: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
};
}
const RUN = { taskId: "task-1", agentId: "agent-1", companyId: "company-1" };
const TASK = { title: "Implement feature X" };
describe("Failure Escalation Pipeline", () => {
let deps: EscalationDeps;
let scheduleRetry: ReturnType<typeof vi.fn<(fn: () => void, delayMs: number) => void>>;
beforeEach(() => {
deps = createMockDeps();
scheduleRetry = vi.fn<(fn: () => void, delayMs: number) => void>();
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
describe("Retry path (count < MAX_RETRY_ATTEMPTS)", () => {
it("retries on first failure (retry_count=0)", () => {
(deps.db.core.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(0);
const result = handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("retry");
expect(deps.db.core.tasks.incrementTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.db.core.tasks.advanceTaskStatus).toHaveBeenCalledWith(
"task-1",
"todo",
expect.stringContaining("attempt 1/2"),
);
expect(scheduleRetry).toHaveBeenCalled();
});
it("retries on second failure (retry_count=1)", () => {
(deps.db.core.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(1);
const result = handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("retry");
expect(deps.db.core.tasks.incrementTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.db.core.tasks.advanceTaskStatus).toHaveBeenCalledWith(
"task-1",
"todo",
expect.stringContaining("attempt 2/2"),
);
expect(scheduleRetry).toHaveBeenCalled();
});
it("calls wakeAgentIfPossible via scheduleRetry callback", () => {
(deps.db.core.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(0);
handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
// Invoke the scheduled callback synchronously to verify it wakes the agent
const [fn] = scheduleRetry.mock.calls[0]!;
fn();
expect(deps.wakeAgentIfPossible).toHaveBeenCalledWith("agent-1", "company-1", "retry_failed");
});
});
describe("Escalation path (count >= MAX_RETRY_ATTEMPTS)", () => {
it("escalates to chain[0] manager after max retries", () => {
(deps.db.core.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.core.agents.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([
{ id: "manager-1", name: "Manager Alpha" },
]);
const result = handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("escalated");
expect(deps.db.core.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.wakeAgentIfPossible).toHaveBeenCalledWith("manager-1", "company-1", "report_failed");
expect(deps.notify).not.toHaveBeenCalled();
});
it("falls through to CEO when chain is empty", () => {
(deps.db.core.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.core.agents.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([]);
(deps.db.core.agents.getCeoForCompany as ReturnType<typeof vi.fn>).mockReturnValue({ id: "ceo-1", name: "CEO Boss" });
const result = handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("escalated");
expect(deps.db.core.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1");
expect(deps.wakeAgentIfPossible).toHaveBeenCalledWith("ceo-1", "company-1", "report_failed");
expect(deps.notify).not.toHaveBeenCalled();
});
it("falls through to CEO when chain lookup throws", () => {
(deps.db.core.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.core.agents.getChainOfCommand as ReturnType<typeof vi.fn>).mockImplementation(() => {
throw new Error("Agent deleted");
});
(deps.db.core.agents.getCeoForCompany as ReturnType<typeof vi.fn>).mockReturnValue({ id: "ceo-1", name: "CEO Boss" });
const result = handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("escalated");
expect(deps.wakeAgentIfPossible).toHaveBeenCalledWith("ceo-1", "company-1", "report_failed");
});
it("notifies user when both chain and CEO are unavailable", () => {
(deps.db.core.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.core.agents.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([]);
(deps.db.core.agents.getCeoForCompany as ReturnType<typeof vi.fn>).mockReturnValue(null);
const result = handleFailureEscalation(
deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("user_notified");
expect(deps.notify).toHaveBeenCalledWith(
expect.stringContaining("manual intervention"),
expect.stringContaining("Implement feature X"),
);
expect(deps.notify).toHaveBeenCalledWith(
expect.any(String),
expect.stringContaining("no manager is available"),
);
});
it("uses taskId as fallback when task is null", () => {
(deps.db.core.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.core.agents.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([]);
(deps.db.core.agents.getCeoForCompany as ReturnType<typeof vi.fn>).mockReturnValue(null);
const result = handleFailureEscalation(
deps, RUN, null, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("user_notified");
expect(deps.notify).toHaveBeenCalledWith(
expect.any(String),
expect.stringContaining("task-1"),
);
});
});
describe("Timed out runs follow same path", () => {
it("retries timed_out runs the same as failed runs", () => {
(deps.db.core.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(0);
const result = handleFailureEscalation(
deps, RUN, TASK, "timed_out", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("retry");
expect(deps.db.core.tasks.incrementTaskRetryCount).toHaveBeenCalledWith("task-1");
});
it("escalates timed_out runs after max retries", () => {
(deps.db.core.tasks.getTaskRetryCount as ReturnType<typeof vi.fn>).mockReturnValue(2);
(deps.db.core.agents.getChainOfCommand as ReturnType<typeof vi.fn>).mockReturnValue([
{ id: "manager-1", name: "Manager Alpha" },
]);
const result = handleFailureEscalation(
deps, RUN, TASK, "timed_out", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry,
);
expect(result.action).toBe("escalated");
expect(deps.db.core.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1");
});
});
describe("Success resets retry count", () => {
it("resets retry_count on successful completion", () => {
handleSuccessReset(deps.db, "task-1");
expect(deps.db.core.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1");
});
it("handles reset errors gracefully", () => {
(deps.db.core.tasks.resetTaskRetryCount as ReturnType<typeof vi.fn>).mockImplementation(() => {
throw new Error("DB error");
});
expect(() => {
handleSuccessReset(deps.db, "task-1");
}).not.toThrow();
});
});
});
+41
View File
@@ -0,0 +1,41 @@
import { describe, expect, test } from "vitest";
import { collectDepartments } from "../../src/renderer/src/pages/files/file-helpers";
import type { AgentRecord, FileObjectRecord } from "../../src/shared/domain";
function agent(id: string, department: string): AgentRecord {
return {
id, orgId: "org", name: id, role: "engineer", department,
modelProviderId: null, modelId: null, budgetCents: 0, managerAgentId: null,
title: id, roleLevel: "ic", modelStrategy: "single", workspaceId: null,
heartbeatEnabled: false, heartbeatIntervalSec: 0, lifecycle: "active", createdAt: "2026-01-01",
};
}
function file(id: string, ownerType: FileObjectRecord["ownerType"], ownerId: string | null): FileObjectRecord {
return {
id, orgId: "org", name: id, kind: "doc", sizeBytes: 1, sha256: "x", storagePath: `/tmp/${id}`,
ownerType, ownerId, spaceId: null, pinned: false, scanState: "approved", scanLabelsJson: "[]",
scannedAt: null, createdAt: "2026-01-01", updatedAt: null,
};
}
describe("collectDepartments", () => {
test("counts files per department, not agents, and omits empty departments", () => {
const agents = [agent("a1", "Engineering"), agent("a2", "Engineering"), agent("a3", "Design"), agent("a4", "Sales")];
const files = [
file("f1", "agent", "a1"),
file("f2", "agent", "a1"),
file("f3", "agent", "a3"),
file("f4", "employee", "emp1"), // not owned by an agent → ignored
];
// Engineering has two agents but only a1 holds files (2); Design has 1; Sales has agents but no files.
expect(collectDepartments(files, agents)).toEqual([
{ name: "Design", count: 1 },
{ name: "Engineering", count: 2 },
]);
});
test("returns nothing when no agent owns a file", () => {
expect(collectDepartments([], [agent("a1", "Engineering")])).toEqual([]);
});
});
+100
View File
@@ -0,0 +1,100 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { ApprovalService } from "../../src/main/services/approval-service";
import { IdentityAccessService } from "../../src/main/services/identity-access-service";
import { OrganizationService } from "../../src/main/services/organization-service";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { RISK_LEVEL, ROLE_KEY } from "../../src/shared/domain";
function createConnection(): DatabaseConnection {
return new DatabaseConnection(join(tmpdir(), `agentcompany-new-identity-${randomUUID()}.db`));
}
function createOrganization(organization: OrganizationService): string {
return organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
}
function firstEmployeeId(connection: DatabaseConnection, orgId: string): string {
return (connection.db.prepare("select id from employees where org_id = ? order by created_at asc limit 1")
.get(orgId) as { id: string }).id;
}
describe("identity access service", () => {
test("syncs SSO employee identities and grants scoped roles", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const identityAccess = new IdentityAccessService(connection.db);
const snapshot = new SnapshotService(connection.db);
let orgId = "";
let employeeId = "";
connection.transaction(() => {
orgId = createOrganization(organization);
employeeId = firstEmployeeId(connection, orgId);
identityAccess.syncEmployeeIdentity({
orgId,
employeeId,
source: "sso",
externalSubject: "okta-00u1",
email: "dana@example.com",
});
identityAccess.grantRole({
orgId,
principalType: "employee",
principalId: employeeId,
roleKey: ROLE_KEY.APPROVAL_DECIDER,
scopeType: "organization",
scopeId: orgId,
grantedByEmployeeId: employeeId,
});
});
const state = snapshot.load();
expect(state.employeeIdentities.some((identity) => identity.source === "sso" && identity.email === "dana@example.com")).toBe(true);
expect(state.roleGrants.some((grant) => grant.principalId === employeeId && grant.roleKey === ROLE_KEY.APPROVAL_DECIDER)).toBe(true);
expect(state.auditEvents.some((event) => event.entityType === "employee_identity" && event.action === "synced")).toBe(true);
connection.close();
});
test("records denied access decisions when approval decider role is missing", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const approvals = new ApprovalService(connection.db);
let orgId = "";
let employeeId = "";
let approvalId = "";
connection.transaction(() => {
orgId = createOrganization(organization);
employeeId = firstEmployeeId(connection, orgId);
connection.db.prepare("delete from role_grants where org_id = ? and principal_id = ?").run(orgId, employeeId);
approvalId = approvals.create({
orgId,
kind: "release",
title: "Release approval",
details: "Approve release train.",
risk: RISK_LEVEL.MEDIUM,
requestedByType: "system",
requestedById: null,
});
});
expect(() => approvals.decide({
orgId,
approvalId,
state: "approved",
decidedByEmployeeId: employeeId,
})).toThrow("Employee requires approval_decider role.");
expect(connection.db.prepare("select decision, reason from access_decisions where org_id = ?").all(orgId))
.toEqual([{ decision: "deny", reason: "missing_role" }]);
connection.close();
});
});
@@ -1,50 +0,0 @@
import { describe, expect, it } from "vitest";
import { InboundCorrelationRegistryStore } from "../../src/main/agent-runtime/inbound-correlation-registry";
import type { AgentMessageRecord } from "../../src/shared/types";
function makeMessage(index: number): AgentMessageRecord {
return {
id: `message-${index}`,
companyId: "company-1",
fromAgentId: `agent-${index}`,
toAgentId: "agent-recipient",
senderKind: "agent",
channel: "direct",
channelTargetId: null,
subject: `message ${index}`,
body: `body ${index}`,
priority: "normal",
readAt: null,
parentMessageId: null,
attachmentsJson: "[]",
createdAt: "2026-04-22T00:00:00.000Z",
};
}
describe("InboundCorrelationRegistryStore", () => {
it("preserves every inbound correlation for the active run", () => {
const store = new InboundCorrelationRegistryStore();
const timestamps: number[] = [];
for (let i = 0; i < 700; i += 1) {
timestamps.push(store.register("run-1", makeMessage(i), 1_000));
}
expect(new Set(timestamps).size).toBe(700);
for (let i = 0; i < timestamps.length; i += 1) {
expect(store.lookup("run-1", timestamps[i])).toMatchObject({
fromAgentId: `agent-${i}`,
messageId: `message-${i}`,
});
}
});
it("drops correlations only when the run is released", () => {
const store = new InboundCorrelationRegistryStore();
const timestamp = store.register("run-1", makeMessage(1), 1_000);
expect(store.lookup("run-1", timestamp)?.messageId).toBe("message-1");
store.deleteRun("run-1");
expect(store.lookup("run-1", timestamp)).toBeNull();
});
});
-137
View File
@@ -1,137 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import type { AppDatabase } from "@main/database";
import { resolveInternalToolsForAgent } from "@main/orchestration/internal-tool-selector";
import type { AgentRecord } from "@shared/types";
function makeAgent(overrides: Partial<AgentRecord>): AgentRecord {
return {
id: "agent-1",
companyId: "company-1",
name: "Agent",
role: "Engineer",
title: "Engineer",
roleLevel: "ic",
department: "engineering",
status: "idle",
reportsTo: "ceo-1",
connectorId: "openai-codex",
workspaceId: null,
model: null,
capabilities: "",
budgetMonthlyUsd: 100,
spentMonthlyUsd: 0,
metadataJson: "{}",
heartbeatEnabled: true,
heartbeatIntervalSec: 120,
lastHeartbeatAt: null,
createdAt: "2026-04-22T00:00:00.000Z",
updatedAt: "2026-04-22T00:00:00.000Z",
...overrides,
};
}
function makeDb(
agent: AgentRecord,
reports: AgentRecord[],
approvals: Record<string, Array<{ id: string; state: string }>> = {},
): AppDatabase {
return {
core: {
entity: {
getAgent: vi.fn((agentId: string) => {
const found = [agent, ...reports].find((candidate) => candidate.id === agentId);
if (!found) throw new Error(`Agent not found: ${agentId}`);
return found;
}),
},
agents: {
hasAgentPermission: vi.fn(() => false),
getDirectReports: vi.fn(() => reports),
getChainOfCommand: vi.fn(() => []),
},
approvals: {
listCompanyApprovalsByType: vi.fn((_companyId: string, type: string) => approvals[type] ?? []),
},
},
} as unknown as AppDatabase;
}
describe("resolveInternalToolsForAgent", () => {
it("exposes only hire_agent during initial CEO team building", () => {
const ceo = makeAgent({
id: "ceo-1",
role: "CEO",
title: "CEO",
roleLevel: "c_suite",
department: "executive",
reportsTo: null,
});
const db = makeDb(ceo, []);
expect(resolveInternalToolsForAgent(db, ceo)).toEqual(["hire_agent"]);
});
it("exposes file_approval after a hire is in flight", () => {
const ceo = makeAgent({
id: "ceo-1",
role: "CEO",
title: "CEO",
roleLevel: "c_suite",
department: "executive",
reportsTo: null,
});
const pendingHire = makeAgent({
id: "pending-cto",
role: "CTO",
title: "CTO",
reportsTo: ceo.id,
status: "pending_approval",
});
const db = makeDb(ceo, [pendingHire], {
hire_agent: [{ id: "approval-1", state: "pending" }],
});
expect(resolveInternalToolsForAgent(db, ceo)).toEqual(["hire_agent", "file_approval"]);
});
it("exposes file_approval when the CEO is operating with active reports", () => {
const ceo = makeAgent({
id: "ceo-1",
role: "CEO",
title: "CEO",
roleLevel: "c_suite",
department: "executive",
reportsTo: null,
});
const cto = makeAgent({
id: "cto-1",
role: "CTO",
title: "CTO",
reportsTo: ceo.id,
status: "idle",
});
const db = makeDb(ceo, [cto]);
expect(resolveInternalToolsForAgent(db, ceo)).toEqual(["hire_agent", "file_approval"]);
});
it("keeps CEO strategy approval scoped to CEO agents", () => {
const manager = makeAgent({
id: "manager-1",
role: "VP Engineering",
title: "VP Engineering",
roleLevel: "vp",
reportsTo: "ceo-1",
});
const ic = makeAgent({
id: "ic-1",
role: "Backend Engineer",
title: "Backend Engineer",
reportsTo: manager.id,
status: "idle",
});
const db = makeDb(manager, [ic]);
expect(resolveInternalToolsForAgent(db, manager)).toEqual(["hire_agent"]);
});
});
@@ -1,115 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { IpcMainInvokeEvent } from "electron";
import type { DesktopResult } from "@shared/contracts";
import { IPC_CHANNELS } from "@shared/contracts";
import { AppDatabase } from "../../src/main/database";
import { registerAgentCoordinationHandlers } from "../../src/main/ipc-agent-coordination-handlers";
import { HUMAN_SENDER_ID } from "../../src/main/messaging/dispatch";
type IpcHandler = (
event: IpcMainInvokeEvent,
...args: unknown[]
) => Promise<DesktopResult<unknown>> | DesktopResult<unknown>;
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.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "IPC Message Co",
description: "Tests desktop message routing",
status: "active",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: "/tmp/ipc-message",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Lead Engineer",
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Ship features",
budgetMonthlyUsd: 180,
});
return { companyId, agentId };
}
describe("ipc agent coordination handlers", () => {
it("routes desktop messages into a recipient's live runtime before falling back to wake", async () => {
const profileDir = await createTempDir("agentcompany-ipc-message-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, agentId } = createScaffold(db);
const handlers = new Map<string, IpcHandler>();
const injectRuntimeMessage = vi.fn().mockReturnValue(true);
const wakeAgentIfPossible = vi.fn().mockReturnValue("queued-run");
const dependencies = {
registerHandle: (channel: string, handler: IpcHandler) => {
handlers.set(channel, handler);
},
db,
ensureCompanyExists: () => null,
publishDomainChanged: vi.fn(),
emitEvent: vi.fn(),
notify: vi.fn(),
wakeAgentIfPossible,
injectRuntimeMessage,
onHumanMessage: vi.fn(),
};
registerAgentCoordinationHandlers(dependencies);
const sendAgentMessage = handlers.get(IPC_CHANNELS.sendAgentMessage);
if (!sendAgentMessage) {
throw new Error("sendAgentMessage handler was not registered.");
}
const result = await sendAgentMessage(null as unknown as IpcMainInvokeEvent, {
companyId,
fromAgentId: HUMAN_SENDER_ID,
toAgentId: agentId,
channel: "direct",
subject: "live steer",
body: "Please use this new context now.",
priority: "normal",
});
expect(result.ok).toBe(true);
expect(injectRuntimeMessage).toHaveBeenCalledTimes(1);
expect(injectRuntimeMessage).toHaveBeenCalledWith(agentId, companyId, expect.objectContaining({
body: "Please use this new context now.",
}));
expect(wakeAgentIfPossible).not.toHaveBeenCalled();
db.close();
});
});
-107
View File
@@ -1,107 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { IpcMainInvokeEvent } from "electron";
import type { DesktopResult } from "@shared/contracts";
import { IPC_CHANNELS } from "@shared/contracts";
import { AppDatabase } from "../../src/main/database";
import { registerCoreAgentGraphHandlers } from "../../src/main/ipc-core-agent-graph-handlers";
type IpcHandler = (
event: IpcMainInvokeEvent,
...args: unknown[]
) => Promise<DesktopResult<unknown>> | DesktopResult<unknown>;
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.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Handler Test Co",
description: "Tests saveAgent model validation",
status: "active",
});
db.core.settings.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const handlers = new Map<string, IpcHandler>();
registerCoreAgentGraphHandlers({
registerHandle: (channel, handler) => {
handlers.set(channel, handler);
},
db,
ensureCompanyExists: (id) => (db.core.settings.hasCompany(id) ? null : { ok: false, error: { code: "COMPANY_NOT_FOUND", message: "missing" } }),
findInvalidReference: () => null,
publishDomainChanged: vi.fn(),
handleApprovalResolved: vi.fn(),
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
});
const saveAgent = handlers.get(IPC_CHANNELS.saveAgent);
if (!saveAgent) {
throw new Error("saveAgent handler was not registered.");
}
const result = await saveAgent(null as unknown as IpcMainInvokeEvent, {
id: agentId,
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
department: "executive",
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
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,70 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { IpcMainInvokeEvent } from "electron";
import type { DesktopResult } from "@shared/contracts";
import { IPC_CHANNELS } from "@shared/contracts";
import { AppDatabase } from "../../src/main/database";
import { registerCoreAdminHandlers } from "../../src/main/ipc-core-admin-handlers";
type IpcHandler = (
event: IpcMainInvokeEvent,
...args: unknown[]
) => Promise<DesktopResult<unknown>> | DesktopResult<unknown>;
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 a valid pi-ai model for a pi-mono connector", async () => {
const profileDir = await createTempDir("agentcompany-admin-handler-");
const db = new AppDatabase(profileDir);
db.init();
const { getModels } = await import("@mariozechner/pi-ai");
const anthropicModel = getModels("anthropic" as never)[0]?.id;
if (!anthropicModel) {
throw new Error("pi-ai anthropic provider must expose at least one model.");
}
const handlers = new Map<string, IpcHandler>();
registerCoreAdminHandlers({
registerHandle: (channel, handler) => {
handlers.set(channel, handler);
},
db,
ensureCompanyExists: () => null,
publishDomainChanged: vi.fn(),
handleApprovalResolved: vi.fn(),
checkConnector: 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 as unknown as IpcMainInvokeEvent, {
id: "anthropic",
model: anthropicModel,
envBindingText: "ANTHROPIC_BASE_URL=https://example.com/anthropic",
notes: "Pi-mono connector",
}) as { ok: boolean; error?: { code: string; message: string } };
expect(result.ok).toBe(true);
expect(db.core.connectors.getConnector("anthropic").model).toBe(anthropicModel);
db.close();
});
});
-100
View File
@@ -1,100 +0,0 @@
import type { IpcMainInvokeEvent } from "electron";
import { describe, expect, it, vi } from "vitest";
import type { DesktopResult } from "@shared/contracts";
import { IPC_CHANNELS } from "@shared/contracts";
import type { AppDatabase } from "../../src/main/database";
import { registerCoreReadHandlers } from "../../src/main/ipc-core-read-handlers";
import type { RegisterCoreHandlersDependencies } from "../../src/main/ipc-core-common";
vi.mock("electron", () => ({
dialog: {
showMessageBox: vi.fn(),
showOpenDialog: vi.fn(),
},
nativeTheme: { themeSource: "system" },
shell: {
openExternal: vi.fn(),
openPath: vi.fn(),
},
}));
type IpcHandler = (
event: IpcMainInvokeEvent,
...args: unknown[]
) => Promise<DesktopResult<unknown>> | DesktopResult<unknown>;
const companyId = "11111111-1111-4111-8111-111111111111";
function createReadHandlerHarness(ensureCompanyExists: RegisterCoreHandlersDependencies["ensureCompanyExists"]) {
const handlers = new Map<string, IpcHandler>();
const getDisabledModels = vi.fn(() => [{ id: "claude-sonnet-4-5", label: "Claude Sonnet 4.5" }]);
const db = {
core: {
settings: {
getCurrentCompanyId: vi.fn(() => companyId),
getDisabledModels,
},
},
projection: {
snapshot: {
listRendererSnapshot: vi.fn(() => ({})),
},
},
} as unknown as AppDatabase;
registerCoreReadHandlers({
registerHandle: (channel, handler) => {
handlers.set(channel, handler);
},
db,
ensureCompanyExists,
publishDomainChanged: vi.fn(),
checkForUpdates: vi.fn(async () => true),
resetAfterRestore: vi.fn(),
listConnectorModels: vi.fn(async () => []),
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
});
const handler = handlers.get(IPC_CHANNELS.getConnectorDisabledModels);
if (!handler) {
throw new Error("getConnectorDisabledModels handler was not registered.");
}
return { handler, getDisabledModels };
}
describe("ipc-core-read-handlers connector model pool reads", () => {
it("reads disabled models after confirming the company exists", async () => {
const ensureCompanyExists = vi.fn(() => null);
const { handler, getDisabledModels } = createReadHandlerHarness(ensureCompanyExists);
const result = await handler(null as unknown as IpcMainInvokeEvent, {
companyId,
connectorId: "anthropic",
});
expect(result).toEqual({
ok: true,
data: [{ id: "claude-sonnet-4-5", label: "Claude Sonnet 4.5" }],
});
expect(ensureCompanyExists).toHaveBeenCalledWith(companyId);
expect(getDisabledModels).toHaveBeenCalledWith(companyId, "anthropic");
});
it("returns the company boundary error before reading disabled models", async () => {
const companyError = {
ok: false,
error: { code: "COMPANY_NOT_FOUND", message: "Company not found." },
} satisfies DesktopResult<never>;
const ensureCompanyExists = vi.fn(() => companyError);
const { handler, getDisabledModels } = createReadHandlerHarness(ensureCompanyExists);
const result = await handler(null as unknown as IpcMainInvokeEvent, {
companyId,
connectorId: "anthropic",
});
expect(result).toEqual(companyError);
expect(ensureCompanyExists).toHaveBeenCalledWith(companyId);
expect(getDisabledModels).not.toHaveBeenCalled();
});
});
-256
View File
@@ -1,256 +0,0 @@
import type { IpcMainInvokeEvent } from "electron";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { DesktopResult } from "../../src/shared/contracts";
import { IPC_CHANNELS } from "../../src/shared/contracts";
import type { CredentialResolver } from "../../src/main/credentials/credential-resolver";
import type { CredentialStore } from "../../src/main/credentials/credential-store";
const streamMocks = vi.hoisted(() => ({
safeStreamSimpleWithEnv: vi.fn(),
}));
vi.mock("electron", () => ({
ipcMain: {
removeHandler: vi.fn(),
},
}));
vi.mock("../../src/main/worker/runtime/pi-safe-stream", () => ({
safeStreamSimpleWithEnv: streamMocks.safeStreamSimpleWithEnv,
}));
import { registerCredentialIpcHandlers } from "../../src/main/ipc-credentials-handlers";
type IpcHandler = (
event: IpcMainInvokeEvent,
...args: unknown[]
) => Promise<DesktopResult<unknown>> | DesktopResult<unknown>;
function createDeps(overrides: { resolver?: Partial<CredentialResolver>; store?: Partial<CredentialStore> } = {}) {
const handlers = new Map<string, IpcHandler>();
const logger = {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const resolver = {
resolve: vi.fn(async () => ({
apiKey: "sk-proj-testcredentialsecret0123456789",
})),
...(overrides.resolver ?? {}),
} as unknown as CredentialResolver;
registerCredentialIpcHandlers({
store: {
list: vi.fn(() => []),
save: vi.fn(),
remove: vi.fn(),
...(overrides.store ?? {}),
} as unknown as CredentialStore,
resolver,
emitEvent: vi.fn(),
refreshConnectorHealth: vi.fn(async () => {}),
registerHandle: (channel, handler) => {
handlers.set(channel, handler);
},
fail: (code, message) => ({ ok: false, error: { code, message } }),
logger,
});
const handler = handlers.get(IPC_CHANNELS.credentialsTest);
if (!handler) {
throw new Error("credentialsTest handler was not registered.");
}
return { handler, handlers, logger };
}
async function runCredentialTest(handler: IpcHandler) {
return handler(null as unknown as IpcMainInvokeEvent, { connectorId: "fireworks" }) as Promise<DesktopResult<{
ok: boolean;
message: string;
}>>;
}
function requireHandler(handlers: Map<string, IpcHandler>, channel: string) {
const handler = handlers.get(channel);
if (!handler) {
throw new Error(`${channel} handler was not registered.`);
}
return handler;
}
function joinLogCalls(mock: ReturnType<typeof vi.fn>) {
return mock.mock.calls.flat().map(String).join(" ");
}
describe("credential IPC handler logging", () => {
beforeEach(() => {
streamMocks.safeStreamSimpleWithEnv.mockReset();
});
it("redacts resolver failures for the UI and logs only safe credential test metadata", async () => {
const secret = "sk-proj-resolversecret012345678901234";
const error = Object.assign(
new Error(`failed for ${secret} at https://api.fireworks.ai/inference/v1`),
{ code: "CREDENTIAL_INVALID", $metadata: { requestId: "req_resolver_123" } },
);
const { handler, logger } = createDeps({
resolver: {
resolve: vi.fn(async () => {
throw error;
}),
},
});
const result = await runCredentialTest(handler);
const log = joinLogCalls(logger.error);
expect(result).toEqual({
ok: true,
data: {
ok: false,
message: "failed for sk-proj-[REDACTED] at https://api.fireworks.ai/inference/v1",
},
});
expect(log).toBe("[credentials] test failed: connector=fireworks provider=fireworks code=CREDENTIAL_INVALID requestId=req_resolver_123");
expect(log).not.toContain(secret);
expect(log).not.toContain("api.fireworks.ai");
});
it("keeps stream error messages out of credential test logs", async () => {
const secret = "Bearer eyJhbGciOiJIUzI1NiJ9.secret.payload";
streamMocks.safeStreamSimpleWithEnv.mockReturnValueOnce({
result: vi.fn(async () => ({
stopReason: "error",
errorMessage: `provider rejected ${secret} for https://api.fireworks.ai/inference/v1`,
})),
});
const { handler, logger } = createDeps();
const result = await runCredentialTest(handler);
const log = joinLogCalls(logger.warn);
expect(result).toEqual({
ok: true,
data: {
ok: false,
message: "provider rejected Bearer [REDACTED] for https://api.fireworks.ai/inference/v1",
},
});
expect(log).toBe("[credentials] test failed: connector=fireworks provider=fireworks code=stream_error");
expect(log).not.toContain(secret);
expect(log).not.toContain("api.fireworks.ai");
});
it("logs safe code and request id when credential test streaming throws", async () => {
const secret = "AKIA1234567890ABCDEF";
const error = Object.assign(
new Error(`upstream threw ${secret} at https://bedrock-runtime.us-east-1.amazonaws.com`),
{
code: "AccessDeniedException",
response: { headers: new Map([["x-request-id", "req_stream_456"]]) },
},
);
streamMocks.safeStreamSimpleWithEnv.mockReturnValueOnce({
result: vi.fn(async () => {
throw error;
}),
});
const { handler, logger } = createDeps();
const result = await runCredentialTest(handler);
const log = joinLogCalls(logger.error);
expect(result).toEqual({
ok: true,
data: {
ok: false,
message: "upstream threw AKIA[REDACTED] at https://bedrock-runtime.us-east-1.amazonaws.com",
},
});
expect(log).toBe("[credentials] test failed: connector=fireworks provider=fireworks code=AccessDeniedException requestId=req_stream_456");
expect(log).not.toContain(secret);
expect(log).not.toContain("bedrock-runtime.us-east-1.amazonaws.com");
});
it("keeps raw save failure messages out of credential logs", async () => {
const secret = "sk-proj-savesecret012345678901234";
const error = Object.assign(
new Error(`save failed for ${secret} at https://api.fireworks.ai/account/acme`),
{ requestId: "req_save_789" },
);
const { handlers, logger } = createDeps({
store: {
save: vi.fn(() => {
throw error;
}),
},
});
const saveHandler = requireHandler(handlers, IPC_CHANNELS.credentialsSave);
const result = await saveHandler(null as unknown as IpcMainInvokeEvent, {
connectorId: "fireworks",
payload: {
kind: "api_key",
value: { source: "literal", value: secret },
},
});
const log = joinLogCalls(logger.error);
expect(result).toEqual({
ok: false,
error: {
code: "SAVE_FAILED",
message: "Could not save credential. Check application logs for details.",
},
});
expect(log).toBe("[credentials] save failed: connector=fireworks provider=fireworks code=credentials.save.failed requestId=req_save_789");
expect(log).not.toContain(secret);
expect(log).not.toContain("api.fireworks.ai");
});
it("sanitizes connector ids on remove failure logs", async () => {
const connectorId = "unknown\nhttps://api.fireworks.ai/account/sk-proj-remove012345678901234";
const { handlers, logger } = createDeps({
store: {
remove: vi.fn(() => {
throw new Error("remove failed for https://api.fireworks.ai/account/sk-proj-remove012345678901234");
}),
},
});
const removeHandler = requireHandler(handlers, IPC_CHANNELS.credentialsRemove);
const result = await removeHandler(null as unknown as IpcMainInvokeEvent, { connectorId });
const log = joinLogCalls(logger.error);
expect(result).toEqual({
ok: false,
error: {
code: "REMOVE_FAILED",
message: "Could not remove credential. Check application logs for details.",
},
});
expect(log).toBe("[credentials] remove failed: connector=invalid provider=unknown code=credentials.remove.failed");
expect(log).not.toContain("api.fireworks.ai");
expect(log).not.toContain("sk-proj-remove012345678901234");
});
it("sanitizes connector ids on unknown connector test logs", async () => {
const connectorId = "missing\nhttps://api.fireworks.ai/account/sk-proj-unknown012345678901234";
const { handler, logger } = createDeps();
const result = await handler(null as unknown as IpcMainInvokeEvent, { connectorId });
const log = joinLogCalls(logger.error);
expect(result).toEqual({
ok: false,
error: {
code: "UNKNOWN_CONNECTOR",
message: "Unknown connector.",
},
});
expect(log).toBe("[credentials] test failed: connector=invalid provider=unknown code=credentials.test.unknown_connector");
expect(log).not.toContain("api.fireworks.ai");
expect(log).not.toContain("sk-proj-unknown012345678901234");
});
});
@@ -1,135 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import { IPC_CHANNELS } from "../../src/shared/contracts";
const electron = vi.hoisted(() => ({
openExternal: vi.fn<() => Promise<void>>(async () => {}),
openPath: vi.fn<() => Promise<string>>(async () => ""),
showMessageBox: vi.fn<() => Promise<{ response: number }>>(async () => ({ response: 0 })),
}));
vi.mock("electron", () => ({
dialog: { showMessageBox: electron.showMessageBox },
nativeTheme: { themeSource: "system" },
shell: {
openExternal: electron.openExternal,
openPath: electron.openPath,
},
}));
import { registerCoreReadHandlers } from "../../src/main/ipc-core-read-handlers";
import type { AppDatabase } from "../../src/main/database";
import type { RegisterCoreHandlersDependencies } from "../../src/main/ipc-core-common";
const companyId = "11111111-1111-4111-8111-111111111111";
const workProductId = "22222222-2222-4222-8222-222222222222";
function registerOpenWorkProductHandler(url: string) {
const handlers = new Map<string, (...args: unknown[]) => Promise<unknown> | unknown>();
const db = {
core: {
documents: {
resolveWorkProductTarget: vi.fn(() => ({ kind: "url", value: url })),
updateWorkProduct: vi.fn(),
},
settings: {
canOpenPath: vi.fn(() => true),
getCurrentCompanyId: vi.fn(() => companyId),
},
},
projection: {
snapshot: {
listRendererSnapshot: vi.fn(() => ({})),
},
},
} as unknown as AppDatabase;
const deps = {
registerHandle: (channel, handler) => handlers.set(channel, handler as (...args: unknown[]) => Promise<unknown> | unknown),
db,
ensureCompanyExists: () => null,
publishDomainChanged: vi.fn(),
checkForUpdates: vi.fn(async () => true),
resetAfterRestore: vi.fn(),
listConnectorModels: vi.fn(async () => []),
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
} satisfies Pick<
RegisterCoreHandlersDependencies,
| "registerHandle"
| "db"
| "ensureCompanyExists"
| "publishDomainChanged"
| "checkForUpdates"
| "resetAfterRestore"
| "listConnectorModels"
| "logger"
>;
registerCoreReadHandlers(deps);
const handler = handlers.get(IPC_CHANNELS.openWorkProduct);
if (!handler) {
throw new Error("openWorkProduct handler was not registered.");
}
return handler;
}
async function openUrl(url: string) {
const handler = registerOpenWorkProductHandler(url);
return handler({}, { companyId, id: workProductId });
}
describe("openWorkProduct external URL policy", () => {
it("opens trusted hosts without confirmation", async () => {
electron.openExternal.mockClear();
electron.showMessageBox.mockClear();
const result = await openUrl("https://github.com/acme/repo");
expect(result).toEqual({ ok: true, data: true });
expect(electron.showMessageBox).not.toHaveBeenCalled();
expect(electron.openExternal).toHaveBeenCalledWith("https://github.com/acme/repo");
});
it("asks before opening trusted hosts over http", async () => {
electron.openExternal.mockClear();
electron.showMessageBox.mockClear();
electron.showMessageBox.mockResolvedValueOnce({ response: 0 });
const result = await openUrl("http://github.com/acme/repo");
expect(result).toEqual({ ok: true, data: true });
expect(electron.showMessageBox).toHaveBeenCalledTimes(1);
expect(electron.openExternal).toHaveBeenCalledWith("http://github.com/acme/repo");
});
it("asks before opening untrusted hosts", async () => {
electron.openExternal.mockClear();
electron.showMessageBox.mockClear();
electron.showMessageBox.mockResolvedValueOnce({ response: 0 });
const result = await openUrl("https://example.com/report");
expect(result).toEqual({ ok: true, data: true });
expect(electron.showMessageBox).toHaveBeenCalledTimes(1);
expect(electron.openExternal).toHaveBeenCalledWith("https://example.com/report");
});
it("blocks custom schemes before shell handoff", async () => {
electron.openExternal.mockClear();
electron.showMessageBox.mockClear();
const result = await openUrl("vscode://file/etc/passwd");
expect(result).toMatchObject({ ok: false, error: { code: "OPEN_EXTERNAL_URL_FORBIDDEN" } });
expect(electron.showMessageBox).not.toHaveBeenCalled();
expect(electron.openExternal).not.toHaveBeenCalled();
});
it("blocks credential-bearing URLs before shell handoff", async () => {
electron.openExternal.mockClear();
electron.showMessageBox.mockClear();
const result = await openUrl("https://user:pass@example.com/report");
expect(result).toMatchObject({ ok: false, error: { code: "OPEN_EXTERNAL_URL_FORBIDDEN" } });
expect(electron.showMessageBox).not.toHaveBeenCalled();
expect(electron.openExternal).not.toHaveBeenCalled();
});
});
-89
View File
@@ -1,89 +0,0 @@
import type { IpcMainInvokeEvent } from "electron";
import { z } from "zod";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { DesktopResult } from "@shared/contracts";
const { ipcHandlers, ipcHandle } = vi.hoisted(() => {
const handlers = new Map<string, (event: IpcMainInvokeEvent, ...args: unknown[]) => Promise<DesktopResult<unknown>>>();
return {
ipcHandlers: handlers,
ipcHandle: vi.fn((channel: string, handler: (event: IpcMainInvokeEvent, ...args: unknown[]) => Promise<DesktopResult<unknown>>) => {
handlers.set(channel, handler);
}),
};
});
vi.mock("electron", () => ({
ipcMain: {
handle: ipcHandle,
},
Notification: {
isSupported: () => false,
},
}));
import { createIpcSupport } from "../../src/main/ipc-support";
describe("IPC support error codes", () => {
beforeEach(() => {
ipcHandlers.clear();
ipcHandle.mockClear();
});
it("returns the classified handler code without leaking the raw exception message", async () => {
const { registerHandle } = createSupport();
registerHandle("test:throws", () => {
throw new Error("database password leaked in raw error");
});
const result = await invokeRegisteredHandler("test:throws");
expect(result).toEqual({
ok: false,
error: {
code: "ipc.handler.failed",
message: "The desktop operation failed. Use the error code when reporting this issue.",
},
});
});
it("returns the classified payload code for zod validation failures", async () => {
const { registerHandle } = createSupport();
registerHandle("test:payload", () => {
z.object({ name: z.string() }).parse({ name: 42 });
return { ok: true, data: true };
});
const result = await invokeRegisteredHandler("test:payload");
expect(result).toEqual({
ok: false,
error: {
code: "ipc.payload.invalid",
message: "The desktop request payload is invalid.",
},
});
});
});
function createSupport() {
return createIpcSupport({
isDev: true,
devServerOrigin: "http://localhost:5173",
logger: { error: vi.fn() },
getMainWindow: () => null,
getDb: () => ({
hasCompany: () => true,
belongsToCompany: () => true,
}),
});
}
async function invokeRegisteredHandler(channel: string): Promise<DesktopResult<unknown>> {
const handler = ipcHandlers.get(channel);
if (!handler) throw new Error(`No IPC handler registered for ${channel}.`);
return handler({
senderFrame: { url: "http://localhost:5173/app" },
sender: { getURL: () => "http://localhost:5173/app" },
} as IpcMainInvokeEvent);
}
-152
View File
@@ -1,152 +0,0 @@
import { describe, it, expect } from "vitest";
import { createRovingTabIndex as useRovingTabIndex } from "@renderer/lib/keyboard";
// Since vitest runs in Node (no DOM), we test the hook logic
// by extracting and testing the pure callback functions directly.
// We create a minimal mock of React hooks to isolate the logic.
// -- Helper to create a mock KeyboardEvent-like object --
function mockKeyEvent(key: string): {
key: string;
preventDefault: () => void;
defaultPrevented: boolean;
} {
let prevented = false;
return {
key,
preventDefault() {
prevented = true;
},
get defaultPrevented() {
return prevented;
},
};
}
describe("useRovingTabIndex", () => {
it("returns containerRef, focusedIndex (default 0), handleKeyDown, getTabIndex", () => {
const result = useRovingTabIndex(5);
expect(result).toHaveProperty("containerRef");
expect(result).toHaveProperty("focusedIndex");
expect(result).toHaveProperty("handleKeyDown");
expect(result).toHaveProperty("getTabIndex");
expect(result.focusedIndex).toBe(0);
expect(typeof result.handleKeyDown).toBe("function");
expect(typeof result.getTabIndex).toBe("function");
});
it("ArrowDown increments focusedIndex, wraps from last to 0", () => {
const result = useRovingTabIndex(3);
expect(result.focusedIndex).toBe(0);
// Press ArrowDown once -> index 1
const event1 = mockKeyEvent("ArrowDown");
result.handleKeyDown(event1 as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(1);
expect(event1.defaultPrevented).toBe(true);
// Press ArrowDown again -> index 2
const event2 = mockKeyEvent("ArrowDown");
result.handleKeyDown(event2 as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(2);
// Press ArrowDown again -> wraps to 0
const event3 = mockKeyEvent("ArrowDown");
result.handleKeyDown(event3 as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(0);
});
it("ArrowUp decrements focusedIndex, wraps from 0 to last", () => {
const result = useRovingTabIndex(3);
expect(result.focusedIndex).toBe(0);
// Press ArrowUp at 0 -> wraps to 2
const event1 = mockKeyEvent("ArrowUp");
result.handleKeyDown(event1 as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(2);
expect(event1.defaultPrevented).toBe(true);
// Press ArrowUp at 2 -> index 1
const event2 = mockKeyEvent("ArrowUp");
result.handleKeyDown(event2 as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(1);
});
it("Home sets focusedIndex to 0", () => {
const result = useRovingTabIndex(5);
// Move to index 3 first
result.handleKeyDown(mockKeyEvent("ArrowDown") as unknown as React.KeyboardEvent);
result.handleKeyDown(mockKeyEvent("ArrowDown") as unknown as React.KeyboardEvent);
result.handleKeyDown(mockKeyEvent("ArrowDown") as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(3);
// Press Home -> index 0
const event = mockKeyEvent("Home");
result.handleKeyDown(event as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(0);
expect(event.defaultPrevented).toBe(true);
});
it("End sets focusedIndex to itemCount - 1", () => {
const result = useRovingTabIndex(5);
expect(result.focusedIndex).toBe(0);
// Press End -> index 4
const event = mockKeyEvent("End");
result.handleKeyDown(event as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(4);
expect(event.defaultPrevented).toBe(true);
});
it("getTabIndex returns 0 for focusedIndex, -1 for others", () => {
const result = useRovingTabIndex(5);
// Default focusedIndex is 0
expect(result.getTabIndex(0)).toBe(0);
expect(result.getTabIndex(1)).toBe(-1);
expect(result.getTabIndex(2)).toBe(-1);
expect(result.getTabIndex(4)).toBe(-1);
// Move to index 2
result.handleKeyDown(mockKeyEvent("ArrowDown") as unknown as React.KeyboardEvent);
result.handleKeyDown(mockKeyEvent("ArrowDown") as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(2);
expect(result.getTabIndex(0)).toBe(-1);
expect(result.getTabIndex(2)).toBe(0);
});
it("j key acts same as ArrowDown, k key acts same as ArrowUp", () => {
const result = useRovingTabIndex(3);
expect(result.focusedIndex).toBe(0);
// j -> index 1
const jEvent = mockKeyEvent("j");
result.handleKeyDown(jEvent as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(1);
expect(jEvent.defaultPrevented).toBe(true);
// j -> index 2
result.handleKeyDown(mockKeyEvent("j") as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(2);
// k -> index 1
const kEvent = mockKeyEvent("k");
result.handleKeyDown(kEvent as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(1);
expect(kEvent.defaultPrevented).toBe(true);
// k -> index 0
result.handleKeyDown(mockKeyEvent("k") as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(0);
});
it("handleKeyDown is no-op when itemCount is 0", () => {
const result = useRovingTabIndex(0);
expect(result.focusedIndex).toBe(0);
const event = mockKeyEvent("ArrowDown");
result.handleKeyDown(event as unknown as React.KeyboardEvent);
expect(result.focusedIndex).toBe(0);
// preventDefault should NOT be called when itemCount is 0
expect(event.defaultPrevented).toBe(false);
});
});
-59
View File
@@ -1,59 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
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.core.company.saveCompany({
name: "Knowledge Provenance Co",
description: "Tests sourceRunId updates on knowledge entry edits",
status: "active",
});
const knowledgeId = db.core.knowledge.saveKnowledgeEntry({
companyId,
category: "lesson_learned",
topic: "Original lesson",
content: "Initial content",
importance: "medium",
sourceRunId: null,
});
db.core.knowledge.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();
});
});
@@ -1,83 +0,0 @@
import { describe, expect, it } from "vitest";
import { platformDownloads, type PlatformDownload } from "../../landing/components/release-links.generated";
import { buildPlatformSupportSummary } from "../../landing/lib/platform-support";
describe("landing platform support copy", () => {
it("derives FAQ support copy from current generated release assets", () => {
const summary = buildPlatformSupportSummary(platformDownloads);
expect(summary.downloadAnswer).toBe(
"Use the download section on this page to choose the build that matches your architecture and package format. " +
"macOS ships as signed and notarized DMG builds for arm64 and x64. " +
"Windows ships as an NSIS installer build for arm64 and x64. " +
"Linux ships as AppImage, DEB, and RPM builds for arm64 and x64.",
);
expect(summary.platformAnswer).toBe(
"AgentCompany is available on macOS (arm64, x64), Windows (arm64, x64), " +
"and Linux (arm64, x64). It is built with Electron and the desktop feature set stays aligned across all supported targets.",
);
});
it("uses structured architectures and platform package notes", () => {
const downloads = [
platform("macos", "macOS", "PKG builds", "PKG Builds", [["Apple Silicon", ["arm64"], "display-only note"]]),
platform("windows", "Windows", "an MSIX build", "MSIX Builds", [["Universal", ["arm64", "x64"], "ARM64 + x64"]]),
platform("linux", "Linux", "tarball builds", "Tarball Builds", [["Linux bundle", ["x64"], "x64 (glibc)"]]),
];
const summary = buildPlatformSupportSummary(downloads);
expect(summary.downloadAnswer).toBe(
"Use the download section on this page to choose the build that matches your architecture and package format. " +
"macOS ships as PKG builds for arm64. " +
"Windows ships as an MSIX build for arm64 and x64. " +
"Linux ships as tarball builds for x64.",
);
expect(summary.platformAnswer).toBe(
"AgentCompany is available on macOS (arm64), Windows (arm64, x64), and Linux (x64). " +
"It is built with Electron and the desktop feature set stays aligned across all supported targets.",
);
});
it("keeps generated asset notes synchronized with structured architectures", () => {
for (const platformDownload of platformDownloads) {
for (const asset of platformDownload.assets) {
expect(asset.note).toBe(asset.architectures.join(" + "));
}
}
});
it("fails when generated release assets omit architecture metadata", () => {
const downloads = [
platform("macos", "macOS", "DMG builds", "DMG Builds", [["Apple Silicon", [], "arm64"]]),
platform("windows", "Windows", "an NSIS installer build", "NSIS Builds", [["Universal", ["arm64", "x64"], "arm64 + x64"]]),
platform("linux", "Linux", "AppImage builds", "AppImage Builds", [["Linux", ["x64"], "x64"]]),
];
expect(() => buildPlatformSupportSummary(downloads)).toThrow(
"Release asset macos/macos-apple-silicon must declare at least one architecture.",
);
});
});
function platform(
id: PlatformDownload["id"],
title: string,
packageDescription: string,
note: string,
assets: Array<[label: string, architectures: PlatformDownload["assets"][number]["architectures"], note: string]>,
): PlatformDownload {
return {
id,
packageDescription,
title,
note,
assets: assets.map(([label, architectures, assetNote]) => ({
architectures,
id: `${id}-${label.toLowerCase().replaceAll(/\s+/g, "-")}`,
label,
href: `https://download.example/${id}`,
note: assetNote,
})),
};
}
@@ -1,71 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { releaseVersion } from "../../landing/components/release-links.generated";
import {
landingHeroNarrative,
landingHowItWorksNarrative,
landingStructuredDataNarrative,
onboardingFlowNarrative,
} from "../../landing/lib/product-narrative";
describe("landing product narrative", () => {
it("uses the generated release version for software structured data", () => {
expect(releaseVersion).toMatch(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/);
expect(landingStructuredDataNarrative.softwareVersion).toBe(releaseVersion);
});
it("describes the current first-run setup flow", () => {
expect(landingHeroNarrative).toMatchObject({
eyebrow: "AgentCompany Desktop",
titleLine1: "Launch an Autonomous AI Company",
titleLine2: "from Your Desktop",
});
expect(landingHeroNarrative.description).toContain("AI CEO by default");
expect(landingHeroNarrative.description).toContain("AI Co-Founder for human-reviewed decisions");
expect(landingHowItWorksNarrative.steps.map((step) => step.title)).toEqual([
"Requirements",
"Providers",
"Company",
"Team Models",
"Mode",
"Mission",
"Launch",
]);
expect(landingHowItWorksNarrative.demoMessages.map((message) => message.text)).toEqual([
"Node.js ready. CLI runtimes can call the local API.",
"Runtime connected.",
"Company profile and workspace saved.",
"Model pool enabled for CEO and workers.",
"AI CEO mode selected.",
"First goal and acceptance description captured.",
"Company, CEO agent, kickoff task, and initial run created.",
]);
expect(landingStructuredDataNarrative.featureList).toContain(
"Multi-connector support (Claude Code, Codex CLI, Gemini CLI)",
);
});
it("keeps first-run documentation ordered by the same setup sections", () => {
const files = [
"README.md",
"docs/rebuild/05-design-system-and-flows.md",
"landing/README.md",
];
for (const file of files) {
const text = readFileSync(file, "utf8");
const positions = [];
let searchOffset = 0;
for (const step of onboardingFlowNarrative.sections) {
const position = text.indexOf(step.title, searchOffset);
positions.push(position);
searchOffset = position + step.title.length;
}
expect(positions.every((position) => position >= 0)).toBe(true);
expect(positions).toEqual([...positions].sort((left, right) => left - right));
}
});
});
@@ -1,406 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
beforeAll(() => {
process.env.AGENTCOMPANY_ALLOW_INSECURE_VAULT = "true";
});
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase } from "../../src/main/database";
const tempDirs: string[] = [];
const ts = "2026-04-24T00:00:00.000Z";
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 insertLegacyMigrationRows(db: DatabaseSync, maxVersion: number, options?: { spacedSchemaTable?: boolean }): void {
db.exec(options?.spacedSchemaTable
? "create table schema_migrations ( version integer primary key, name text not null, applied_at text not null )"
: "create table schema_migrations (version integer primary key, name text not null, applied_at text not null)");
for (let version = 1; version <= maxVersion; version += 1) {
db
.prepare("insert into schema_migrations (version, name, applied_at) values (?, ?, ?)")
.run(version, `legacy-${version}`, ts);
}
}
function seedLegacyOperationalProfile(db: DatabaseSync, options?: { spacedSchemaTable?: boolean }): void {
insertLegacyMigrationRows(db, 55, options);
db.exec(`
create table settings (key text primary key, value text not null);
create table companies (id text primary key, name text not null, description text not null, status text not null, created_at text not null, updated_at text not null);
create table agents (
id text primary key,
company_id text not null,
name text not null,
role text not null,
title text not null,
status text not null,
reports_to text,
connector_id text not null,
workspace_id text,
model text,
capabilities text not null,
budget_monthly_usd real not null default 0,
spent_monthly_usd real not null default 0,
metadata_json text not null default '{}',
created_at text not null,
updated_at text not null,
department text
);
create table tasks (
id text primary key,
company_id text not null,
title text not null,
description text not null,
assignee_agent_id text,
workspace_id text,
status text not null,
priority text not null,
active_run_id text,
session_json text,
session_display_id text,
phase text,
is_heartbeat integer not null default 0,
created_at text not null,
updated_at text not null
);
create table runs (
id text primary key,
company_id text not null,
task_id text,
agent_id text not null,
workspace_id text,
connector_id text not null,
status text not null,
summary text not null,
unattributed_cost integer not null default 0,
session_display_id text,
context_snapshot_json text,
created_at text not null,
updated_at text not null
);
create table work_products (
id text primary key,
company_id text not null,
project_id text,
task_id text,
type text not null,
provider text not null,
external_id text,
title text not null,
url text,
status text not null default 'active',
review_state text not null default 'none',
is_primary integer not null default 0,
summary text,
metadata_json text,
created_by_run_id text,
created_at text not null,
updated_at text not null,
foreign key (task_id) references tasks(id) on delete cascade
);
create table pending_wakes (id text primary key, company_id text not null, agent_id text not null, trigger_reason text not null, created_at text not null);
create table secrets (id text primary key, name text not null, description text not null, backend text not null, ciphertext blob not null, updated_at text not null, created_at text not null);
`);
db.prepare("insert into settings (key, value) values ('current_company_id', 'company-1')").run();
db
.prepare("insert into companies (id, name, description, status, created_at, updated_at) values (?, ?, ?, ?, ?, ?)")
.run("company-1", "Legacy Co", "", "active", ts, ts);
db
.prepare("insert into agents (id, company_id, name, role, title, status, reports_to, connector_id, workspace_id, model, capabilities, budget_monthly_usd, spent_monthly_usd, metadata_json, created_at, updated_at, department) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("agent-1", "company-1", "Engineer", "Engineer", "Engineer", "active", null, "openai-codex", null, null, "code", 12.34, 0, "{}", ts, ts, "engineering");
db
.prepare("insert into tasks (id, company_id, title, description, assignee_agent_id, workspace_id, status, priority, active_run_id, session_json, session_display_id, phase, is_heartbeat, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("task-1", "company-1", "Legacy task", "", "agent-1", "workspace-task", "todo", "medium", null, "{\"provider\":\"legacy\"}", "display-1", "todo", 0, ts, ts);
db
.prepare("insert into tasks (id, company_id, title, description, assignee_agent_id, workspace_id, status, priority, active_run_id, session_json, session_display_id, phase, is_heartbeat, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("heartbeat-task", "company-1", "[Heartbeat] agent-1", "", "agent-1", "workspace-task", "in_progress", "low", "run-heartbeat", null, null, "in_progress", 1, ts, ts);
db
.prepare("insert into runs (id, company_id, task_id, agent_id, workspace_id, connector_id, status, summary, unattributed_cost, session_display_id, context_snapshot_json, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("run-heartbeat", "company-1", "heartbeat-task", "agent-1", "workspace-task", "openai-codex", "running", "", 0, "run-display", "{}", ts, ts);
db
.prepare("insert into runs (id, company_id, task_id, agent_id, workspace_id, connector_id, status, summary, unattributed_cost, session_display_id, context_snapshot_json, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("run-product", "company-1", null, "agent-1", "workspace-run", "openai-codex", "done", "", 0, "run-display-2", "{}", ts, ts);
db
.prepare("insert into runs (id, company_id, task_id, agent_id, workspace_id, connector_id, status, summary, unattributed_cost, session_display_id, context_snapshot_json, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("run-cancelled", "company-1", null, "agent-1", "workspace-run", "openai-codex", "cancelled", "", 1, "run-display-3", "{}", ts, ts);
db
.prepare("insert into work_products (id, company_id, task_id, type, provider, external_id, title, status, review_state, is_primary, created_by_run_id, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("wp-task", "company-1", "task-1", "file", "filesystem", "task.md", "Task file", "active", "none", 1, null, ts, ts);
db
.prepare("insert into work_products (id, company_id, task_id, type, provider, external_id, title, status, review_state, is_primary, created_by_run_id, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("wp-run", "company-1", null, "file", "filesystem", "run.md", "Run file", "active", "none", 0, "run-product", ts, ts);
db
.prepare("insert into pending_wakes (id, company_id, agent_id, trigger_reason, created_at) values (?, ?, ?, ?, ?)")
.run("wake-1", "company-1", "agent-1", "manual", ts);
db
.prepare("insert into secrets (id, name, description, backend, ciphertext, updated_at, created_at) values (?, ?, ?, ?, ?, ?, ?)")
.run("secret-1", "api", "API key", "safeStorage", Buffer.from("encrypted"), ts, ts);
}
describe("legacy to v56 baseline migration", () => {
it("migrates legacy data helpers and compresses schema history", async () => {
const profileDir = await createTempDir("agentcompany-legacy-baseline-");
const rawDb = new DatabaseSync(join(profileDir, "profile.sqlite"));
try {
seedLegacyOperationalProfile(rawDb);
} finally {
rawDb.close();
}
let appDb = new AppDatabase(profileDir);
appDb.init();
try {
expect(appDb.queryAll("select version, name from schema_migrations")).toEqual([
{ version: 56, name: "baseline_v56" },
{ version: 57, name: "task_leases_runtime_ownership" },
{ version: 58, name: "approval_policy_table" },
{ version: 59, name: "social_company_scope" },
{ version: 60, name: "filesystem_task_workspaces" },
]);
expect(appDb.queryAll<{ companyId: string }>("select company_id as companyId from secrets where id = 'secret-1'")[0])
.toEqual({ companyId: "company-1" });
expect(appDb.queryAll<{ source: string; triggerDetail: string }>("select source, trigger_detail as triggerDetail from wakeup_requests where id = 'wake-1'")[0])
.toEqual({ source: "manual", triggerDetail: JSON.stringify([{ trigger: "manual", at: ts, detail: "manual" }]) });
expect(appDb.queryAll<{ amount: number }>("select amount from budget_policies where scope_type = 'agent' and scope_id = 'agent-1'")[0])
.toEqual({ amount: 1234 });
expect(appDb.queryAll<{ agentId: string; adapterType: string }>("select agent_id as agentId, adapter_type as adapterType from task_sessions")[0])
.toEqual({ agentId: "agent-1", adapterType: "openai-codex" });
expect(appDb.queryAll("select id from tasks where id = 'heartbeat-task'")).toEqual([]);
expect(appDb.queryAll<{ taskId: string | null }>("select task_id as taskId from runs where id = 'run-heartbeat'")[0])
.toEqual({ taskId: null });
expect(appDb.queryAll<{ id: string; usageStatus: string }>("select id, usage_status as usageStatus from runs where id in ('run-product', 'run-cancelled') order by id"))
.toEqual([
{ id: "run-cancelled", usageStatus: "aborted_before_usage" },
{ id: "run-product", usageStatus: "ok" },
]);
expect(appDb.queryAll<{ id: string; workspaceId: string | null; lastTouchedByRunId: string | null }>("select id, workspace_id as workspaceId, last_touched_by_run_id as lastTouchedByRunId from work_products order by id"))
.toEqual([
{ id: "wp-run", workspaceId: "workspace-run", lastTouchedByRunId: "run-product" },
{ id: "wp-task", workspaceId: "workspace-task", lastTouchedByRunId: null },
]);
} finally {
appDb.close();
}
appDb = new AppDatabase(profileDir);
appDb.init();
try {
expect(appDb.queryAll("select version, name from schema_migrations")).toEqual([
{ version: 56, name: "baseline_v56" },
{ version: 57, name: "task_leases_runtime_ownership" },
{ version: 58, name: "approval_policy_table" },
{ version: 59, name: "social_company_scope" },
{ version: 60, name: "filesystem_task_workspaces" },
]);
expect(appDb.queryAll("select id from budget_policies where scope_type = 'agent' and scope_id = 'agent-1'"))
.toHaveLength(1);
} finally {
appDb.close();
}
});
it("accepts legacy schema_migrations formatting during baseline assertion", async () => {
const profileDir = await createTempDir("agentcompany-legacy-schema-format-");
const rawDb = new DatabaseSync(join(profileDir, "profile.sqlite"));
try {
seedLegacyOperationalProfile(rawDb, { spacedSchemaTable: true });
} finally {
rawDb.close();
}
const appDb = new AppDatabase(profileDir);
appDb.init();
try {
expect(appDb.queryAll("select version, name from schema_migrations")).toEqual([
{ version: 56, name: "baseline_v56" },
{ version: 57, name: "task_leases_runtime_ownership" },
{ version: 58, name: "approval_policy_table" },
{ version: 59, name: "social_company_scope" },
{ version: 60, name: "filesystem_task_workspaces" },
]);
} finally {
appDb.close();
}
});
it("rolls back when a legacy table would drop an unknown column", async () => {
const profileDir = await createTempDir("agentcompany-legacy-rollback-");
const rawDb = new DatabaseSync(join(profileDir, "profile.sqlite"));
try {
insertLegacyMigrationRows(rawDb, 1);
rawDb.exec(`
create table companies (
id text primary key,
name text not null,
description text not null,
status text not null,
created_at text not null,
updated_at text not null,
rogue_payload text not null
);
`);
rawDb
.prepare("insert into companies (id, name, description, status, created_at, updated_at, rogue_payload) values (?, ?, ?, ?, ?, ?, ?)")
.run("company-1", "Legacy Co", "", "active", ts, ts, "keep");
} finally {
rawDb.close();
}
const appDb = new AppDatabase(profileDir);
try {
expect(() => appDb.init()).toThrow(/unexpected legacy columns/);
expect(appDb.queryAll<{ name: string }>("pragma table_info(companies)").map((row) => row.name))
.toContain("rogue_payload");
expect(appDb.queryAll("select version from schema_migrations")).toEqual([{ version: 1 }]);
} finally {
appDb.close();
}
});
it("rolls back v56 compression when baseline drift is detected", async () => {
const profileDir = await createTempDir("agentcompany-v56-drift-rollback-");
const rawDb = new DatabaseSync(join(profileDir, "profile.sqlite"));
try {
rawDb.exec(`
create table schema_migrations (version integer primary key, name text not null, applied_at text not null);
insert into schema_migrations (version, name, applied_at) values (55, 'legacy-55', '${ts}');
insert into schema_migrations (version, name, applied_at) values (56, 'legacy-56', '${ts}');
create table companies (id text primary key, name text not null);
`);
} finally {
rawDb.close();
}
const appDb = new AppDatabase(profileDir);
try {
expect(() => appDb.init()).toThrow(/Baseline v56 table drift/);
expect(appDb.queryAll("select version, name from schema_migrations order by version")).toEqual([
{ version: 55, name: "legacy-55" },
{ version: 56, name: "legacy-56" },
]);
expect(appDb.queryAll("select name from sqlite_master where type = 'table' and name = 'agents'")).toEqual([]);
} finally {
appDb.close();
}
});
it("migrates task session rows through the old task-scoped table shape", async () => {
const profileDir = await createTempDir("agentcompany-old-task-sessions-");
const rawDb = new DatabaseSync(join(profileDir, "profile.sqlite"));
try {
insertLegacyMigrationRows(rawDb, 40);
rawDb.exec(`
create table settings (key text primary key, value text not null);
create table companies (id text primary key, name text not null, description text not null, status text not null, created_at text not null, updated_at text not null);
create table agents (
id text primary key,
company_id text not null,
name text not null,
role text not null,
title text not null,
status text not null,
reports_to text,
connector_id text not null,
workspace_id text,
model text,
capabilities text not null,
budget_monthly_usd real not null default 0,
spent_monthly_usd real not null default 0,
metadata_json text not null default '{}',
created_at text not null,
updated_at text not null
);
create table tasks (
id text primary key,
company_id text not null,
title text not null,
description text not null,
assignee_agent_id text,
status text not null,
priority text not null,
session_json text,
session_display_id text,
active_session_id text,
created_at text not null,
updated_at text not null
);
create table task_sessions (
id text primary key,
company_id text not null,
task_id text not null,
agent_id text not null,
adapter_type text not null,
session_params_json text,
session_display_id text,
created_at text not null,
updated_at text not null
);
`);
rawDb
.prepare("insert into companies (id, name, description, status, created_at, updated_at) values (?, ?, ?, ?, ?, ?)")
.run("company-1", "Legacy Co", "", "active", ts, ts);
rawDb
.prepare("insert into agents (id, company_id, name, role, title, status, reports_to, connector_id, workspace_id, model, capabilities, budget_monthly_usd, spent_monthly_usd, metadata_json, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("agent-1", "company-1", "Engineer", "Engineer", "Engineer", "active", null, "openai-codex", null, null, "code", 0, 0, "{}", ts, ts);
rawDb
.prepare("insert into tasks (id, company_id, title, description, assignee_agent_id, status, priority, session_json, session_display_id, active_session_id, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run("task-1", "company-1", "Legacy task", "", "agent-1", "todo", "medium", "{\"provider\":\"legacy\"}", "display-1", null, ts, ts);
} finally {
rawDb.close();
}
const appDb = new AppDatabase(profileDir);
appDb.init();
try {
expect(appDb.queryAll<{ name: string }>("pragma table_info(task_sessions)").map((row) => row.name))
.toEqual([
"id",
"company_id",
"agent_id",
"adapter_type",
"last_run_id",
"last_error",
"run_count",
"total_input_tokens",
"total_output_tokens",
"rotated_at",
"rotation_reason",
"handoff_markdown",
"created_at",
"updated_at",
]);
expect(appDb.queryAll<{ agentId: string; adapterType: string }>("select agent_id as agentId, adapter_type as adapterType from task_sessions"))
.toEqual([{ agentId: "agent-1", adapterType: "openai-codex" }]);
expect(appDb.queryAll("select version, name from schema_migrations")).toEqual([
{ version: 56, name: "baseline_v56" },
{ version: 57, name: "task_leases_runtime_ownership" },
{ version: 58, name: "approval_policy_table" },
{ version: 59, name: "social_company_scope" },
{ version: 60, name: "filesystem_task_workspaces" },
]);
} finally {
appDb.close();
}
});
});
-61
View File
@@ -1,61 +0,0 @@
import { describe, expect, it } from "vitest";
import { redactTokens } from "../../src/main/worker/observability/log-redactor";
/** Verifies the worker process token redactor strips known
* credential-shaped substrings from log strings before they leave the
* worker process. */
describe("worker log redactor", () => {
it("redacts Bearer tokens", () => {
const input = "Authorization: Bearer abc.def.ghi-jkl_mnop";
expect(redactTokens(input)).toBe("Authorization: Bearer [REDACTED]");
});
it("redacts authorization header values regardless of case", () => {
expect(redactTokens("authorization=secret-value-1234567890")).toContain("[REDACTED]");
expect(redactTokens("Authorization: secret-value-1234567890")).toContain("[REDACTED]");
});
it("redacts access_token query strings", () => {
expect(redactTokens("https://api.example.com/?access_token=abc123xyz&foo=1")).toBe(
"https://api.example.com/?access_token=[REDACTED]&foo=1",
);
});
it("redacts refresh_token values", () => {
expect(redactTokens("refresh_token=long.refresh.tok&grant_type=refresh_token")).toContain(
"refresh_token=[REDACTED]",
);
});
it("redacts api_key key/value pairs", () => {
const input = "config { api_key: \"sk-test-1234567890abcdef\", model: \"gpt-4o\" }";
const result = redactTokens(input);
expect(result).not.toContain("sk-test-1234567890abcdef");
expect(result).toContain("[REDACTED]");
});
it("redacts sk- and sk-ant- prefixed keys anywhere in a string", () => {
expect(redactTokens("error using sk-ant-api03_abcdefghijklmnop, retrying")).toContain("sk-[REDACTED]");
expect(redactTokens("token=sk-test1234567890_abcd")).toContain("sk-[REDACTED]");
});
it("redacts GitHub personal access token shapes", () => {
expect(redactTokens("git push https://x:ghp_aaaaaaaaaaaaaaaaaa@github.com/foo")).toContain("ghp_[REDACTED]");
});
it("redacts AWS access key id prefixes", () => {
expect(redactTokens("aws cred AKIAIOSFODNN7EXAMPLE in trace")).toContain("AKIA[REDACTED]");
});
it("leaves credential-free strings unchanged", () => {
const safe = "Just a regular log line about model claude-opus-4-5 with usage 123 tokens.";
expect(redactTokens(safe)).toBe(safe);
});
it("redacts multiple matches in one string", () => {
const input = "Bearer abc.def.ghi.jkl Authorization=secretsecretsecret";
const result = redactTokens(input);
expect(result).not.toContain("abc.def.ghi.jkl");
expect(result).not.toContain("secretsecretsecret");
});
});
@@ -1,44 +0,0 @@
import { describe, expect, it } from "vitest";
import {
CONSOLE_ERROR_SUPPRESSION_RULES,
WARNING_SUPPRESSION_RULES,
classifyConsoleErrorSuppression,
classifyWarningSuppression,
} from "../../src/main/main-warning-suppression-rules";
describe("main warning suppression rules", () => {
it("documents every warning suppression category with an operator reason", () => {
for (const rule of [...WARNING_SUPPRESSION_RULES, ...CONSOLE_ERROR_SUPPRESSION_RULES]) {
expect(rule.category).toMatch(/^[a-z0-9-]+$/);
expect(rule.reason.length).toBeGreaterThan(40);
}
});
it("suppresses only the node sqlite experimental warning", () => {
const sqliteWarning = new Error("SQLite is an experimental feature and might change at any time");
sqliteWarning.name = "ExperimentalWarning";
const genericWarning = new Error("fetch is an experimental feature");
genericWarning.name = "ExperimentalWarning";
expect(classifyWarningSuppression(sqliteWarning)).toBe("node-sqlite-experimental-warning");
expect(classifyWarningSuppression("SQLite is an experimental feature and might change at any time"))
.toBe("node-sqlite-experimental-warning");
expect(classifyWarningSuppression(genericWarning)).toBeNull();
expect(classifyWarningSuppression("deprecation warning")).toBeNull();
});
it("suppresses only disposed renderer frame send noise", () => {
expect(classifyConsoleErrorSuppression([
"Error sending from webFrameMain:",
new Error("Render frame was disposed before WebFrameMain could send"),
])).toBe("electron-disposed-web-frame-send");
expect(classifyConsoleErrorSuppression([
"Error sending from webFrameMain:",
new Error("Permission denied"),
])).toBeNull();
expect(classifyConsoleErrorSuppression(["Unhandled renderer error", new Error("Render frame was disposed")]))
.toBeNull();
});
});
-12
View File
@@ -1,12 +0,0 @@
import { describe, expect, it } from "vitest";
import { normalizeMarkdownText } from "../../src/shared/markdown-text";
describe("normalizeMarkdownText", () => {
it("converts escaped markdown line breaks into renderable markdown", () => {
expect(normalizeMarkdownText("Intro\\n\\n## Plan\\n- First\\n- Second")).toBe("Intro\n\n## Plan\n- First\n- Second");
});
it("normalizes actual CRLF input to LF", () => {
expect(normalizeMarkdownText("One\r\nTwo\rThree")).toBe("One\nTwo\nThree");
});
});
+128
View File
@@ -0,0 +1,128 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ChatService } from "../../src/main/services/chat-service";
import { canonicalMentionHandle } from "../../src/shared/mention-handles";
import { dispatchAgentTool, type AgentToolContext } from "../../src/main/services/agent-tools";
function call(name: string, args: Record<string, unknown>) {
return { id: `call_${randomUUID()}`, name, arguments: args };
}
describe("mention resolution", () => {
test("canonicalMentionHandle strips spaces and lowercases", () => {
expect(canonicalMentionHandle("Sarah Chen")).toBe("sarahchen");
expect(canonicalMentionHandle("J.P. Morgan")).toBe("jpmorgan");
expect(canonicalMentionHandle("alice")).toBe("alice");
});
test("@mention with canonical handle resolves to multi-word agent name", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-mention-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let providerId = "";
let engineerId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "TestCorp", employeeName: "Yang", mission: "m", goal: "g" });
providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
ceoId = organization.createAgent({
orgId, name: "Alex Kim", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null,
});
engineerId = organization.createAgent({
orgId, name: "Sarah Chen", role: "Engineer", department: "Engineering",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 10000, managerAgentId: ceoId,
});
});
const handle = canonicalMentionHandle("Sarah Chen");
expect(handle).toBe("sarahchen");
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null, taskGoalId: null, workspace: null,
};
const result = dispatchAgentTool(deps, ctx, call("send_message", {
channel: "engineering",
body: `@${handle} can you review this?`,
}));
expect(result).toContain("Sarah Chen");
const mentions = connection.db.prepare(
"select target_type, target_id, wake_state from chat_mentions where target_type = 'agent' and target_id = ?",
).all(engineerId) as Array<{ target_type: string; target_id: string; wake_state: string }>;
expect(mentions.length).toBeGreaterThan(0);
expect(mentions[0].target_id).toBe(engineerId);
});
test("read_messages returns recent channel messages", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-read-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let providerId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "ReadCorp", employeeName: "Yang", mission: "m", goal: "g" });
providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null,
});
});
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ctx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: null, workspace: null,
};
dispatchAgentTool(deps, ctx, call("send_message", { channel: "general", body: "Hello team!" }));
dispatchAgentTool(deps, ctx, call("send_message", { channel: "general", body: "Kickoff meeting at 3pm." }));
const readResult = dispatchAgentTool(deps, ctx, call("read_messages", { channel: "general" }));
expect(readResult).toContain("Hello team!");
expect(readResult).toContain("Kickoff meeting at 3pm.");
});
test("read_messages with no channel returns agent mentions", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-inbox-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let engineerId = "";
let providerId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "InboxCorp", employeeName: "Yang", mission: "m", goal: "g" });
providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null,
});
engineerId = organization.createAgent({
orgId, name: "Dana", role: "Engineer", department: "Engineering",
modelProviderId: null, modelId: null, budgetCents: 1000, managerAgentId: ceoId,
});
});
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ceoCtx: AgentToolContext = {
orgId, agent: { id: ceoId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: null, workspace: null,
};
dispatchAgentTool(deps, ceoCtx, call("send_message", { channel: "engineering", body: "@dana check the deployment" }));
const danaCtx: AgentToolContext = {
orgId, agent: { id: engineerId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: null, workspace: null,
};
const inbox = dispatchAgentTool(deps, danaCtx, call("read_messages", {}));
expect(inbox).toContain("check the deployment");
});
});
-461
View File
@@ -1,461 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { createThreadIntegration } from "../../src/main/thread-integration";
import { dispatchAgentMessage, HUMAN_SENDER_ID } from "../../src/main/messaging/dispatch";
import { executeAutomationAction } from "../../src/main/automation/automation-actions";
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.core.connectors.updateConnectorHealth({
id: "openai-codex",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.core.company.saveCompany({
name: "Message Dispatch Co",
description: "Tests board wake and wait-state resolution",
status: "active",
executionMode: "ai_cofounder",
});
const workspaceId = db.core.workspaces.saveWorkspace({
companyId,
name: "Primary",
localPath: "/tmp/message-dispatch",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.core.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.core.agents.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "idle",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 200,
department: "engineering",
});
const cmoId = db.core.agents.saveAgent({
companyId,
name: "CMO",
role: "CMO",
title: "Chief Marketing Officer",
status: "idle",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Lead marketing",
budgetMonthlyUsd: 200,
department: "marketing",
});
const pendingId = db.core.agents.saveAgent({
companyId,
name: "Pending Recruit",
role: "IC",
title: "Pending Hire",
status: "pending_approval",
reportsTo: ctoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "Pending",
budgetMonthlyUsd: 100,
department: "engineering",
});
const terminatedId = db.core.agents.saveAgent({
companyId,
name: "Former COO",
role: "COO",
title: "Former COO",
status: "terminated",
reportsTo: ceoId,
connectorId: "openai-codex",
workspaceId,
model: null,
capabilities: "No longer active",
budgetMonthlyUsd: 100,
department: "operations",
});
const taskId = db.core.tasks.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 every active agent for company channel broadcasts (Slack-style all-hands)", 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: "All-hands broadcast",
});
expect(companyResult.ok).toBe(true);
if (!companyResult.ok) throw new Error("Expected company message dispatch to succeed.");
expect(new Set(companyResult.data.wakeTargets)).toEqual(new Set([ceoId, ctoId, cmoId]));
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("routes a DM addressed to one agent into the @mentioned agent's inbox (group-chat @mention visibility)", async () => {
const profileDir = await createTempDir("agentcompany-message-mention-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, ceoId, ctoId } = createScaffold(db);
const wakeAgentIfPossible = vi.fn().mockReturnValue(null);
const result = dispatchAgentMessage({
db,
wakeAgentIfPossible,
}, {
companyId,
fromAgentId: HUMAN_SENDER_ID,
fromHumanUser: true,
channel: "direct",
toAgentId: ctoId,
subject: "status",
body: "Hi @CEO, please share a progress update, @CTO is on infra.",
});
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("Expected dispatch to succeed.");
expect(new Set(result.data.wakeTargets)).toEqual(new Set([ceoId, ctoId]));
const ceoInbox = db.core.messages.listAgentInboxUnread(companyId, ceoId);
expect(ceoInbox.map((entry) => entry.id)).toContain(result.data.id);
const ctoInbox = db.core.messages.listAgentInboxUnread(companyId, ctoId);
expect(ctoInbox.map((entry) => entry.id)).toContain(result.data.id);
db.close();
});
it("normalizes escaped markdown line breaks before persisting messages and comments", async () => {
const profileDir = await createTempDir("agentcompany-message-markdown-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, ceoId, ctoId, taskId } = createScaffold(db);
const result = dispatchAgentMessage({
db,
}, {
companyId,
fromAgentId: ctoId,
channel: "direct",
toAgentId: ceoId,
subject: "Markdown update",
body: "CEO,\\n\\n## Plan\\n- Confirm scope\\n- Ship cleanly",
});
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("Expected dispatch to succeed.");
expect(result.data.message?.body).toBe("CEO,\n\n## Plan\n- Confirm scope\n- Ship cleanly");
const comment = db.core.activity.addComment({
companyId,
taskId,
authorAgentId: ctoId,
authorName: "CTO",
body: "Progress:\\n- Scope confirmed\\n- Review queued",
});
expect(comment.body).toBe("Progress:\n- Scope confirmed\n- Review queued");
db.close();
});
it("redirects a DM's wake to the @mentioned agent instead of the DM recipient (chatroom semantics)", async () => {
const profileDir = await createTempDir("agentcompany-message-redirect-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, ceoId, ctoId } = createScaffold(db);
const wakeAgentIfPossible = vi.fn().mockReturnValue(null);
const result = dispatchAgentMessage({
db,
wakeAgentIfPossible,
}, {
companyId,
fromAgentId: HUMAN_SENDER_ID,
fromHumanUser: true,
channel: "direct",
toAgentId: ctoId,
subject: "progress",
body: "@CEO 更新一下进度。",
});
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("Expected dispatch to succeed.");
// CEO is the only wake target — the DM recipient (CTO) is NOT
// woken because an explicit @mention overrides the implicit DM
// recipient. This is what lets the operator say "@CEO" inside a
// chat with CTO and have CEO (not CTO) take the turn.
expect(result.data.wakeTargets).toEqual([ceoId]);
// The message is still visible to CTO's inbox — they were the
// addressed recipient — but CTO is not required to answer.
const ctoInbox = db.core.messages.listAgentInboxUnread(companyId, ctoId);
expect(ctoInbox.map((entry) => entry.id)).toContain(result.data.id);
const ceoInbox = db.core.messages.listAgentInboxUnread(companyId, ceoId);
expect(ceoInbox.map((entry) => entry.id)).toContain(result.data.id);
db.close();
});
it("keeps __board__ out of wake targets even when the recipient is the human reader", async () => {
const profileDir = await createTempDir("agentcompany-message-board-wake-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, ceoId } = createScaffold(db);
const wakeAgentIfPossible = vi.fn().mockReturnValue(null);
const result = dispatchAgentMessage({
db,
wakeAgentIfPossible,
}, {
companyId,
fromAgentId: ceoId,
channel: "direct",
toAgentId: HUMAN_SENDER_ID,
subject: "report",
body: "Update ready for review.",
});
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("Expected dispatch to succeed.");
expect(result.data.wakeTargets).not.toContain(HUMAN_SENDER_ID);
expect(wakeAgentIfPossible).not.toHaveBeenCalledWith(
HUMAN_SENDER_ID,
expect.anything(),
expect.anything(),
);
db.close();
});
it("rejects direct messages addressed to the sender", async () => {
const profileDir = await createTempDir("agentcompany-message-self-direct-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, ceoId } = createScaffold(db);
const result = dispatchAgentMessage({
db,
}, {
companyId,
fromAgentId: ceoId,
channel: "direct",
toAgentId: ceoId,
subject: "self",
body: "This should not create a CEO to CEO channel.",
});
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected self-DM dispatch to fail.");
expect(result.error.code).toBe("SELF_DIRECT_MESSAGE_FORBIDDEN");
expect(() => db.core.messages.sendAgentMessage({
companyId,
fromAgentId: ceoId,
toAgentId: ceoId,
subject: "self",
body: "Direct DB writers share the same invariant.",
})).toThrow("Direct messages require two distinct participants.");
db.close();
});
it("skips automation notifications that would address the sender", async () => {
const profileDir = await createTempDir("agentcompany-automation-self-direct-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, ceoId } = createScaffold(db);
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn() };
executeAutomationAction(
{
db,
logger,
emitEvent: vi.fn(),
publishDomainChanged: vi.fn(),
wakeAgentIfPossible: vi.fn(),
},
{
handleTaskCreated: vi.fn(),
handleApprovalResolved: vi.fn(),
handleTaskStatusChange: vi.fn(),
},
{
id: "rule-self",
name: "Self notification",
action: "notify_agent",
actionConfigJson: "{}",
targetDepartment: null,
},
{ companyId, agentId: ceoId },
);
expect(db.core.messages.listAgentMessages(companyId, { channel: "direct" })).toHaveLength(0);
expect(logger.info).toHaveBeenCalledWith(
"[automation] Skipping rule \"Self notification\" - direct messages require two distinct participants",
);
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: () => {}, warn: () => {}, error: () => {} },
});
threadIntegration.onTaskCreated(taskId, companyId);
const thread = db.core.threads.listThreads({
companyId,
type: "task",
relatedEntityType: "task",
relatedEntityId: taskId,
limit: 1,
})[0];
if (!thread) {
throw new Error("Expected task thread to exist.");
}
const replyWait = db.core.threads.setThreadWaitState({
threadId: thread.id,
companyId,
waitingFor: "user_reply",
relatedApprovalId: null,
reason: "Reply needed",
});
const reviewWait = db.core.threads.setThreadWaitState({
threadId: thread.id,
companyId,
waitingFor: "user_review",
relatedApprovalId: null,
reason: "Review needed",
});
const approvalWait = db.core.threads.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.core.threads.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.core.threads.listThreadWaitStates(companyId);
expect(unresolved.some((entry) => entry.id === reviewWait.id)).toBe(false);
expect(unresolved.some((entry) => entry.id === approvalWait.id)).toBe(true);
db.close();
});
});
-93
View File
@@ -1,93 +0,0 @@
import { describe, expect, it } from "vitest";
import type { AgentMessageRecord, AgentRecord } from "../../src/shared/types";
import {
DEFAULT_MESSAGE_FILTERS,
filterChannelMessages,
hasActiveMessageFilters,
} from "../../src/renderer/components/communication/message-filters";
function agent(id: string, name: string): AgentRecord {
return {
id,
companyId: "company-1",
name,
role: name,
title: name,
department: null,
status: "active",
reportsTo: null,
connectorId: "openai-codex",
workspaceId: null,
model: null,
capabilities: "",
budgetMonthlyUsd: 0,
spentMonthlyUsd: 0,
metadataJson: "{}",
heartbeatEnabled: false,
heartbeatIntervalSec: 300,
lastHeartbeatAt: null,
createdAt: "2026-04-22T00:00:00.000Z",
updatedAt: "2026-04-22T00:00:00.000Z",
};
}
function message(input: Partial<AgentMessageRecord>): AgentMessageRecord {
return {
id: input.id ?? "msg-1",
companyId: "company-1",
fromAgentId: input.fromAgentId ?? "agent-ceo",
toAgentId: input.toAgentId ?? null,
senderKind: input.senderKind ?? "agent",
channel: input.channel ?? "company",
channelTargetId: input.channelTargetId ?? null,
subject: input.subject ?? "Subject",
body: input.body ?? "Body",
priority: input.priority ?? "normal",
parentMessageId: input.parentMessageId ?? null,
attachmentsJson: input.attachmentsJson ?? "[]",
createdAt: input.createdAt ?? "2026-04-22T00:00:00.000Z",
readAt: input.readAt ?? null,
};
}
describe("message filters", () => {
it("keeps read and unread channel messages in the default channel view", () => {
const read = message({ id: "read", readAt: "2026-04-22T00:00:01.000Z" });
const unread = message({ id: "unread", readAt: null });
const result = filterChannelMessages(
[read, unread],
DEFAULT_MESSAGE_FILTERS,
new Map(),
"",
);
expect(result.map((entry) => entry.id)).toEqual(["read", "unread"]);
});
it("filters by text and sender inside the selected channel", () => {
const ceo = agent("agent-ceo", "Ada CEO");
const cto = agent("agent-cto", "Grace CTO");
const messages = [
message({ id: "ceo", fromAgentId: ceo.id, subject: "Launch", body: "Board update" }),
message({ id: "cto", fromAgentId: cto.id, subject: "Infra", body: "Runtime ready" }),
];
const result = filterChannelMessages(
messages,
{ ...DEFAULT_MESSAGE_FILTERS, search: "grace" },
new Map([
[ceo.id, ceo],
[cto.id, cto],
]),
"",
);
expect(result.map((entry) => entry.id)).toEqual(["cto"]);
});
it("detects active message filters", () => {
expect(hasActiveMessageFilters(DEFAULT_MESSAGE_FILTERS)).toBe(false);
expect(hasActiveMessageFilters({ ...DEFAULT_MESSAGE_FILTERS, priority: "urgent" })).toBe(true);
});
});
-207
View File
@@ -1,207 +0,0 @@
import { join } from "node:path";
import { tmpdir } from "node:os";
import { mkdtemp, rm } from "node:fs/promises";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase } from "../../src/main/database";
describe("AppDatabase.searchMessages", () => {
let tempDir: string;
let db: AppDatabase;
let companyId: string;
let agentA: string;
let agentB: string;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-search-"));
db = new AppDatabase(tempDir);
db.init();
companyId = db.core.company.saveCompany({
name: "Search Test Corp",
description: "Company for testing message search",
status: "active",
});
agentA = db.core.agents.saveAgent({
companyId,
name: "Agent Alpha",
role: "Engineer",
title: "Senior Engineer",
status: "active",
reportsTo: null,
connectorId: "claude-pro",
workspaceId: null,
model: null,
capabilities: "coding",
budgetMonthlyUsd: 100,
});
agentB = db.core.agents.saveAgent({
companyId,
name: "Agent Beta",
role: "Designer",
title: "Lead Designer",
status: "active",
reportsTo: null,
connectorId: "claude-pro",
workspaceId: null,
model: null,
capabilities: "design",
budgetMonthlyUsd: 100,
});
// Seed messages across different channels
db.core.messages.sendAgentMessage({
companyId,
fromAgentId: agentA,
toAgentId: agentB,
channel: "direct",
subject: "Database migration plan",
body: "We need to migrate the PostgreSQL tables to the new schema.",
priority: "normal",
});
db.core.messages.sendAgentMessage({
companyId,
fromAgentId: agentB,
toAgentId: agentA,
channel: "direct",
subject: "Design review feedback",
body: "The button colors look great, but the migration banner needs work.",
priority: "normal",
});
db.core.messages.sendAgentMessage({
companyId,
fromAgentId: agentA,
channel: "company",
subject: "Sprint retrospective",
body: "Team velocity improved by 20% this sprint.",
priority: "normal",
});
db.core.messages.sendAgentMessage({
companyId,
fromAgentId: agentA,
channel: "department",
channelTargetId: "engineering",
subject: "Code review standards",
body: "All PRs must have at least two approvals before merging.",
priority: "urgent",
});
db.core.messages.sendAgentMessage({
companyId,
fromAgentId: agentB,
channel: "department",
channelTargetId: "design",
subject: "Design system update",
body: "We updated the color tokens for dark mode compatibility.",
priority: "low",
});
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("returns messages where body contains the query string", () => {
const results = db.core.messages.searchMessages(companyId, "velocity");
expect(results.length).toBe(1);
expect(results[0].companyId).toBe(companyId);
expect(results[0].body.toLowerCase()).toContain("velocity");
});
it("returns messages where subject contains the query string", () => {
const results = db.core.messages.searchMessages(companyId, "retrospective");
expect(results.length).toBe(1);
expect(results[0].subject).toBe("Sprint retrospective");
});
it("escapes LIKE wildcards so % and _ match literally", () => {
// Insert a message with literal % and _ characters
db.core.messages.sendAgentMessage({
companyId,
fromAgentId: agentA,
channel: "company",
subject: "Performance metrics",
body: "CPU usage was 95% and disk_io dropped significantly.",
priority: "normal",
});
// Search for the literal "95%"
const percentResults = db.core.messages.searchMessages(companyId, "95%");
expect(percentResults.length).toBe(1);
expect(percentResults[0].body).toContain("95%");
// Search for the literal "disk_io"
const underscoreResults = db.core.messages.searchMessages(companyId, "disk_io");
expect(underscoreResults.length).toBe(1);
expect(underscoreResults[0].body).toContain("disk_io");
});
it("scopes to a specific channel when channel param is provided", () => {
const results = db.core.messages.searchMessages(companyId, "approvals", { channel: "department" });
expect(results.length).toBe(1);
expect(results[0].channel).toBe("department");
expect(results[0].body).toContain("approvals");
});
it("scopes to channelTargetId when provided alongside channel", () => {
const results = db.core.messages.searchMessages(companyId, "updated", {
channel: "department",
channelTargetId: "design",
});
expect(results.length).toBe(1);
expect(results[0].channelTargetId).toBe("design");
});
it("returns empty array when no matches", () => {
const results = db.core.messages.searchMessages(companyId, "xyznonexistentquery");
expect(results).toEqual([]);
});
it("respects the limit parameter (default 50)", () => {
// Insert 60 messages
for (let i = 0; i < 60; i++) {
db.core.messages.sendAgentMessage({
companyId,
fromAgentId: agentA,
channel: "company",
subject: `Bulk message ${i}`,
body: `This is a flood test message number ${i}`,
priority: "normal",
});
}
// Default limit should be 50
const defaultResults = db.core.messages.searchMessages(companyId, "flood test");
expect(defaultResults.length).toBe(50);
// Explicit limit of 10
const limitedResults = db.core.messages.searchMessages(companyId, "flood test", { limit: 10 });
expect(limitedResults.length).toBe(10);
});
it("returns results ordered by created_at DESC (most recent first)", () => {
const results = db.core.messages.searchMessages(companyId, "migration");
expect(results.length).toBeGreaterThanOrEqual(2);
for (let i = 1; i < results.length; i++) {
const prev = new Date(results[i - 1].createdAt).getTime();
const curr = new Date(results[i].createdAt).getTime();
expect(prev).toBeGreaterThanOrEqual(curr);
}
});
});
+346
View File
@@ -0,0 +1,346 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { MigrationService } from "../../src/main/services/migration-service";
import { OrganizationService } from "../../src/main/services/organization-service";
import { SnapshotService } from "../../src/main/services/snapshot-service";
function createConnection(): DatabaseConnection {
return new DatabaseConnection(join(tmpdir(), `agentcompany-new-migration-${randomUUID()}.db`));
}
function createOrganization(organization: OrganizationService): string {
return organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
}
function createLegacyDatabase(sql: string): string {
const legacyPath = join(tmpdir(), `agentcompany-legacy-${randomUUID()}.db`);
const legacy = new DatabaseSync(legacyPath);
legacy.exec(sql);
legacy.close();
return legacyPath;
}
interface MigrationReportBody {
sourceFingerprint: string;
tableCounts: Record<string, number>;
tableChecksums: Record<string, string>;
idMaps: Record<string, string[]>;
failures: Array<{ table: string; reason: string; detail: string }>;
validationSummary: {
missingTableCount: number;
totalSourceRows: number;
rowsWithIntegrityFailures: number;
rowCoverageBasisPoints: number;
checksumCoverageBasisPoints: number;
idMapCoverageBasisPoints: number;
relationFailureCount: number;
passed: boolean;
};
rollbackPack: {
sourceDatabasePath: string;
sourceFingerprint: string;
tableChecksums: Record<string, string>;
idMaps: Record<string, string[]>;
};
}
describe("migration service", () => {
test("creates idempotent reports with checksums, id maps, rollback pack, and integrity failures", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const migrations = new MigrationService(connection.db);
const snapshot = new SnapshotService(connection.db);
const legacyPath = createLegacyDatabase(`
create table organizations(id text primary key);
insert into organizations(id) values ('org-1');
create table employees(id text primary key, org_id text);
insert into employees(id, org_id) values ('emp-1', 'org-1');
create table agents(id text primary key, org_id text);
insert into agents(id, org_id) values ('agent-1', 'org-missing');
create table tasks(id text primary key, org_id text, assignee_agent_id text);
insert into tasks(id, org_id, assignee_agent_id) values ('task-1', 'org-1', 'agent-missing');
`);
let orgId = "";
let firstReportId = "";
let secondReportId = "";
connection.transaction(() => {
orgId = createOrganization(organization);
firstReportId = migrations.createReport({ orgId, sourceDatabasePath: legacyPath });
secondReportId = migrations.createReport({ orgId, sourceDatabasePath: legacyPath });
});
const state = snapshot.load();
const body = JSON.parse(state.migrationReports[0]!.reportJson) as MigrationReportBody;
expect(secondReportId).toBe(firstReportId);
expect(state.migrationReports).toHaveLength(1);
expect(body.sourceFingerprint).toHaveLength(64);
expect(body.tableCounts.organizations).toBe(1);
expect(body.tableChecksums.agents).toHaveLength(64);
expect(body.idMaps.tasks).toEqual(["task-1"]);
expect(body.rollbackPack.sourceFingerprint).toBe(body.sourceFingerprint);
expect(body.rollbackPack.tableChecksums.agents).toBe(body.tableChecksums.agents);
expect(body.rollbackPack.idMaps.tasks).toEqual(["task-1"]);
expect(body.failures).toContainEqual({ table: "agents", reason: "dangling_org_id", detail: "agent-1 -> org-missing" });
expect(body.failures).toContainEqual({ table: "tasks", reason: "dangling_assignee_agent_id", detail: "task-1 -> agent-missing" });
expect(body.validationSummary).toMatchObject({
totalSourceRows: 4,
rowsWithIntegrityFailures: 2,
rowCoverageBasisPoints: 5000,
checksumCoverageBasisPoints: 10000,
idMapCoverageBasisPoints: 10000,
relationFailureCount: 3,
passed: false,
});
connection.close();
});
test("captures rebuilt domain tables and direct relation health", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const migrations = new MigrationService(connection.db);
const snapshot = new SnapshotService(connection.db);
const legacyPath = createLegacyDatabase(`
create table organizations(id text primary key);
create table org_departments(id text primary key, org_id text, lead_agent_id text);
create table hiring_policies(id text primary key, org_id text);
create table employees(id text primary key, org_id text);
create table employee_identities(id text primary key, org_id text, employee_id text);
create table service_identities(id text primary key, org_id text, agent_id text);
create table role_grants(id text primary key, org_id text, granted_by_employee_id text);
create table access_decisions(id text primary key, org_id text);
create table agents(id text primary key, org_id text, model_provider_id text, manager_agent_id text, workspace_id text);
create table tasks(id text primary key, org_id text, assignee_agent_id text, project_id text, goal_id text, workspace_id text);
create table workspaces(id text primary key, org_id text, project_id text);
create table goals(id text primary key, org_id text, owner_agent_id text);
create table projects(id text primary key, org_id text, goal_id text, lead_agent_id text);
create table task_runs(id text primary key, org_id text, task_id text, agent_id text, provider_id text, provider_request_id text, service_identity_id text);
create table approvals(id text primary key, org_id text, decided_by_employee_id text);
create table approval_routing_rules(id text primary key, org_id text);
create table approval_assignments(id text primary key, org_id text, approval_id text, routing_rule_id text);
create table feature_flags(id text primary key, org_id text);
create table slo_targets(id text primary key, org_id text);
create table operational_events(id text primary key, org_id text);
create table chaos_drills(id text primary key, org_id text);
create table chaos_drill_runs(id text primary key, org_id text, drill_id text);
create table sdlc_review_agents(id text primary key, org_id text);
create table sdlc_review_runs(id text primary key, org_id text, review_agent_id text);
create table eval_suites(id text primary key, org_id text);
create table eval_cases(id text primary key, org_id text, suite_id text);
create table eval_runs(id text primary key, org_id text, suite_id text);
create table pr_bot_gates(id text primary key, org_id text);
create table chat_spaces(id text primary key, org_id text);
create table chat_messages(id text primary key, org_id text, space_id text, thread_id text);
create table chat_threads(id text primary key, org_id text, space_id text, root_message_id text);
create table chat_memberships(space_id text, principal_type text, principal_id text);
create table chat_attachments(message_id text, file_id text);
create table chat_mentions(message_id text, target_type text, target_id text);
create table chat_slash_invocations(id text primary key, message_id text);
create table board_interventions(id text primary key, org_id text, thread_id text, message_id text, approval_id text);
create table chat_presence(principal_type text, principal_id text);
create table chat_read_state(org_id text, employee_id text, space_id text, last_read_message_id text);
create table providers(id text primary key, org_id text, credential_id text);
create table provider_requests(id text primary key, org_id text, provider_id text, workspace_id text);
create table provider_usage_windows(id text primary key, org_id text, provider_id text);
create table provider_rate_limit_observations(id text primary key, org_id text, provider_id text);
create table provider_auth_profiles(id text primary key, org_id text, provider_id text, credential_id text);
create table provider_auth_sessions(id text primary key, org_id text, provider_id text, employee_id text);
create table credentials(id text primary key, org_id text);
create table skill_definitions(id text primary key, org_id text);
create table skill_revisions(id text primary key, skill_id text);
create table skill_artifacts(id text primary key, revision_id text);
create table skill_tests(id text primary key, revision_id text);
create table skill_dependencies(revision_id text, depends_on_revision_id text);
create table skill_bindings(id text primary key, org_id text, agent_id text, skill_id text, pinned_revision_id text);
create table skill_runtime_snapshots(id text primary key, agent_id text, run_id text);
create table skill_catalog_tags(skill_id text, tag text, tag_type text);
create table file_objects(id text primary key, org_id text, space_id text);
create table file_access_grants(id text primary key, org_id text, file_id text, source_message_id text, source_thread_id text);
create table file_handoff_records(id text primary key, org_id text, message_id text, file_id text);
create table social_accounts(id text primary key, org_id text, authorized_employee_id text);
create table social_account_grants(id text primary key, org_id text, social_account_id text, employee_id text);
create table browser_session_containers(id text primary key, org_id text, social_account_id text);
create table browser_action_policies(id text primary key, org_id text, social_account_id text);
create table social_approval_routes(id text primary key, org_id text, social_account_id text, route_to_id text);
create table browser_actions(id text primary key, org_id text, social_account_id text, evidence_file_id text);
create table social_audit_records(id text primary key, org_id text, social_account_id text, evidence_file_id text);
create table scheduled_posts(id text primary key, org_id text, social_account_id text, approval_id text, created_by_employee_id text);
create table policy_rules(id text primary key, org_id text);
create table compliance_exports(id text primary key, org_id text, requested_by_employee_id text);
create table legal_holds(id text primary key, org_id text, created_by_employee_id text);
create table retention_freezes(id text primary key, org_id text, created_by_employee_id text);
create table search_documents(entity_id text);
create table audit_replay_runs(id text primary key, org_id text, thread_id text, created_by_employee_id text);
create table rollback_snapshots(id text primary key, org_id text, migration_report_id text, created_by_employee_id text);
create table migration_cutover_events(id text primary key, org_id text, migration_report_id text, rollback_snapshot_id text);
create table migration_reconciliation_items(id text primary key, org_id text, migration_report_id text, rollback_snapshot_id text, resolved_by_employee_id text);
create table audit_events(id text primary key, org_id text);
insert into organizations(id) values ('org-1');
insert into employees(id, org_id) values ('emp-1', 'org-1');
insert into employee_identities(id, org_id, employee_id) values ('eid-1', 'org-1', 'emp-1');
insert into credentials(id, org_id) values ('cred-1', 'org-1');
insert into providers(id, org_id, credential_id) values ('prov-1', 'org-1', 'cred-1');
insert into chat_spaces(id, org_id) values ('space-1', 'org-1');
insert into file_objects(id, org_id, space_id) values ('file-1', 'org-1', 'space-1');
insert into workspaces(id, org_id, project_id) values ('wsp-1', 'org-1', 'prj-1');
insert into agents(id, org_id, model_provider_id, manager_agent_id, workspace_id) values ('agent-1', 'org-1', 'prov-1', null, 'wsp-1');
insert into org_departments(id, org_id, lead_agent_id) values ('dep-1', 'org-1', 'agent-1');
insert into hiring_policies(id, org_id) values ('hpl-1', 'org-1');
insert into service_identities(id, org_id, agent_id) values ('sid-1', 'org-1', 'agent-1');
insert into role_grants(id, org_id, granted_by_employee_id) values ('role-1', 'org-1', 'emp-1');
insert into access_decisions(id, org_id) values ('access-1', 'org-1');
insert into goals(id, org_id, owner_agent_id) values ('goal-1', 'org-1', 'agent-1');
insert into projects(id, org_id, goal_id, lead_agent_id) values ('prj-1', 'org-1', 'goal-1', 'agent-1');
insert into tasks(id, org_id, assignee_agent_id, project_id, goal_id, workspace_id) values ('task-1', 'org-1', 'agent-1', 'prj-1', 'goal-1', 'wsp-1');
insert into task_runs(id, org_id, task_id, agent_id, provider_id, provider_request_id, service_identity_id) values ('run-1', 'org-1', 'task-1', 'agent-1', 'prov-1', 'req-1', 'sid-1');
insert into approvals(id, org_id, decided_by_employee_id) values ('approval-1', 'org-1', 'emp-1');
insert into approval_routing_rules(id, org_id) values ('route-1', 'org-1');
insert into approval_assignments(id, org_id, approval_id, routing_rule_id) values ('assign-1', 'org-1', 'approval-1', 'route-1');
insert into feature_flags(id, org_id) values ('flag-1', 'org-1');
insert into slo_targets(id, org_id) values ('slo-1', 'org-1');
insert into operational_events(id, org_id) values ('op-1', 'org-1');
insert into chaos_drills(id, org_id) values ('drill-1', 'org-1');
insert into chaos_drill_runs(id, org_id, drill_id) values ('drill-run-1', 'org-1', 'drill-1');
insert into sdlc_review_agents(id, org_id) values ('reviewer-1', 'org-1');
insert into sdlc_review_runs(id, org_id, review_agent_id) values ('review-run-1', 'org-1', 'reviewer-1');
insert into eval_suites(id, org_id) values ('suite-1', 'org-1');
insert into eval_cases(id, org_id, suite_id) values ('case-1', 'org-1', 'suite-1');
insert into eval_runs(id, org_id, suite_id) values ('eval-run-1', 'org-1', 'suite-1');
insert into pr_bot_gates(id, org_id) values ('gate-1', 'org-1');
insert into chat_threads(id, org_id, space_id, root_message_id) values ('thread-1', 'org-1', 'space-1', null);
insert into chat_messages(id, org_id, space_id, thread_id) values ('msg-1', 'org-1', 'space-1', 'thread-1');
insert into chat_memberships(space_id, principal_type, principal_id) values ('space-1', 'employee', 'emp-1');
insert into chat_attachments(message_id, file_id) values ('msg-1', 'file-1');
insert into file_access_grants(id, org_id, file_id, source_message_id, source_thread_id) values ('file-grant-1', 'org-1', 'file-1', 'msg-1', 'thread-1');
insert into file_handoff_records(id, org_id, message_id, file_id) values ('handoff-1', 'org-1', 'msg-1', 'file-1');
insert into chat_mentions(message_id, target_type, target_id) values ('msg-1', 'agent', 'agent-1');
insert into chat_slash_invocations(id, message_id) values ('slash-1', 'msg-1');
insert into board_interventions(id, org_id, thread_id, message_id, approval_id) values ('bdi-1', 'org-1', 'thread-1', 'msg-1', 'approval-1');
insert into chat_presence(principal_type, principal_id) values ('agent', 'agent-1');
insert into chat_read_state(org_id, employee_id, space_id, last_read_message_id) values ('org-1', 'emp-1', 'space-1', 'msg-1');
insert into provider_requests(id, org_id, provider_id, workspace_id) values ('req-1', 'org-1', 'prov-1', 'wsp-1');
insert into provider_usage_windows(id, org_id, provider_id) values ('usage-1', 'org-1', 'prov-1');
insert into provider_rate_limit_observations(id, org_id, provider_id) values ('quota-1', 'org-1', 'prov-1');
insert into provider_auth_profiles(id, org_id, provider_id, credential_id) values ('authp-1', 'org-1', 'prov-1', 'cred-1');
insert into provider_auth_sessions(id, org_id, provider_id, employee_id) values ('auths-1', 'org-1', 'prov-1', 'emp-1');
insert into skill_definitions(id, org_id) values ('skill-1', 'org-1');
insert into skill_revisions(id, skill_id) values ('rev-1', 'skill-1'), ('rev-2', 'skill-1');
insert into skill_artifacts(id, revision_id) values ('artifact-1', 'rev-1');
insert into skill_tests(id, revision_id) values ('test-1', 'rev-1');
insert into skill_dependencies(revision_id, depends_on_revision_id) values ('rev-1', 'rev-2');
insert into skill_bindings(id, org_id, agent_id, skill_id, pinned_revision_id) values ('binding-1', 'org-1', 'agent-1', 'skill-1', 'rev-1');
insert into skill_runtime_snapshots(id, agent_id, run_id) values ('snapshot-1', 'agent-1', 'run-1');
insert into skill_catalog_tags(skill_id, tag, tag_type) values ('skill-1', 'engineering', 'department');
insert into social_accounts(id, org_id, authorized_employee_id) values ('social-1', 'org-1', 'emp-1');
insert into social_account_grants(id, org_id, social_account_id, employee_id) values ('grant-1', 'org-1', 'social-1', 'emp-1');
insert into browser_session_containers(id, org_id, social_account_id) values ('container-1', 'org-1', 'social-1');
insert into browser_action_policies(id, org_id, social_account_id) values ('browser-policy-1', 'org-1', 'social-1');
insert into social_approval_routes(id, org_id, social_account_id, route_to_id) values ('social-route-1', 'org-1', 'social-1', 'emp-1');
insert into browser_actions(id, org_id, social_account_id, evidence_file_id) values ('browser-1', 'org-1', 'social-1', 'file-1');
insert into social_audit_records(id, org_id, social_account_id, evidence_file_id) values ('social-audit-1', 'org-1', 'social-1', 'file-1');
insert into scheduled_posts(id, org_id, social_account_id, approval_id, created_by_employee_id) values ('post-1', 'org-1', 'social-1', 'approval-1', 'emp-1');
insert into policy_rules(id, org_id) values ('policy-1', 'org-1');
insert into compliance_exports(id, org_id, requested_by_employee_id) values ('cmp-1', 'org-1', 'emp-1');
insert into legal_holds(id, org_id, created_by_employee_id) values ('hold-1', 'org-1', 'emp-1');
insert into retention_freezes(id, org_id, created_by_employee_id) values ('freeze-1', 'org-1', 'emp-1');
insert into search_documents(entity_id) values ('task-1');
insert into audit_replay_runs(id, org_id, thread_id, created_by_employee_id) values ('replay-1', 'org-1', 'thread-1', 'emp-1');
insert into rollback_snapshots(id, org_id, migration_report_id, created_by_employee_id) values ('rollback-1', 'org-1', 'migration-1', 'emp-1');
insert into migration_cutover_events(id, org_id, migration_report_id, rollback_snapshot_id) values ('cutover-1', 'org-1', 'migration-1', 'rollback-1');
insert into migration_reconciliation_items(id, org_id, migration_report_id, rollback_snapshot_id, resolved_by_employee_id) values ('reconcile-1', 'org-1', 'migration-1', 'rollback-1', 'emp-1');
insert into audit_events(id, org_id) values ('audit-1', 'org-1');
`);
let orgId = "";
connection.transaction(() => {
orgId = createOrganization(organization);
migrations.createReport({ orgId, sourceDatabasePath: legacyPath });
});
const body = JSON.parse(snapshot.load().migrationReports[0]!.reportJson) as MigrationReportBody;
expect(body.failures).toEqual([]);
expect(body.validationSummary).toMatchObject({
missingTableCount: 0,
rowCoverageBasisPoints: 10000,
checksumCoverageBasisPoints: 10000,
idMapCoverageBasisPoints: 10000,
relationFailureCount: 0,
passed: true,
});
expect(body.tableCounts.credentials).toBe(1);
expect(body.tableCounts.employee_identities).toBe(1);
expect(body.tableCounts.org_departments).toBe(1);
expect(body.tableCounts.hiring_policies).toBe(1);
expect(body.tableCounts.feature_flags).toBe(1);
expect(body.tableCounts.chaos_drill_runs).toBe(1);
expect(body.tableCounts.sdlc_review_runs).toBe(1);
expect(body.tableCounts.eval_cases).toBe(1);
expect(body.tableCounts.role_grants).toBe(1);
expect(body.tableCounts.skill_artifacts).toBe(1);
expect(body.tableCounts.chat_read_state).toBe(1);
expect(body.tableCounts.board_interventions).toBe(1);
expect(body.tableCounts.file_access_grants).toBe(1);
expect(body.tableCounts.file_handoff_records).toBe(1);
expect(body.tableCounts.provider_requests).toBe(1);
expect(body.tableCounts.provider_usage_windows).toBe(1);
expect(body.tableCounts.provider_rate_limit_observations).toBe(1);
expect(body.tableCounts.provider_auth_profiles).toBe(1);
expect(body.tableCounts.provider_auth_sessions).toBe(1);
expect(body.tableCounts.browser_session_containers).toBe(1);
expect(body.tableCounts.browser_action_policies).toBe(1);
expect(body.tableCounts.social_approval_routes).toBe(1);
expect(body.tableCounts.social_audit_records).toBe(1);
expect(body.tableCounts.compliance_exports).toBe(1);
expect(body.tableCounts.audit_replay_runs).toBe(1);
expect(body.tableCounts.rollback_snapshots).toBe(1);
expect(body.tableCounts.migration_cutover_events).toBe(1);
expect(body.tableCounts.migration_reconciliation_items).toBe(1);
expect(body.tableCounts.search_documents).toBe(1);
expect(body.tableChecksums.audit_events).toHaveLength(64);
expect(body.idMaps.skill_runtime_snapshots).toEqual(["snapshot-1"]);
expect(body.idMaps.service_identities).toEqual(["sid-1"]);
expect(body.idMaps.access_decisions).toEqual(["access-1"]);
expect(body.idMaps.approval_assignments).toEqual(["assign-1"]);
expect(body.idMaps.operational_events).toEqual(["op-1"]);
expect(body.idMaps.chaos_drill_runs).toEqual(["drill-run-1"]);
expect(body.idMaps.sdlc_review_runs).toEqual(["review-run-1"]);
expect(body.idMaps.eval_runs).toEqual(["eval-run-1"]);
expect(body.idMaps.board_interventions).toEqual(["bdi-1"]);
expect(body.idMaps.file_handoff_records).toEqual(["handoff-1"]);
expect(body.idMaps.retention_freezes).toEqual(["freeze-1"]);
expect(body.idMaps.audit_replay_runs).toEqual(["replay-1"]);
expect(body.idMaps.rollback_snapshots).toEqual(["rollback-1"]);
expect(body.idMaps.migration_cutover_events).toEqual(["cutover-1"]);
expect(body.idMaps.migration_reconciliation_items).toEqual(["reconcile-1"]);
expect(body.idMaps.provider_usage_windows).toEqual(["usage-1"]);
expect(body.idMaps.provider_rate_limit_observations).toEqual(["quota-1"]);
expect(body.idMaps.provider_auth_profiles).toEqual(["authp-1"]);
expect(body.idMaps.provider_auth_sessions).toEqual(["auths-1"]);
expect(body.idMaps.browser_session_containers).toEqual(["container-1"]);
expect(body.idMaps.browser_action_policies).toEqual(["browser-policy-1"]);
expect(body.idMaps.social_approval_routes).toEqual(["social-route-1"]);
expect(body.idMaps.social_audit_records).toEqual(["social-audit-1"]);
connection.close();
});
test("rejects reports for unknown target organizations", () => {
const connection = createConnection();
const migrations = new MigrationService(connection.db);
const legacyPath = createLegacyDatabase("create table organizations(id text primary key);");
expect(() => connection.transaction(() => migrations.createReport({
orgId: "org_missing",
sourceDatabasePath: legacyPath,
}))).toThrow("Organization was not found.");
connection.close();
});
});
-101
View File
@@ -1,101 +0,0 @@
import { join } from "node:path";
import { tmpdir } from "node:os";
import { mkdtemp, rm } from "node:fs/promises";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { ConnectorId } from "@shared/types";
vi.mock("electron", () => ({
safeStorage: {
isEncryptionAvailable: () => false,
getSelectedStorageBackend: () => "basic_text",
encryptString: (value: string) => Buffer.from(value, "utf8"),
decryptString: (value: Buffer) => value.toString("utf8"),
},
}));
import { AppDatabase } from "../../src/main/database";
import { CONNECTORS } from "../../src/main/connector-definitions";
import { resolveModels } from "../../src/main/model-resolver";
import { pickFromCandidates } from "../../src/main/model-dispatch";
describe("model capability seed", () => {
let tempDir: string;
let db: AppDatabase;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-model-capabilities-"));
db = new AppDatabase(tempDir);
db.init();
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("keeps catalog-seeded rows current on existing profiles without overwriting user edits", () => {
db.core.modelCapabilities.upsertModelCapability({
provider: "zai",
modelId: "glm-4.6",
reasoning: 2,
coding: 2,
writing: 2,
tier: "trivial",
costPerMtokOut: 99,
});
db.core.modelCapabilities.deleteModelCapability("zai", "glm-5.1");
db.close();
db = new AppDatabase(tempDir);
db.init();
expect(db.core.modelCapabilities.getModelCapability("zai", "glm-5.1")).toMatchObject({
provider: "zai",
modelId: "glm-5.1",
reasoning: 5,
coding: 4,
writing: 4,
tier: "hard",
});
expect(db.core.modelCapabilities.getModelCapability("zai", "glm-4.6")).toMatchObject({
reasoning: 2,
coding: 2,
writing: 2,
tier: "trivial",
costPerMtokOut: 99,
});
});
it("seeds a capability row for every static connector catalog model", () => {
const seeded = new Set(
db.core.modelCapabilities
.listModelCapabilities()
.map((entry) => `${entry.provider}::${entry.modelId}`),
);
const expected = new Set<string>();
for (const connector of CONNECTORS) {
for (const model of resolveModels(connector.provider)) {
expected.add(`${connector.provider}::${model.id}`);
}
if (connector.auth.type !== "local" && connector.defaultModel.trim()) {
expected.add(`${connector.provider}::${connector.defaultModel}`);
}
}
expect(expected.size).toBeGreaterThan(700);
for (const key of expected) {
expect(seeded.has(key), key).toBe(true);
}
});
it("uses the same catalog seed path for hard reasoning picks", () => {
const winner = pickFromCandidates(
{ domain: "reasoning", requiredTier: "hard" },
[{ provider: "zai", connectorId: "zai" as ConnectorId, modelId: "glm-5.1" }],
db.core.modelCapabilities.listModelCapabilities(),
);
expect(winner?.modelId).toBe("glm-5.1");
});
});
-354
View File
@@ -1,354 +0,0 @@
import { describe, expect, it } from "vitest";
import type { AgentRecord, ConnectorId, ConnectorRecord, ModelCapability } from "@shared/types";
import {
pickFromCandidates,
pickModelForAgent,
type EnabledModel,
type PickModelDeps,
} from "../../src/main/model-dispatch";
/**
* Minimum fields required to build a plausible AgentRecord for the
* dispatcher. The shared type has many optional/legacy columns; the picker
* only reads role, title, roleLevel, connectorId, model, modelStrategy, and
* companyId.
*/
function makeAgent(overrides: Partial<AgentRecord>): AgentRecord {
return {
id: "agent-1",
companyId: "company-1",
name: "Test Agent",
role: "engineer",
title: "Staff Engineer",
department: null,
status: "active",
reportsTo: null,
connectorId: "anthropic-api" as ConnectorId,
workspaceId: null,
model: "claude-sonnet-4-6",
capabilities: "[]",
budgetMonthlyUsd: 0,
spentMonthlyUsd: 0,
metadataJson: "{}",
heartbeatEnabled: false,
heartbeatIntervalSec: 120,
lastHeartbeatAt: null,
createdAt: "2026-01-01T00:00:00Z",
updatedAt: "2026-01-01T00:00:00Z",
modelStrategy: "auto",
roleLevel: "ic",
...overrides,
};
}
/**
* Build a ConnectorRecord that the execution-readiness check treats as
* healthy. Only the fields the check reads are populated realistically;
* the rest default to empty.
*/
function makeConnector(id: string): ConnectorRecord {
return {
id: id as ConnectorId,
label: id,
description: "",
status: "ready",
version: null,
authState: "ok",
lastCheckedAt: null,
lastError: null,
model: null,
envBindingText: "",
notes: "",
capabilityMatrix: {
sessionResume: true,
gracefulCancel: true,
modelDiscovery: true,
structuredTranscript: true,
costAttribution: true,
},
configurationDoc: "",
};
}
/**
* Build picker deps from a flat list of (connectorId, provider, modelId)
* triples + a capability matrix. Anything unspecified behaves as if the
* connector is healthy and has no disabled models.
*/
function makeDeps(options: {
enabledModels: Array<{ connectorId: string; provider: string; modelId: string }>;
capabilities: ModelCapability[];
}): PickModelDeps {
const connectorsById = new Map<string, ConnectorRecord>();
for (const entry of options.enabledModels) {
if (!connectorsById.has(entry.connectorId)) {
connectorsById.set(entry.connectorId, makeConnector(entry.connectorId));
}
}
return {
getConnector: (id) => {
const c = connectorsById.get(id);
if (!c) throw new Error(`unknown connector ${id}`);
return c;
},
getDisabledModels: () => [],
listModelCapabilities: () => options.capabilities,
readConnectors: () => Array.from(connectorsById.values()),
};
}
/**
* Patch the getConnectorModelPolicy path used inside listEnabledModels.
* We don't want to stand up the full connector definition catalog for
* every test, so we intercept policy queries by wiring each connector's
* provider + model list through a per-test lookup.
*
* The picker imports `getConnectorDefinition` and `getConnectorModelPolicy`
* at module scope. Integration tests stand up the whole system; these
* unit tests exercise the pure scoring math by stubbing those imports via
* vitest.
*/
// Stubbing is handled by the helper above — tests below only assert the
// sort/filter/fallback behaviour on a hand-crafted capability matrix plus
// a small enabled-model set. The heavy integration path (connector
// definitions + policy) is covered by higher-level tests.
describe("pickModelForAgent", () => {
const claudeOpus: ModelCapability = {
provider: "anthropic",
modelId: "claude-opus-4-7",
reasoning: 5,
coding: 5,
writing: 5,
tier: "hard",
costPerMtokOut: 75,
updatedAt: "",
};
const llama: ModelCapability = {
provider: "fireworks",
modelId: "llama-70b",
reasoning: 2,
coding: 2,
writing: 2,
tier: "trivial",
costPerMtokOut: 1,
updatedAt: "",
};
it("explicit strategy honours agent.connectorId + agent.model verbatim", () => {
const agent = makeAgent({
modelStrategy: "explicit",
connectorId: "anthropic-api" as ConnectorId,
model: "claude-opus-4-7",
});
const deps = makeDeps({
enabledModels: [
{ connectorId: "anthropic-api", provider: "anthropic", modelId: "claude-opus-4-7" },
],
capabilities: [claudeOpus],
});
const pick = pickModelForAgent(agent, deps);
expect(pick.ok).toBe(true);
if (pick.ok) {
expect(pick.assignment).toEqual({ connectorId: "anthropic-api", modelId: "claude-opus-4-7" });
}
});
it("CEO role always takes the user-picked pair regardless of modelStrategy", () => {
const agent = makeAgent({
modelStrategy: "auto",
roleLevel: "c_suite",
role: "CEO",
reportsTo: "some-parent",
connectorId: "anthropic-api" as ConnectorId,
model: "claude-opus-4-7",
});
const deps = makeDeps({
enabledModels: [
{ connectorId: "anthropic-api", provider: "anthropic", modelId: "claude-opus-4-7" },
{ connectorId: "fireworks", provider: "fireworks", modelId: "llama-70b" },
],
capabilities: [claudeOpus, llama],
});
const pick = pickModelForAgent(agent, deps);
expect(pick.ok).toBe(true);
if (pick.ok) expect(pick.assignment.modelId).toBe("claude-opus-4-7");
});
it("CEO is identified by reportsTo === null even when roleLevel stays at the schema default 'ic'", () => {
const agent = makeAgent({
modelStrategy: "auto",
roleLevel: "ic",
reportsTo: null,
role: "CEO",
connectorId: "anthropic-api" as ConnectorId,
model: "claude-opus-4-7",
});
const deps = makeDeps({
enabledModels: [
{ connectorId: "anthropic-api", provider: "anthropic", modelId: "claude-opus-4-7" },
{ connectorId: "fireworks", provider: "fireworks", modelId: "llama-70b" },
],
capabilities: [claudeOpus, llama],
});
const pick = pickModelForAgent(agent, deps);
expect(pick.ok).toBe(true);
if (pick.ok) expect(pick.assignment.modelId).toBe("claude-opus-4-7");
});
it("auto strategy with no qualifying candidate fails explicitly (no silent CEO fallback)", () => {
const agent = makeAgent({
modelStrategy: "auto",
roleLevel: "ic",
reportsTo: "manager-1",
role: "Senior Architect",
title: "Principal Architect",
connectorId: "fireworks" as ConnectorId,
model: "llama-70b",
});
// Two enabled models, both scoring too low for a hard reasoning agent.
const deps = makeDeps({
enabledModels: [
{ connectorId: "fireworks", provider: "fireworks", modelId: "llama-70b" },
{ connectorId: "fireworks-b", provider: "fireworks", modelId: "llama-7b" },
],
capabilities: [
llama,
{ provider: "fireworks", modelId: "llama-7b", reasoning: 2, coding: 2, writing: 2, tier: "trivial", costPerMtokOut: 0.5, updatedAt: "" },
],
});
const pick = pickModelForAgent(agent, deps);
expect(pick.ok).toBe(false);
if (!pick.ok) {
expect(pick.reason).toBe("NO_QUALIFYING_CANDIDATE");
expect(pick.message).toContain(agent.name);
}
});
});
describe("inferTaskProfile — i18n", () => {
it("classifies Chinese role strings via substring match", async () => {
const { inferTaskProfile } = await import("../../src/main/agent-role-helpers");
expect(inferTaskProfile("高级架构师", null, "director").domain).toBe("reasoning");
expect(inferTaskProfile("前端工程师", null, "ic").domain).toBe("coding");
expect(inferTaskProfile("内容运营", null, "ic").domain).toBe("writing");
expect(inferTaskProfile("产品经理", null, "manager").domain).toBe("writing");
});
it("recognises Chinese trivial-tier cues", async () => {
const { inferTaskProfile } = await import("../../src/main/agent-role-helpers");
expect(inferTaskProfile("实习生", "数据标注实习生", "ic").requiredTier).toBe("trivial");
expect(inferTaskProfile("初级工程师", null, "ic").requiredTier).toBe("trivial");
expect(inferTaskProfile("助理", null, "ic").requiredTier).toBe("trivial");
});
it("English classification still works after unicode tokenizer swap", async () => {
const { inferTaskProfile } = await import("../../src/main/agent-role-helpers");
expect(inferTaskProfile("Senior Architect", null, "director").domain).toBe("reasoning");
expect(inferTaskProfile("Staff Engineer", null, "lead").domain).toBe("coding");
expect(inferTaskProfile("Marketing Lead", null, "lead").domain).toBe("writing");
// 'lead' keyword must not match 'leaderboard' / 'cleaner' substrings —
// token-based matching is still in effect for Latin keywords.
expect(inferTaskProfile("Cleaner", null, "ic").domain).toBe("writing");
});
});
describe("pickFromCandidates — pure rubric", () => {
const claudeOpus: ModelCapability = {
provider: "anthropic", modelId: "claude-opus-4-7",
reasoning: 5, coding: 5, writing: 5,
tier: "hard", costPerMtokOut: 75, updatedAt: "",
};
const claudeSonnet: ModelCapability = {
provider: "anthropic", modelId: "claude-sonnet-4-6",
reasoning: 4, coding: 5, writing: 5,
tier: "standard", costPerMtokOut: 15, updatedAt: "",
};
const kimiK2: ModelCapability = {
provider: "fireworks", modelId: "kimi-k2",
reasoning: 4, coding: 4, writing: 5,
tier: "standard", costPerMtokOut: 3, updatedAt: "",
};
const llama: ModelCapability = {
provider: "fireworks", modelId: "llama-70b",
reasoning: 2, coding: 2, writing: 2,
tier: "trivial", costPerMtokOut: 1, updatedAt: "",
};
const allEnabled: EnabledModel[] = [
{ provider: "anthropic", connectorId: "claude-pro" as ConnectorId, modelId: "claude-opus-4-7" },
{ provider: "anthropic", connectorId: "claude-pro" as ConnectorId, modelId: "claude-sonnet-4-6" },
{ provider: "fireworks", connectorId: "fireworks" as ConnectorId, modelId: "kimi-k2" },
{ provider: "fireworks", connectorId: "fireworks" as ConnectorId, modelId: "llama-70b" },
];
it("picks the cheapest qualifying model (standard/writing → kimi beats sonnet on price)", () => {
const winner = pickFromCandidates(
{ domain: "writing", requiredTier: "standard" },
allEnabled,
[claudeOpus, claudeSonnet, kimiK2, llama],
);
expect(winner?.modelId).toBe("kimi-k2");
});
it("hard tier requires tier≥hard even if domain score clears the floor", () => {
// writing=5 qualifies sonnet and kimi numerically, but both are tier=standard.
// Only opus (tier=hard) survives.
const winner = pickFromCandidates(
{ domain: "writing", requiredTier: "hard" },
allEnabled,
[claudeOpus, claudeSonnet, kimiK2, llama],
);
expect(winner?.modelId).toBe("claude-opus-4-7");
});
it("returns null when no candidate clears the bar", () => {
// Ask for hard/reasoning ≥4 but only provide trivial-tier llama.
const winner = pickFromCandidates(
{ domain: "reasoning", requiredTier: "hard" },
[{ provider: "fireworks", connectorId: "fireworks" as ConnectorId, modelId: "llama-70b" }],
[llama],
);
expect(winner).toBeNull();
});
it("returns null on an empty candidate list", () => {
expect(pickFromCandidates({ domain: "coding", requiredTier: "trivial" }, [], [llama])).toBeNull();
});
it("trivial tier still honours the domain floor (2)", () => {
// llama scores 2 on writing — exactly on the floor, so it qualifies.
const winner = pickFromCandidates(
{ domain: "writing", requiredTier: "trivial" },
[{ provider: "fireworks", connectorId: "fireworks" as ConnectorId, modelId: "llama-70b" }],
[llama],
);
expect(winner?.modelId).toBe("llama-70b");
});
it("ignores enabled models with no capability row", () => {
// Only claudeOpus has a capability row; mystery model is filtered out
// despite appearing in the enabled list.
const winner = pickFromCandidates(
{ domain: "reasoning", requiredTier: "hard" },
[
{ provider: "mystery", connectorId: "mystery" as ConnectorId, modelId: "unknown" },
{ provider: "anthropic", connectorId: "claude-pro" as ConnectorId, modelId: "claude-opus-4-7" },
],
[claudeOpus],
);
expect(winner?.modelId).toBe("claude-opus-4-7");
});
});
describe("pickModelForAgent — internal rubric", () => {
it("exposes DOMAIN_SCORE_FLOOR and TIER_RANK for downstream tests", async () => {
const { __INTERNAL__ } = await import("../../src/main/model-dispatch");
expect(__INTERNAL__.DOMAIN_SCORE_FLOOR.trivial).toBe(2);
expect(__INTERNAL__.DOMAIN_SCORE_FLOOR.standard).toBe(3);
expect(__INTERNAL__.DOMAIN_SCORE_FLOOR.hard).toBe(4);
expect(__INTERNAL__.TIER_RANK.trivial).toBe(1);
expect(__INTERNAL__.TIER_RANK.standard).toBe(2);
expect(__INTERNAL__.TIER_RANK.hard).toBe(3);
});
});
-46
View File
@@ -1,46 +0,0 @@
import { describe, expect, it } from "vitest";
import type { SectionId } from "@shared/types";
import {
COMMAND_PALETTE_NAV_ITEM_DEFS,
DASHBOARD_NAV_TARGETS,
SECTION_NAV_IDS,
SETTINGS_NAV_ITEM_DEF,
SIDEBAR_NAV_GROUP_DEFS,
} from "../../src/renderer/navigation";
function sidebarSectionIds(): SectionId[] {
return SIDEBAR_NAV_GROUP_DEFS.flatMap((group) => group.items.map((item) => item.id));
}
describe("renderer navigation model", () => {
it("keeps the sidebar focused on the primary user journey", () => {
expect(sidebarSectionIds()).toEqual([
"overview",
"inbox",
"tasks",
"approvals",
"agents",
"communication",
"documents",
]);
});
it("keeps every section reachable from command navigation", () => {
const commandIds = COMMAND_PALETTE_NAV_ITEM_DEFS.map((item) => item.id);
expect(new Set(commandIds).size).toBe(SECTION_NAV_IDS.length);
expect(commandIds).toEqual(expect.arrayContaining(SECTION_NAV_IDS));
});
it("keeps Dashboard targets reachable from command navigation", () => {
const commandIds = new Set(COMMAND_PALETTE_NAV_ITEM_DEFS.map((item) => item.id));
for (const sectionId of DASHBOARD_NAV_TARGETS) {
expect(commandIds.has(sectionId)).toBe(true);
}
});
it("keeps settings outside the scrollable sidebar groups", () => {
expect(sidebarSectionIds()).not.toContain(SETTINGS_NAV_ITEM_DEF.id);
});
});
-270
View File
@@ -1,270 +0,0 @@
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<{
options: Record<string, unknown>;
clickHandler?: () => void;
closeHandler?: () => void;
}> = [];
vi.mock("electron", () => {
return {
Notification: class MockNotification {
static isSupported = () => true;
options: Record<string, unknown>;
_clickHandler?: () => void;
_closeHandler?: () => void;
constructor(options: Record<string, unknown>) {
this.options = options;
}
show() {
constructedNotifications.push({
options: this.options,
clickHandler: this._clickHandler,
closeHandler: this._closeHandler,
});
}
on(event: string, handler: () => void) {
if (event === "click") this._clickHandler = handler;
if (event === "close") this._closeHandler = handler;
// Update the last entry if already shown, or store for later
const last = constructedNotifications[constructedNotifications.length - 1];
if (last && event === "click") last.clickHandler = handler;
if (last && event === "close") last.closeHandler = handler;
}
},
app: { focus: vi.fn() },
};
});
// Import after mocking
const { createNotificationService } = await import("@main/notification-service");
describe("notification-service", () => {
let service: ReturnType<typeof createNotificationService>;
let mockGetMainWindow: ReturnType<typeof vi.fn>;
let mockEmitEvent: ReturnType<typeof vi.fn>;
let mockLogger: { info: ReturnType<typeof vi.fn>; warn: ReturnType<typeof vi.fn>; error: ReturnType<typeof vi.fn> };
beforeEach(() => {
vi.useFakeTimers();
constructedNotifications.length = 0;
mockGetMainWindow = vi.fn(() => null);
mockEmitEvent = vi.fn();
mockLogger = {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
service = createNotificationService({
getMainWindow: mockGetMainWindow as unknown as () => BrowserWindow | null,
emitEvent: mockEmitEvent as unknown as (event: DesktopEvent) => void,
logger: mockLogger as { info: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void },
});
});
afterEach(() => {
service.destroy();
vi.useRealTimers();
});
it("Test 1: fireNotification with urgency 'critical' creates Notification with silent: false", () => {
service.notify({
title: "Budget Exhausted",
body: "Agent paused",
urgency: "critical",
});
expect(constructedNotifications).toHaveLength(1);
expect(constructedNotifications[0]!.options.silent).toBe(false);
});
it("Test 2: fireNotification with urgency 'informational' creates Notification with silent: true", () => {
service.notify({
title: "Agent Hired",
body: "Bob joined",
urgency: "informational",
});
expect(constructedNotifications).toHaveLength(1);
expect(constructedNotifications[0]!.options.silent).toBe(true);
});
it("Test 3: notify without batchKey fires immediately (no batching)", () => {
service.notify({
title: "Update Available",
body: "Version 2.0 is ready",
urgency: "informational",
});
expect(constructedNotifications).toHaveLength(1);
});
it("Test 4: notify with batchKey fires first event immediately, starts batch timer", () => {
service.notify({
title: "Task Failed",
body: "Task A failed",
urgency: "informational",
navigation: { section: "tasks" },
batchKey: "failed_runs",
});
expect(constructedNotifications).toHaveLength(1);
expect(constructedNotifications[0]!.options.title).toBe("Task Failed");
});
it("Test 5: notify with same batchKey within 10s increments count, does NOT fire second notification", () => {
service.notify({
title: "Task Failed",
body: "Task A failed",
urgency: "informational",
navigation: { section: "tasks" },
batchKey: "failed_runs",
});
service.notify({
title: "Task Failed",
body: "Task B failed",
urgency: "informational",
navigation: { section: "tasks" },
batchKey: "failed_runs",
});
// Only the first should have fired
expect(constructedNotifications).toHaveLength(1);
});
it("Test 6: After 10s batch timer expires, fires summary notification with count in title", () => {
service.notify({
title: "Task Failed",
body: "Task A failed",
urgency: "informational",
navigation: { section: "tasks" },
batchKey: "failed_runs",
});
// Add 2 more batched notifications
service.notify({
title: "Task Failed",
body: "Task B failed",
urgency: "informational",
navigation: { section: "tasks" },
batchKey: "failed_runs",
});
service.notify({
title: "Task Failed",
body: "Task C failed",
urgency: "informational",
navigation: { section: "tasks" },
batchKey: "failed_runs",
});
expect(constructedNotifications).toHaveLength(1); // Only first
// Advance timer by 10 seconds
vi.advanceTimersByTime(10_000);
// Summary notification should have fired
expect(constructedNotifications).toHaveLength(2);
const summary = constructedNotifications[1]!;
expect(summary.options.title).toContain("2");
expect(summary.options.title).toContain("tasks failed");
expect(summary.options.body).toBe("Click to view failed runs");
});
it("Test 7: Batched summary notification has no entityId (navigates to section overview)", () => {
service.notify({
title: "Agent Hired",
body: "Alice joined",
urgency: "informational",
navigation: { section: "agents", entityId: "agent-1" },
batchKey: "agent_hired",
});
service.notify({
title: "Agent Hired",
body: "Bob joined",
urgency: "informational",
navigation: { section: "agents", entityId: "agent-2" },
batchKey: "agent_hired",
});
vi.advanceTimersByTime(10_000);
// Summary notification fires -- simulate click
expect(constructedNotifications).toHaveLength(2);
const summaryClickHandler = constructedNotifications[1]!.clickHandler;
expect(summaryClickHandler).toBeInstanceOf(Function);
summaryClickHandler!();
// emitEvent should have been called with section but no entityId
const emitCall = mockEmitEvent.mock.calls.find(
(args: unknown[]) => (args[0] as { type?: string })?.type === "notification-click",
);
expect(emitCall).toBeDefined();
expect(emitCall![0].section).toBe("agents");
expect(emitCall![0].entityId).toBeUndefined();
});
it("Test 8: Events in NEVER_BATCH list always fire individually regardless of batchKey", () => {
// pending_approval is in NEVER_BATCH
service.notify({
title: "Approval Required",
body: "First approval",
urgency: "critical",
navigation: { section: "approvals" },
batchKey: "pending_approval",
});
service.notify({
title: "Approval Required",
body: "Second approval",
urgency: "critical",
navigation: { section: "approvals" },
batchKey: "pending_approval",
});
// Both should fire individually
expect(constructedNotifications).toHaveLength(2);
});
it("Test 9: Active Notification objects are held in a Set and removed on 'close' event", () => {
service.notify({
title: "Test",
body: "Test body",
urgency: "informational",
});
expect(constructedNotifications).toHaveLength(1);
// The close handler should exist and be callable without error
const closeHandler = constructedNotifications[0]!.closeHandler;
expect(closeHandler).toBeInstanceOf(Function);
// Calling close handler should not throw (notification removed from internal Set)
closeHandler!();
});
it("Test 10: On Linux, urgency 'critical' sets urgency option to 'critical'", () => {
const originalPlatform = process.platform;
Object.defineProperty(process, "platform", { value: "linux", configurable: true });
service.notify({
title: "Critical Alert",
body: "Something critical",
urgency: "critical",
});
expect(constructedNotifications).toHaveLength(1);
expect(constructedNotifications[0]!.options.urgency).toBe("critical");
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true });
});
});
-64
View File
@@ -1,64 +0,0 @@
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { CredentialStore } from "../../src/main/credentials/credential-store";
import { OAuthLoginService } from "../../src/main/credentials/oauth-login-service";
vi.mock("electron", () => ({
shell: { openExternal: vi.fn(async () => true) },
}));
beforeAll(() => {
process.env.AGENTCOMPANY_ALLOW_INSECURE_VAULT = "true";
});
const noopLogger = { info: vi.fn(), warn: vi.fn(), error: vi.fn() };
describe("OAuthLoginService", () => {
let tempDir: string;
let db: AppDatabase;
let store: CredentialStore;
let service: OAuthLoginService;
const emit = vi.fn();
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-oauth-service-"));
db = new AppDatabase(tempDir);
db.init();
store = new CredentialStore({ db });
service = new OAuthLoginService({
store,
emitEvent: emit,
refreshConnectorHealth: async () => {},
logger: noopLogger,
});
emit.mockClear();
});
afterEach(async () => {
db.close();
await rm(tempDir, { recursive: true, force: true });
});
it("rejects start() for a non-OAuth connector", async () => {
await expect(service.start("anthropic")).rejects.toThrow(/does not use OAuth/);
});
it("rejects start() for an unknown connector id", async () => {
await expect(service.start("definitely-not-a-connector")).rejects.toThrow(/Unknown connector/);
});
it("submitPromptResponse returns false when there is no pending prompt", () => {
expect(service.submitPromptResponse("nonexistent-request-id", "value")).toBe(false);
});
it("submitManualCode returns false when there is no pending request", () => {
expect(service.submitManualCode("nonexistent-request-id", "code")).toBe(false);
});
it("cancel() of a connector with no active flow is a no-op (does not throw)", () => {
expect(() => service.cancel("claude-pro")).not.toThrow();
});
});
Loaded 100 of 1904 files, more files were not shown because too many files have changed in this diff. Show more