Initialize project and ignore local references

This commit is contained in:
ZacharyZhang-NY committed 2026-03-15 22:17:07 -04:00
1 parent a11ac3eaba
commit 2af82b6bb3
132 files changed
+29426

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
import { existsSync } from "node:fs";
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { _electron as electron, expect, test } from "@playwright/test";
import { getElectronExecutablePath, getMainEntryPath } from "./launch";
test("first launch onboarding creates a company and opens the desktop shell", async () => {
const entry = getMainEntryPath();
expect(existsSync(entry)).toBeTruthy();
const profileDir = await mkdtemp(join(tmpdir(), "agentcompany-e2e-"));
const workspaceDir = await mkdtemp(join(tmpdir(), "agentcompany-workspace-"));
const app = await electron.launch({
executablePath: getElectronExecutablePath(),
args: [entry],
cwd: process.cwd(),
env: {
...process.env,
AGENTCOMPANY_PROFILE_DIR: profileDir,
AGENTCOMPANY_DISABLE_UPDATES: "true",
AGENTCOMPANY_DISABLE_SINGLE_INSTANCE: "true",
},
});
try {
const page = await app.firstWindow();
await expect(page.getByText("AgentCompany Desktop")).toBeVisible();
await page.getByLabel("Company name").fill("Orbit Foundry");
await page.getByLabel("Workspace folder").fill(workspaceDir);
await page.getByRole("button", { name: /Continue to dashboard/i }).click();
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
// Company name is rendered in a custom dropdown trigger button in the sidebar.
await expect(page.locator("aside button", { hasText: "Orbit Foundry" }).first()).toBeVisible();
const showConsoleButton = page.getByRole("button", { name: /show console/i });
await expect(showConsoleButton).toBeVisible();
await showConsoleButton.click();
await expect(page.getByText("Console", { exact: true })).toBeVisible();
const hideConsoleButton = page.getByRole("button", { name: /hide console/i });
await expect(hideConsoleButton).toBeVisible();
await hideConsoleButton.click();
await expect(page.getByText("Console", { exact: true })).toBeHidden();
await expect(showConsoleButton).toBeVisible();
} finally {
await app.close();
await rm(profileDir, { recursive: true, force: true });
await rm(workspaceDir, { recursive: true, force: true });
}
});
+13
View File
@@ -0,0 +1,13 @@
import { execFileSync } from "node:child_process";
/**
* Playwright globalSetup: build the Electron app from source before E2E tests.
* Uses execFileSync (no shell) for safety and deterministic builds.
*/
export default function globalSetup() {
execFileSync("pnpm", ["electron-vite", "build"], {
stdio: "inherit",
cwd: process.cwd(),
timeout: 120_000,
});
}
+9
View File
@@ -0,0 +1,9 @@
import { resolve } from "node:path";
export function getElectronExecutablePath() {
return resolve(process.cwd(), process.platform === "win32" ? "node_modules/.bin/electron.cmd" : "node_modules/.bin/electron");
}
export function getMainEntryPath() {
return resolve(process.cwd(), "out/main/index.js");
}
+18
View File
@@ -0,0 +1,18 @@
import { defineConfig } from "@playwright/test";
export default defineConfig({
testDir: ".",
testMatch: ["desktop.smoke.spec.ts"],
timeout: 120_000,
fullyParallel: false,
workers: 1,
reporter: "list",
use: {
trace: "on-first-retry",
screenshot: "only-on-failure",
},
// Build from current source before running e2e tests.
// Uses globalSetup (not webServer) because electron-vite build is a
// one-shot build command, not a long-running server process.
globalSetup: "./e2e-global-setup.ts",
});
File diff suppressed because it is too large. Load diff
+150
View File
@@ -0,0 +1,150 @@
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("AppDatabase", () => {
it("persists core entities, secrets, run logs, and backup restore state", async () => {
const profileDir = await createTempDir("agentcompany-profile-");
const backupDir = await createTempDir("agentcompany-backup-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.saveCompany({
name: "Orbit Foundry",
description: "Test company",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "https://example.com/repo.git",
repoRef: "main",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "Helios",
role: "CEO",
title: "Lead agent",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Operate the company",
budgetMonthlyUsd: 200,
});
const goalId = db.saveGoal({
companyId,
title: "Ship the desktop app",
description: "Reach packaged desktop parity.",
ownerAgentId: agentId,
status: "active",
});
const projectId = db.saveProject({
companyId,
goalId,
name: "Desktop rebuild",
description: "Rebuild as Electron desktop app.",
leadAgentId: agentId,
status: "in_progress",
});
const taskId = db.saveTask({
companyId,
projectId,
goalId,
title: "Validate connector state",
description: "Run a health check and capture output.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
db.saveSecret({
companyId,
name: "vault-openai",
description: "Test key",
value: "sk-test-123",
});
db.saveConnector({
id: "codex_local",
command: "codex",
model: null,
envBindingText: "OPENAI_API_KEY=vault-openai",
notes: "Connector note",
});
expect(db.resolveSecretEnv("OPENAI_API_KEY=vault-openai", companyId)).toEqual({
OPENAI_API_KEY: "sk-test-123",
});
const run = db.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "codex_local",
});
await db.appendRunLog(run.id, "stdout", "hello from run\n");
expect(db.readRunLog(run.id)).toContain("hello from run");
db.markRunStatus(run.id, "running", "Connector launched");
db.finishRun({
runId: run.id,
status: "succeeded",
summary: "Completed successfully",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gpt-5-codex",
sessionDisplayId: "session-1",
costUsd: 12.5,
unattributedCost: false,
});
const snapshotBeforeBackup = db.listSnapshot();
expect(snapshotBeforeBackup.companies).toHaveLength(1);
expect(snapshotBeforeBackup.runs[0]?.status).toBe("succeeded");
expect(snapshotBeforeBackup.tasks[0]?.activeRunId).toBeNull();
await db.exportBackup(backupDir);
db.saveCompany({
name: "Temporary company",
description: "Should disappear after restore",
status: "active",
});
expect(db.listSnapshot().companies).toHaveLength(2);
await db.restoreFromBackup(backupDir);
const restoredSnapshot = db.listSnapshot();
expect(restoredSnapshot.companies).toHaveLength(1);
expect(restoredSnapshot.companies[0]?.name).toBe("Orbit Foundry");
expect(restoredSnapshot.runs[0]?.summary).toBe("Completed successfully");
db.close();
});
});
+37
View File
@@ -0,0 +1,37 @@
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("keeps security-sensitive commands pinned to connector defaults", () => {
const [codex] = buildDefaultConnectorRows([
{
id: "codex_local",
command: "/usr/local/bin/codex",
status: "ready",
notes: "custom note",
},
]);
expect(codex.id).toBe("codex_local");
expect(codex.command).toBe("codex");
expect(codex.status).toBe("ready");
expect(codex.notes).toBe("custom note");
expect(codex.capabilityMatrix.structuredTranscript).toBe(true);
});
});
+67
View File
@@ -0,0 +1,67 @@
import { describe, expect, it } from "vitest";
import {
connectorInputSchema,
runLogSchema,
secretInputSchema,
startRunSchema,
workspaceInputSchema,
} from "../../src/shared/contracts";
describe("shared contracts", () => {
it("fills connector defaults for optional fields", () => {
const parsed = connectorInputSchema.parse({
id: "codex_local",
command: "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();
});
});
+661
View File
@@ -0,0 +1,661 @@
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", () => {
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("persists company-scoped entities and resolves secret env bindings", () => {
const companyId = db.saveCompany({
name: "Northline Systems",
description: "Operator company",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary Workspace",
localPath: "/tmp/northline",
repoUrl: "https://github.com/example/northline",
repoRef: "main",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "Chief Operator",
role: "CEO",
title: "Lead operator",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: "gpt-5",
capabilities: "Directs execution",
budgetMonthlyUsd: 250,
});
const goalId = db.saveGoal({
companyId,
title: "Ship the desktop product",
description: "Stabilize the local operator console.",
ownerAgentId: agentId,
status: "active",
});
const projectId = db.saveProject({
companyId,
goalId,
name: "Desktop Rebuild",
description: "Main delivery lane",
leadAgentId: agentId,
status: "in_progress",
targetDate: null,
});
const taskId = db.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.saveSecret({
companyId,
name: "vault-openai",
description: "OpenAI API token",
value: "sk-test-secret",
});
db.saveConnector({
id: "codex_local",
command: "codex",
model: "gpt-5",
envBindingText: "OPENAI_API_KEY=vault-openai",
notes: "Primary coding connector",
});
const snapshot = db.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.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.saveCompany({
name: "Backup Test Co",
description: "Backup validation company",
status: "active",
});
const backupDir = await mkdtemp(join(tmpdir(), "agentcompany-backup-"));
await db.exportBackup(backupDir);
db.saveCompany({
id: companyId,
name: "Mutated Company",
description: "Changed after backup",
status: "paused",
});
await db.restoreFromBackup(backupDir);
const snapshot = db.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.saveCompany({
name: "Alpha Company",
description: "Alpha",
status: "active",
});
const betaCompanyId = db.saveCompany({
name: "Beta Company",
description: "Beta",
status: "active",
});
db.saveSecret({
companyId: alphaCompanyId,
name: "shared-secret-name",
description: "Alpha secret",
value: "alpha-secret",
});
expect(db.resolveSecretEnv("API_KEY=shared-secret-name", alphaCompanyId)).toEqual({
API_KEY: "alpha-secret",
});
expect(db.resolveSecretEnv("API_KEY=shared-secret-name", betaCompanyId)).toEqual({});
});
it("atomically checks out unclaimed work to a single agent", () => {
const companyId = db.saveCompany({
name: "Checkout Company",
description: "Atomic checkout validation",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Execution Workspace",
localPath: "/tmp/checkout",
repoUrl: "https://github.com/example/checkout",
repoRef: "main",
isPrimary: true,
});
const leadId = db.saveAgent({
companyId,
name: "Lead",
role: "CEO",
title: "Lead",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Leads",
budgetMonthlyUsd: 100,
});
const contributorId = db.saveAgent({
companyId,
name: "Contributor",
role: "IC",
title: "Contributor",
status: "active",
reportsTo: leadId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Executes",
budgetMonthlyUsd: 100,
});
const taskId = db.saveTask({
companyId,
title: "Claim me",
description: "First agent wins the checkout.",
assigneeAgentId: null,
workspaceId,
priority: "medium",
status: "todo",
});
expect(db.checkoutTask(taskId, leadId)).toBe(true);
expect(db.checkoutTask(taskId, contributorId)).toBe(false);
const task = db.getTask(taskId);
expect(task.assigneeAgentId).toBe(leadId);
expect(task.status).toBe("in_progress");
});
it("auto-pauses an agent when a run exhausts the monthly budget", () => {
const companyId = db.saveCompany({
name: "Budget Company",
description: "Budget hard stop validation",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Budget Workspace",
localPath: "/tmp/budget",
repoUrl: "https://github.com/example/budget",
repoRef: "main",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "Budget Owner",
role: "CTO",
title: "Budget Owner",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Ships work",
budgetMonthlyUsd: 10,
});
const taskId = db.saveTask({
companyId,
title: "Spend budget",
description: "One run should exhaust the allowance.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "todo",
});
const run = db.createRun({
companyId,
taskId,
agentId,
workspaceId,
connectorId: "codex_local",
});
const outcome = db.finishRun({
runId: run.id,
status: "succeeded",
summary: "Budget consumed",
errorMessage: null,
exitCode: 0,
signal: null,
model: "gpt-5",
sessionDisplayId: "session-budget",
costUsd: 12,
unattributedCost: false,
});
expect(outcome.budgetHardStopped).toBe(true);
expect(db.getAgent(agentId).status).toBe("paused");
expect(db.getAgent(agentId).spentMonthlyUsd).toBe(12);
});
it("activates pending hires when the corresponding approval is granted", () => {
const companyId = db.saveCompany({
name: "Hiring Company",
description: "Approval-driven hiring",
status: "active",
});
const managerId = db.saveAgent({
companyId,
name: "Manager",
role: "CEO",
title: "Manager",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId: null,
model: null,
capabilities: "Manages",
budgetMonthlyUsd: 100,
});
const pendingHireId = db.saveAgent({
companyId,
name: "Pending Hire",
role: "IC",
title: "Pending Hire",
status: "pending_approval",
reportsTo: managerId,
connectorId: "codex_local",
workspaceId: null,
model: null,
capabilities: "Will execute after approval",
budgetMonthlyUsd: 50,
});
const approvalId = db.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.decideApproval({
approvalId,
state: "approved",
decisionNote: "Approved",
});
expect(db.getAgent(pendingHireId).status).toBe("idle");
});
it("detects pending-approval agents via isAgentPendingApproval", () => {
const companyId = db.saveCompany({
name: "Gate Test Co",
description: "Approval gate validation",
status: "active",
});
const activeAgentId = db.saveAgent({
companyId,
name: "Active Agent",
role: "CTO",
title: "Active",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId: null,
model: null,
capabilities: "Ships",
budgetMonthlyUsd: 100,
});
const pendingAgentId = db.saveAgent({
companyId,
name: "Pending Agent",
role: "IC",
title: "Pending",
status: "pending_approval",
reportsTo: activeAgentId,
connectorId: "codex_local",
workspaceId: null,
model: null,
capabilities: "Waiting",
budgetMonthlyUsd: 50,
});
expect(db.isAgentPendingApproval(activeAgentId)).toBe(false);
expect(db.isAgentPendingApproval(pendingAgentId)).toBe(true);
});
it("detects budget exhaustion via isAgentBudgetExhausted", () => {
const companyId = db.saveCompany({
name: "Budget Gate Co",
description: "Budget gate validation",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "WS",
localPath: "/tmp/budget-gate",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "Budget Agent",
role: "IC",
title: "Budget",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Test",
budgetMonthlyUsd: 10,
});
expect(db.isAgentBudgetExhausted(agentId)).toBe(false);
// Exhaust the budget via a run
const taskId = db.saveTask({
companyId,
title: "Budget drain",
description: "Test",
assigneeAgentId: agentId,
workspaceId,
priority: "medium",
status: "todo",
});
const run = db.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "codex_local" });
db.finishRun({
runId: run.id,
status: "succeeded",
summary: "Done",
errorMessage: null,
exitCode: 0,
signal: null,
model: null,
sessionDisplayId: null,
costUsd: 15,
unattributedCost: false,
});
expect(db.isAgentBudgetExhausted(agentId)).toBe(true);
});
it("runs forward-only migrations and creates schema_migrations table", () => {
// The migration system runs during init() — verify the tracking table exists
const snapshot = db.listSnapshot();
expect(snapshot).toBeTruthy();
// Verify we can re-init without errors (migrations are idempotent)
db.close();
db = new AppDatabase(tempDir);
db.init();
const snapshot2 = db.listSnapshot();
expect(snapshot2).toBeTruthy();
});
it("acquires and releases workspace locks", () => {
const companyId = db.saveCompany({
name: "Lock Test Co",
description: "Workspace lock test",
status: "active",
});
const wsId = db.saveWorkspace({
companyId,
name: "Lockable WS",
localPath: "/tmp/lock-test",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
// Acquire lock for run-1
expect(db.acquireWorkspaceLock(wsId, "run-1")).toBe(true);
let ws = db.getWorkspace(wsId);
expect(ws?.state).toBe("busy");
// Same run can re-acquire (idempotent)
expect(db.acquireWorkspaceLock(wsId, "run-1")).toBe(true);
// Different run cannot acquire while run-1 holds the lock
expect(db.acquireWorkspaceLock(wsId, "run-2")).toBe(false);
// Release lock
db.releaseWorkspaceLock(wsId, "run-1");
ws = db.getWorkspace(wsId);
expect(ws?.state).toBe("ready");
// Now run-2 can acquire
expect(db.acquireWorkspaceLock(wsId, "run-2")).toBe(true);
db.releaseWorkspaceLock(wsId, "run-2");
});
it("releases all workspace locks on crash recovery", () => {
const companyId = db.saveCompany({
name: "Recovery Co",
description: "Lock recovery test",
status: "active",
});
const wsId = db.saveWorkspace({
companyId,
name: "Recovery WS",
localPath: "/tmp/recovery-test",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
db.acquireWorkspaceLock(wsId, "stale-run");
expect(db.getWorkspace(wsId)?.state).toBe("busy");
db.releaseAllWorkspaceLocks();
expect(db.getWorkspace(wsId)?.state).toBe("ready");
});
it("purges expired run logs based on retention setting", async () => {
const companyId = db.saveCompany({
name: "Purge Co",
description: "Log purge test",
status: "active",
});
const wsId = db.saveWorkspace({
companyId,
name: "PurgeWS",
localPath: "/tmp/purge",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "Purge Agent",
role: "IC",
title: "Test",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId: wsId,
model: null,
capabilities: "test",
budgetMonthlyUsd: 100,
});
const taskId = db.saveTask({
companyId,
title: "Purge task",
description: "test",
assigneeAgentId: agentId,
workspaceId: wsId,
priority: "medium",
status: "todo",
});
const run = db.createRun({ companyId, taskId, agentId, workspaceId: wsId, connectorId: "codex_local" });
await db.appendRunLog(run.id, "stdout", "test log line\n");
// Finish the run with a very old timestamp
db.finishRun({
runId: run.id,
status: "succeeded",
summary: "done",
errorMessage: null,
exitCode: 0,
signal: null,
model: null,
sessionDisplayId: null,
costUsd: 0,
unattributedCost: true,
});
// Log should exist before purge
expect(db.readRunLog(run.id)).toContain("test log line");
// Set retention to 1 day and manually backdate the run's finished_at
db.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.purgeExpiredRunLogs();
expect(purged).toBe(1);
// Log should be gone after purge
expect(db.readRunLog(run.id)).toBe("");
});
it("populates related_agent_id on hire approvals", () => {
const companyId = db.saveCompany({
name: "Agent Approval Co",
description: "test",
status: "active",
});
const managerId = db.saveAgent({
companyId,
name: "Manager",
role: "CEO",
title: "CEO",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId: null,
model: null,
capabilities: "manage",
budgetMonthlyUsd: 100,
});
const hireId = db.saveAgent({
companyId,
name: "New Hire",
role: "IC",
title: "IC",
status: "pending_approval",
reportsTo: managerId,
connectorId: "codex_local",
workspaceId: null,
model: null,
capabilities: "code",
budgetMonthlyUsd: 50,
});
const approvalId = db.requestApproval({
companyId,
requestedByAgentId: managerId,
type: "hire_agent",
payloadSummary: "Hire New Hire",
impactSummary: "test",
payloadJson: JSON.stringify({ agentId: hireId }),
});
const approval = db.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.requestApproval({
companyId,
requestedByAgentId: managerId,
relatedAgentId: hireId,
type: "hire_agent",
payloadSummary: "Hire New Hire v2",
impactSummary: "test",
});
const approval2 = db.getApprovalRecord(approvalId2);
expect(approval2.relatedAgentId).toBe(hireId);
});
it("updates workspace state", () => {
const companyId = db.saveCompany({
name: "WS State Co",
description: "Workspace state test",
status: "active",
});
const wsId = db.saveWorkspace({
companyId,
name: "Test WS",
localPath: "/tmp/ws-state",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
let ws = db.getWorkspace(wsId);
expect(ws?.state).toBe("ready");
db.setWorkspaceState(wsId, "busy");
ws = db.getWorkspace(wsId);
expect(ws?.state).toBe("busy");
db.setWorkspaceState(wsId, "error");
ws = db.getWorkspace(wsId);
expect(ws?.state).toBe("error");
});
});
+128
View File
@@ -0,0 +1,128 @@
import { describe, expect, it } from "vitest";
// The worker runs in a utility process so we can't import it directly.
// Instead, we duplicate the pure utility functions here to test their logic.
const ENV_ALLOWLIST: ReadonlySet<string> = new Set([
"PATH", "HOME", "USER", "SHELL", "LANG", "LC_ALL", "LC_CTYPE", "TERM", "TMPDIR",
"DEVELOPER_DIR", "SDKROOT", "NODE_ENV", "NODE_OPTIONS",
"GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL",
"GIT_SSH_COMMAND", "GIT_ASKPASS", "GIT_TERMINAL_PROMPT",
"XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_CACHE_HOME", "XDG_RUNTIME_DIR",
"DISPLAY", "WAYLAND_DISPLAY", "DBUS_SESSION_BUS_ADDRESS",
"APPDATA", "LOCALAPPDATA", "USERPROFILE", "SYSTEMROOT", "COMSPEC", "PROGRAMFILES",
"AGENT_COMPANY_API_URL", "AGENT_COMPANY_API_KEY", "AGENT_COMPANY_RUN_ID",
]);
function buildSanitizedEnv(
hostEnv: Record<string, string>,
secretBindings: Record<string, string>,
): Record<string, string> {
const result: Record<string, string> = {};
for (const key of ENV_ALLOWLIST) {
if (key in hostEnv) {
result[key] = hostEnv[key];
}
}
Object.assign(result, secretBindings);
return result;
}
function buildRedactor(env: Record<string, string>): (text: string) => string {
const sensitivePatterns: string[] = [];
for (const [key, value] of Object.entries(env)) {
if (value.length < 8) continue;
if (["PATH", "HOME", "USER", "SHELL", "LANG", "TMPDIR", "TERM"].includes(key)) continue;
sensitivePatterns.push(value);
}
if (sensitivePatterns.length === 0) return (text) => text;
sensitivePatterns.sort((a, b) => b.length - a.length);
return (text: string) => {
let result = text;
for (const pattern of sensitivePatterns) {
if (result.includes(pattern)) {
result = result.replaceAll(pattern, "[REDACTED]");
}
}
return result;
};
}
describe("buildSanitizedEnv", () => {
it("only includes allowlisted env vars from host", () => {
const result = buildSanitizedEnv(
{
PATH: "/usr/bin",
HOME: "/home/user",
OPENAI_API_KEY: "sk-secret-12345",
AWS_SECRET_ACCESS_KEY: "aws-secret-key",
RANDOM_ENV: "should-not-appear",
},
{},
);
expect(result.PATH).toBe("/usr/bin");
expect(result.HOME).toBe("/home/user");
expect(result.OPENAI_API_KEY).toBeUndefined();
expect(result.AWS_SECRET_ACCESS_KEY).toBeUndefined();
expect(result.RANDOM_ENV).toBeUndefined();
});
it("secret bindings override allowlisted vars", () => {
const result = buildSanitizedEnv(
{ PATH: "/usr/bin" },
{ OPENAI_API_KEY: "sk-resolved-from-vault", AGENT_COMPANY_API_URL: "http://127.0.0.1:9999" },
);
expect(result.PATH).toBe("/usr/bin");
expect(result.OPENAI_API_KEY).toBe("sk-resolved-from-vault");
expect(result.AGENT_COMPANY_API_URL).toBe("http://127.0.0.1:9999");
});
it("produces empty env when host has no allowlisted vars", () => {
const result = buildSanitizedEnv(
{ DANGEROUS_VAR: "should-not-appear" },
{},
);
expect(Object.keys(result)).toHaveLength(0);
});
});
describe("buildRedactor", () => {
it("redacts secret values from log text", () => {
const redact = buildRedactor({
OPENAI_API_KEY: "sk-test-secret-12345",
ANTHROPIC_API_KEY: "sk-ant-secret-67890",
});
const text = "Calling API with key sk-test-secret-12345 and sk-ant-secret-67890";
const result = redact(text);
expect(result).toBe("Calling API with key [REDACTED] and [REDACTED]");
});
it("does not redact short values or system vars", () => {
const redact = buildRedactor({
PATH: "/usr/local/bin:/usr/bin",
HOME: "/Users/testuser",
SHORT: "abc",
});
const text = "PATH=/usr/local/bin:/usr/bin HOME=/Users/testuser SHORT=abc";
expect(redact(text)).toBe(text);
});
it("handles multiple occurrences of the same secret", () => {
const redact = buildRedactor({
TOKEN: "my-secret-token-value",
});
const text = "Token: my-secret-token-value and again: my-secret-token-value";
expect(redact(text)).toBe("Token: [REDACTED] and again: [REDACTED]");
});
it("returns identity function when no secrets present", () => {
const redact = buildRedactor({});
const text = "No secrets here";
expect(redact(text)).toBe(text);
});
});