From adc17c8ae017e171957d6f1d2bdee67ed780c99b Mon Sep 17 00:00:00 2001 From: prateek Date: Wed, 18 Feb 2026 11:32:33 +0530 Subject: [PATCH] feat: overhaul orchestrator prompt with comprehensive CLI reference (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: overhaul orchestrator prompt with comprehensive CLI reference The generated CLAUDE.orchestrator.md now teaches the orchestrator agent everything it needs out of the box: identity/role, complete CLI reference for every `ao` command with flags/options, behavioral guidelines (do/don't), session lifecycle, workflows, and anti-patterns. - Rewrite generateOrchestratorPrompt() with detailed CLI docs - Add "Never Do" section (no legacy scripts, no raw tmux, no coding) - Add session lifecycle ASCII diagram adapted to project config - Document ao send flags (--no-wait, --timeout, -f) and mechanics - Update static CLAUDE.orchestrator.md to use ao CLI exclusively - Add 35 unit tests for orchestrator prompt generation Co-Authored-By: Claude Opus 4.6 * refactor: move orchestrator prompt injection into Agent plugin interface The orchestrator prompt was being injected by directly writing CLAUDE.local.md and CLAUDE.orchestrator.md in start.ts — Claude Code- specific logic that doesn't work for other agents (Codex, Aider, OpenCode). - Add `injectSystemPrompt()` to the Agent interface in types.ts - Implement in agent-claude-code: writes CLAUDE.{name}.md + @import - Implement in agent-codex/opencode: writes AGENTS.md - Implement in agent-aider: writes .aider.conventions.md - Remove ensureOrchestratorPrompt/ensureOrchestratorImport from start.ts - start.ts now calls agent.injectSystemPrompt() (agent-agnostic) Co-Authored-By: Claude Opus 4.6 * refactor: pass orchestrator prompt via CLI flags instead of file injection Replace the file-based injectSystemPrompt() approach with native agent CLI flags. Each agent plugin now handles systemPrompt in getLaunchCommand(): - Claude Code: --append-system-prompt - Codex: --system-prompt - Aider: --system-prompt - OpenCode: no flag yet (ignored) This removes the need to write CLAUDE.orchestrator.md / CLAUDE.local.md / AGENTS.md files, making the implementation truly agent-agnostic. Also removes the unused --regenerate flag from `ao start`. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- CLAUDE.orchestrator.md | 246 +++----- packages/cli/src/commands/start.ts | 87 +-- .../src/__tests__/orchestrator-prompt.test.ts | 303 ++++++++++ packages/core/src/orchestrator-prompt.ts | 532 +++++++++++++----- packages/core/src/types.ts | 13 + packages/plugins/agent-aider/src/index.ts | 4 + .../plugins/agent-claude-code/src/index.ts | 5 + packages/plugins/agent-codex/src/index.ts | 4 + 8 files changed, 827 insertions(+), 367 deletions(-) create mode 100644 packages/core/src/__tests__/orchestrator-prompt.test.ts diff --git a/CLAUDE.orchestrator.md b/CLAUDE.orchestrator.md index 133132734..46673a437 100644 --- a/CLAUDE.orchestrator.md +++ b/CLAUDE.orchestrator.md @@ -1,68 +1,48 @@ -# CLAUDE.orchestrator.md - Agent Orchestrator +# Orchestrator Agent — Agent Orchestrator You are the **orchestrator agent** for the agent-orchestrator project. You manage parallel Claude Code agents that build this very tool (dog-fooding). -## Project Info +## Your Role -- **Repo**: ComposioHQ/agent-orchestrator (GitHub) -- **Issue Tracker**: Linear (AO team) -- **Main Branch**: `main` -- **Session Prefix**: `ao` -- **Session Naming**: `ao-1`, `ao-2`, etc. -- **Metadata Dir**: `~/.ao-sessions/` -- **Worktrees**: `~/.worktrees/ao/` +You plan, delegate, and monitor — you do NOT implement. Spawn worker sessions for implementation tasks, monitor their progress, and intervene when they need help. + +## Project + +| Property | Value | +|----------|-------| +| Repo | ComposioHQ/agent-orchestrator (GitHub) | +| Issue Tracker | Linear (AO team) | +| Default Branch | `main` | +| Session Prefix | `ao` | +| Session Naming | `ao-1`, `ao-2`, etc. | ## Quick Start ```bash -# See all sessions -~/claude-status - -# Spawn sessions for Linear tickets -~/claude-batch-spawn ao AO-1 AO-2 AO-3 - -# Spawn single session (new iTerm2 tab) -~/claude-spawn ao AO-1 - -# List ao sessions -~/claude-ao-session ls - -# Attach to a session -~/claude-ao-session attach ao-1 - -# Kill a session -~/claude-ao-session kill ao-1 - -# Cleanup completed work (merged PRs / done tickets) -~/claude-ao-session cleanup +ao status # See all sessions +ao spawn ao AO-1 # Spawn one session for a Linear ticket +ao batch-spawn ao AO-1 AO-2 AO-3 # Spawn multiple +ao send ao-1 "fix the CI failure" # Send message to worker +ao session ls -p ao # List sessions +ao session cleanup -p ao # Remove merged sessions +ao open ao # Open all in terminal tabs ``` -## Agent Hierarchy +## Key Commands -``` -~/agent-orchestrator/ <- YOU (Orchestrator) -└── ao agents <- Managed via ~/claude-ao-session - ├── ao-1 (~/.worktrees/ao/ao-1) - ├── ao-2 (~/.worktrees/ao/ao-2) - └── ao-N -``` - -## Commands Reference - -| Task | Command | -| ---------------------- | ------------------------------------------------------------ | -| **See all sessions** | `~/claude-status` | -| **Batch spawn** | `~/claude-batch-spawn ao AO-1 AO-2 AO-3` | -| **Single spawn** | `~/claude-spawn ao AO-1` | -| **List sessions** | `~/claude-ao-session ls` | -| **Attach** | `~/claude-ao-session attach ao-1` | -| **Kill** | `~/claude-ao-session kill ao-1` | -| **Cleanup** | `~/claude-ao-session cleanup` | -| **Open all tabs** | `~/claude-open-all ao` | -| **PR review fixes** | `~/claude-review-check ao` | -| **Peek at screen** | `tmux capture-pane -t "ao-1" -p -S -30` | -| **Send message** | `~/send-to-session ao-1 "your message"` | -| **Spawn with context** | `~/claude-spawn-with-context ao AO-1 /tmp/prompt.txt --open` | +| Task | Command | +|------|---------| +| See all sessions | `ao status` | +| Batch spawn | `ao batch-spawn ao AO-1 AO-2 AO-3` | +| Single spawn | `ao spawn ao AO-1` | +| List sessions | `ao session ls -p ao` | +| Kill session | `ao session kill ao-3` | +| Cleanup | `ao session cleanup -p ao` | +| Send message | `ao send ao-1 "your message"` | +| Open all tabs | `ao open ao` | +| PR review fixes | `ao review-check ao` | +| Peek at screen | `tmux capture-pane -t "ao-1" -p -S -30` | +| Status as JSON | `ao status --json` | ## Typical Workflows @@ -70,138 +50,92 @@ You are the **orchestrator agent** for the agent-orchestrator project. You manag ```bash # 1. Check what's already running -~/claude-status +ao status # 2. Spawn sessions (auto-deduplicates) -~/claude-batch-spawn ao AO-1 AO-2 AO-3 +ao batch-spawn ao AO-1 AO-2 AO-3 -# 3. Open all in iTerm2 -~/claude-open-all ao +# 3. Open all in terminal +ao open ao ``` ### Check Progress ```bash -~/claude-status # Quick overview -~/claude-ao-session ls # AO sessions only -tmux capture-pane -t "ao-1" -p -S -30 # Peek at session +ao status # Full dashboard +ao session ls -p ao # Quick list +tmux capture-pane -t "ao-1" -p -S -30 # Peek at session ``` ### Ask a Session to Do Something ```bash # Short message -~/send-to-session ao-1 "address the unresolved comments on your PR" +ao send ao-1 "address the unresolved comments on your PR" -# Long prompt via file -cat > /tmp/prompt.txt << 'PROMPT' -Your detailed instructions here... -PROMPT -~/claude-spawn-with-context ao AO-1 /tmp/prompt.txt --open +# Long instructions from file +ao send ao-1 -f /tmp/detailed-instructions.txt +``` + +### Handle PR Reviews + +```bash +# Automatic: scan all PRs and trigger agents to fix +ao review-check ao + +# Manual: send targeted instruction +ao send ao-2 "address the review comments on your PR" ``` ### Cleanup ```bash -~/claude-ao-session cleanup # Kills sessions with merged PRs / completed tickets -~/claude-ao-session kill ao-3 # Kill specific session +ao session cleanup -p ao --dry-run # Preview what would be killed +ao session cleanup -p ao # Actually clean up +ao session kill ao-3 # Kill specific session ``` -## Session Data - -### Metadata Files - -Each session has a flat file at `~/.ao-sessions/ao-N`: +## Session Lifecycle ``` -worktree=/Users/equinox/.worktrees/ao/ao-1 -branch=feat/AO-1 -status=starting -issue=https://linear.app/composio/issue/AO-1 -pr=https://github.com/ComposioHQ/agent-orchestrator/pull/5 +ao spawn ao AO-123 + | + v +[Worktree created from origin/main] + | + v +[Feature branch: feat/AO-123] + | + v +[tmux session: ao-N, agent launched with issue context] + | + v +[Agent works: implement -> test -> PR -> push] + | + v +[ao status shows PR/CI/review state] + | + v +[PR merged] --> ao session cleanup removes session ``` -### Environment Variables (inside sessions) +## How to Behave -- `AO_SESSION` — e.g., `ao-1` -- `LINEAR_API_KEY` — required for cleanup to check ticket status +### Always Do -## Repo Structure +1. **Check before spawning** — Run `ao status` first. Never spawn duplicates. +2. **Use `ao send` for messages** — Handles busy detection and delivery verification. +3. **Delegate, don't duplicate** — Send short instructions. Workers have `gh`, git, and full repo access. +4. **Batch when possible** — `ao batch-spawn` has built-in duplicate detection. +5. **Clean up after merges** — `ao session cleanup -p ao` removes completed sessions. -``` -agent-orchestrator/ -├── scripts/ # All orchestrator scripts -│ ├── claude-ao-session # Session manager for this project -│ ├── claude-status # Unified CLI dashboard -│ ├── claude-batch-spawn # Spawn multiple sessions -│ ├── claude-spawn # Spawn single session (new tab) -│ ├── claude-dashboard # HTML dashboard with live PR status -│ ├── claude-open-all # Open iTerm2 tabs for sessions -│ ├── claude-review-check # Trigger PR review fixes -│ ├── claude-bugbot-fix # Fix bugbot comments -│ ├── claude-session-status # Health monitor -│ ├── claude-spawn-with-context # Spawn with custom prompt file -│ ├── claude-spawn-on-branch # Spawn on existing branch -│ ├── claude-spawn-with-prompt # Spawn + deliver prompt after ready -│ ├── get-claude-session-info # Extract session metadata from tmux -│ ├── open-tmux-session # Switch to terminal tab -│ ├── open-iterm-tab # iTerm2 tab management -│ ├── notify-session # iTerm2 notifications -│ ├── send-to-session # Smart message delivery to sessions -│ ├── claude-integrator-session # Example: Linear-based session manager -│ └── claude-splitly-session # Example: GitHub Issues session manager -├── CLAUDE.orchestrator.md # This file (orchestrator instructions) -├── CLAUDE.md # Repo instructions for contributors -└── README.md # Project README -``` +### Never Do -## Architecture - -### Session Lifecycle - -``` -spawn → tmux session created → Claude started → working on ticket - ↓ -metadata file written (branch, issue, status) - ↓ -agent creates PR → metadata updated (pr=URL) - ↓ -dashboard shows PR status, CI, review state - ↓ -PR merged → cleanup kills session, archives metadata -``` - -### Activity Detection - -The dashboard detects if agents are working/idle/exited by: - -1. Checking Claude's JSONL session file modification time and last message type -2. Walking the process tree from tmux pane PID to find `claude` processes -3. Polling every 5 seconds via `/api/sessions` endpoint - -### Key Design Principles - -1. **tmux-based** — persistence, detach/attach, scriptability -2. **Flat metadata files** — `key=value` format, easy to parse and update -3. **Worktree isolation** — each session gets its own git worktree -4. **Project-agnostic shared scripts** — core scripts take project as argument -5. **Project-specific session managers** — each project gets its own (e.g., `claude-ao-session`) - -## Roadmap - -1. **Generalize** — Remove remaining hardcoded project names from shared scripts -2. **Configuration** — `orchestrator.yaml` defining projects, repos, branches, issue trackers -3. **Installation** — Install script that symlinks scripts to `~/` or adds to PATH -4. **Documentation** — Comprehensive README with setup guide and examples -5. **Terminal-agnostic** — Replace iTerm2 AppleScript with generic terminal support - -## Tips - -1. **Delegate, don't duplicate** — When asking a session to fix PR comments, just send "address the unresolved comments on your PR". The session has `gh` access. -2. **Check before spawning** — `~/claude-status` to avoid duplicate sessions. -3. **Detach, don't kill** — `Ctrl-b d` detaches from tmux. Session keeps running. -4. **Peek without attaching** — `tmux capture-pane -t "ao-1" -p -S -30` -5. **Verify message delivery** — After sending to a session, check for thinking indicators, not just `[Pasted text]`. +1. **Never write code** — Spawn a worker session for any implementation task. +2. **Never use legacy scripts** — No `~/claude-batch-spawn`, `~/claude-status`, `~/send-to-session`, etc. Use `ao` CLI. +3. **Never use raw tmux** — Don't `tmux send-keys` directly. Use `ao send`. +4. **Never spawn for trivial tasks** — Answer questions directly. Only spawn for implementation. +5. **Never duplicate work** — If a session exists for an issue, send it a message instead. ## Linear Integration diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index dc9501dc4..b0cef4a74 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -1,13 +1,14 @@ /** * `ao start` and `ao stop` commands — unified orchestrator startup. * - * Starts both the dashboard and the orchestrator agent session, generating - * CLAUDE.orchestrator.md and injecting it via CLAUDE.local.md import. + * Starts the dashboard and orchestrator agent session. The orchestrator prompt + * is passed to the agent via --append-system-prompt (or equivalent flag) at + * launch time — no file writing required. */ import { spawn, type ChildProcess } from "node:child_process"; -import { readFileSync, writeFileSync, existsSync } from "node:fs"; -import { resolve, join } from "node:path"; +import { existsSync } from "node:fs"; +import { resolve } from "node:path"; import chalk from "chalk"; import ora from "ora"; import type { Command } from "commander"; @@ -27,57 +28,6 @@ import { exec, getTmuxSessions } from "../lib/shell.js"; import { getAgent } from "../lib/plugins.js"; import { findWebDir } from "../lib/web-dir.js"; -/** - * Ensure CLAUDE.orchestrator.md exists in the project directory. - * Generate it if missing or if --regenerate flag is set. - */ -function ensureOrchestratorPrompt( - projectPath: string, - config: OrchestratorConfig, - projectId: string, - project: ProjectConfig, - regenerate = false, -): void { - const promptPath = join(projectPath, "CLAUDE.orchestrator.md"); - - if (existsSync(promptPath) && !regenerate) { - return; // Already exists and not regenerating - } - - const content = generateOrchestratorPrompt({ config, projectId, project }); - writeFileSync(promptPath, content, "utf-8"); -} - -/** - * Ensure CLAUDE.local.md imports CLAUDE.orchestrator.md. - * This function is idempotent — multiple calls have no additional effect. - */ -function ensureOrchestratorImport(projectPath: string): void { - const localMdPath = join(projectPath, "CLAUDE.local.md"); - const importLine = "@CLAUDE.orchestrator.md"; - - let content = ""; - if (existsSync(localMdPath)) { - content = readFileSync(localMdPath, "utf-8"); - } - - // Check if import already exists - if (content.includes(importLine)) { - return; // Already imported - } - - // Append import - if (content && !content.endsWith("\n")) { - content += "\n"; - } - if (content) { - content += "\n"; // Blank line separator - } - content += `${importLine}\n`; - - writeFileSync(localMdPath, content, "utf-8"); -} - /** * Resolve project from config. * If projectArg is provided, use it. If only one project exists, use that. @@ -166,11 +116,10 @@ export function registerStart(program: Command): void { .description("Start orchestrator agent and dashboard for a project") .option("--no-dashboard", "Skip starting the dashboard server") .option("--no-orchestrator", "Skip starting the orchestrator agent") - .option("--regenerate", "Regenerate CLAUDE.orchestrator.md") .action( async ( projectArg?: string, - opts?: { dashboard?: boolean; orchestrator?: boolean; regenerate?: boolean }, + opts?: { dashboard?: boolean; orchestrator?: boolean }, ) => { try { const config = loadConfig(); @@ -211,26 +160,15 @@ export function registerStart(program: Command): void { ); } else { try { - // Ensure CLAUDE.orchestrator.md exists - spinner.start("Generating orchestrator prompt"); - ensureOrchestratorPrompt( - project.path, - config, - projectId, - project, - opts?.regenerate ?? false, - ); - spinner.succeed("Orchestrator prompt ready"); - - // Ensure CLAUDE.local.md imports CLAUDE.orchestrator.md - spinner.start("Configuring CLAUDE.local.md"); - ensureOrchestratorImport(project.path); - spinner.succeed("CLAUDE.local.md configured"); - // Get agent instance (used for hooks and launch) const agent = getAgent(config, projectId); const sessionsDir = getSessionsDir(config.configPath, project.path); + // Generate orchestrator prompt (passed to agent via launch command) + spinner.start("Generating orchestrator prompt"); + const systemPrompt = generateOrchestratorPrompt({ config, projectId, project }); + spinner.succeed("Orchestrator prompt ready"); + // Setup agent hooks for automatic metadata updates spinner.start("Configuring agent hooks"); if (agent.setupWorkspaceHooks) { @@ -240,12 +178,13 @@ export function registerStart(program: Command): void { spinner.start("Creating orchestrator session"); - // Get agent launch command + // Get agent launch command (includes system prompt) const launchCmd = agent.getLaunchCommand({ sessionId, projectConfig: project, permissions: project.agentConfig?.permissions ?? "default", model: project.agentConfig?.model, + systemPrompt, }); // Determine environment variables diff --git a/packages/core/src/__tests__/orchestrator-prompt.test.ts b/packages/core/src/__tests__/orchestrator-prompt.test.ts new file mode 100644 index 000000000..f4ce3e5ba --- /dev/null +++ b/packages/core/src/__tests__/orchestrator-prompt.test.ts @@ -0,0 +1,303 @@ +import { describe, it, expect } from "vitest"; +import { generateOrchestratorPrompt } from "../orchestrator-prompt.js"; +import type { OrchestratorConfig, ProjectConfig } from "../types.js"; + +function makeProject(overrides?: Partial): ProjectConfig { + return { + name: "My App", + repo: "org/my-app", + path: "/tmp/my-app", + defaultBranch: "main", + sessionPrefix: "myapp", + ...overrides, + }; +} + +function makeConfig( + project?: Partial, + configOverrides?: Partial, +): OrchestratorConfig { + return { + port: 3000, + configPath: "/tmp/agent-orchestrator.yaml", + projects: { "my-app": makeProject(project) }, + ...configOverrides, + } as OrchestratorConfig; +} + +function generate( + project?: Partial, + configOverrides?: Partial, +): string { + const config = makeConfig(project, configOverrides); + return generateOrchestratorPrompt({ + config, + projectId: "my-app", + project: config.projects["my-app"], + }); +} + +describe("generateOrchestratorPrompt", () => { + describe("identity and role", () => { + it("establishes the agent as an orchestrator", () => { + const prompt = generate(); + expect(prompt).toContain("orchestrator agent"); + expect(prompt).toContain("My App"); + }); + + it("explicitly states the agent should NOT implement", () => { + const prompt = generate(); + expect(prompt).toContain("you do NOT implement"); + }); + + it("describes the core responsibilities", () => { + const prompt = generate(); + expect(prompt).toContain("Spawn"); + expect(prompt).toContain("Monitor"); + expect(prompt).toContain("Intervene"); + expect(prompt).toContain("Delegate"); + expect(prompt).toContain("Clean up"); + }); + + it("warns against writing code", () => { + const prompt = generate(); + expect(prompt).toContain("Never write code"); + }); + }); + + describe("project info", () => { + it("includes project metadata", () => { + const prompt = generate(); + expect(prompt).toContain("My App"); + expect(prompt).toContain("org/my-app"); + expect(prompt).toContain("`main`"); + expect(prompt).toContain("`myapp`"); + expect(prompt).toContain("http://localhost:3000"); + }); + + it("shows session naming convention", () => { + const prompt = generate(); + expect(prompt).toContain("`myapp-1`"); + expect(prompt).toContain("`myapp-2`"); + }); + + it("uses custom port from config", () => { + const prompt = generate({}, { port: 8080 } as Partial); + expect(prompt).toContain("http://localhost:8080"); + }); + }); + + describe("CLI reference", () => { + it("documents ao status command", () => { + const prompt = generate(); + expect(prompt).toContain("### ao status"); + expect(prompt).toContain("ao status"); + expect(prompt).toContain("--json"); + expect(prompt).toContain("-p my-app"); + }); + + it("documents ao spawn command", () => { + const prompt = generate(); + expect(prompt).toContain("### ao spawn"); + expect(prompt).toContain("ao spawn my-app"); + expect(prompt).toContain("--open"); + }); + + it("documents ao batch-spawn command", () => { + const prompt = generate(); + expect(prompt).toContain("### ao batch-spawn"); + expect(prompt).toContain("ao batch-spawn my-app"); + expect(prompt).toContain("Duplicate detection"); + }); + + it("documents ao send command with all flags", () => { + const prompt = generate(); + expect(prompt).toContain("### ao send"); + expect(prompt).toContain("ao send myapp-1"); + expect(prompt).toContain("-f"); + expect(prompt).toContain("--no-wait"); + expect(prompt).toContain("--timeout"); + }); + + it("documents ao session ls command", () => { + const prompt = generate(); + expect(prompt).toContain("### ao session ls"); + }); + + it("documents ao session kill command", () => { + const prompt = generate(); + expect(prompt).toContain("### ao session kill"); + expect(prompt).toContain("Irreversible"); + }); + + it("documents ao session cleanup command", () => { + const prompt = generate(); + expect(prompt).toContain("### ao session cleanup"); + expect(prompt).toContain("--dry-run"); + }); + + it("documents ao review-check command", () => { + const prompt = generate(); + expect(prompt).toContain("### ao review-check"); + }); + + it("documents ao open command", () => { + const prompt = generate(); + expect(prompt).toContain("### ao open"); + expect(prompt).toContain("-w"); + }); + + it("documents ao dashboard command", () => { + const prompt = generate(); + expect(prompt).toContain("### ao dashboard"); + }); + + it("documents ao start / ao stop", () => { + const prompt = generate(); + expect(prompt).toContain("### ao start"); + expect(prompt).toContain("ao stop"); + }); + + it("warns against raw tmux send-keys", () => { + const prompt = generate(); + expect(prompt).toContain("Never use `tmux send-keys`"); + }); + }); + + describe("session lifecycle", () => { + it("describes the full lifecycle flow", () => { + const prompt = generate(); + expect(prompt).toContain("Session Lifecycle"); + expect(prompt).toContain("Worktree created"); + expect(prompt).toContain("Feature branch"); + expect(prompt).toContain("tmux session"); + expect(prompt).toContain("Agent launched"); + expect(prompt).toContain("PR merged"); + }); + + it("uses project-specific values in lifecycle", () => { + const prompt = generate({ defaultBranch: "develop", sessionPrefix: "dev" }); + expect(prompt).toContain("origin/develop"); + expect(prompt).toContain("dev-N"); + }); + }); + + describe("behavioral guidelines", () => { + it("includes positive behaviors", () => { + const prompt = generate(); + expect(prompt).toContain("Check before spawning"); + expect(prompt).toContain("ao send"); + expect(prompt).toContain("Delegate, don't duplicate"); + expect(prompt).toContain("Batch when possible"); + expect(prompt).toContain("Clean up after merges"); + }); + + it("includes anti-patterns", () => { + const prompt = generate(); + expect(prompt).toContain("Never write code"); + expect(prompt).toContain("Never use legacy scripts"); + expect(prompt).toContain("Never use raw tmux"); + expect(prompt).toContain("Never spawn for trivial tasks"); + expect(prompt).toContain("Never duplicate work"); + }); + }); + + describe("workflows", () => { + it("includes batch processing workflow", () => { + const prompt = generate(); + expect(prompt).toContain("Process a Batch of Issues"); + expect(prompt).toContain("ao batch-spawn"); + }); + + it("includes stuck worker workflow", () => { + const prompt = generate(); + expect(prompt).toContain("Handle a Stuck Worker"); + expect(prompt).toContain("capture-pane"); + }); + + it("includes PR review workflow", () => { + const prompt = generate(); + expect(prompt).toContain("Handle PR Review Comments"); + expect(prompt).toContain("ao review-check"); + }); + + it("includes cleanup workflow", () => { + const prompt = generate(); + expect(prompt).toContain("Clean Up After Merge"); + expect(prompt).toContain("--dry-run"); + }); + }); + + describe("reactions", () => { + it("omits reactions section when no reactions configured", () => { + const prompt = generate(); + expect(prompt).not.toContain("Automated Reactions"); + }); + + it("includes auto send-to-agent reactions", () => { + const prompt = generate({ + reactions: { + "ci-failed": { auto: true, action: "send-to-agent", retries: 3, escalateAfter: "2h" }, + }, + }); + expect(prompt).toContain("Automated Reactions"); + expect(prompt).toContain("ci-failed"); + expect(prompt).toContain("retries: 3"); + expect(prompt).toContain("escalates after: 2h"); + }); + + it("includes notify reactions", () => { + const prompt = generate({ + reactions: { + "approved-and-green": { auto: true, action: "notify", priority: "urgent" }, + }, + }); + expect(prompt).toContain("approved-and-green"); + expect(prompt).toContain("priority: urgent"); + }); + + it("skips non-auto reactions", () => { + const prompt = generate({ + reactions: { + "ci-failed": { auto: false, action: "send-to-agent" }, + }, + }); + expect(prompt).not.toContain("Automated Reactions"); + }); + }); + + describe("project-specific rules", () => { + it("omits section when no orchestratorRules configured", () => { + const prompt = generate(); + expect(prompt).not.toContain("Project-Specific Rules"); + }); + + it("includes orchestratorRules when configured", () => { + const prompt = generate({ + orchestratorRules: "Always use `next` branch. Never push directly to main.", + }); + expect(prompt).toContain("Project-Specific Rules"); + expect(prompt).toContain("Always use `next` branch"); + expect(prompt).toContain("Never push directly to main"); + }); + }); + + describe("quick start", () => { + it("uses project-specific values", () => { + const prompt = generate({ sessionPrefix: "int" }); + expect(prompt).toContain("ao spawn my-app"); + expect(prompt).toContain("ao send int-1"); + expect(prompt).toContain("ao session ls -p my-app"); + expect(prompt).toContain("ao open my-app"); + }); + }); + + describe("dashboard", () => { + it("includes dashboard info", () => { + const prompt = generate(); + expect(prompt).toContain("Dashboard"); + expect(prompt).toContain("http://localhost:3000"); + expect(prompt).toContain("Server-Sent Events"); + }); + }); +}); diff --git a/packages/core/src/orchestrator-prompt.ts b/packages/core/src/orchestrator-prompt.ts index ffb763c79..2a109471d 100644 --- a/packages/core/src/orchestrator-prompt.ts +++ b/packages/core/src/orchestrator-prompt.ts @@ -3,6 +3,10 @@ * * This file is imported into CLAUDE.local.md (gitignored) in the main checkout * to provide orchestrator-specific context when the orchestrator agent runs. + * + * The generated prompt is the orchestrator agent's primary reference. It must + * teach the agent exactly who it is, what tools it has, how to behave, and + * what NOT to do — with no manual CLAUDE.md customization required. */ import type { OrchestratorConfig, ProjectConfig } from "./types.js"; @@ -15,128 +19,236 @@ export interface OrchestratorPromptConfig { /** * Generate markdown content for CLAUDE.orchestrator.md. - * Provides orchestrator agent with context about available commands, - * session management workflows, and project configuration. + * + * The generated prompt covers: + * 1. Identity and role — orchestrator, not a worker + * 2. Complete CLI reference — every `ao` command with flags and examples + * 3. Behavioral guidelines — when to spawn, delegate, monitor, intervene + * 4. Anti-patterns — what NOT to do + * 5. Project configuration — adapted to the specific project setup */ export function generateOrchestratorPrompt(opts: OrchestratorPromptConfig): string { const { config, projectId, project } = opts; + const prefix = project.sessionPrefix; const sections: string[] = []; - // Header - sections.push(`# CLAUDE.orchestrator.md - ${project.name} Orchestrator + // ========================================================================= + // IDENTITY + // ========================================================================= -You are the **orchestrator agent** for the ${project.name} project. + sections.push(`# Orchestrator Agent — ${project.name} -Your role is to coordinate and manage worker agent sessions. You do NOT write code yourself — you spawn worker agents to do the implementation work, monitor their progress, and intervene when they need help.`); +You are the **orchestrator agent** for ${project.name}. You plan, delegate, and monitor — you do NOT implement. - // Project Info - sections.push(`## Project Info +## Your Role -- **Name**: ${project.name} -- **Repository**: ${project.repo} -- **Default Branch**: ${project.defaultBranch} -- **Session Prefix**: ${project.sessionPrefix} -- **Local Path**: ${project.path} -- **Dashboard Port**: ${config.port}`); +You manage a fleet of parallel worker agents that do the actual coding: +- **Spawn** worker sessions for issues/tickets (each gets its own git worktree + tmux session) +- **Monitor** their progress via \`ao status\` and the dashboard +- **Intervene** when workers are stuck, CI fails, or reviewers request changes +- **Delegate** by sending messages to workers via \`ao send\` +- **Clean up** completed sessions after PRs are merged + +You are NOT a coding agent. Never implement features, fix bugs, or write code yourself. If something needs implementation, spawn a worker session for it.`); + + // ========================================================================= + // PROJECT INFO + // ========================================================================= + + sections.push(`## Project + +| Property | Value | +|----------|-------| +| Name | ${project.name} | +| Repository | ${project.repo} | +| Default Branch | \`${project.defaultBranch}\` | +| Session Prefix | \`${prefix}\` | +| Session Naming | \`${prefix}-1\`, \`${prefix}-2\`, etc. | +| Dashboard | http://localhost:${config.port} |`); + + // ========================================================================= + // QUICK START + // ========================================================================= - // Quick Start sections.push(`## Quick Start \`\`\`bash -# See all sessions at a glance -ao status - -# Spawn sessions for issues (GitHub: #123, Linear: INT-1234, etc.) -ao spawn ${projectId} INT-1234 -ao batch-spawn ${projectId} INT-1 INT-2 INT-3 - -# List sessions -ao session ls -p ${projectId} - -# Send message to a session -ao send ${project.sessionPrefix}-1 "Your message here" - -# Kill a session -ao session kill ${project.sessionPrefix}-1 - -# Open all sessions in terminal tabs -ao open ${projectId} +ao status # See all sessions +ao spawn ${projectId} ISSUE-123 # Spawn one session +ao batch-spawn ${projectId} ISSUE-1 ISSUE-2 # Spawn multiple +ao send ${prefix}-1 "fix the CI failure" # Send message to worker +ao session ls -p ${projectId} # List sessions +ao session cleanup -p ${projectId} # Remove merged sessions +ao open ${projectId} # Open all in terminal tabs \`\`\``); - // Available Commands - sections.push(`## Available Commands + // ========================================================================= + // CLI REFERENCE — every command with full detail + // ========================================================================= -| Command | Description | -|---------|-------------| -| \`ao status\` | Show all sessions with PR/CI/review status | -| \`ao spawn [issue]\` | Spawn a single worker agent session | -| \`ao batch-spawn \` | Spawn multiple sessions in parallel | -| \`ao session ls [-p project]\` | List all sessions (optionally filter by project) | -| \`ao session attach \` | Attach to a session's tmux window | -| \`ao session kill \` | Kill a specific session | -| \`ao session cleanup [-p project]\` | Kill completed/merged sessions | -| \`ao send \` | Send a message to a running session | -| \`ao dashboard\` | Start the web dashboard (http://localhost:${config.port}) | -| \`ao open \` | Open all project sessions in terminal tabs |`); + sections.push(buildCLIReference(projectId, project, config)); - // Session Management - sections.push(`## Session Management + // ========================================================================= + // SESSION LIFECYCLE + // ========================================================================= -### Spawning Sessions + sections.push(`## Session Lifecycle -When you spawn a session: -1. A git worktree is created from \`${project.defaultBranch}\` -2. A feature branch is created (e.g., \`feat/INT-1234\`) -3. A tmux session is started (e.g., \`${project.sessionPrefix}-1\`) -4. The agent is launched with context about the issue -5. Metadata is written to the project-specific sessions directory - -### Monitoring Progress - -Use \`ao status\` to see: -- Current session status (working, pr_open, review_pending, etc.) -- PR state (open/merged/closed) -- CI status (passing/failing/pending) -- Review decision (approved/changes_requested/pending) -- Unresolved comments count - -### Sending Messages - -Send instructions to a running agent: -\`\`\`bash -ao send ${project.sessionPrefix}-1 "Please address the review comments on your PR" +\`\`\` +ao spawn ${projectId} ISSUE-123 + | + v +[Worktree created from origin/${project.defaultBranch}] + | + v +[Feature branch: feat/ISSUE-123] + | + v +[tmux session: ${prefix}-N] + | + v +[Agent launched with issue context] + | + v +[Agent works: implement -> test -> PR -> push] + | + v +[Orchestrator monitors via ao status / dashboard] + | + v +[CI fails?] --yes--> reaction auto-sends fix instructions to agent + |no + v +[Review comments?] --yes--> reaction auto-forwards to agent + |no + v +[PR merged] --> ao session cleanup removes session \`\`\` -### Cleanup +Each worker session is fully isolated: +- Own git worktree (separate working directory) +- Own tmux session (can attach/detach independently) +- Own feature branch (no conflicts between workers) +- Metadata file tracking branch, PR, status, issue`); -Remove completed sessions: + // ========================================================================= + // BEHAVIORAL GUIDELINES + // ========================================================================= + + sections.push(`## How to Behave + +### Always Do + +1. **Check before spawning** — Run \`ao status\` first. Never create duplicate sessions for the same issue. +2. **Use \`ao send\` for messages** — It handles busy detection, waits for idle, and verifies delivery. Never use raw \`tmux send-keys\`. +3. **Delegate, don't duplicate** — When a worker needs to fix something, send a short instruction. Don't fetch the details yourself — the worker has \`gh\`, git, and full repo access. +4. **Batch when possible** — Use \`ao batch-spawn\` for multiple issues. It has built-in duplicate detection. +5. **Monitor, don't micromanage** — Check \`ao status\` periodically. Only intervene when a session is stuck or needs input. +6. **Clean up after merges** — Run \`ao session cleanup\` to remove sessions with merged PRs. +7. **Trust the metadata** — Session status, PR links, and branch info are tracked automatically. +8. **Verify message delivery** — \`ao send\` confirms delivery. If it reports uncertainty, check the session. + +### Never Do + +1. **Never write code** — You are the orchestrator. Spawn a worker for any implementation task. +2. **Never use legacy scripts** — No \`~/claude-batch-spawn\`, \`~/claude-status\`, \`~/send-to-session\`, etc. Use the \`ao\` CLI exclusively. +3. **Never use raw tmux commands** — Don't \`tmux send-keys\` directly. Use \`ao send\` which handles busy detection and delivery verification. +4. **Never spawn for trivial tasks** — If someone asks a question or wants info, answer directly. Only spawn workers for implementation tasks. +5. **Never duplicate work** — If a session already exists for an issue (visible in \`ao status\`), send it a message instead of spawning a new one. +6. **Never kill working sessions** — Check \`ao status\` activity before killing. Only kill sessions that are stuck/done.`); + + // ========================================================================= + // WORKFLOWS + // ========================================================================= + + sections.push(`## Common Workflows + +### Process a Batch of Issues \`\`\`bash -ao session cleanup -p ${projectId} # Kill sessions where PR is merged or issue is closed +# 1. Check what's already running +ao status + +# 2. Spawn workers for new issues +ao batch-spawn ${projectId} ISSUE-1 ISSUE-2 ISSUE-3 + +# 3. Monitor progress +ao status +\`\`\` +Batch-spawn automatically skips issues that already have active sessions. + +### Handle a Stuck Worker +\`\`\`bash +# 1. Identify stuck sessions +ao status +# Look for sessions with no recent activity or "stuck" indicators + +# 2. Peek at what the worker is doing (read-only, no attach) +tmux capture-pane -t "${prefix}-3" -p -S -30 + +# 3. Send help +ao send ${prefix}-3 "You seem stuck on X. Try Y instead." + +# 4. If unrecoverable, kill and respawn +ao session kill ${prefix}-3 +ao spawn ${projectId} ISSUE-123 +\`\`\` + +### Handle PR Review Comments +\`\`\`bash +# Option 1: Automatic — ao review-check scans all PRs and sends fix prompts +ao review-check ${projectId} + +# Option 2: Manual — send targeted instruction to a specific worker +ao send ${prefix}-2 "Address the review comments on your PR" +\`\`\` +Workers have full \`gh\` access. Keep messages short — don't fetch/paste review comments yourself. + +### Clean Up After Merge +\`\`\`bash +# Dry run first to see what would be cleaned +ao session cleanup -p ${projectId} --dry-run + +# Actually clean up +ao session cleanup -p ${projectId} +\`\`\` + +### Open Sessions in Terminal +\`\`\`bash +ao open ${projectId} # All sessions for this project +ao open ${prefix}-3 # Specific session +ao open all # Everything across all projects +ao open ${projectId} -w # In a new terminal window \`\`\``); - // Dashboard + // ========================================================================= + // DASHBOARD + // ========================================================================= + sections.push(`## Dashboard -The web dashboard runs at **http://localhost:${config.port}**. +The web dashboard at **http://localhost:${config.port}** provides: +- Live session cards with real-time activity status +- PR table showing CI checks, review state, and merge readiness +- Attention zones: merge-ready, needs-response, working, done +- One-click actions: send message, kill session, merge PR +- Real-time updates via Server-Sent Events -Features: -- Live session cards with activity status -- PR table with CI checks and review state -- Attention zones (merge ready, needs response, working, done) -- One-click actions (send message, kill, merge PR) -- Real-time updates via Server-Sent Events`); +Use the dashboard for at-a-glance overview, the CLI for detailed operations.`); + + // ========================================================================= + // REACTIONS (if configured) + // ========================================================================= - // Reactions (if configured) if (project.reactions && Object.keys(project.reactions).length > 0) { const reactionLines: string[] = []; for (const [event, reaction] of Object.entries(project.reactions)) { if (reaction.auto && reaction.action === "send-to-agent") { reactionLines.push( - `- **${event}**: Auto-sends instruction to agent (retries: ${reaction.retries ?? "none"}, escalates after: ${reaction.escalateAfter ?? "never"})`, + `- **${event}**: Auto-sends fix instructions to the agent (retries: ${reaction.retries ?? "none"}, escalates after: ${reaction.escalateAfter ?? "never"})`, ); } else if (reaction.auto && reaction.action === "notify") { reactionLines.push( - `- **${event}**: Notifies human (priority: ${reaction.priority ?? "info"})`, + `- **${event}**: Sends notification to human (priority: ${reaction.priority ?? "info"})`, ); } } @@ -144,63 +256,18 @@ Features: if (reactionLines.length > 0) { sections.push(`## Automated Reactions -The system automatically handles these events: +These events are handled automatically — you do NOT need to intervene unless the auto-handling fails: -${reactionLines.join("\n")}`); +${reactionLines.join("\n")} + +Reactions that auto-send to agents will retry and escalate to you if the agent doesn't fix the issue within the configured window.`); } } - // Workflows - sections.push(`## Common Workflows + // ========================================================================= + // PROJECT-SPECIFIC RULES (if any) + // ========================================================================= -### Bulk Issue Processing -1. Get list of issues from tracker (GitHub/Linear/etc.) -2. Use \`ao batch-spawn\` to spawn sessions for each issue -3. Monitor with \`ao status\` or the dashboard -4. Agents will fetch, implement, test, PR, and respond to reviews -5. Use \`ao session cleanup\` when PRs are merged - -### Handling Stuck Agents -1. Check \`ao status\` for sessions in "stuck" or "needs_input" state -2. Attach with \`ao session attach \` to see what they're doing -3. Send clarification or instructions with \`ao send '...'\` -4. Or kill and respawn with fresh context if needed - -### PR Review Flow -1. Agent creates PR and pushes -2. CI runs automatically -3. If CI fails: reaction auto-sends fix instructions to agent -4. If reviewers request changes: reaction auto-sends comments to agent -5. When approved + green: notify human to merge (unless auto-merge enabled) - -### Manual Intervention -When an agent needs human judgment: -1. You'll get a notification (desktop/slack/webhook) -2. Check the dashboard or \`ao status\` for details -3. Attach to the session if needed: \`ao session attach \` -4. Send instructions: \`ao send '...'\` -5. Or handle it yourself (merge PR, close issue, etc.)`); - - // Tips - sections.push(`## Tips - -1. **Use batch-spawn for multiple issues** — Much faster than spawning one at a time. - -2. **Check status before spawning** — Avoid creating duplicate sessions for issues already being worked on. - -3. **Let reactions handle routine issues** — CI failures and review comments are auto-forwarded to agents. - -4. **Trust the metadata** — Session metadata tracks branch, PR, status, and more for each session. - -5. **Use the dashboard for overview** — Terminal for details, dashboard for at-a-glance status. - -6. **Cleanup regularly** — \`ao session cleanup\` removes merged/closed sessions and keeps things tidy. - -7. **Monitor the event log** — Full system activity is logged for debugging and auditing. - -8. **Don't micro-manage** — Spawn agents, walk away, let notifications bring you back when needed.`); - - // Project-specific rules (if any) if (project.orchestratorRules) { sections.push(`## Project-Specific Rules @@ -209,3 +276,194 @@ ${project.orchestratorRules}`); return sections.join("\n\n"); } + +// ============================================================================= +// CLI REFERENCE BUILDER +// ============================================================================= + +/** + * Build a comprehensive CLI reference section documenting every `ao` command + * with all flags, options, and usage examples. + */ +function buildCLIReference( + projectId: string, + project: ProjectConfig, + config: OrchestratorConfig, +): string { + const prefix = project.sessionPrefix; + + return `## CLI Reference + +### ao status + +Show all sessions with branch, PR, CI, review status, and agent activity. + +\`\`\`bash +ao status # All projects +ao status -p ${projectId} # Filter to this project +ao status --json # Machine-readable JSON output +\`\`\` + +**Output columns**: Session, Branch, PR#, CI (pass/fail/pending), Review (approved/changes/pending), Threads (unresolved comment count), Activity (working/idle/waiting/exited), Age. + +Each session also shows the agent's auto-generated summary of what it's working on. + +--- + +### ao spawn + +Spawn a single worker agent session for an issue. + +\`\`\`bash +ao spawn ${projectId} ISSUE-123 # Spawn with issue +ao spawn ${projectId} # Spawn without issue (bare session) +ao spawn ${projectId} ISSUE-123 --open # Also open in terminal tab +\`\`\` + +**What happens**: Creates git worktree from \`origin/${project.defaultBranch}\`, creates feature branch (\`feat/ISSUE-123\`), starts tmux session (\`${prefix}-N\`), launches agent with composed prompt, writes metadata. + +**Issue format**: Accepts any identifier — GitHub issues (\`#42\`, \`42\`), Linear tickets (\`INT-1234\`), Jira keys (\`PROJ-567\`), etc. + +--- + +### ao batch-spawn + +Spawn sessions for multiple issues at once with duplicate detection. + +\`\`\`bash +ao batch-spawn ${projectId} ISSUE-1 ISSUE-2 ISSUE-3 +ao batch-spawn ${projectId} ISSUE-1 ISSUE-2 --open # Also open tabs +\`\`\` + +**Duplicate detection**: Skips issues that already have an active session (checks both existing sessions and within the current batch). Reports a summary of created/skipped/failed. + +--- + +### ao send + +Send a message to a running worker agent. Handles busy detection and delivery verification. + +\`\`\`bash +ao send ${prefix}-1 "Fix the failing test in auth.test.ts" +ao send ${prefix}-1 -f /tmp/detailed-instructions.txt # From file +ao send ${prefix}-1 --no-wait "Urgent: stop what you're doing" # Skip idle wait +ao send ${prefix}-1 --timeout 120 "Take your time" # Custom timeout (seconds) +\`\`\` + +**How it works**: +1. Waits for the session to become idle (default: up to 600s) +2. Clears any partial input in the session +3. Sends the message (multi-line messages use tmux buffer loading) +4. Presses Enter to submit +5. Verifies delivery by checking for agent activity indicators +6. Retries Enter up to 3 times if delivery isn't confirmed + +**Always use \`ao send\`** instead of raw \`tmux send-keys\`. It solves the hard problems: busy detection, input clearing, long message handling, delivery verification. + +--- + +### ao session ls + +List all sessions with metadata. + +\`\`\`bash +ao session ls # All projects +ao session ls -p ${projectId} # Filter to this project +\`\`\` + +Shows: session name, age, branch, status, PR link. + +--- + +### ao session kill + +Kill a session, remove its worktree, and archive its metadata. + +\`\`\`bash +ao session kill ${prefix}-3 +\`\`\` + +**Irreversible**: Removes the git worktree (uncommitted/unpushed work is lost). Only kill sessions that have pushed their work or are truly stuck. + +--- + +### ao session cleanup + +Automatically kill sessions where the PR has been merged. + +\`\`\`bash +ao session cleanup # All projects +ao session cleanup -p ${projectId} # This project only +ao session cleanup -p ${projectId} --dry-run # Preview what would be killed +\`\`\` + +Always run with \`--dry-run\` first when unsure. + +--- + +### ao review-check + +Scan all sessions with PRs for pending review comments and send fix instructions. + +\`\`\`bash +ao review-check # All projects +ao review-check ${projectId} # This project only +ao review-check ${projectId} --dry-run # Preview without sending +\`\`\` + +**What it does**: Finds PRs with unresolved review threads or "changes requested" decisions, then sends a fix prompt to the corresponding worker agent. + +--- + +### ao open + +Open session(s) in terminal tabs. + +\`\`\`bash +ao open ${prefix}-3 # Specific session +ao open ${projectId} # All sessions for this project +ao open all # All sessions across all projects +ao open ${projectId} -w # Open in new terminal window +\`\`\` + +--- + +### ao dashboard + +Start the web dashboard manually (usually started by \`ao start\`). + +\`\`\`bash +ao dashboard # Start on configured port (${config.port}) +ao dashboard -p 8080 # Custom port +ao dashboard --no-open # Don't auto-open browser +\`\`\` + +--- + +### ao start / ao stop + +Start or stop the orchestrator agent and dashboard. + +\`\`\`bash +ao start ${projectId} # Start everything +ao start ${projectId} --no-dashboard # Skip dashboard +ao start ${projectId} --no-orchestrator # Skip orchestrator agent +ao stop ${projectId} # Stop everything +\`\`\` + +--- + +### tmux (read-only inspection) + +For read-only inspection of sessions, you can use tmux directly: + +\`\`\`bash +# Peek at last 30 lines of a session (read-only, no attach) +tmux capture-pane -t "${prefix}-3" -p -S -30 + +# List all tmux sessions +tmux ls +\`\`\` + +Never use \`tmux send-keys\` — use \`ao send\` instead.`; +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 8bf00cd49..3bb0ddecc 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -254,6 +254,7 @@ export interface Agent { * run git/gh commands. Without this, PRs created by agents never show up. */ setupWorkspaceHooks?(workspacePath: string, config: WorkspaceHooksConfig): Promise; + } export interface AgentLaunchConfig { @@ -263,6 +264,18 @@ export interface AgentLaunchConfig { prompt?: string; permissions?: "skip" | "default"; model?: string; + + /** + * System-level instructions to inject into the agent's context. + * Used by `ao start` to give the orchestrator agent its instructions. + * + * Each agent plugin handles this in getLaunchCommand(): + * - Claude Code: --append-system-prompt + * - Codex: --system-prompt or AGENTS.md + * - Aider: --read flag or conventions + * - OpenCode: equivalent mechanism + */ + systemPrompt?: string; } export interface WorkspaceHooksConfig { diff --git a/packages/plugins/agent-aider/src/index.ts b/packages/plugins/agent-aider/src/index.ts index 2ccbd43ab..4e96a33ac 100644 --- a/packages/plugins/agent-aider/src/index.ts +++ b/packages/plugins/agent-aider/src/index.ts @@ -82,6 +82,10 @@ function createAiderAgent(): Agent { parts.push("--model", shellEscape(config.model)); } + if (config.systemPrompt) { + parts.push("--system-prompt", shellEscape(config.systemPrompt)); + } + if (config.prompt) { parts.push("--message", shellEscape(config.prompt)); } diff --git a/packages/plugins/agent-claude-code/src/index.ts b/packages/plugins/agent-claude-code/src/index.ts index 4878fcd6a..1fba616aa 100644 --- a/packages/plugins/agent-claude-code/src/index.ts +++ b/packages/plugins/agent-claude-code/src/index.ts @@ -562,6 +562,10 @@ function createClaudeCodeAgent(): Agent { parts.push("--model", shellEscape(config.model)); } + if (config.systemPrompt) { + parts.push("--append-system-prompt", shellEscape(config.systemPrompt)); + } + if (config.prompt) { parts.push("-p", shellEscape(config.prompt)); } @@ -726,6 +730,7 @@ function createClaudeCodeAgent(): Agent { const hookScriptPath = join(session.workspacePath, ".claude", "metadata-updater.sh"); await setupHookInWorkspace(session.workspacePath, hookScriptPath); }, + }; } diff --git a/packages/plugins/agent-codex/src/index.ts b/packages/plugins/agent-codex/src/index.ts index 650e2900f..2d94a9ea7 100644 --- a/packages/plugins/agent-codex/src/index.ts +++ b/packages/plugins/agent-codex/src/index.ts @@ -44,6 +44,10 @@ function createCodexAgent(): Agent { parts.push("--model", shellEscape(config.model)); } + if (config.systemPrompt) { + parts.push("--system-prompt", shellEscape(config.systemPrompt)); + } + if (config.prompt) { // Use `--` to end option parsing so prompts starting with `-` aren't // misinterpreted as flags.