Ship skills built-in: bundle the catalogue, assign per hire, force humanizer

Skills no longer come from the user's ~/.agents library - most users have
none, and the CEO branch that compiled the entire catalogue into its
prompt overflowed the model context (1.1MB) and failed every CEO run.

- Bundle "built-in skills/" with the app (extraResources) and stage it
  at startup into <profile>/builtin-skills as one dir-per-skill copy -
  both bundle shapes (SKILL.md dirs and flat category .md files)
  normalize into the same importable layout
- Import the staged catalogue per org at startup and inside onboarding,
  so the CEO's first run already carries it
- Forced house style: resolveRuntimeSnapshot unions the humanizer skill
  into every agent's runtime (CEO included) as a resolution rule - no
  binding row to forget, delete, or bypass; revoke_skill refuses it
- CEO staffs hires with skills: hire_agent accepts a skills array,
  grant_skill/revoke_skill adjust later, list_skills now shows
  descriptions from skill frontmatter
- Kill the c_suite compile-all branch; everyone resolves bindings-only,
  capped at 200k chars with omissions named in the prompt
- Remove the Skills settings page and its import/sync/test IPC surface;
  drop ~/.agents discovery and the AGENTCOMPANY_NEW_SKILLS_ROOT seam in
  favor of AGENTCOMPANY_NEW_BUILTIN_SKILLS_DIR
- Tests: staging/import/forced-resolution/cap units, real-bundle CI
  check, e2e asserts the tab is gone and the catalogue self-imports
