update
This commit is contained in:
1 parent
2ab001b18a
commit
810e2d5967
28 files changed
+1015
-85
No files matched your search
@@ -8,7 +8,7 @@ describe("buildCapabilityRegistrySection", () => {
|
||||
{ name: "React Standards", slug: "react-standards", description: "Use when building React UI" },
|
||||
],
|
||||
companyWorkspaces: [
|
||||
{ name: "Primary", localPath: "/tmp/workspace" },
|
||||
{ id: "workspace-1", name: "Primary", localPath: "/tmp/workspace" },
|
||||
],
|
||||
allConnectors: [
|
||||
{ id: "openai-codex", label: "Codex", status: "ready", enabledModels: ["gpt-5.4"] },
|
||||
@@ -37,7 +37,7 @@ describe("buildCapabilityRegistrySection", () => {
|
||||
|
||||
expect(section).toContain("# Company Capability Registry");
|
||||
expect(section).toContain("React Standards (`react-standards`)");
|
||||
expect(section).toContain("Primary: /tmp/workspace");
|
||||
expect(section).toContain("Primary (`workspace-1`): /tmp/workspace");
|
||||
expect(section).toContain("Codex (`openai-codex`) — models: gpt-5.4");
|
||||
expect(section).toContain("architecture_decision");
|
||||
});
|
||||
|
||||
@@ -482,6 +482,7 @@ describe("AppDatabase", () => {
|
||||
{ version: 57, name: "task_leases_runtime_ownership" },
|
||||
{ version: 58, name: "approval_policy_table" },
|
||||
{ version: 59, name: "social_company_scope" },
|
||||
{ version: 60, name: "filesystem_task_workspaces" },
|
||||
]);
|
||||
|
||||
db.close();
|
||||
@@ -496,6 +497,7 @@ describe("AppDatabase", () => {
|
||||
{ version: 57, name: "task_leases_runtime_ownership" },
|
||||
{ version: 58, name: "approval_policy_table" },
|
||||
{ version: 59, name: "social_company_scope" },
|
||||
{ version: 60, name: "filesystem_task_workspaces" },
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -176,6 +176,7 @@ describe("legacy to v56 baseline migration", () => {
|
||||
{ version: 57, name: "task_leases_runtime_ownership" },
|
||||
{ version: 58, name: "approval_policy_table" },
|
||||
{ version: 59, name: "social_company_scope" },
|
||||
{ version: 60, name: "filesystem_task_workspaces" },
|
||||
]);
|
||||
expect(appDb.queryAll<{ companyId: string }>("select company_id as companyId from secrets where id = 'secret-1'")[0])
|
||||
.toEqual({ companyId: "company-1" });
|
||||
@@ -210,6 +211,7 @@ describe("legacy to v56 baseline migration", () => {
|
||||
{ version: 57, name: "task_leases_runtime_ownership" },
|
||||
{ version: 58, name: "approval_policy_table" },
|
||||
{ version: 59, name: "social_company_scope" },
|
||||
{ version: 60, name: "filesystem_task_workspaces" },
|
||||
]);
|
||||
expect(appDb.queryAll("select id from budget_policies where scope_type = 'agent' and scope_id = 'agent-1'"))
|
||||
.toHaveLength(1);
|
||||
@@ -235,6 +237,7 @@ describe("legacy to v56 baseline migration", () => {
|
||||
{ version: 57, name: "task_leases_runtime_ownership" },
|
||||
{ version: 58, name: "approval_policy_table" },
|
||||
{ version: 59, name: "social_company_scope" },
|
||||
{ version: 60, name: "filesystem_task_workspaces" },
|
||||
]);
|
||||
} finally {
|
||||
appDb.close();
|
||||
@@ -394,6 +397,7 @@ describe("legacy to v56 baseline migration", () => {
|
||||
{ version: 57, name: "task_leases_runtime_ownership" },
|
||||
{ version: 58, name: "approval_policy_table" },
|
||||
{ version: 59, name: "social_company_scope" },
|
||||
{ version: 60, name: "filesystem_task_workspaces" },
|
||||
]);
|
||||
} finally {
|
||||
appDb.close();
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ConnectorId } from "@shared/types";
|
||||
|
||||
vi.mock("electron", () => ({
|
||||
safeStorage: {
|
||||
isEncryptionAvailable: () => false,
|
||||
getSelectedStorageBackend: () => "basic_text",
|
||||
encryptString: (value: string) => Buffer.from(value, "utf8"),
|
||||
decryptString: (value: Buffer) => value.toString("utf8"),
|
||||
},
|
||||
}));
|
||||
|
||||
import { AppDatabase } from "../../src/main/database";
|
||||
import { CONNECTORS } from "../../src/main/connector-definitions";
|
||||
import { resolveModels } from "../../src/main/model-resolver";
|
||||
import { pickFromCandidates } from "../../src/main/model-dispatch";
|
||||
|
||||
describe("model capability seed", () => {
|
||||
let tempDir: string;
|
||||
let db: AppDatabase;
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-model-capabilities-"));
|
||||
db = new AppDatabase(tempDir);
|
||||
db.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
db.close();
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("keeps catalog-seeded rows current on existing profiles without overwriting user edits", () => {
|
||||
db.core.modelCapabilities.upsertModelCapability({
|
||||
provider: "zai",
|
||||
modelId: "glm-4.6",
|
||||
reasoning: 2,
|
||||
coding: 2,
|
||||
writing: 2,
|
||||
tier: "trivial",
|
||||
costPerMtokOut: 99,
|
||||
});
|
||||
db.core.modelCapabilities.deleteModelCapability("zai", "glm-5.1");
|
||||
|
||||
db.close();
|
||||
db = new AppDatabase(tempDir);
|
||||
db.init();
|
||||
|
||||
expect(db.core.modelCapabilities.getModelCapability("zai", "glm-5.1")).toMatchObject({
|
||||
provider: "zai",
|
||||
modelId: "glm-5.1",
|
||||
reasoning: 5,
|
||||
coding: 4,
|
||||
writing: 4,
|
||||
tier: "hard",
|
||||
});
|
||||
expect(db.core.modelCapabilities.getModelCapability("zai", "glm-4.6")).toMatchObject({
|
||||
reasoning: 2,
|
||||
coding: 2,
|
||||
writing: 2,
|
||||
tier: "trivial",
|
||||
costPerMtokOut: 99,
|
||||
});
|
||||
});
|
||||
|
||||
it("seeds a capability row for every static connector catalog model", () => {
|
||||
const seeded = new Set(
|
||||
db.core.modelCapabilities
|
||||
.listModelCapabilities()
|
||||
.map((entry) => `${entry.provider}::${entry.modelId}`),
|
||||
);
|
||||
const expected = new Set<string>();
|
||||
|
||||
for (const connector of CONNECTORS) {
|
||||
for (const model of resolveModels(connector.provider)) {
|
||||
expected.add(`${connector.provider}::${model.id}`);
|
||||
}
|
||||
if (connector.auth.type !== "local" && connector.defaultModel.trim()) {
|
||||
expected.add(`${connector.provider}::${connector.defaultModel}`);
|
||||
}
|
||||
}
|
||||
|
||||
expect(expected.size).toBeGreaterThan(700);
|
||||
for (const key of expected) {
|
||||
expect(seeded.has(key), key).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("uses the same catalog seed path for hard reasoning picks", () => {
|
||||
const winner = pickFromCandidates(
|
||||
{ domain: "reasoning", requiredTier: "hard" },
|
||||
[{ provider: "zai", connectorId: "zai" as ConnectorId, modelId: "glm-5.1" }],
|
||||
db.core.modelCapabilities.listModelCapabilities(),
|
||||
);
|
||||
|
||||
expect(winner?.modelId).toBe("glm-5.1");
|
||||
});
|
||||
});
|
||||
@@ -18,6 +18,7 @@ vi.mock("electron", () => ({
|
||||
}));
|
||||
|
||||
import { AppDatabase } from "../../src/main/database";
|
||||
import { migrateFilesystemTaskWorkspacesV60 } from "../../src/main/database/migrations/v60-filesystem-task-workspaces";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
@@ -116,16 +117,74 @@ describe("legacy to v56 baseline migration", () => {
|
||||
{ id: "task-inflight", requiresFilesystem: 1 },
|
||||
]);
|
||||
const migrations = appDb.queryAll<{ version: number; name: string }>(
|
||||
"select version, name from schema_migrations where version in (56, 57, 58, 59) order by version",
|
||||
"select version, name from schema_migrations where version in (56, 57, 58, 59, 60) order by version",
|
||||
);
|
||||
expect(migrations).toEqual([
|
||||
{ version: 56, name: "baseline_v56" },
|
||||
{ version: 57, name: "task_leases_runtime_ownership" },
|
||||
{ version: 58, name: "approval_policy_table" },
|
||||
{ version: 59, name: "social_company_scope" },
|
||||
{ version: 60, name: "filesystem_task_workspaces" },
|
||||
]);
|
||||
} finally {
|
||||
appDb.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("backfills active filesystem tasks in single-workspace companies", async () => {
|
||||
const profileDir = await createTempDir("agentcompany-fs-workspace-v60-");
|
||||
const rawDb = new DatabaseSync(join(profileDir, "profile.sqlite"));
|
||||
const ts = "2026-05-15T00:00:00.000Z";
|
||||
try {
|
||||
rawDb.exec(`
|
||||
create table schema_migrations (version integer primary key, name text not null, applied_at text not null);
|
||||
create table workspaces (
|
||||
id text primary key,
|
||||
company_id text not null,
|
||||
project_id text,
|
||||
is_primary integer not null,
|
||||
created_at text not null
|
||||
);
|
||||
create table agents (
|
||||
id text primary key,
|
||||
company_id text not null,
|
||||
workspace_id text
|
||||
);
|
||||
create table tasks (
|
||||
id text primary key,
|
||||
company_id text not null,
|
||||
project_id text,
|
||||
assignee_agent_id text,
|
||||
workspace_id text,
|
||||
status text not null,
|
||||
requires_filesystem integer not null,
|
||||
updated_at text not null
|
||||
);
|
||||
`);
|
||||
rawDb
|
||||
.prepare("insert into workspaces (id, company_id, project_id, is_primary, created_at) values (?, ?, ?, ?, ?)")
|
||||
.run("workspace-1", "company-1", null, 1, ts);
|
||||
rawDb
|
||||
.prepare("insert into agents (id, company_id, workspace_id) values (?, ?, ?)")
|
||||
.run("agent-1", "company-1", null);
|
||||
rawDb
|
||||
.prepare("insert into tasks (id, company_id, project_id, assignee_agent_id, workspace_id, status, requires_filesystem, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
.run("task-stuck", "company-1", "project-1", "agent-1", null, "todo", 1, ts);
|
||||
rawDb
|
||||
.prepare("insert into tasks (id, company_id, project_id, assignee_agent_id, workspace_id, status, requires_filesystem, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
.run("task-cognitive", "company-1", "project-1", "agent-1", null, "todo", 0, ts);
|
||||
|
||||
migrateFilesystemTaskWorkspacesV60(rawDb);
|
||||
|
||||
const rows = rawDb
|
||||
.prepare("select id, workspace_id as workspaceId from tasks order by id")
|
||||
.all() as Array<{ id: string; workspaceId: string | null }>;
|
||||
expect(rows).toEqual([
|
||||
{ id: "task-cognitive", workspaceId: null },
|
||||
{ id: "task-stuck", workspaceId: "workspace-1" },
|
||||
]);
|
||||
} finally {
|
||||
rawDb.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("electron", () => ({
|
||||
safeStorage: {
|
||||
isEncryptionAvailable: () => false,
|
||||
getSelectedStorageBackend: () => "basic_text",
|
||||
encryptString: (value: string) => Buffer.from(value, "utf8"),
|
||||
decryptString: (value: Buffer) => value.toString("utf8"),
|
||||
},
|
||||
}));
|
||||
|
||||
import { AppDatabase } from "../../src/main/database";
|
||||
import { resolveTaskWorkspaceBinding } from "../../src/main/task-workspace-binding";
|
||||
|
||||
describe("resolveTaskWorkspaceBinding", () => {
|
||||
let tempDir: string;
|
||||
let db: AppDatabase;
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await mkdtemp(join(tmpdir(), "agentcompany-task-workspace-binding-"));
|
||||
db = new AppDatabase(tempDir);
|
||||
db.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
db.close();
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("binds filesystem tasks to the creator agent workspace", () => {
|
||||
const { companyId, workspaceId, agentId } = seedCompany(db);
|
||||
const result = resolveTaskWorkspaceBinding(db, {
|
||||
companyId,
|
||||
requiresFilesystem: true,
|
||||
creatorAgentId: agentId,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ ok: true, workspaceId, source: "creator" });
|
||||
});
|
||||
|
||||
it("binds filesystem tasks to the only company workspace", () => {
|
||||
const { companyId, workspaceId } = seedCompany(db, { agentWorkspace: false });
|
||||
const result = resolveTaskWorkspaceBinding(db, {
|
||||
companyId,
|
||||
requiresFilesystem: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ ok: true, workspaceId, source: "single_company" });
|
||||
});
|
||||
|
||||
it("rejects filesystem tasks when the workspace is ambiguous", () => {
|
||||
const { companyId } = seedCompany(db, { agentWorkspace: false });
|
||||
db.core.workspaces.saveWorkspace({
|
||||
companyId,
|
||||
name: "Second",
|
||||
localPath: "/tmp/second",
|
||||
repoUrl: "",
|
||||
repoRef: "",
|
||||
isPrimary: false,
|
||||
});
|
||||
|
||||
const result = resolveTaskWorkspaceBinding(db, {
|
||||
companyId,
|
||||
requiresFilesystem: true,
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
function seedCompany(db: AppDatabase, options: { agentWorkspace?: boolean } = {}) {
|
||||
const companyId = db.core.company.saveCompany({
|
||||
name: "Workspace Binding Co",
|
||||
description: "",
|
||||
status: "active",
|
||||
});
|
||||
db.core.connectors.saveConnector({
|
||||
id: "openai-codex",
|
||||
model: "gpt-5",
|
||||
envBindingText: "",
|
||||
notes: "",
|
||||
});
|
||||
const workspaceId = db.core.workspaces.saveWorkspace({
|
||||
companyId,
|
||||
name: "Default",
|
||||
localPath: "/tmp/default",
|
||||
repoUrl: "",
|
||||
repoRef: "",
|
||||
isPrimary: true,
|
||||
});
|
||||
const agentId = db.core.agents.saveAgent({
|
||||
companyId,
|
||||
name: "CEO",
|
||||
role: "CEO",
|
||||
title: "CEO",
|
||||
status: "active",
|
||||
reportsTo: null,
|
||||
connectorId: "openai-codex",
|
||||
workspaceId: options.agentWorkspace === false ? null : workspaceId,
|
||||
model: null,
|
||||
capabilities: "Leadership",
|
||||
budgetMonthlyUsd: 100,
|
||||
});
|
||||
return { companyId, workspaceId, agentId };
|
||||
}
|
||||
Reference in new issue
Block a user