diff --git a/agents/artist/README.md b/agents/artist/README.md new file mode 100644 index 000000000..220bed49c --- /dev/null +++ b/agents/artist/README.md @@ -0,0 +1,35 @@ +# @corbits/code-agent-artist + +## What this is + +Ready `@corbits/code-agent-artist` agent + independently importable components: +`agent`, `defineAgent`, `director`, `tools`, `systemPrompt`, `config`. + +## Consume (runLocal) + +```ts +import { agent } from "@corbits/code-agent-artist"; +const run = runLocal(defining(agent), env); +``` + +## Consume (deploy runWorkflow / hub / sidecar) + +Hand the same `AgentDefinition` to a workflow — the package never exports a +workflow itself. + +## Remix components + +```ts +import { director, tools, systemPrompt, config } from "@corbits/code-agent-artist"; +defineAgent({ director, ... }); // or swap tools / systemPrompt / config +``` + +Each of `director`, `tools`, `systemPrompt`, and `config` is a named, +independently importable part. + +## Single authority + +`prompt.ts` `systemPrompt.build()` is byte-stable (the raw card, no formatting). +`toolset.ts` `tools` literal equals the `REVIEW_TOOLS` surface constant in +`src/agent/directors/tool-sets.ts`; drift is enforced by the in-tree drift-guard +`src/agent/directors/artist/package.test.ts`. diff --git a/agents/artist/package.json b/agents/artist/package.json new file mode 100644 index 000000000..b7ee2bf51 --- /dev/null +++ b/agents/artist/package.json @@ -0,0 +1,13 @@ +{ + "name": "@corbits/code-agent-artist", + "version": "0.1.0", + "private": true, + "type": "module", + "license": "SEE LICENSE IN LICENSE.md", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + } +} diff --git a/agents/artist/src/agent.ts b/agents/artist/src/agent.ts new file mode 100644 index 000000000..a150b13c9 --- /dev/null +++ b/agents/artist/src/agent.ts @@ -0,0 +1,17 @@ +import { + defineAgent as coreDefineAgent, + type AgentDefinition, +} from "@intx/agent"; +import { systemPrompt } from "./prompt.js"; + +export const agent: AgentDefinition = defineAgent(); + +export function defineAgent(): AgentDefinition { + return coreDefineAgent({ + id: "artist", + systemPrompt: systemPrompt.build(), + tools: [], + capabilities: [], + inference: { sources: [] }, + }); +} diff --git a/agents/artist/src/config.ts b/agents/artist/src/config.ts new file mode 100644 index 000000000..7080c5b82 --- /dev/null +++ b/agents/artist/src/config.ts @@ -0,0 +1,9 @@ +export const config = { + spawn: { maySpawn: false }, + tier: "leaf", + modelRole: "implement", +} as const satisfies { + spawn: { maySpawn: boolean }; + tier: string; + modelRole: string; +}; diff --git a/agents/artist/src/director.ts b/agents/artist/src/director.ts new file mode 100644 index 000000000..63e02fa4b --- /dev/null +++ b/agents/artist/src/director.ts @@ -0,0 +1,21 @@ +import { tools } from "./toolset.js"; +import { systemPrompt } from "./prompt.js"; +import { config } from "./config.js"; + +export const director = { + id: "artist", + primaryIntent: + "Author hand-crafted SVGs, visual diagrams, and generative graphic prompts", + outOfLane: [ + "backend or core product implementation", + "code defect review or testing", + "fleet orchestration or spawning", + ], + description: + "Visual asset specialist — SVGs, visual diagrams, and generative graphic prompts", + systemPrompt: systemPrompt.build(), + tools: { allow: [...tools] }, + spawn: config.spawn, + tier: config.tier, + modelRole: config.modelRole, +} as const; diff --git a/agents/artist/src/index.ts b/agents/artist/src/index.ts new file mode 100644 index 000000000..c2c15f6c6 --- /dev/null +++ b/agents/artist/src/index.ts @@ -0,0 +1,5 @@ +export { agent, defineAgent } from "./agent.js"; +export { director } from "./director.js"; +export { tools } from "./toolset.js"; +export { systemPrompt } from "./prompt.js"; +export { config } from "./config.js"; diff --git a/src/agent/directors/artist/package.ts b/agents/artist/src/prompt.ts similarity index 65% rename from src/agent/directors/artist/package.ts rename to agents/artist/src/prompt.ts index e5d9b3a44..02da3e9a3 100644 --- a/src/agent/directors/artist/package.ts +++ b/agents/artist/src/prompt.ts @@ -1,27 +1,6 @@ -import type { DirectorPackage } from "../types.js"; -import { BUILD_TOOLS } from "../tool-sets.js"; +// The app formats the identity header via formatDirectorSystemPrompt. -/** - * Artist worker: visual asset specialist. - * Hand-crafts SVGs, visual diagrams (Mermaid, ASCII art), and structured - * generative graphic prompts for image generation models. - */ -export const artistPackage: DirectorPackage = { - id: "artist", - primaryIntent: - "Author hand-crafted SVGs, visual diagrams, and generative graphic prompts", - outOfLane: [ - "backend or core product implementation", - "code defect review or testing", - "fleet orchestration or spawning", - ], - description: - "Visual asset specialist — SVGs, visual diagrams, and generative graphic prompts", - tools: { allow: BUILD_TOOLS }, - spawn: { maySpawn: false }, - tier: "leaf", - modelRole: "implement", - systemPrompt: `You are ArtistDirector (Artist), a specialist in Corbits Code. +const CARD = `You are ArtistDirector (Artist), a specialist in Corbits Code. PRIMARY INTENT: create visual assets — clean vector SVGs, technical and architectural diagrams, and structured generative image prompts. @@ -48,5 +27,9 @@ Workflow: 3. Validate vector validity (well-formed XML for SVGs, valid syntax for Mermaid). 4. Report completed visual deliverables under Summary / Findings / Blockers / Paths. -OUT OF LANE: non-visual code implementation, backend logic, code defect review, fleet orchestration.`, +OUT OF LANE: non-visual code implementation, backend logic, code defect review, fleet orchestration.`; + +export const systemPrompt = { + theme: "artist", + build: (): string => CARD, }; diff --git a/agents/artist/src/toolset.ts b/agents/artist/src/toolset.ts new file mode 100644 index 000000000..d8041a3ef --- /dev/null +++ b/agents/artist/src/toolset.ts @@ -0,0 +1,15 @@ +export const tools: readonly string[] = [ + "read_file", + "grep", + "search_files", + "list_dir", + "lsp", + "run_shell", + "web_fetch", + "web_search", + "skill_search", + "use_skill", + "write_file", + "edit_file", + "delete_file", +] as const; diff --git a/agents/artist/tsconfig.json b/agents/artist/tsconfig.json new file mode 100644 index 000000000..0d7100cbc --- /dev/null +++ b/agents/artist/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src/**/*.ts"] +} diff --git a/bun.lock b/bun.lock index 61cbead3c..95f06b9f9 100644 --- a/bun.lock +++ b/bun.lock @@ -50,6 +50,10 @@ "@opentui/core-win32-x64": "0.5.12", }, }, + "agents/artist": { + "name": "@corbits/code-agent-artist", + "version": "0.1.0", + }, "packages/first-class-providers": { "name": "@corbits/first-class-providers", "version": "0.1.0", @@ -241,6 +245,8 @@ "@better-fetch/fetch": ["@better-fetch/fetch@1.3.2", "", {}, "sha512-Gs7n99b5tqUC6cQAPbV0uED3IraHB6xQbHLQ/C3l7ZFafHScOx9pQ+DYmP5blbLFShVWLqxNUlI9wi4xU/X+ow=="], + "@corbits/code-agent-artist": ["@corbits/code-agent-artist@workspace:agents/artist"], + "@corbits/codex-provider": ["@corbits/codex-provider@0.1.1", "", { "dependencies": { "arktype": "^2.2.3" }, "peerDependencies": { "@corbits/oauth-core": "^0.2.0", "@corbits/openai-responses": "^0.2.0", "@intx/inference": "^0.4.0", "@intx/types": "^0.4.0" } }, "sha512-MiGA5djQNtRpHqLvJAGdFgHEbUl808AbveMPwt7krLjOH0i20DyElpitHPZF3ltCXkFMhcKIoyTAEpRDqeB/Rw=="], "@corbits/first-class-providers": ["@corbits/first-class-providers@workspace:packages/first-class-providers"], diff --git a/scripts/dead-export-allowlist.txt b/scripts/dead-export-allowlist.txt index 76623ea68..2769cf3a3 100644 --- a/scripts/dead-export-allowlist.txt +++ b/scripts/dead-export-allowlist.txt @@ -51,3 +51,13 @@ packages/prompt-variance/src/index.ts: FAMILY_IDS packages/prompt-variance/src/index.ts: FAMILY_ROWS packages/prompt-variance/src/index.ts: PromptVarianceFamily packages/prompt-variance/src/index.ts: PromptVarianceRow + +# Agent-package public API (CL-9800 extraction wave): each @corbits/code-agent-* +# workspace package's public surface (agent/defineAgent + the config card) is +# consumed by the app registry/barrel once the fleet merges; on a per-extract +# branch they have no in-repo consumer yet. Public API by design — owned by the +# agent-template lane; remove with the code if the package is deleted. +agents/artist/src/index.ts: agent +agents/artist/src/index.ts: defineAgent +agents/artist/src/config.ts: satisfies +agents/artist/src/config.ts: boolean diff --git a/src/agent/directors/artist/package.test.ts b/src/agent/directors/artist/package.test.ts new file mode 100644 index 000000000..bb5c0a835 --- /dev/null +++ b/src/agent/directors/artist/package.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, test } from "bun:test"; + +import { + director, + tools, + config, + systemPrompt, +} from "@corbits/code-agent-artist"; +import { REVIEW_TOOLS } from "../tool-sets.js"; +import { DIRECTOR_REGISTRY } from "../registry.js"; +import type { DirectorPackage } from "../types.js"; + +describe("artistPackage", () => { + test("workspace package satisfies the director contract and feeds the registry", () => { + const asDirector: DirectorPackage = director; + expect(asDirector.id).toBe("artist"); + expect(DIRECTOR_REGISTRY.artist).toBe(director); + expect([...director.tools.allow]).toEqual([...REVIEW_TOOLS]); + expect(new Set(director.tools.allow).size).toBe( + director.tools.allow.length, + ); + }); + + test("spawn/tier/modelRole match config", () => { + expect(director.spawn).toEqual(config.spawn); + expect(director.tier).toBe(config.tier); + expect(director.modelRole).toBe(config.modelRole); + expect(director.spawn.maySpawn).toBe(false); + }); + + test("prompt authority is byte-stable", () => { + expect(director.systemPrompt).toBe(systemPrompt.build()); + expect(systemPrompt.build()).toBe(systemPrompt.build()); + }); + + test("tools surface matches the tool-sets constant and is leaf-scoped", () => { + expect([...tools]).toEqual([...REVIEW_TOOLS]); + expect(tools).not.toContain("spawn_agent"); + expect(tools).not.toContain("ask_operator"); + expect(tools).not.toContain("wait_agents"); + expect(tools).not.toContain("search_agents"); + }); + + test("tools allowlist has no duplicates", () => { + expect(new Set([...tools]).size).toBe(tools.length); + }); +}); diff --git a/src/agent/directors/registry.ts b/src/agent/directors/registry.ts index f2f338a8f..18231564c 100644 --- a/src/agent/directors/registry.ts +++ b/src/agent/directors/registry.ts @@ -1,5 +1,5 @@ import type { AgentProfile, CapabilityFilter } from "../profile-types.js"; -import { artistPackage } from "./artist/package.js"; +import { director as artistDirector } from "@corbits/code-agent-artist"; import { coderPackage } from "./coder/package.js"; import { designerPackage } from "./designer/package.js"; import { dispatchPackage } from "./dispatch/package.js"; @@ -43,7 +43,7 @@ export const DIRECTOR_REGISTRY: Readonly> = coder: coderPackage, reviewer: reviewerPackage, designer: designerPackage, - artist: artistPackage, + artist: artistDirector, warden: wardenPackage, shakespeare: shakespearePackage, prober: proberPackage, diff --git a/tsconfig.json b/tsconfig.json index b593b3775..19b14b736 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "composite": false, "baseUrl": ".", "paths": { + "@corbits/code-agent-artist": ["./agents/artist/src/index.ts"], "@corbits/provider-opencode-go": ["./packages/opencode-go/src/index.ts"], "@corbits/first-class-providers": [ "./packages/first-class-providers/src/index.ts" @@ -19,7 +20,8 @@ "packages/**/*.ts", "e2e/**/*.ts", "evals/**/*.ts", - "scripts/**/*.ts" + "scripts/**/*.ts", + "agents/**/*.ts" ], // Excluded: hermetic sandbox repos for the capability eval suite, not real // application code. They own their own package.json and are copied into