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:
1 parent
55c043f8b4
commit
86aa6fdb29
318 files changed
+77467
-520
No files matched your search
@@ -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
@@ -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);
|
||||
|
||||
@@ -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,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()}`);
|
||||
|
||||
Reference in new issue
Block a user