Build the current workspace snapshot on native CI runners

This branch snapshots the in-progress desktop workspace so GitHub-hosted Windows and Linux runners can package the same source tree that already passes local verification on macOS.

Constraint: Local Apple Silicon Docker/Wine packaging crashes before Windows artifacts are produced
Rejected: Publish from the incomplete base repo state | remote typecheck failed because current source additions were missing
Confidence: medium
Scope-risk: broad
Directive: Treat this branch as a build snapshot only; do not merge it without reviewing the unrelated workspace changes it contains
Tested: local typecheck, lint, workspace typecheck, unit tests, integration tests, e2e smoke, macOS signed release upload
Not-tested: Native Windows/Linux artifacts before this CI run
This commit is contained in:
ZacharyZhang-NY committed 2026-04-04 03:28:14 -04:00
1 parent 0e497cd892
commit b772e1c758
271 files changed
+35047 -22879

No files matched your search

+249
View File
@@ -0,0 +1,249 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
import {
canAssignTask,
canControlAgent,
canManageGoal,
canManageOrganization,
canManageProject,
canRequestHire,
} from "../../src/main/agent-permissions";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("canManageOrganization", () => {
it("allows executive leaders without direct reports to manage organization growth", async () => {
const profileDir = await createTempDir("agentcompany-agent-perms-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.saveCompany({
name: "Org Permissions Co",
description: "Tests recursive org authority",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 220,
department: "engineering",
});
const engineerId = db.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Software Engineer",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Build features",
budgetMonthlyUsd: 180,
department: "engineering",
});
expect(canManageOrganization(db, companyId, ceoId)).toBe(true);
expect(canManageOrganization(db, companyId, ctoId)).toBe(false);
expect(canRequestHire(db, companyId, ctoId)).toBe(true);
expect(canManageOrganization(db, companyId, engineerId)).toBe(false);
expect(canRequestHire(db, companyId, engineerId)).toBe(false);
db.close();
});
it("allows explicit organization_manage permission to grant recursive org authority", async () => {
const profileDir = await createTempDir("agentcompany-agent-perms-override-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.saveCompany({
name: "Org Permission Override Co",
description: "Tests permission-based org authority",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const agentId = db.saveAgent({
companyId,
name: "Operations Lead",
role: "Specialist",
title: "Operations Specialist",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Coordinate operations",
budgetMonthlyUsd: 180,
department: "operations",
});
expect(canManageOrganization(db, companyId, agentId)).toBe(false);
db.setAgentPermission(agentId, companyId, "organization_manage", true);
expect(canManageOrganization(db, companyId, agentId)).toBe(true);
expect(canRequestHire(db, companyId, agentId)).toBe(true);
db.close();
});
it("manager subtree growth does not become org-wide admin after the first hire", async () => {
const profileDir = await createTempDir("agentcompany-agent-perms-scope-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.saveCompany({
name: "Scoped Authority Co",
description: "Tests that branch growth does not unlock org-wide admin powers",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 220,
department: "engineering",
});
const cmoId = db.saveAgent({
companyId,
name: "CMO",
role: "CMO",
title: "Chief Marketing Officer",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead marketing",
budgetMonthlyUsd: 220,
department: "marketing",
});
const ctoReportId = db.saveAgent({
companyId,
name: "Engineer",
role: "Engineer",
title: "Frontend Engineer",
status: "active",
reportsTo: ctoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Build frontend features",
budgetMonthlyUsd: 180,
department: "engineering",
});
const cmoTaskId = db.saveTask({
companyId,
title: "Launch campaign",
description: "Owned by the marketing branch",
assigneeAgentId: cmoId,
workspaceId,
priority: "high",
status: "todo",
});
expect(canRequestHire(db, companyId, ctoId)).toBe(true);
expect(canManageOrganization(db, companyId, ctoId)).toBe(false);
expect(canControlAgent(db, companyId, ctoId, ctoReportId)).toBe(true);
expect(canControlAgent(db, companyId, ctoId, cmoId)).toBe(false);
expect(canAssignTask(db, companyId, ctoId, ctoReportId)).toBe(true);
expect(canAssignTask(db, companyId, ctoId, cmoId)).toBe(false);
expect(canManageGoal(db, companyId, ctoId, cmoId)).toBe(false);
expect(canManageProject(db, companyId, ctoId, cmoId)).toBe(false);
expect(canManageGoal(db, companyId, ceoId, cmoId)).toBe(true);
expect(canManageProject(db, companyId, ceoId, cmoId)).toBe(true);
expect(db.getTask(cmoTaskId).assigneeAgentId).toBe(cmoId);
db.close();
});
});
+131
View File
@@ -0,0 +1,131 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { handleApprovalResolved } from "../../src/main/automation/automation-approval-handler";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("hire approval routing", () => {
it("approved subordinate hires wake the hire, requester, and CEO", async () => {
const profileDir = await createTempDir("agentcompany-hire-routing-");
const db = new AppDatabase(profileDir);
db.init();
db.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Hire Routing Co",
description: "Tests recursive hire wake routing",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "active",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 220,
department: "engineering",
});
const { agentId: hireId, approvalId } = db.createHireRequest({
companyId,
requestedByAgentId: ctoId,
name: "New Engineer",
role: "Engineer",
title: "Frontend Engineer",
department: "engineering",
reportsTo: ctoId,
connectorId: "codex_local",
workspaceId,
model: null,
metadataJson: "{}",
capabilities: "React, TypeScript, Tailwind",
budgetMonthlyUsd: 180,
payloadSummary: "Hire new engineer",
impactSummary: "Need another IC under the CTO",
payloadJson: "{}",
});
db.decideApproval({
approvalId,
state: "approved",
decisionNote: "Approved by board",
});
const wakeAgentIfPossible = vi.fn().mockReturnValue(null);
const dispatchAutomation = vi.fn();
const notify = vi.fn();
handleApprovalResolved({
db,
logger: { info: () => {}, warn: () => {}, error: () => {} },
publishDomainChanged: () => {},
notify,
wakeAgentIfPossible,
dispatchBrowserAction: async () => {},
isQuitting: () => false,
}, {
dispatchAutomation,
handleTaskStatusChange: vi.fn(),
runWorkflowPipeline: async () => {},
trackTimer: vi.fn(),
}, approvalId, companyId, "approved");
expect(db.getAgent(hireId).status).toBe("idle");
expect(dispatchAutomation).toHaveBeenCalledWith("approval_resolved", expect.objectContaining({ companyId, agentId: ctoId }));
expect(dispatchAutomation).toHaveBeenCalledWith("hire_approved", expect.objectContaining({ companyId, agentId: hireId }));
expect(wakeAgentIfPossible).toHaveBeenCalledWith(hireId, companyId, "assignment");
expect(wakeAgentIfPossible).toHaveBeenCalledWith(ceoId, companyId, "assignment");
expect(wakeAgentIfPossible).toHaveBeenCalledWith(ctoId, companyId, "approval_resolved");
expect(notify).toHaveBeenCalledWith(expect.objectContaining({
body: expect.stringContaining("New Engineer"),
}));
db.close();
});
});
@@ -0,0 +1,125 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { handleApprovalResolved } from "../../src/main/automation/automation-approval-handler";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("deliverable review feedback writeback", () => {
it("persists high-importance knowledge when the board requests revisions on a deliverable", async () => {
const profileDir = await createTempDir("agentcompany-review-feedback-");
const db = new AppDatabase(profileDir);
db.init();
db.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Review Feedback Co",
description: "Tests review feedback knowledge writeback",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "Research Lead",
role: "Researcher",
title: "Research Lead",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Research and synthesis",
budgetMonthlyUsd: 180,
department: "research",
});
const taskId = db.saveTask({
companyId,
title: "Draft market report",
description: "Produce a market report with concrete evidence and business insight.",
assigneeAgentId: agentId,
workspaceId,
priority: "high",
status: "in_review",
});
const documentId = db.saveDocument({
companyId,
type: "knowledge_article",
title: "Market report",
content: "Draft report body",
authorAgentId: agentId,
status: "in_review",
});
const approvalId = db.requestApproval({
companyId,
relatedTaskId: taskId,
requestedByAgentId: agentId,
type: "deliverable_review",
payloadSummary: "Review market report deliverable",
impactSummary: "Board review required before the report is finalized.",
payloadJson: JSON.stringify({ documentIds: [documentId] }),
});
db.decideApproval({
approvalId,
state: "revision_requested",
decisionNote: "Add stronger competitor evidence and a clearer recommendation.",
});
const wakeAgentIfPossible = vi.fn().mockReturnValue(null);
handleApprovalResolved({
db,
logger: { info: () => {}, warn: () => {}, error: () => {} },
publishDomainChanged: () => {},
notify: vi.fn(),
wakeAgentIfPossible,
dispatchBrowserAction: async () => {},
isQuitting: () => false,
}, {
dispatchAutomation: vi.fn(),
handleTaskStatusChange: vi.fn(),
runWorkflowPipeline: async () => {},
trackTimer: vi.fn(),
}, approvalId, companyId, "revision_requested");
const knowledgeEntries = db.listSnapshot().knowledgeBase.filter((entry) => entry.companyId === companyId);
expect(knowledgeEntries).toHaveLength(1);
expect(knowledgeEntries[0]?.category).toBe("lesson_learned");
expect(knowledgeEntries[0]?.importance).toBe("high");
expect(knowledgeEntries[0]?.referencedEntityType).toBe("task");
expect(knowledgeEntries[0]?.referencedEntityId).toBe(taskId);
expect(knowledgeEntries[0]?.content).toContain("revision_requested");
expect(knowledgeEntries[0]?.content).toContain("stronger competitor evidence");
expect(wakeAgentIfPossible).toHaveBeenCalledWith(agentId, companyId, "revision_needed");
expect(db.getTask(taskId).status).toBe("in_progress");
const document = db.listSnapshot().documents.find((entry) => entry.id === documentId);
expect(document?.status).toBe("draft");
db.close();
});
});
+3 -3
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { describe, it, expect, beforeEach } from "vitest";
/**
* Tests for budget incident lifecycle: creation, deduplication (D-08),
@@ -231,7 +231,7 @@ describe("autoCloseExpiredIncidents", () => {
});
it("closes incidents whose window_end is in the past", () => {
const policy = makePolicy();
const _policy = makePolicy();
// Manually create an expired incident
db.incidents.push({
id: "expired-1",
@@ -329,7 +329,7 @@ describe("checkAutoResolve (D-06)", () => {
// Spent is now 7500 (below warn threshold of 8000 = 10000*80/100)
const spent = 7500;
const warnAmount = Math.floor((policy.amount * policy.warnPercent) / 100);
const _warnAmount = Math.floor((policy.amount * policy.warnPercent) / 100);
if (spent < policy.amount) {
db.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
}
+44
View File
@@ -0,0 +1,44 @@
import { describe, expect, it } from "vitest";
import { buildCapabilityRegistrySection } from "@main/prompt-builder/capability-registry";
describe("buildCapabilityRegistrySection", () => {
it("renders a company-wide capability summary for delegation decisions", () => {
const section = buildCapabilityRegistrySection({
companySkillCatalog: [
{ name: "React Standards", slug: "react-standards", description: "Use when building React UI" },
],
companyWorkspaces: [
{ name: "Primary", localPath: "/tmp/workspace" },
],
allConnectors: [
{ id: "codex_local", label: "Codex", status: "ready", enabledModels: ["gpt-5.4"] },
],
companyDocuments: [
{
id: "doc-1",
companyId: "company-1",
title: "Architecture Decision",
type: "architecture_decision",
content: "",
status: "approved",
authorAgentId: null,
reviewerAgentId: null,
projectId: null,
goalId: null,
parentDocId: null,
version: 1,
tagsJson: "[]",
latestRevisionId: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
],
});
expect(section).toContain("# Company Capability Registry");
expect(section).toContain("React Standards (`react-standards`)");
expect(section).toContain("Primary: /tmp/workspace");
expect(section).toContain("Codex (`codex_local`) — models: gpt-5.4");
expect(section).toContain("architecture_decision");
});
});
+24
View File
@@ -0,0 +1,24 @@
import { describe, expect, it } from "vitest";
import { normalizeCommandValue } from "@agentcompany/adapter-utils/server-utils";
import { commandLooksLike } from "@agentcompany/adapter-utils/test-utils";
describe("command path normalization", () => {
it("removes surrounding quotes from absolute executable paths", () => {
expect(normalizeCommandValue('"C:\\Users\\First Last\\AppData\\Local\\bin\\claude.exe"')).toBe(
"C:\\Users\\First Last\\AppData\\Local\\bin\\claude.exe",
);
expect(normalizeCommandValue("'C:\\Users\\First Last\\AppData\\Local\\bin\\claude.exe'")).toBe(
"C:\\Users\\First Last\\AppData\\Local\\bin\\claude.exe",
);
});
it("keeps bare commands unchanged", () => {
expect(normalizeCommandValue("claude")).toBe("claude");
expect(normalizeCommandValue("codex")).toBe("codex");
});
it("matches quoted commands against connector executables", () => {
expect(commandLooksLike('"C:\\Users\\First Last\\AppData\\Local\\bin\\claude.exe"', "claude")).toBe(true);
expect(commandLooksLike("'C:\\Users\\First Last\\AppData\\Local\\bin\\codex.exe'", "codex")).toBe(true);
});
});
+3 -4
View File
@@ -4,8 +4,8 @@ import {
inferTaskType,
getConnectorStrengths,
TASK_TYPE_PREFERENCE,
CONNECTOR_STRENGTHS,
ROLE_DEPARTMENT_HINTS,
CONNECTOR_STRENGTHS as _CONNECTOR_STRENGTHS,
ROLE_DEPARTMENT_HINTS as _ROLE_DEPARTMENT_HINTS,
} from "../../src/main/connector-matching";
import type { ConnectorId, ConnectorStatus } from "@shared/types";
@@ -35,10 +35,9 @@ describe("connector-matching", () => {
expect(TASK_TYPE_PREFERENCE.general[0]).toBe("claude_local");
});
it("includes all three connectors for every task type", () => {
it("includes all three core connectors for every task type", () => {
for (const key of Object.keys(TASK_TYPE_PREFERENCE)) {
const prefs = TASK_TYPE_PREFERENCE[key as keyof typeof TASK_TYPE_PREFERENCE];
expect(prefs).toHaveLength(3);
expect(prefs).toContain("claude_local");
expect(prefs).toContain("codex_local");
expect(prefs).toContain("gemini_local");
+81
View File
@@ -0,0 +1,81 @@
import { describe, expect, it } from "vitest";
import { getConnectorModelPolicy, validateConnectorModelSelection } from "@main/connector-model-policy";
import { getConnectorDefinition } from "@main/connectors";
function createDeps(overrides: {
connectorModel?: string | null;
disabledModels?: Array<{ id: string; label: string }>;
} = {}) {
return {
getConnector: () => ({
id: "claude_compatible" as const,
label: "Claude Compatible",
model: overrides.connectorModel ?? "model-alpha,model-beta,model-gamma",
}),
getDisabledModels: () => overrides.disabledModels ?? [],
};
}
describe("connector model policy", () => {
it("derives enabled compat models from connector config minus disabled entries", () => {
const policy = getConnectorModelPolicy(
createDeps({ disabledModels: [{ id: "model-beta", label: "model-beta" }] }),
"claude_compatible",
);
expect(policy.enabledModels.map((model) => model.id)).toEqual(["model-alpha", "model-gamma"]);
});
it("accepts an explicit enabled model", () => {
const result = validateConnectorModelSelection(createDeps(), "claude_compatible", "model-alpha");
expect(result).toEqual({ ok: true, effectiveModel: "model-alpha" });
});
it("rejects an explicit disabled model", () => {
const result = validateConnectorModelSelection(
createDeps({ disabledModels: [{ id: "model-beta", label: "model-beta" }] }),
"claude_compatible",
"model-beta",
);
expect(result.ok).toBe(false);
if (result.ok) {
throw new Error("Expected explicit disabled model to be rejected.");
}
expect(result.error).toContain('does not allow model "model-beta"');
});
});
describe("native connector defaults", () => {
it("uses the persisted connector model as the native default when configured", () => {
const codexModels = getConnectorDefinition("codex_local").module.models ?? [];
const configuredDefault = codexModels[1]?.id;
if (!configuredDefault) {
throw new Error("codex_local requires at least two models for this default policy test.");
}
const policy = getConnectorModelPolicy({
getConnector: () => ({
id: "codex_local" as const,
label: "Codex",
model: configuredDefault,
}),
getDisabledModels: () => [],
}, "codex_local");
expect(policy.defaultModel).toBe(configuredDefault);
expect(validateConnectorModelSelection({
getConnector: () => ({
id: "codex_local" as const,
label: "Codex",
model: configuredDefault,
}),
getDisabledModels: () => [],
}, "codex_local", null)).toEqual({
ok: true,
effectiveModel: configuredDefault,
});
});
});
+55
View File
@@ -0,0 +1,55 @@
import { describe, expect, it } from "vitest";
import {
mergeConnectorModelEntries,
resolveSelectedConnectorModel,
} from "@shared/connector-models";
describe("connector model helpers", () => {
it("merges enabled and disabled models into one toggle list", () => {
const entries = mergeConnectorModelEntries(
[
{ id: "gpt-5.4", label: "GPT-5.4" },
{ id: "gpt-5.4-mini", label: "GPT-5.4 Mini" },
],
[{ id: "o4-mini", label: "o4-mini" }],
);
expect(entries).toEqual([
{ id: "gpt-5.4", label: "GPT-5.4", enabled: true },
{ id: "gpt-5.4-mini", label: "GPT-5.4 Mini", enabled: true },
{ id: "o4-mini", label: "o4-mini", enabled: false },
]);
});
it("keeps the current selection only if it remains enabled", () => {
const entries = mergeConnectorModelEntries(
[{ id: "gpt-5.4", label: "GPT-5.4" }],
[{ id: "gpt-5.4-mini", label: "GPT-5.4 Mini" }],
);
expect(resolveSelectedConnectorModel(entries, "gpt-5.4")).toBe("gpt-5.4");
expect(resolveSelectedConnectorModel(entries, "gpt-5.4-mini")).toBe("gpt-5.4");
expect(resolveSelectedConnectorModel(entries, "")).toBe("gpt-5.4");
});
it("dedupes duplicate ids and keeps enabled entries ahead of disabled ones", () => {
const entries = mergeConnectorModelEntries(
[{ id: "gpt-5.4", label: "GPT-5.4" }],
[
{ id: "gpt-5.4", label: "GPT-5.4" },
{ id: "gpt-4.1", label: "GPT-4.1" },
],
);
expect(entries).toEqual([
{ id: "gpt-5.4", label: "GPT-5.4", enabled: true },
{ id: "gpt-4.1", label: "GPT-4.1", enabled: false },
]);
});
it("returns an empty selection when all entries are disabled", () => {
const entries = mergeConnectorModelEntries([], [{ id: "gpt-5.4", label: "GPT-5.4" }]);
expect(resolveSelectedConnectorModel(entries, "gpt-5.4")).toBe("");
});
});
+8 -8
View File
@@ -11,13 +11,13 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
*/
interface MockStatement {
run: ReturnType<typeof vi.fn>;
get: ReturnType<typeof vi.fn>;
all: ReturnType<typeof vi.fn>;
run: (...args: unknown[]) => unknown;
get: (...args: unknown[]) => unknown;
all: (...args: unknown[]) => unknown[];
}
interface MockDatabase {
prepare: ReturnType<typeof vi.fn>;
prepare: (key: string) => MockStatement;
}
/**
@@ -25,7 +25,7 @@ interface MockDatabase {
*/
function simulateInterruptStaleRuns(
db: MockDatabase,
releaseAllWorkspaceLocks: ReturnType<typeof vi.fn>,
releaseAllWorkspaceLocks: () => void,
transactionFn: (fn: () => void) => void,
runningCount: number,
): number {
@@ -95,7 +95,7 @@ function simulateMarkInterruptedTaskFailed(
describe("Crash Recovery Detection", () => {
let db: MockDatabase;
let releaseAllWorkspaceLocks: ReturnType<typeof vi.fn>;
let releaseAllWorkspaceLocks: () => void;
beforeEach(() => {
db = {
@@ -104,8 +104,8 @@ describe("Crash Recovery Detection", () => {
get: vi.fn().mockReturnValue(undefined),
all: vi.fn().mockReturnValue([]),
}),
};
releaseAllWorkspaceLocks = vi.fn();
} as unknown as MockDatabase;
releaseAllWorkspaceLocks = vi.fn() as unknown as () => void;
});
describe("interruptStaleRuns", () => {
+3 -3
View File
@@ -7,9 +7,9 @@ import {
formatShortDate,
} from "../../src/renderer/components/dashboard/charts/chart-utils";
import type {
DailyRunData,
DailyCostData,
AgentWorkloadData,
DailyRunData as _DailyRunData,
DailyCostData as _DailyCostData,
AgentWorkloadData as _AgentWorkloadData,
} from "../../src/renderer/components/dashboard/charts/chart-utils";
// Helper to create a minimal RunRecord for testing
+2 -2
View File
@@ -195,7 +195,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
});
db.finishRun({
runId: run.id,
status: "completed",
status: "succeeded",
summary: "Completed initial implementation",
errorMessage: null,
exitCode: 0,
@@ -341,7 +341,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
});
db.finishRun({
runId: run.id,
status: "completed",
status: "succeeded",
summary: "Previous work done",
errorMessage: null,
exitCode: 0,
+11 -11
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { describe, it, expect, vi, beforeEach, afterEach, type MockInstance } from "vitest";
/**
* Tests for the retry-aware failure escalation pipeline.
@@ -15,19 +15,19 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
// and verifying the calls it makes to the database and notification system.
interface MockDb {
getTaskRetryCount: ReturnType<typeof vi.fn>;
incrementTaskRetryCount: ReturnType<typeof vi.fn>;
resetTaskRetryCount: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
getCeoForCompany: ReturnType<typeof vi.fn>;
advanceTaskStatus: ReturnType<typeof vi.fn>;
getTaskRetryCount: MockInstance & ((taskId: string) => number);
incrementTaskRetryCount: MockInstance & ((taskId: string) => void);
resetTaskRetryCount: MockInstance & ((taskId: string) => void);
getChainOfCommand: MockInstance & ((agentId: string) => { id: string; name: string }[]);
getCeoForCompany: MockInstance & ((companyId: string) => { id: string; name: string } | null);
advanceTaskStatus: MockInstance & ((taskId: string, status: string, note: string) => { previousStatus: string });
}
interface MockDeps {
db: MockDb;
wakeAgentIfPossible: ReturnType<typeof vi.fn>;
notify: ReturnType<typeof vi.fn>;
logger: { info: ReturnType<typeof vi.fn>; warn: ReturnType<typeof vi.fn>; error: ReturnType<typeof vi.fn> };
wakeAgentIfPossible: MockInstance & ((agentId: string, companyId: string, reason: string) => string);
notify: MockInstance & ((...args: unknown[]) => void);
logger: { info: MockInstance & ((...args: unknown[]) => void); warn: MockInstance & ((...args: unknown[]) => void); error: MockInstance & ((...args: unknown[]) => void) };
}
const MAX_RETRY_ATTEMPTS = 2;
@@ -146,7 +146,7 @@ function createMockDeps(): MockDeps {
warn: vi.fn(),
error: vi.fn(),
},
};
} as unknown as MockDeps;
}
const RUN = { taskId: "task-1", agentId: "agent-1", companyId: "company-1" };
+1 -1
View File
@@ -144,7 +144,7 @@ describe("Heartbeat task coalescing", () => {
const agentId = createTestAgent(db, companyId, wsId, "CEO");
// Create two done heartbeat tasks
const oldTaskId = createHeartbeatTask(db, companyId, agentId, "done", "CEO");
const _oldTaskId = createHeartbeatTask(db, companyId, agentId, "done", "CEO");
const newTaskId = createHeartbeatTask(db, companyId, agentId, "done", "CEO");
// Advance the newer one so its updated_at is more recent
+100
View File
@@ -0,0 +1,100 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { IPC_CHANNELS } from "@shared/contracts";
import { AppDatabase } from "../../src/main/database";
import { registerCoreAgentGraphHandlers } from "../../src/main/ipc-core-agent-graph-handlers";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("ipc-core-agent-graph-handlers model enforcement", () => {
it("rejects saveAgent when the selected model is disabled for the connector", async () => {
const profileDir = await createTempDir("agentcompany-agent-handler-");
const db = new AppDatabase(profileDir);
db.init();
db.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
db.setDisabledModels("codex_local", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
const companyId = db.saveCompany({
name: "Handler Test Co",
description: "Tests saveAgent model validation",
status: "active",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: profileDir,
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const agentId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const handlers = new Map<string, (...args: any[]) => any>();
registerCoreAgentGraphHandlers({
registerHandle: (channel, handler) => {
handlers.set(channel, handler);
},
db,
ensureCompanyExists: (id) => (db.hasCompany(id) ? null : { ok: false, error: { code: "COMPANY_NOT_FOUND", message: "missing" } }),
findInvalidReference: () => null,
publishDomainChanged: vi.fn(),
handleApprovalResolved: vi.fn(),
});
const saveAgent = handlers.get(IPC_CHANNELS.saveAgent);
if (!saveAgent) {
throw new Error("saveAgent handler was not registered.");
}
const result = await saveAgent(null, {
id: agentId,
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
department: "executive",
status: "idle",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: "gpt-5.4",
metadataJson: "{}",
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
}) as { ok: boolean; error?: { code: string; message: string } };
expect(result.ok).toBe(false);
expect(result.error?.code).toBe("CONNECTOR_MODEL_NOT_ALLOWED");
});
});
+1 -1
View File
@@ -144,7 +144,7 @@ describe("createDomainEventBatcher", () => {
const batcher = createDomainEventBatcher(onFull, onGranular, 50);
// These events are not recognized as granular -- per D-05, unknown triggers full refresh
batcher.push({ type: "run-log", runId: "r-1", stream: "stdout", text: "hi" } as DesktopEvent);
batcher.push({ type: "run-log", runId: "r-1", stream: "stdout", chunk: "hi" } as DesktopEvent);
vi.advanceTimersByTime(50);
expect(onFull).toHaveBeenCalledTimes(1);
@@ -0,0 +1,58 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { IPC_CHANNELS } from "@shared/contracts";
import { AppDatabase } from "../../src/main/database";
import { registerCoreAdminHandlers } from "../../src/main/ipc-core-admin-handlers";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("ipc-core-admin-handlers connector saves", () => {
it("accepts compatible connector model catalogs stored as csv", async () => {
const profileDir = await createTempDir("agentcompany-admin-handler-");
const db = new AppDatabase(profileDir);
db.init();
const handlers = new Map<string, (...args: any[]) => any>();
registerCoreAdminHandlers({
registerHandle: (channel, handler) => {
handlers.set(channel, handler);
},
db,
ensureCompanyExists: () => null,
publishDomainChanged: vi.fn(),
checkConnector: vi.fn(),
openConnectorAuthTerminal: vi.fn(),
openGenericTerminal: vi.fn(),
});
const saveConnector = handlers.get(IPC_CHANNELS.saveConnector);
if (!saveConnector) {
throw new Error("saveConnector handler was not registered.");
}
const result = await saveConnector(null, {
id: "claude_compatible",
command: "claude",
model: "model-alpha,model-beta,model-gamma",
envBindingText: "ANTHROPIC_BASE_URL=https://example.com/anthropic",
notes: "Compat provider",
}) as { ok: boolean; error?: { code: string; message: string } };
expect(result.ok).toBe(true);
expect(db.getConnector("claude_compatible").model).toBe("model-alpha,model-beta,model-gamma");
db.close();
});
});
+59
View File
@@ -0,0 +1,59 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import { AppDatabase } from "../../src/main/database";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
describe("knowledge provenance", () => {
it("preserves or refreshes sourceRunId when an existing knowledge entry is updated", async () => {
const profileDir = await createTempDir("agentcompany-knowledge-provenance-");
const db = new AppDatabase(profileDir);
db.init();
const companyId = db.saveCompany({
name: "Knowledge Provenance Co",
description: "Tests sourceRunId updates on knowledge entry edits",
status: "active",
});
const knowledgeId = db.saveKnowledgeEntry({
companyId,
category: "lesson_learned",
topic: "Original lesson",
content: "Initial content",
importance: "medium",
sourceRunId: null,
});
db.saveKnowledgeEntry({
id: knowledgeId,
companyId,
category: "lesson_learned",
topic: "Original lesson",
content: "Updated content with better guidance",
importance: "high",
sourceRunId: "run-123",
});
const entry = db.queryAll<{ sourceRunId: string | null; importance: string }>(
"select source_run_id as sourceRunId, importance from knowledge_base where id = ? limit 1",
knowledgeId,
)[0];
expect(entry?.sourceRunId).toBe("run-123");
expect(entry?.importance).toBe("high");
db.close();
});
});
+251
View File
@@ -0,0 +1,251 @@
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AppDatabase } from "../../src/main/database";
import { createThreadIntegration } from "../../src/main/thread-integration";
import { dispatchAgentMessage, HUMAN_SENDER_ID } from "../../src/main/message-dispatch";
const tempDirs: string[] = [];
async function createTempDir(prefix: string) {
const dir = await mkdtemp(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
function createScaffold(db: AppDatabase) {
db.updateConnectorHealth({
id: "codex_local",
status: "ready",
authState: "authenticated",
version: "test",
lastError: null,
});
const companyId = db.saveCompany({
name: "Message Dispatch Co",
description: "Tests board wake and wait-state resolution",
status: "active",
executionMode: "ai_cofounder",
});
const workspaceId = db.saveWorkspace({
companyId,
name: "Primary",
localPath: "/tmp/message-dispatch",
repoUrl: "",
repoRef: "",
isPrimary: true,
});
const ceoId = db.saveAgent({
companyId,
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
status: "idle",
reportsTo: null,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
});
const ctoId = db.saveAgent({
companyId,
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
status: "idle",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 200,
department: "engineering",
});
const cmoId = db.saveAgent({
companyId,
name: "CMO",
role: "CMO",
title: "Chief Marketing Officer",
status: "idle",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Lead marketing",
budgetMonthlyUsd: 200,
department: "marketing",
});
const pendingId = db.saveAgent({
companyId,
name: "Pending Recruit",
role: "IC",
title: "Pending Hire",
status: "pending_approval",
reportsTo: ctoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "Pending",
budgetMonthlyUsd: 100,
department: "engineering",
});
const terminatedId = db.saveAgent({
companyId,
name: "Former COO",
role: "COO",
title: "Former COO",
status: "terminated",
reportsTo: ceoId,
connectorId: "codex_local",
workspaceId,
model: null,
capabilities: "No longer active",
budgetMonthlyUsd: 100,
department: "operations",
});
const taskId = db.saveTask({
companyId,
title: "Review architecture plan",
description: "Need board feedback in the thread.",
assigneeAgentId: ctoId,
workspaceId,
priority: "high",
status: "todo",
});
return { companyId, workspaceId, ceoId, ctoId, cmoId, pendingId, terminatedId, taskId };
}
describe("message dispatch", () => {
it("wakes top-level active agents for company messages and active agents for incident messages", async () => {
const profileDir = await createTempDir("agentcompany-message-dispatch-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, ceoId, ctoId, cmoId } = createScaffold(db);
const wakeAgentIfPossible = vi.fn().mockReturnValue(null);
const companyResult = dispatchAgentMessage({
db,
wakeAgentIfPossible,
}, {
companyId,
fromAgentId: HUMAN_SENDER_ID,
fromHumanUser: true,
channel: "company",
body: "Board broadcast",
});
expect(companyResult.ok).toBe(true);
if (!companyResult.ok) throw new Error("Expected company message dispatch to succeed.");
expect(companyResult.data.wakeTargets).toEqual([ceoId]);
const incidentResult = dispatchAgentMessage({
db,
wakeAgentIfPossible,
}, {
companyId,
fromAgentId: HUMAN_SENDER_ID,
fromHumanUser: true,
channel: "incident",
body: "Urgent issue",
});
expect(incidentResult.ok).toBe(true);
if (!incidentResult.ok) throw new Error("Expected incident message dispatch to succeed.");
expect(new Set(incidentResult.data.wakeTargets)).toEqual(new Set([ceoId, ctoId, cmoId]));
db.close();
});
it("resolves only user_reply wait states for company-wide human messages, but scoped replies resolve thread wait states", async () => {
const profileDir = await createTempDir("agentcompany-message-wait-");
const db = new AppDatabase(profileDir);
db.init();
const { companyId, ctoId, taskId } = createScaffold(db);
const threadIntegration = createThreadIntegration({
db,
logger: { info: () => {}, error: () => {} },
});
threadIntegration.onTaskCreated(taskId, companyId);
const thread = db.listThreads({
companyId,
type: "task",
relatedEntityType: "task",
relatedEntityId: taskId,
limit: 1,
})[0];
if (!thread) {
throw new Error("Expected task thread to exist.");
}
const replyWait = db.setThreadWaitState({
threadId: thread.id,
companyId,
waitingFor: "user_reply",
relatedApprovalId: null,
reason: "Reply needed",
});
const reviewWait = db.setThreadWaitState({
threadId: thread.id,
companyId,
waitingFor: "user_review",
relatedApprovalId: null,
reason: "Review needed",
});
const approvalWait = db.setThreadWaitState({
threadId: thread.id,
companyId,
waitingFor: "user_approval",
relatedApprovalId: "approval-1",
reason: "Approval needed",
});
dispatchAgentMessage({
db,
onHumanMessage: (currentCompanyId, toAgentId, threadId, waitStateId) =>
threadIntegration.onHumanMessage(currentCompanyId, toAgentId, threadId, waitStateId),
}, {
companyId,
fromAgentId: HUMAN_SENDER_ID,
fromHumanUser: true,
channel: "company",
body: "Board-wide update",
});
let unresolved = db.listThreadWaitStates(companyId);
expect(unresolved.some((entry) => entry.id === replyWait.id)).toBe(false);
expect(unresolved.some((entry) => entry.id === reviewWait.id)).toBe(true);
dispatchAgentMessage({
db,
onHumanMessage: (currentCompanyId, toAgentId, threadId, waitStateId) =>
threadIntegration.onHumanMessage(currentCompanyId, toAgentId, threadId, waitStateId),
}, {
companyId,
fromAgentId: HUMAN_SENDER_ID,
fromHumanUser: true,
channel: "direct",
toAgentId: ctoId,
threadId: thread.id,
body: "Specific reply for the thread",
});
unresolved = db.listThreadWaitStates(companyId);
expect(unresolved.some((entry) => entry.id === reviewWait.id)).toBe(false);
expect(unresolved.some((entry) => entry.id === approvalWait.id)).toBe(true);
db.close();
});
});
+6 -4
View File
@@ -1,4 +1,6 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import type { BrowserWindow } from "electron";
import type { DesktopEvent } from "@shared/types";
// Track all constructed notification instances
const constructedNotifications: Array<{
@@ -61,9 +63,9 @@ describe("notification-service", () => {
};
service = createNotificationService({
getMainWindow: mockGetMainWindow,
emitEvent: mockEmitEvent,
logger: mockLogger,
getMainWindow: mockGetMainWindow as unknown as () => BrowserWindow | null,
emitEvent: mockEmitEvent as unknown as (event: DesktopEvent) => void,
logger: mockLogger as { info: (...args: unknown[]) => void; error: (...args: unknown[]) => void },
});
});
@@ -203,7 +205,7 @@ describe("notification-service", () => {
// emitEvent should have been called with section but no entityId
const emitCall = mockEmitEvent.mock.calls.find(
([event]: [{ type: string }]) => event.type === "notification-click",
(args: unknown[]) => (args[0] as { type?: string })?.type === "notification-click",
);
expect(emitCall).toBeDefined();
expect(emitCall![0].section).toBe("orgchart");
@@ -0,0 +1,80 @@
import { describe, expect, it } from "vitest";
import { buildSystemPrompt } from "../../src/main/prompt-builder";
import type { PromptContext } from "../../src/main/prompt-builder/index";
function buildCtx(overrides: Partial<PromptContext> = {}): PromptContext {
return {
agent: {
id: "agent-1",
companyId: "company-1",
name: "CTO",
role: "CTO",
title: "Chief Technology Officer",
department: "engineering",
status: "active",
reportsTo: "ceo-1",
connectorId: "codex_local",
workspaceId: null,
model: null,
capabilities: "Lead engineering",
budgetMonthlyUsd: 200,
spentMonthlyUsd: 0,
metadataJson: "{}",
heartbeatEnabled: true,
heartbeatIntervalSec: 120,
lastHeartbeatAt: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
apiUrl: "http://127.0.0.1:3210",
apiKey: "token",
runId: "run-1",
task: null,
companyName: "Prompt Co",
companyDescription: "Tests leadership prompt classification",
goals: [],
projects: [],
directReports: [],
chainOfCommand: [{
id: "ceo-1",
companyId: "company-1",
name: "CEO",
role: "CEO",
title: "Chief Executive Officer",
department: "executive",
status: "active",
reportsTo: null,
connectorId: "codex_local",
workspaceId: null,
model: null,
capabilities: "Lead the company",
budgetMonthlyUsd: 250,
spentMonthlyUsd: 0,
metadataJson: "{}",
heartbeatEnabled: true,
heartbeatIntervalSec: 120,
lastHeartbeatAt: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}],
assignedTasks: [],
allAgents: [],
pendingApprovals: [],
tasksAwaitingReview: [],
wakeReason: null,
allTasks: [],
recentComments: [],
socialAccounts: [],
...overrides,
};
}
describe("buildSystemPrompt leadership classification", () => {
it("treats first-layer executive leaders as managers even before they have direct reports", () => {
const prompt = buildSystemPrompt(buildCtx());
expect(prompt).toContain("Operating Instructions (Manager)");
expect(prompt).toContain("New hires for your team should report to you by default");
expect(prompt).not.toContain("Operating Instructions (Individual Contributor)");
});
});
+2
View File
@@ -106,6 +106,8 @@ describe("selective IPC event types", () => {
return "agent-updated";
case "budget-changed":
return "budget-changed";
case "thread-wait-state-updated":
return "thread-wait-state-updated";
default: {
const _exhaustive: never = event;
return _exhaustive;
+5 -5
View File
@@ -205,7 +205,7 @@ describe("task_sessions CRUD", () => {
const run1 = db.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "claude_local" });
db.finishRun({
runId: run1.id,
status: "completed",
status: "succeeded",
summary: "First run done",
errorMessage: null,
exitCode: 0,
@@ -237,7 +237,7 @@ describe("task_sessions CRUD", () => {
expect(runs[0].status).toBe("failed");
expect(runs[0].errorMessage).toBe("Timeout");
expect(runs[1].id).toBe(run1.id);
expect(runs[1].status).toBe("completed");
expect(runs[1].status).toBe("succeeded");
});
});
@@ -561,7 +561,7 @@ describe("session management service", () => {
const run = db.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "gemini_local" as ConnectorId });
db.finishRun({
runId: run.id,
status: "completed",
status: "succeeded",
summary: "Implemented initial logic",
errorMessage: null,
exitCode: 0,
@@ -582,7 +582,7 @@ describe("session management service", () => {
expect(handoff).toContain("# Session Handoff");
expect(handoff).toContain("Implement session rotation"); // task title
expect(handoff).toContain("## Recent Run History");
expect(handoff).toContain("completed");
expect(handoff).toContain("succeeded");
expect(handoff).toContain("15000 total tokens"); // 10000 + 5000
});
});
@@ -736,7 +736,7 @@ describe("wiring integration", () => {
function simulateRunFinishedSessionWiring(
sessionService: ReturnType<typeof createMockSessionService>,
db: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getActiveTaskSession: (...args: any[]) => any;
},
task: { id: string } | null,
+51
View File
@@ -0,0 +1,51 @@
import { describe, expect, it } from "vitest";
import { buildConnectorSection } from "@main/prompt-builder/skill-injection";
describe("buildConnectorSection", () => {
it("includes enabled model pools for ready connectors", () => {
const section = buildConnectorSection({
allConnectors: [
{
id: "codex_local",
label: "Codex",
status: "ready",
enabledModels: ["gpt-5.4", "gpt-5.4-mini"],
},
],
});
expect(section).toContain("Enabled models: gpt-5.4, gpt-5.4-mini.");
expect(section).toContain("choose from the enabled model pool");
});
it("does not render disabled models in the connector section", () => {
const section = buildConnectorSection({
allConnectors: [
{
id: "codex_local",
label: "Codex",
status: "ready",
enabledModels: ["gpt-5.4"],
},
],
});
expect(section).toContain("Enabled models: gpt-5.4.");
expect(section).not.toContain("gpt-5.4-mini");
});
it("omits ready connectors whose enabled pool is empty", () => {
const section = buildConnectorSection({
allConnectors: [
{
id: "codex_local",
label: "Codex",
status: "ready",
enabledModels: [],
},
],
});
expect(section).toBeNull();
});
});
+31 -6
View File
@@ -1,6 +1,7 @@
import { describe, it, expect } from "vitest";
import {
TOKEN_BUDGETS,
resolveTokenBudget,
} from "@main/skill-types";
import type {
SkillSourceType,
@@ -16,20 +17,36 @@ import type {
describe("skill-types", () => {
describe("TOKEN_BUDGETS", () => {
it("has entries for all five adapter types", () => {
it("has entries for all supported adapter types", () => {
expect(TOKEN_BUDGETS).toHaveProperty("claude_local");
expect(TOKEN_BUDGETS).toHaveProperty("codex_local");
expect(TOKEN_BUDGETS).toHaveProperty("gemini_local");
expect(TOKEN_BUDGETS).toHaveProperty("opencode_local");
expect(TOKEN_BUDGETS).toHaveProperty("kimi_local");
expect(TOKEN_BUDGETS).toHaveProperty("claude_compatible");
});
it("has numeric token caps for each adapter", () => {
expect(TOKEN_BUDGETS.claude_local).toBe(50000);
expect(TOKEN_BUDGETS.codex_local).toBe(30000);
expect(TOKEN_BUDGETS.gemini_local).toBe(50000);
expect(TOKEN_BUDGETS.opencode_local).toBe(30000);
expect(TOKEN_BUDGETS.kimi_local).toBe(20000);
expect(TOKEN_BUDGETS.claude_compatible).toBe(50000);
});
});
describe("resolveTokenBudget", () => {
it("returns exact match for known adapter types", () => {
expect(resolveTokenBudget("claude_local")).toBe(50000);
expect(resolveTokenBudget("codex_local")).toBe(30000);
expect(resolveTokenBudget("gemini_local")).toBe(50000);
});
it("resolves claude_compat_* variants to claude_compatible budget", () => {
expect(resolveTokenBudget("claude_compat_2")).toBe(50000);
expect(resolveTokenBudget("claude_compat_10")).toBe(50000);
expect(resolveTokenBudget("claude_compatible")).toBe(50000);
});
it("returns 30000 default for unknown adapter types", () => {
expect(resolveTokenBudget("unknown_adapter")).toBe(30000);
});
});
@@ -105,14 +122,22 @@ describe("skill-types", () => {
expect(result.isUpdate).toBe(false);
});
it("RuntimeSkillEntry has runtimeName, sourcePath, name, tokenEstimate", () => {
it("RuntimeSkillEntry has runtimeName, sourcePath, name, tokenEstimate and semantics", () => {
const entry: RuntimeSkillEntry = {
runtimeName: "claude",
sourcePath: "/path/to/skill",
name: "test-skill",
tokenEstimate: 1000,
allowedTools: ["read", "write"],
hooks: ["on_task_start"],
contextMode: "inline",
isAgent: false,
effort: "medium",
whenToUse: "when writing code",
};
expect(entry.tokenEstimate).toBe(1000);
expect(entry.allowedTools).toEqual(["read", "write"]);
expect(entry.contextMode).toBe("inline");
});
it("ManagedSkillRef has runtimeName and sourcePath only", () => {
+7 -2
View File
@@ -272,9 +272,12 @@ const fakeTask = (id: string, title = "Task"): TaskRecord => ({
sessionDisplayId: null,
costUsd: 0,
requiresUserReview: false,
startedAt: null,
completedAt: null,
cancelledAt: null,
createdAt: "2026-01-01",
updatedAt: "2026-01-01",
} as TaskRecord);
} as unknown as TaskRecord);
const fakeRun = (id: string): RunRecord => ({
id,
@@ -326,15 +329,17 @@ const fakeApproval = (id: string): ApprovalRecord => ({
id,
companyId: "company-1",
relatedTaskId: "t-1",
relatedAgentId: null,
requestedByAgentId: "ag-1",
type: "task_completion",
payloadSummary: "",
impactSummary: "",
payloadJson: null,
decisionNote: null,
state: "pending",
createdAt: "2026-01-01",
updatedAt: "2026-01-01",
} as ApprovalRecord);
} as unknown as ApprovalRecord);
describe("CompanySlice patch methods", () => {
beforeEach(() => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { describe, expect, it } from "vitest";
/**
* Tests for non-code task deliverable enforcement in automation-service.ts.
+249
View File
@@ -0,0 +1,249 @@
import { describe, expect, it, vi } from "vitest";
import {
REVIEW_TIMEOUT_MS,
sweepStaleReviews,
} from "../../src/main/task-quality-gates";
import type { SweepStaleReviewsDeps } from "../../src/main/task-quality-gates";
describe("sweepStaleReviews", () => {
const NOW = Date.now();
const THIRTY_FIVE_MIN_AGO = new Date(NOW - 35 * 60 * 1000).toISOString();
const TWENTY_FIVE_MIN_AGO = new Date(NOW - 25 * 60 * 1000).toISOString();
function makeSweepDeps(overrides: Partial<SweepStaleReviewsDeps> = {}): SweepStaleReviewsDeps {
return {
db: {
queryAll: vi.fn().mockReturnValue([]),
getManagerForAgent: vi.fn().mockReturnValue(null),
getChainOfCommand: vi.fn().mockReturnValue([]),
advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_review" }),
},
getCompanyExecutionMode: vi.fn().mockReturnValue("ai_ceo"),
dispatchAgentMessage: vi.fn().mockReturnValue({ ok: true }),
resolveApprovalDecision: vi.fn(),
notify: vi.fn(),
handleTaskStatusChange: vi.fn(),
handleApprovalResolved: vi.fn(),
emitEvent: vi.fn(),
publishDomainChanged: vi.fn(),
wakeAgentIfPossible: vi.fn(),
logger: { info: vi.fn(), warn: vi.fn() },
...overrides,
};
}
it("escalates to manager's manager when task in_review for 35 minutes", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
queryAll: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
};
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager One", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-manager-1", name: "Higher Manager" }]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(1);
expect(deps.dispatchAgentMessage).toHaveBeenCalledWith(
expect.objectContaining({ db: deps.db }),
expect.objectContaining({
companyId: "company-1",
fromAgentId: "manager-1",
toAgentId: "higher-manager-1",
channel: "direct",
priority: "urgent",
}),
"subtask_completed",
);
});
it("does not escalate when task in_review for only 25 minutes", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: TWENTY_FIVE_MIN_AGO, title: "Build login" },
]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.notify).not.toHaveBeenCalled();
});
it("notifies the human when the reviewer has no higher manager", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
queryAll: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
};
dbMock.queryAll
.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
])
.mockReturnValueOnce([]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "ceo-1", name: "CEO", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(1);
expect(deps.notify).toHaveBeenCalledWith(
expect.objectContaining({
title: "Review Timeout",
urgency: "critical",
batchKey: "review_escalation",
navigation: expect.objectContaining({ section: "tasks", entityId: "task-1" }),
}),
);
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
});
it("auto-approves pending approval linked to a stale review task", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
queryAll: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
};
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.queryAll.mockReturnValueOnce([{ id: "approval-1", type: "deliverable_review" }]);
sweepStaleReviews("company-1", deps, NOW);
expect(deps.resolveApprovalDecision).toHaveBeenCalledWith(
expect.objectContaining({
db: deps.db,
companyId: "company-1",
approvalId: "approval-1",
state: "approved",
decisionNote: "Auto-approved: review timeout exceeded 30 minutes.",
actor: "system",
}),
);
});
it("swallows resolveApprovalDecision races gracefully", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
queryAll: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
};
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.queryAll.mockReturnValueOnce([{ id: "approval-1", type: "deliverable_review" }]);
(deps.resolveApprovalDecision as ReturnType<typeof vi.fn>).mockImplementationOnce(() => {
throw new Error("Approval already decided");
});
expect(() => sweepStaleReviews("company-1", deps, NOW)).not.toThrow();
});
it("advances task status to done after escalation", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as {
queryAll: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
getChainOfCommand: ReturnType<typeof vi.fn>;
advanceTaskStatus: ReturnType<typeof vi.fn>;
};
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.queryAll.mockReturnValueOnce([]);
sweepStaleReviews("company-1", deps, NOW);
expect(dbMock.advanceTaskStatus).toHaveBeenCalledWith(
"task-1",
"done",
"Auto-advanced: review timeout exceeded 30 minutes.",
);
expect(deps.handleTaskStatusChange).toHaveBeenCalledWith("task-1", "company-1", "in_review", "done");
});
it("skips tasks without an assignee", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: null, updatedAt: THIRTY_FIVE_MIN_AGO, title: "Orphan task" },
]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.notify).not.toHaveBeenCalled();
});
it("exports REVIEW_TIMEOUT_MS as 30 minutes in milliseconds", () => {
expect(REVIEW_TIMEOUT_MS).toBe(30 * 60 * 1000);
});
it("does not remind before the cofounder review timeout threshold", () => {
const deps = makeSweepDeps({
getCompanyExecutionMode: vi.fn().mockReturnValue("ai_cofounder"),
});
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: TWENTY_FIVE_MIN_AGO, title: "Review me" },
]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
expect(deps.notify).not.toHaveBeenCalled();
expect(deps.resolveApprovalDecision).not.toHaveBeenCalled();
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.db.advanceTaskStatus).not.toHaveBeenCalled();
});
it("sends a critical cofounder review reminder without auto-approving", () => {
const deps = makeSweepDeps({
getCompanyExecutionMode: vi.fn().mockReturnValue("ai_cofounder"),
});
const dbMock = deps.db as unknown as {
queryAll: ReturnType<typeof vi.fn>;
getManagerForAgent: ReturnType<typeof vi.fn>;
};
dbMock.queryAll
.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Review me" },
])
.mockReturnValueOnce([{ id: "approval-1", type: "deliverable_review" }]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
expect(deps.notify).toHaveBeenCalledWith(
expect.objectContaining({
title: "Your Review Required",
urgency: "critical",
batchKey: "cofounder_review_needed",
navigation: expect.objectContaining({ section: "approvals", entityId: "approval-1" }),
}),
);
expect(deps.resolveApprovalDecision).not.toHaveBeenCalled();
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.db.advanceTaskStatus).not.toHaveBeenCalled();
});
});
+5 -192
View File
@@ -1,16 +1,11 @@
import { describe, expect, it, vi } from "vitest";
import { describe, expect, it } from "vitest";
import {
validateTaskCreation,
checkAcceptanceCriteria,
checkCapabilityMatch,
ACCEPTANCE_CRITERIA_KEYWORDS,
sweepStaleReviews,
REVIEW_TIMEOUT_MS,
} from "../../src/main/task-quality-gates";
import type {
TaskValidationResult,
SweepStaleReviewsDeps,
} from "../../src/main/task-quality-gates";
import type { TaskValidationResult as _TaskValidationResult } from "../../src/main/task-quality-gates";
// --- TASK-01: validateTaskCreation tests ---
@@ -183,10 +178,11 @@ describe("checkCapabilityMatch", () => {
expect(result.warning).toContain("Data Analyst");
});
it("returns matched=true when agent has empty capabilities and no skills (graceful degradation)", () => {
it("returns matched=false with warning when agent has empty capabilities and no skills", () => {
const db = makeMockDb({ name: "New Agent", capabilities: "" });
const result = checkCapabilityMatch(db, "agent-1", "Build React login form", "company-1");
expect(result.matched).toBe(true);
expect(result.matched).toBe(false);
expect(result.warning).toContain("New Agent");
});
it("returns matched=true when agent has no capabilities but skill name overlaps with task", () => {
@@ -206,186 +202,3 @@ describe("checkCapabilityMatch", () => {
expect(result.matched).toBe(true);
});
});
// --- TASK-04: sweepStaleReviews tests ---
describe("sweepStaleReviews", () => {
const NOW = Date.now();
const THIRTY_FIVE_MIN_AGO = new Date(NOW - 35 * 60 * 1000).toISOString();
const TWENTY_FIVE_MIN_AGO = new Date(NOW - 25 * 60 * 1000).toISOString();
function makeSweepDeps(overrides: Partial<SweepStaleReviewsDeps> = {}): SweepStaleReviewsDeps {
return {
db: {
queryAll: vi.fn().mockReturnValue([]),
getManagerForAgent: vi.fn().mockReturnValue(null),
getChainOfCommand: vi.fn().mockReturnValue([]),
advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_review" }),
},
dispatchAgentMessage: vi.fn().mockReturnValue({ ok: true }),
resolveApprovalDecision: vi.fn(),
notify: vi.fn(),
handleTaskStatusChange: vi.fn(),
handleApprovalResolved: vi.fn(),
emitEvent: vi.fn(),
publishDomainChanged: vi.fn(),
wakeAgentIfPossible: vi.fn(),
logger: { info: vi.fn(), warn: vi.fn() },
...overrides,
};
}
it("escalates to manager's manager when task in_review for 35 minutes", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn>; getManagerForAgent: ReturnType<typeof vi.fn>; getChainOfCommand: ReturnType<typeof vi.fn> };
// Task in review for 35 min
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
// Direct manager of the assignee (the reviewer)
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager One", companyId: "company-1" });
// Chain of command for the reviewer — the reviewer's manager (escalation target)
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-manager-1", name: "Higher Manager" }]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(1);
expect(deps.dispatchAgentMessage).toHaveBeenCalledWith(
expect.objectContaining({ db: deps.db }),
expect.objectContaining({
companyId: "company-1",
fromAgentId: "manager-1",
toAgentId: "higher-manager-1",
channel: "direct",
priority: "urgent",
}),
"subtask_completed",
);
});
it("does not escalate when task in_review for only 25 minutes (under threshold)", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: TWENTY_FIVE_MIN_AGO, title: "Build login" },
]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.notify).not.toHaveBeenCalled();
});
it("notifies human when no higher manager (reviewer is CEO)", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn>; getManagerForAgent: ReturnType<typeof vi.fn>; getChainOfCommand: ReturnType<typeof vi.fn> };
dbMock.queryAll
.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
])
.mockReturnValueOnce([]); // no pending approvals
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "ceo-1", name: "CEO", companyId: "company-1" });
// Empty chain means reviewer is the top of hierarchy
dbMock.getChainOfCommand.mockReturnValueOnce([]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(1);
expect(deps.notify).toHaveBeenCalledWith(
expect.objectContaining({
title: "Review Timeout",
urgency: "critical",
batchKey: "review_escalation",
navigation: expect.objectContaining({ section: "tasks", entityId: "task-1" }),
}),
);
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
});
it("auto-approves pending approval linked to stale review task", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn>; getManagerForAgent: ReturnType<typeof vi.fn>; getChainOfCommand: ReturnType<typeof vi.fn> };
// First queryAll: stale task
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
// Second queryAll: pending approval
dbMock.queryAll.mockReturnValueOnce([{ id: "approval-1" }]);
sweepStaleReviews("company-1", deps, NOW);
expect(deps.resolveApprovalDecision).toHaveBeenCalledWith(
expect.objectContaining({
db: deps.db,
companyId: "company-1",
approvalId: "approval-1",
state: "approved",
decisionNote: "Auto-approved: review timeout exceeded 30 minutes.",
actor: "system",
}),
);
});
it("catches resolveApprovalDecision error gracefully (race condition)", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn>; getManagerForAgent: ReturnType<typeof vi.fn>; getChainOfCommand: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.queryAll.mockReturnValueOnce([{ id: "approval-1" }]);
// resolveApprovalDecision throws (already decided)
(deps.resolveApprovalDecision as ReturnType<typeof vi.fn>).mockImplementationOnce(() => {
throw new Error("Approval already decided");
});
// Should not throw
expect(() => sweepStaleReviews("company-1", deps, NOW)).not.toThrow();
});
it("advances task status to done after escalation", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn>; getManagerForAgent: ReturnType<typeof vi.fn>; getChainOfCommand: ReturnType<typeof vi.fn>; advanceTaskStatus: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" },
]);
dbMock.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" });
dbMock.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]);
dbMock.queryAll.mockReturnValueOnce([]); // no approvals
sweepStaleReviews("company-1", deps, NOW);
expect(dbMock.advanceTaskStatus).toHaveBeenCalledWith(
"task-1",
"done",
"Auto-advanced: review timeout exceeded 30 minutes.",
);
expect(deps.handleTaskStatusChange).toHaveBeenCalledWith("task-1", "company-1", "in_review", "done");
});
it("skips task with no assigneeAgentId", () => {
const deps = makeSweepDeps();
const dbMock = deps.db as unknown as { queryAll: ReturnType<typeof vi.fn> };
dbMock.queryAll.mockReturnValueOnce([
{ id: "task-1", assigneeAgentId: null, updatedAt: THIRTY_FIVE_MIN_AGO, title: "Orphan task" },
]);
const count = sweepStaleReviews("company-1", deps, NOW);
expect(count).toBe(0);
expect(deps.dispatchAgentMessage).not.toHaveBeenCalled();
expect(deps.notify).not.toHaveBeenCalled();
});
it("exports REVIEW_TIMEOUT_MS as 30 minutes in milliseconds", () => {
expect(REVIEW_TIMEOUT_MS).toBe(30 * 60 * 1000);
});
});