This commit is contained in:
ZacharyZhang-NY committed 2026-07-08 21:18:50 -04:00
1 parent 55c043f8b4
commit 86aa6fdb29
318 files changed
+77467 -520

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
/**
* A miniature of the real bundled catalogue: the forced humanizer skill plus
* one category of flat skills. E2E launches point
* AGENTCOMPANY_NEW_BUILTIN_SKILLS_DIR here so startup imports stay small and
* hermetic instead of reading the full repo bundle.
*/
export function seedBuiltinSkillsFixture(root: string): string {
mkdirSync(join(root, "humanizer"), { recursive: true });
writeFileSync(
join(root, "humanizer", "SKILL.md"),
"---\nname: humanizer\nversion: 2.1.1\ndescription: |\n Remove signs of AI-generated writing from text.\n---\n\nWrite like a person.\n",
"utf8",
);
mkdirSync(join(root, "engineering"), { recursive: true });
writeFileSync(
join(root, "engineering", "engineering-backend-architect.md"),
"---\nname: Backend Architect\ndescription: Scalable system design specialist.\n---\n\nDesign the systems that hold everything up.\n",
"utf8",
);
return root;
}
+16 -48
View File
@@ -1,24 +1,23 @@
import { _electron as electron, expect, test, type Page } from "@playwright/test";
import { mkdirSync, writeFileSync } from "node:fs";
import { mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { randomUUID } from "node:crypto";
import { seedBuiltinSkillsFixture } from "./builtin-skills-fixture";
test("onboards, hires, and creates work in the Electron shell", async () => {
const profileDir = join(tmpdir(), `agentcompany-new-e2e-${randomUUID()}`);
mkdirSync(profileDir, { recursive: true });
// The app reads skills from the user's global library, so the test gets a
// library of its own — seeded with one on-disk skill the Skills page must
// surface without a manual import.
const skillsRoot = join(tmpdir(), `agentcompany-new-e2e-skills-${randomUUID()}`);
createGlobalDiskSkill(skillsRoot);
// Skills ship with the app; the test points the bundle seam at a miniature
// catalogue so onboarding imports it hermetically.
const builtinDir = seedBuiltinSkillsFixture(join(tmpdir(), `agentcompany-new-e2e-builtin-${randomUUID()}`));
const app = await electron.launch({
args: [resolve("out/main/index.js")],
env: {
...process.env,
AGENTCOMPANY_NEW_PROFILE_DIR: profileDir,
AGENTCOMPANY_NEW_AUTORUN: "0",
AGENTCOMPANY_NEW_SKILLS_ROOT: skillsRoot,
AGENTCOMPANY_NEW_BUILTIN_SKILLS_DIR: builtinDir,
},
});
@@ -145,16 +144,15 @@ test("onboards, hires, and creates work in the Electron shell", async () => {
await page.getByRole("button", { name: "Save OpenAI policy" }).click();
await expect(page.getByText("0 / 25").first()).toBeVisible();
await page.getByRole("button", { name: "Skills" }).click();
// The seeded home-directory skill syncs in on its own when the page opens.
await expect(page.getByText("Disk Runbook").first()).toBeVisible();
await page.getByRole("button", { name: "Import" }).click();
await page.getByLabel("Directory path").fill(createSkillDirectory());
await page.locator(".modal").getByRole("button", { name: "Import" }).click();
await expect(page.getByText("Release Gate").first()).toBeVisible();
await expect(page.getByText("Never run").first()).toBeVisible();
await page.locator(".table-row").filter({ hasText: "Release Gate" }).getByRole("button", { name: "Run tests" }).click();
await expect(page.getByText("1/1 passed").first()).toBeVisible();
// Skills are built-in now: no Skills tab exists, yet the bundled
// catalogue imported itself during onboarding.
await expect(page.getByRole("button", { name: "Skills" })).toHaveCount(0);
const skillSlugs = await page.evaluate(async () => {
const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<unknown> } }).agentCompany;
const result = await api.loadSnapshot() as { ok: true; data: { skillDefinitions: Array<{ slug: string }> } };
return result.data.skillDefinitions.map((skill) => skill.slug).sort();
});
expect(skillSlugs).toEqual(["engineering-backend-architect", "humanizer"]);
// Virtual office page
await page.getByRole("button", { name: "Office" }).click();
@@ -173,36 +171,6 @@ test("onboards, hires, and creates work in the Electron shell", async () => {
}
});
function createGlobalDiskSkill(skillsRoot: string): void {
const dir = join(skillsRoot, "disk-runbook");
mkdirSync(dir, { recursive: true });
writeFileSync(
join(dir, "SKILL.md"),
"---\nname: Disk Runbook\ndescription: Recorded incident response steps.\n---\n\nFollow the recorded steps.\n",
"utf8",
);
}
function createSkillDirectory(): string {
const root = join(tmpdir(), `agentcompany-skill-${randomUUID()}`);
mkdirSync(join(root, "checks"), { recursive: true });
writeFileSync(join(root, "SKILL.md"), "Review release evidence and produce a concise gate decision.\n", "utf8");
writeFileSync(join(root, "checks", "acceptance.txt"), "release evidence accepted\n", "utf8");
writeFileSync(join(root, "skill.manifest.json"), JSON.stringify({
name: "Release Gate",
version: "1.0.0",
entrypoint: "SKILL.md",
capabilities: ["release-review"],
required_tools: ["git"],
compatible_models: ["gpt-5.4"],
risk_level: "low",
tests: ["checks/acceptance.txt"],
inputs: {},
outputs: {},
}, null, 2), "utf8");
return root;
}
async function verifyPagesDoNotOverflow(page: Page): Promise<void> {
for (const size of [{ width: 1280, height: 860 }, { width: 980, height: 700 }]) {
await page.setViewportSize(size);
@@ -210,7 +178,7 @@ async function verifyPagesDoNotOverflow(page: Page): Promise<void> {
await page.getByRole("button", { name: nav, exact: true }).click();
await expectPageToFit(page);
}
for (const tab of ["General", "Providers", "Skills", "Advanced"]) {
for (const tab of ["General", "Providers", "Advanced"]) {
await page.getByRole("button", { name: "Settings" }).click();
await page.getByRole("button", { name: tab, exact: true }).click();
await expectPageToFit(page);
+2 -1
View File
@@ -5,6 +5,7 @@ import { join, resolve } from "node:path";
import { randomUUID } from "node:crypto";
import { buildFloorPlan } from "../../src/renderer/src/pages/office/floor-plan";
import { cameraFor } from "../../src/renderer/src/pages/office/simulation";
import { seedBuiltinSkillsFixture } from "./builtin-skills-fixture";
test("the office floor opens agent details on click", async () => {
const profileDir = join(tmpdir(), `agentcompany-new-office-${randomUUID()}`);
@@ -15,7 +16,7 @@ test("the office floor opens agent details on click", async () => {
...process.env,
AGENTCOMPANY_NEW_PROFILE_DIR: profileDir,
AGENTCOMPANY_NEW_AUTORUN: "0",
AGENTCOMPANY_NEW_SKILLS_ROOT: join(profileDir, "skills-root"),
AGENTCOMPANY_NEW_BUILTIN_SKILLS_DIR: seedBuiltinSkillsFixture(join(profileDir, "builtin-src")),
},
});
+3 -2
View File
@@ -3,6 +3,7 @@ import { mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { randomUUID } from "node:crypto";
import { seedBuiltinSkillsFixture } from "./builtin-skills-fixture";
test("captures every page for visual review", async () => {
const profileDir = join(tmpdir(), `agentcompany-new-shots-${randomUUID()}`);
@@ -13,7 +14,7 @@ test("captures every page for visual review", async () => {
...process.env,
AGENTCOMPANY_NEW_PROFILE_DIR: profileDir,
AGENTCOMPANY_NEW_AUTORUN: "0",
AGENTCOMPANY_NEW_SKILLS_ROOT: join(profileDir, "skills-root"),
AGENTCOMPANY_NEW_BUILTIN_SKILLS_DIR: seedBuiltinSkillsFixture(join(profileDir, "builtin-src")),
},
});
const outDir = resolve("test-results/screenshots");
@@ -59,7 +60,7 @@ test("captures every page for visual review", async () => {
await go(page, "Settings", outDir, "17-settings");
await go(page, "Office", outDir, "18-office");
for (const tab of ["General", "Providers", "Skills", "Advanced"]) {
for (const tab of ["General", "Providers", "Advanced"]) {
await page.getByRole("button", { name: "Settings" }).click();
await page.getByRole("button", { name: tab }).first().click();
await snap(page, outDir, `settings-${tab.toLowerCase()}`);