diff --git a/packages/coding-agent/extensions/swarm/.gitignore b/packages/omp-extension-swarm/.gitignore similarity index 100% rename from packages/coding-agent/extensions/swarm/.gitignore rename to packages/omp-extension-swarm/.gitignore diff --git a/packages/coding-agent/extensions/swarm/README.md b/packages/omp-extension-swarm/README.md similarity index 98% rename from packages/coding-agent/extensions/swarm/README.md rename to packages/omp-extension-swarm/README.md index 5f4af6c96..124132e50 100644 --- a/packages/coding-agent/extensions/swarm/README.md +++ b/packages/omp-extension-swarm/README.md @@ -9,7 +9,7 @@ Use it for anything: research pipelines, code generation, data processing, conte ## Setup ```bash -cd packages/coding-agent/extensions/swarm +cd packages/omp-extension-swarm bun install ``` @@ -19,10 +19,10 @@ bun install ```bash # Foreground — runs until complete, no timeout: -bun packages/coding-agent/extensions/swarm/run-pipeline.ts path/to/swarm.yaml +bun packages/omp-extension-swarm/run-pipeline.ts path/to/swarm.yaml # Background — survives terminal close: -nohup bun packages/coding-agent/extensions/swarm/run-pipeline.ts path/to/swarm.yaml \ +nohup bun packages/omp-extension-swarm/run-pipeline.ts path/to/swarm.yaml \ > pipeline.log 2>&1 & disown ``` @@ -34,7 +34,7 @@ Register the extension in your config (`~/.omp/config.json` or `.omp/config.json ```json { - "extensions": ["packages/coding-agent/extensions/swarm"] + "extensions": ["packages/omp-extension-swarm"] } ``` diff --git a/packages/coding-agent/extensions/swarm/bun.lock b/packages/omp-extension-swarm/bun.lock similarity index 100% rename from packages/coding-agent/extensions/swarm/bun.lock rename to packages/omp-extension-swarm/bun.lock diff --git a/packages/coding-agent/extensions/swarm/extension.ts b/packages/omp-extension-swarm/extension.ts similarity index 93% rename from packages/coding-agent/extensions/swarm/extension.ts rename to packages/omp-extension-swarm/extension.ts index d4b7189f5..22b2ecb3c 100644 --- a/packages/coding-agent/extensions/swarm/extension.ts +++ b/packages/omp-extension-swarm/extension.ts @@ -8,26 +8,25 @@ * Usage: Add this extension's directory to your extensions config, * then use /swarm in any oh-my-pi session. */ -import * as path from "node:path"; + import * as fs from "node:fs/promises"; +import * as path from "node:path"; import type { ExtensionAPI, ExtensionCommandContext } from "@oh-my-pi/pi-coding-agent"; -import { parseSwarmYaml, validateSwarmDefinition, type SwarmDefinition } from "./swarm/schema"; -import { buildDependencyGraph, detectCycles, buildExecutionWaves } from "./swarm/dag"; -import { StateTracker } from "./swarm/state"; +import { buildDependencyGraph, buildExecutionWaves, detectCycles } from "./swarm/dag"; import { PipelineController } from "./swarm/pipeline"; import { renderSwarmProgress } from "./swarm/render"; +import { parseSwarmYaml, type SwarmDefinition, validateSwarmDefinition } from "./swarm/schema"; +import { StateTracker } from "./swarm/state"; export default function swarmExtension(pi: ExtensionAPI): void { pi.setLabel("Swarm Orchestrator"); pi.registerCommand("swarm", { description: "Run a multi-agent swarm pipeline from YAML", - getArgumentCompletions: (prefix) => { + getArgumentCompletions: prefix => { const subcommands = ["run", "status", "help"]; - if (!prefix) return subcommands.map((s) => ({ label: s, value: s })); - return subcommands - .filter((s) => s.startsWith(prefix)) - .map((s) => ({ label: s, value: s })); + if (!prefix) return subcommands.map(s => ({ label: s, value: s })); + return subcommands.filter(s => s.startsWith(prefix)).map(s => ({ label: s, value: s })); }, handler: async (args: string, ctx: ExtensionCommandContext) => { const parts = args.trim().split(/\s+/); @@ -47,7 +46,6 @@ export default function swarmExtension(pi: ExtensionAPI): void { await handleStatus(parts[1], ctx); return; } - case "help": default: ctx.ui.notify( [ @@ -93,7 +91,7 @@ async function handleRun(yamlPath: string, ctx: ExtensionCommandContext, pi: Ext // 3. Validate const validationErrors = validateSwarmDefinition(def); if (validationErrors.length > 0) { - ctx.ui.notify(`Validation errors:\n${validationErrors.map((e) => ` - ${e}`).join("\n")}`, "error"); + ctx.ui.notify(`Validation errors:\n${validationErrors.map(e => ` - ${e}`).join("\n")}`, "error"); return; } @@ -249,9 +247,7 @@ function buildSummaryMessage( lines.push(""); for (const [name, agent] of Object.entries(stateTracker.state.agents)) { const duration = - agent.startedAt && agent.completedAt - ? formatDuration(agent.completedAt - agent.startedAt) - : "n/a"; + agent.startedAt && agent.completedAt ? formatDuration(agent.completedAt - agent.startedAt) : "n/a"; lines.push(`- **${name}**: ${agent.status} (${duration})${agent.error ? ` — ${agent.error}` : ""}`); } diff --git a/packages/coding-agent/extensions/swarm/package.json b/packages/omp-extension-swarm/package.json similarity index 100% rename from packages/coding-agent/extensions/swarm/package.json rename to packages/omp-extension-swarm/package.json diff --git a/packages/coding-agent/extensions/swarm/run-pipeline.ts b/packages/omp-extension-swarm/run-pipeline.ts similarity index 98% rename from packages/coding-agent/extensions/swarm/run-pipeline.ts rename to packages/omp-extension-swarm/run-pipeline.ts index 904160235..1b84025a0 100644 --- a/packages/coding-agent/extensions/swarm/run-pipeline.ts +++ b/packages/omp-extension-swarm/run-pipeline.ts @@ -3,16 +3,17 @@ * * Usage: bun run-pipeline.ts */ -import * as path from "node:path"; + import * as fs from "node:fs/promises"; -import { parseSwarmYaml, validateSwarmDefinition } from "./swarm/schema"; -import { buildDependencyGraph, detectCycles, buildExecutionWaves } from "./swarm/dag"; -import { StateTracker } from "./swarm/state"; -import { PipelineController } from "./swarm/pipeline"; -import { renderSwarmProgress } from "./swarm/render"; +import * as path from "node:path"; import { discoverAuthStorage } from "@oh-my-pi/pi-coding-agent"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; +import { buildDependencyGraph, buildExecutionWaves, detectCycles } from "./swarm/dag"; +import { PipelineController } from "./swarm/pipeline"; +import { renderSwarmProgress } from "./swarm/render"; +import { parseSwarmYaml, validateSwarmDefinition } from "./swarm/schema"; +import { StateTracker } from "./swarm/state"; const yamlPath = process.argv[2]; if (!yamlPath) { diff --git a/packages/coding-agent/extensions/swarm/swarm/dag.ts b/packages/omp-extension-swarm/swarm/dag.ts similarity index 100% rename from packages/coding-agent/extensions/swarm/swarm/dag.ts rename to packages/omp-extension-swarm/swarm/dag.ts diff --git a/packages/coding-agent/extensions/swarm/swarm/executor.ts b/packages/omp-extension-swarm/swarm/executor.ts similarity index 100% rename from packages/coding-agent/extensions/swarm/swarm/executor.ts rename to packages/omp-extension-swarm/swarm/executor.ts diff --git a/packages/coding-agent/extensions/swarm/swarm/pipeline.ts b/packages/omp-extension-swarm/swarm/pipeline.ts similarity index 100% rename from packages/coding-agent/extensions/swarm/swarm/pipeline.ts rename to packages/omp-extension-swarm/swarm/pipeline.ts diff --git a/packages/coding-agent/extensions/swarm/swarm/render.ts b/packages/omp-extension-swarm/swarm/render.ts similarity index 100% rename from packages/coding-agent/extensions/swarm/swarm/render.ts rename to packages/omp-extension-swarm/swarm/render.ts diff --git a/packages/coding-agent/extensions/swarm/swarm/schema.ts b/packages/omp-extension-swarm/swarm/schema.ts similarity index 93% rename from packages/coding-agent/extensions/swarm/swarm/schema.ts rename to packages/omp-extension-swarm/swarm/schema.ts index 83958b63a..5bfeb47e1 100644 --- a/packages/coding-agent/extensions/swarm/swarm/schema.ts +++ b/packages/omp-extension-swarm/swarm/schema.ts @@ -55,6 +55,7 @@ export interface SwarmDefinition { // ============================================================================ const VALID_MODES = new Set(["pipeline", "parallel", "sequential"]); +const VALID_SWARM_NAME = /^[a-zA-Z0-9._-]+$/; export function parseSwarmYaml(content: string): SwarmDefinition { const raw = YAML.parse(content) as { swarm?: RawSwarmConfig } | null; @@ -66,6 +67,9 @@ export function parseSwarmYaml(content: string): SwarmDefinition { if (!swarm.name || typeof swarm.name !== "string") { throw new Error("swarm.name is required and must be a string"); } + if (!VALID_SWARM_NAME.test(swarm.name)) { + throw new Error("swarm.name may only contain letters, numbers, dot, underscore, and dash"); + } if (!swarm.workspace || typeof swarm.workspace !== "string") { throw new Error("swarm.workspace is required and must be a string"); } @@ -141,6 +145,9 @@ export function validateSwarmDefinition(def: SwarmDefinition): string[] { if (def.targetCount < 1) { errors.push("target_count must be at least 1"); } + if (def.mode !== "pipeline" && def.targetCount !== 1) { + errors.push("target_count is only supported in pipeline mode"); + } return errors; } diff --git a/packages/coding-agent/extensions/swarm/swarm/state.ts b/packages/omp-extension-swarm/swarm/state.ts similarity index 100% rename from packages/coding-agent/extensions/swarm/swarm/state.ts rename to packages/omp-extension-swarm/swarm/state.ts diff --git a/packages/coding-agent/extensions/swarm/tsconfig.json b/packages/omp-extension-swarm/tsconfig.json similarity index 51% rename from packages/coding-agent/extensions/swarm/tsconfig.json rename to packages/omp-extension-swarm/tsconfig.json index 6dec4c6cd..5582ca890 100644 --- a/packages/coding-agent/extensions/swarm/tsconfig.json +++ b/packages/omp-extension-swarm/tsconfig.json @@ -8,9 +8,9 @@ "skipLibCheck": true, "types": ["bun-types"], "paths": { - "@oh-my-pi/pi-coding-agent": ["../../src/index.ts"], - "@oh-my-pi/pi-coding-agent/*": ["../../src/*.ts"] + "@oh-my-pi/pi-coding-agent": ["../coding-agent/src/index.ts"], + "@oh-my-pi/pi-coding-agent/*": ["../coding-agent/src/*.ts"] } }, - "include": ["extension.ts", "swarm/**/*.ts"] + "include": ["extension.ts", "run-pipeline.ts", "swarm/**/*.ts"] }