diff --git a/.changeset/README.md b/.changeset/README.md index c63edce496..e5b6d8d6a6 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -1,8 +1,8 @@ -# Changesets - -Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works -with multi-package repos, or single-package repos to help you version and publish your code. You can -find the full documentation for it [in our repository](https://github.com/changesets/changesets) - -We have a quick list of common questions to get you started engaging with this project in -[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/activity-events-webhooks-mux.md b/.changeset/activity-events-webhooks-mux.md index b6e8a494b0..6f4dcc73f4 100644 --- a/.changeset/activity-events-webhooks-mux.md +++ b/.changeset/activity-events-webhooks-mux.md @@ -1,18 +1,18 @@ ---- -"@aoagents/ao-core": minor -"@aoagents/ao-web": minor ---- - -Wire activity events into webhook ingress and the mux WebSocket terminal server (sub-issue of #1511, follows #1620). - -- `api.webhook_unverified` (warn) — signature verification failed; data includes `slug`, `remoteAddr`, `candidateCount` (never the failed signature) -- `api.webhook_rejected` (warn) — payload exceeded `maxBodyBytes`; data includes counts and `maxBodyBytes` (never the body) -- `api.webhook_received` (info|warn) — accepted webhook; data includes `projectIds`, `matchedSessions`, `parseErrorCount`, `lifecycleErrorCount` (never the body) -- `api.webhook_failed` (error) — outer pipeline crash with `errorMessage` -- `ui.terminal_connected` / `ui.terminal_disconnected` — one event per mux WS connection lifecycle -- `ui.terminal_heartbeat_lost` (warn) — fires once on 3 missed pongs (was console-only) -- `ui.terminal_pty_lost` (warn) — fires when PTY exits with subscribers attached (distinguishes "PTY died" from "user closed browser") -- `ui.terminal_protocol_error` (warn) — invalid mux client message -- `ui.session_broadcast_failed` (warn) — emitted on the healthy→failing transition only (re-arms after a successful poll), so a long outage produces one event, not 20/min - -`api.webhook_unverified` is the security-audit event; treat 401s on webhooks as a signal worth retaining for the full 7-day window. +--- +"@aoagents/ao-core": minor +"@aoagents/ao-web": minor +--- + +Wire activity events into webhook ingress and the mux WebSocket terminal server (sub-issue of #1511, follows #1620). + +- `api.webhook_unverified` (warn) — signature verification failed; data includes `slug`, `remoteAddr`, `candidateCount` (never the failed signature) +- `api.webhook_rejected` (warn) — payload exceeded `maxBodyBytes`; data includes counts and `maxBodyBytes` (never the body) +- `api.webhook_received` (info|warn) — accepted webhook; data includes `projectIds`, `matchedSessions`, `parseErrorCount`, `lifecycleErrorCount` (never the body) +- `api.webhook_failed` (error) — outer pipeline crash with `errorMessage` +- `ui.terminal_connected` / `ui.terminal_disconnected` — one event per mux WS connection lifecycle +- `ui.terminal_heartbeat_lost` (warn) — fires once on 3 missed pongs (was console-only) +- `ui.terminal_pty_lost` (warn) — fires when PTY exits with subscribers attached (distinguishes "PTY died" from "user closed browser") +- `ui.terminal_protocol_error` (warn) — invalid mux client message +- `ui.session_broadcast_failed` (warn) — emitted on the healthy→failing transition only (re-arms after a successful poll), so a long outage produces one event, not 20/min + +`api.webhook_unverified` is the security-audit event; treat 401s on webhooks as a signal worth retaining for the full 7-day window. diff --git a/.changeset/cli-activity-events.md b/.changeset/cli-activity-events.md index c558ebb5b1..7528033c49 100644 --- a/.changeset/cli-activity-events.md +++ b/.changeset/cli-activity-events.md @@ -1,6 +1,6 @@ ---- -"@aoagents/ao-core": minor -"@aoagents/ao-cli": minor ---- - -Wire CLI activity events into `ao start`, `ao stop`, `ao spawn`, `ao update`, `ao setup`, `ao migrate-storage`, and shared CLI helpers. `ao events list --source cli` now answers RCA questions like "did AO start cleanly?", "was AO killed or did it crash?", and "did `ao spawn`/`ao stop` fail and why?". Adds `"cli"` to the `ActivityEventSource` union and 30+ event-emit sites covering startup, graceful and forced shutdown, restore, project resolution, config recovery, and migration paths. +--- +"@aoagents/ao-core": minor +"@aoagents/ao-cli": minor +--- + +Wire CLI activity events into `ao start`, `ao stop`, `ao spawn`, `ao update`, `ao setup`, `ao migrate-storage`, and shared CLI helpers. `ao events list --source cli` now answers RCA questions like "did AO start cleanly?", "was AO killed or did it crash?", and "did `ao spawn`/`ao stop` fail and why?". Adds `"cli"` to the `ActivityEventSource` union and 30+ event-emit sites covering startup, graceful and forced shutdown, restore, project resolution, config recovery, and migration paths. diff --git a/.changeset/config.json b/.changeset/config.json index 00648117d1..f588dcbcc1 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,45 +1,45 @@ -{ - "$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json", - "changelog": "@changesets/cli/changelog", - "commit": false, - "fixed": [], - "linked": [ - [ - "@aoagents/ao-core", - "@aoagents/ao-cli", - "@aoagents/ao", - "@aoagents/ao-plugin-runtime-tmux", - "@aoagents/ao-plugin-runtime-process", - "@aoagents/ao-plugin-agent-claude-code", - "@aoagents/ao-plugin-agent-codex", - "@aoagents/ao-plugin-agent-aider", - "@aoagents/ao-plugin-agent-opencode", - "@aoagents/ao-plugin-agent-cursor", - "@aoagents/ao-plugin-agent-kimicode", - "@aoagents/ao-plugin-workspace-worktree", - "@aoagents/ao-plugin-workspace-clone", - "@aoagents/ao-plugin-tracker-github", - "@aoagents/ao-plugin-tracker-linear", - "@aoagents/ao-plugin-tracker-gitlab", - "@aoagents/ao-plugin-scm-github", - "@aoagents/ao-plugin-scm-gitlab", - "@aoagents/ao-plugin-notifier-desktop", - "@aoagents/ao-plugin-notifier-slack", - "@aoagents/ao-plugin-notifier-webhook", - "@aoagents/ao-plugin-notifier-composio", - "@aoagents/ao-plugin-notifier-discord", - "@aoagents/ao-plugin-notifier-openclaw", - "@aoagents/ao-plugin-terminal-iterm2", - "@aoagents/ao-plugin-terminal-web", - "@aoagents/ao-web" - ] - ], - "snapshot": { - "useCalculatedVersionForSnapshots": true, - "prereleaseTemplate": "{tag}-{commit}" - }, - "access": "public", - "baseBranch": "main", - "updateInternalDependencies": "patch", - "ignore": ["@aoagents/ao-integration-tests"] -} +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [ + [ + "@aoagents/ao-core", + "@aoagents/ao-cli", + "@aoagents/ao", + "@aoagents/ao-plugin-runtime-tmux", + "@aoagents/ao-plugin-runtime-process", + "@aoagents/ao-plugin-agent-claude-code", + "@aoagents/ao-plugin-agent-codex", + "@aoagents/ao-plugin-agent-aider", + "@aoagents/ao-plugin-agent-opencode", + "@aoagents/ao-plugin-agent-cursor", + "@aoagents/ao-plugin-agent-kimicode", + "@aoagents/ao-plugin-workspace-worktree", + "@aoagents/ao-plugin-workspace-clone", + "@aoagents/ao-plugin-tracker-github", + "@aoagents/ao-plugin-tracker-linear", + "@aoagents/ao-plugin-tracker-gitlab", + "@aoagents/ao-plugin-scm-github", + "@aoagents/ao-plugin-scm-gitlab", + "@aoagents/ao-plugin-notifier-desktop", + "@aoagents/ao-plugin-notifier-slack", + "@aoagents/ao-plugin-notifier-webhook", + "@aoagents/ao-plugin-notifier-composio", + "@aoagents/ao-plugin-notifier-discord", + "@aoagents/ao-plugin-notifier-openclaw", + "@aoagents/ao-plugin-terminal-iterm2", + "@aoagents/ao-plugin-terminal-web", + "@aoagents/ao-web" + ] + ], + "snapshot": { + "useCalculatedVersionForSnapshots": true, + "prereleaseTemplate": "{tag}-{commit}" + }, + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": ["@aoagents/ao-integration-tests"] +} diff --git a/.changeset/issue-1660-recovery-metadata-events.md b/.changeset/issue-1660-recovery-metadata-events.md index ae9b945626..4ad89f8b07 100644 --- a/.changeset/issue-1660-recovery-metadata-events.md +++ b/.changeset/issue-1660-recovery-metadata-events.md @@ -1,6 +1,6 @@ ---- -"@aoagents/ao-cli": patch -"@aoagents/ao-core": minor ---- - -Wire activity events for the recovery subsystem, metadata-corruption detection, and agent-report apply path. New event kinds: `recovery.session_failed`, `recovery.action_failed`, `metadata.corrupt_detected`, `api.agent_report.session_not_found`, `api.agent_report.transition_rejected`. Adds `"recovery"` to the `ActivityEventSource` union. Lets RCA reconstruct `ao recover` invocations, find every silent metadata overwrite, and audit rejected agent transitions. Adds `ao events list --source` and `--kind` so these forensic event queries are available from the CLI. +--- +"@aoagents/ao-cli": patch +"@aoagents/ao-core": minor +--- + +Wire activity events for the recovery subsystem, metadata-corruption detection, and agent-report apply path. New event kinds: `recovery.session_failed`, `recovery.action_failed`, `metadata.corrupt_detected`, `api.agent_report.session_not_found`, `api.agent_report.transition_rejected`. Adds `"recovery"` to the `ActivityEventSource` union. Lets RCA reconstruct `ao recover` invocations, find every silent metadata overwrite, and audit rejected agent transitions. Adds `ao events list --source` and `--kind` so these forensic event queries are available from the CLI. diff --git a/.changeset/launch-orchestrator-clean.md b/.changeset/launch-orchestrator-clean.md index 10b49866fd..a13c2f7086 100644 --- a/.changeset/launch-orchestrator-clean.md +++ b/.changeset/launch-orchestrator-clean.md @@ -1,8 +1,8 @@ ---- -"@aoagents/ao-core": minor -"@aoagents/ao-web": minor ---- - -feat: "Launch Orchestrator (clean context)" action on the orchestrator session page - -Adds a `Relaunch (clean)` action on the orchestrator session page that replaces the project's canonical orchestrator with a fresh one — killing the existing orchestrator, deleting its metadata, and spawning a new session with no carryover state. Backed by a new `SessionManager.relaunchOrchestrator(config)` method that ignores `orchestratorSessionStrategy`. Removes the now-redundant Orchestrator Selector page (`/orchestrators?project=X`) — there is only ever one orchestrator per project, so a selector page is no longer meaningful. Closes #1900 and #1080. +--- +"@aoagents/ao-core": minor +"@aoagents/ao-web": minor +--- + +feat: "Launch Orchestrator (clean context)" action on the orchestrator session page + +Adds a `Relaunch (clean)` action on the orchestrator session page that replaces the project's canonical orchestrator with a fresh one — killing the existing orchestrator, deleting its metadata, and spawning a new session with no carryover state. Backed by a new `SessionManager.relaunchOrchestrator(config)` method that ignores `orchestratorSessionStrategy`. Removes the now-redundant Orchestrator Selector page (`/orchestrators?project=X`) — there is only ever one orchestrator per project, so a selector page is no longer meaningful. Closes #1900 and #1080. diff --git a/.changeset/linear-transient-retry.md b/.changeset/linear-transient-retry.md index 08614bdde0..cb1ab27645 100644 --- a/.changeset/linear-transient-retry.md +++ b/.changeset/linear-transient-retry.md @@ -1,5 +1,5 @@ ---- -"@aoagents/ao-plugin-tracker-linear": patch ---- - -Retry transient Linear API HTTP failures in the direct transport to reduce flakes from brief 5xx/429 responses. +--- +"@aoagents/ao-plugin-tracker-linear": patch +--- + +Retry transient Linear API HTTP failures in the direct transport to reduce flakes from brief 5xx/429 responses. diff --git a/.changeset/quiet-sqlite-rebuild.md b/.changeset/quiet-sqlite-rebuild.md index 06a1566499..79dbfd120b 100644 --- a/.changeset/quiet-sqlite-rebuild.md +++ b/.changeset/quiet-sqlite-rebuild.md @@ -1,7 +1,7 @@ ---- -"@aoagents/ao": patch -"@aoagents/ao-core": patch -"@aoagents/ao-cli": patch ---- - -Rebuild missing better-sqlite3 native bindings during ao postinstall and replace noisy activity-events native-binding failures with a one-line diagnostic. +--- +"@aoagents/ao": patch +"@aoagents/ao-core": patch +"@aoagents/ao-cli": patch +--- + +Rebuild missing better-sqlite3 native bindings during ao postinstall and replace noisy activity-events native-binding failures with a one-line diagnostic. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..da9b5384d7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Line-ending policy. +# +# This repo is developed on Windows as well as Linux/macOS, and CRLF endings +# have broken CI twice: a CRLF shebang makes a shell script unrunnable on the +# Unix runners, and Vite/esbuild fails to parse a JS module whose hashbang line +# ends in \r. Pin the formats where CRLF is actively harmful. + +# Shell scripts are executed directly by the Unix runners. +*.sh text eol=lf + +# Parsed by esbuild/Vite, which chokes on a CRLF hashbang. +*.js text eol=lf +*.mjs text eol=lf +*.cjs text eol=lf + +# Generated — LF keeps regeneration a no-op instead of a whole-file diff. +pnpm-lock.yaml text eol=lf +package-lock.json text eol=lf diff --git a/.github/.gitleaks.toml b/.github/.gitleaks.toml index 8eeef52349..8adb39e2bd 100644 --- a/.github/.gitleaks.toml +++ b/.github/.gitleaks.toml @@ -64,7 +64,11 @@ title = "gitleaks config" [[rules]] description = "Slack" - regex = '''xox[baprs]-([0-9a-zA-Z]{10,48})?''' + # The token body is required, not optional. With the trailing `?` this + # matched a bare `xoxb-` anywhere in the tree — including prose and code + # comments that merely name the prefix — while catching no real token the + # stricter form misses. + regex = '''xox[baprs]-[0-9a-zA-Z]{10,48}''' tags = ["key", "Slack"] [[rules]] @@ -172,6 +176,39 @@ title = "gitleaks config" files = ['''^\.?gitleaks.toml$''', '''(.*?)(png|jpg|gif|doc|docx|pdf|bin|xls|pyc|zip)$''', '''(go.mod|go.sum)$'''] + + # The secret-redaction code, its tests, and the audit note documenting it. + # These exist precisely to carry example token patterns: activity-events.ts + # names the prefixes it strips, and the test asserts the stripping works + # using AWS's own published example key (AKIAIOSFODNN7EXAMPLE). + # + # `paths`, not `files` — the latter is matched against the bare filename, + # so full-path patterns silently never match. Scoped by path rather than + # added to the commit list below, because that list only holds until + # something touches these files again, which is what the prettier + # auto-format job did. + paths = [ + '''packages/core/src/activity-events\.ts$''', + '''packages/core/src/__tests__/activity-events\.test\.ts$''', + '''docs/SECURITY-AUDIT-SUMMARY\.md$''', + ] + + # The two literal fixture values, so the redaction tests stay covered even + # if they move file. Both are documented non-secrets: AWS publishes the + # first as its canonical example key, and the second is a made-up Slack + # token the test feeds in to prove it gets redacted back out. + regexes = [ + '''AKIAIOSFODNN7EXAMPLE''', + '''xoxb-1234567890-abcdefghij''', + # Documentation placeholders, e.g. SECURITY.md's + # "GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" in the section + # telling people to keep tokens in env vars. `x` is alphanumeric, so the + # token-shaped rules match the filler. A real credential does not carry + # a 20-character run of literal x, so this cannot mask one — and it has + # to be matched by pattern, because the finding lands on the PR's merge + # commit, whose SHA is different on every run. + '''x{20,}''', + ] # Historical false positives from the ReverbCode rewrite graft (#2166). # All offenders are example secret-patterns in security docs and # secret-redaction test fixtures, living in files already deleted from @@ -188,4 +225,9 @@ title = "gitleaks config" "cb2fc088a83b394fc128235f4106bbfc88d8ede8", "f56338cbabd2185cab706ae09e1471b0bd9b7b43", "fc7d76ad54d6a7350de0fca298a1294ce8a1f3a2", + # The prettier auto-format job reflowed the whole tree, so the same + # redaction fixtures and security-doc examples reappear as additions in + # its commits. Same offenders, same rationale as the graft above. + "2f6a59e2e800b2ee23fc4cc305aecd95a90b8bac", + "d5a979623724fe26f012598958695a7429046e72", ] diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b2e10a417e..7823d16611 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,250 +1,257 @@ -# Copilot Instructions - -Instructions for GitHub Copilot when generating code, reviewing PRs, and suggesting changes in this repository. - -## Project Overview - -Agent Orchestrator (AO) is a TypeScript monorepo that manages fleets of parallel AI coding agents. Each agent gets its own git worktree, branch, and PR. The system handles CI feedback routing, review comment handling, and session lifecycle. - -**Stack:** TypeScript (strict), pnpm monorepo, Next.js 15 + React 19, Tailwind CSS v4, Vitest, ESLint flat config. - -**Architecture:** 8 plugin slots (Runtime, Agent, Workspace, Tracker, SCM, Notifier, Terminal, Lifecycle). All interfaces are defined in `packages/core/src/types.ts`. There is no database; the system uses flat files and memory. - -Full conventions: `CLAUDE.md`. Plugin development: `docs/DEVELOPMENT.md`. Design system: `DESIGN.md`. - ---- - -## Code Generation Rules - -### Think Before Generating - -- If a task is ambiguous, suggest the two most likely interpretations and ask which one applies. Do not choose silently. -- If there is a simpler approach than the one requested, say so. Push back when warranted. -- State assumptions explicitly when generating non-trivial code. - -### Simplicity First - -- No speculative features. No abstractions for single-use code. No "flexibility" that was not requested. -- Plugin slots are the extension point. If the user asks for configurability, consider whether a new plugin slot is the right answer instead. -- If you are generating 200 lines and it could be 50, rewrite it. -- Do not add error handling for impossible scenarios. - -### Match Existing Patterns - -- Before generating new code in an existing file, read how similar features are already implemented in that same file. Match the pattern. -- Do not introduce new patterns when established ones already exist. Search the codebase first. -- Match existing naming conventions, import styles, and file organization. -- Use `@aoagents/ao-core` for cross-package imports. -- Use the `workspace:*` protocol in `package.json`. - -### TypeScript Strict Mode - -- No `any` types unless they are in test files, where `any` and `console.log` are allowed. -- Use `import type { Foo }` for type-only imports. -- Prefix unused variables with `_`. -- Do not use `eval`, `new Function`, or `require()`; use ES module imports. - -### Web / UI Specific - -- Use Tailwind utility classes only. Do not use inline `style=` attributes. -- Use CSS custom properties via `var(--color-*)` from the `globals.css` `@theme` block. Never hardcode hex colors. -- Do not use external UI component libraries such as Radix, shadcn, or Headless UI. -- Preserve the dark theme at all times. -- Border radius must be `0px` everywhere except status dots and avatar circles. Hard edges are part of the visual identity. -- Mark client components with `"use client"`. Use server components for pages. -- SSE updates run at a 5-second interval via the `useSessionEvents` hook. Do not change this interval. -- Keep component files under 400 lines. - ---- - -## PR Review Instructions - -### What to Focus On - -These are the areas where Copilot review adds the most value: issues CI cannot catch. - -**1. Design over implementation.** A perfectly coded bad design is worse than a messy good one. Question: -- Side-channel communication, such as hidden flags or dynamic attribute setting -- Boolean parameters that switch between fundamentally different behaviors and should be separate code paths -- New internal contracts between components without interface documentation -- Missing migration paths for behavioral changes - -**2. Pattern consistency.** If a file uses one pattern and the PR introduces a different one, flag it. Common violations: -- Using class attributes in one place and instance properties in another for the same concept -- Mixing callback styles when the file uses one style consistently -- Introducing a new error-handling pattern when the file uses `throw new Error("msg", { cause: err })` - -**3. State machine safety.** Changes to `SessionStatus`, `ActivityState`, or lifecycle transitions require extra scrutiny: -- Verify that no invalid state transitions are introduced -- Check that `isTerminalSession()` and `TERMINAL_STATUSES` are updated if new statuses are added -- Flag any change that could cause a session to be incorrectly marked `killed` or `exited` - -**4. Plugin interface stability.** Any change to interfaces in `types.ts` is potentially breaking: -- New required methods on plugin interfaces break all existing plugins -- Changed method signatures break all existing plugins -- New optional methods are acceptable -- Flag any non-optional interface change as "breaking — requires updating all N plugins implementing this slot" - -**5. Backward compatibility.** Flag changes to: -- CLI flags or arguments in `packages/cli/` -- Config schema, including `agent-orchestrator.yaml` structure and Zod validation in `packages/core/src/config.ts` -- Exported types from `packages/core/src/index.ts`, which are a stable public API and should not break -- Default config values or behavior - -**6. Plugin isolation.** Plugins must never import each other directly. They communicate through: -- The `Session` object -- The `LifecycleManager` event system -- Core utilities exported from `@aoagents/ao-core` - -**7. Resource cleanup.** Check that: -- File handles, subprocesses, and runtime sessions (tmux on Unix, ConPTY pty-host processes on Windows) are cleaned up on all exit paths: success, error, and early return -- `destroy()` methods exist and use best-effort semantics -- There are no resource leaks in error paths - -**8. Shell safety.** Any command construction must use `shellEscape()` from `@aoagents/ao-core` for all dynamic arguments. Flag raw string interpolation in shell commands. - -### What to Ignore - -These are handled by automated tooling and should not be raised in review: - -- Formatting, whitespace, and trailing commas; Prettier handles them -- Import ordering; ESLint handles it -- Type errors; TypeScript strict mode and CI catch them -- Lint rule violations; ESLint and CI catch them -- Conventional commit format; CI validates it -- Test file style, including `any` types and `console.log`; relaxed rules apply there - -### High-Risk Files - -These files have a wide blast radius and deserve extra scrutiny: - -| File | Why it's risky | -|------|----------------| -| `packages/core/src/types.ts` | All 8 plugin interfaces live here. Changes can break every plugin. | -| `packages/core/src/lifecycle-manager.ts` | State machine and polling loop with subtle state dependencies. | -| `packages/core/src/session-manager.ts` | Session CRUD + stale runtime reconciliation. `list()` persists `runtime_lost` to disk when enrichment detects dead runtimes. Invariant violations can cause phantom `killed` or `exited` sessions. | -| `packages/core/src/lifecycle-state.ts` | Canonical lifecycle → legacy status mapping. New terminal reasons (e.g. `runtime_lost`) must be added to `deriveLegacyStatus()`. | -| `packages/cli/src/commands/start.ts` | ao start/stop + Ctrl+C shutdown. Cross-project scoping logic is subtle — `ao stop ` must not kill parent process. On Windows, also calls `sweepWindowsPtyHosts()` to gracefully tear down detached ConPTY pty-host processes that `taskkill /T` cannot reach. | -| `packages/core/src/config.ts` | Zod validation schema. Changes affect every `ao` command. | -| `packages/core/src/index.ts` | Stable public API. Do not break it without deprecation. | -| `packages/web/src/app/globals.css` | Design tokens used by 50+ components. Renaming tokens breaks the UI. | -| `packages/cli/src/index.ts` | CLI entry point. Flag and argument changes are user-facing. | -| `agent-orchestrator.yaml.example` | Config reference. It must stay in sync with the Zod schema. | - -### Behavioral Rules for Reviews - -1. **If it is worth mentioning, it is worth fixing.** Do not leave "nits" or minor suggestions. Only raise actionable findings with specific remediation. -2. **Reference file paths and line numbers.** Name the specific function, class, or pattern the author should use instead. Do not give generic advice like "consider using a different approach." -3. **Do not suggest refactoring adjacent code that already works.** Review the diff, not the whole file. -4. **Every finding must trace to a specific line in the diff.** If you cannot point to the line, do not raise it. -5. **Do not repeat points.** Each observation should appear exactly once in the review. -6. **Assume competence.** The author knows the codebase. Explain only non-obvious context: why something is risky, not what it does. -7. **For backward-compatible deprecations, provide the specific pattern:** - - TypeScript: `@deprecated` JSDoc, `console.warn`, and preserved old behavior during the deprecation period - - Config: keep the old key working with a warning and add the new key - - CLI: keep the old flag working and add a deprecation notice to `--help` - -### Review Output Format - -Omit sections where you have no findings. Do not write "No concerns" for empty sections. - -Summary -[1-2 sentence overall assessment] - -Architecture & Design -[Pattern violations, design issues, missing abstractions] - -State Machine / Lifecycle -[Any changes to session status, activity state, or transitions] - -Plugin Interface Stability -[Breaking interface changes, new required methods] - -Backward Compatibility -[Breaking changes to CLI, config, or exported APIs] - -Testing -[Missing edge cases, uncovered error paths, test adequacy] - -Security -[Shell injection, credential exposure, input validation] - -Performance -[Unnecessary allocations, missing cleanup, hot path regressions] - ---- - -## Common Patterns to Use - -### Plugin Implementation - -```typescript -import type { PluginModule, Runtime } from "@aoagents/ao-core"; - -export const manifest = { - name: "tmux", - slot: "runtime" as const, - description: "tmux session runtime", - version: "0.1.0", -}; - -export function create(config?: Record): Runtime { - // Validate config here and store it via closure. - return { /* ... */ }; -} - -export function detect(): boolean { - /* ... */ -} - -export default { manifest, create, detect } satisfies PluginModule; -``` - -### Error Handling - -```typescript -// Wrap with cause for debugging. -throw new Error("Failed to create tmux session", { cause: err }); - -// Return null for "not found", throw for unexpected errors. -const issue = await tracker.getIssue("123"); // null if not found -``` - -### Activity Detection - -```typescript -// Always implement the full cascade: -// 1. Process check (exited if not running) -// 2. Actionable states (waiting_input/blocked from JSONL) -// 3. Native signal (agent-specific API) -// 4. JSONL entry fallback (MUST NOT skip — use getActivityFallbackState()) -``` - -### Shell Commands - -```typescript -import { shellEscape } from "@aoagents/ao-core"; - -const cmd = `git checkout ${shellEscape(branchName)}`; -// NEVER: `git checkout ${branchName}` -``` - ---- - -## Common Mistakes to Flag - -- **Cross-plugin imports.** Plugin A importing plugin B directly. It must go through core. -- **Hardcoded secrets.** Use `process.env` and throw if the value is missing. -- **Shell injection.** Dynamic values in shell commands without `shellEscape()`. -- **Missing `setupWorkspaceHooks`.** A new agent plugin without metadata hooks means the dashboard will not show PRs. -- **Skipping JSONL fallback.** An agent plugin's `getActivityState` without `getActivityFallbackState()` means the dashboard shows no activity. -- **New `SessionStatus` without updating `isTerminalSession` / `TERMINAL_STATUSES`.** The session can get stuck in limbo. -- **New session reason without updating `deriveLegacyStatus()`.** Terminal reasons like `runtime_lost` must map to a legacy status (e.g. `killed`), or sessions show wrong status. -- **Scoping `useSessionEvents` with project filter in Dashboard.tsx.** The sidebar must see ALL sessions — only the Kanban filters by project (client-side via `projectSessions`). -- **ao stop killing parent process when targeting a specific project.** `ao stop ` must only kill that project's sessions, not the parent `ao start` process or dashboard. -- **CSS color hardcoding.** Using `#hex` or `rgb()` instead of `var(--color-*)` tokens. -- **Rounded corners.** Using `rounded-md` or `rounded-lg` on cards or buttons. Hard edges only. -- **External UI libraries.** Importing from Radix, shadcn, or Headless UI. Use native HTML and Tailwind. -- **SSE interval changes.** Modifying the 5-second polling interval in `useSessionEvents`. -- **Inline styles.** Using `style={{ ... }}` for theme values. Use Tailwind with `var(--token)` or a CSS class instead. -- **New `package.json` dependencies without justification.** The monorepo should stay lean. +# Copilot Instructions + +Instructions for GitHub Copilot when generating code, reviewing PRs, and suggesting changes in this repository. + +## Project Overview + +Agent Orchestrator (AO) is a TypeScript monorepo that manages fleets of parallel AI coding agents. Each agent gets its own git worktree, branch, and PR. The system handles CI feedback routing, review comment handling, and session lifecycle. + +**Stack:** TypeScript (strict), pnpm monorepo, Next.js 15 + React 19, Tailwind CSS v4, Vitest, ESLint flat config. + +**Architecture:** 8 plugin slots (Runtime, Agent, Workspace, Tracker, SCM, Notifier, Terminal, Lifecycle). All interfaces are defined in `packages/core/src/types.ts`. There is no database; the system uses flat files and memory. + +Full conventions: `CLAUDE.md`. Plugin development: `docs/DEVELOPMENT.md`. Design system: `DESIGN.md`. + +--- + +## Code Generation Rules + +### Think Before Generating + +- If a task is ambiguous, suggest the two most likely interpretations and ask which one applies. Do not choose silently. +- If there is a simpler approach than the one requested, say so. Push back when warranted. +- State assumptions explicitly when generating non-trivial code. + +### Simplicity First + +- No speculative features. No abstractions for single-use code. No "flexibility" that was not requested. +- Plugin slots are the extension point. If the user asks for configurability, consider whether a new plugin slot is the right answer instead. +- If you are generating 200 lines and it could be 50, rewrite it. +- Do not add error handling for impossible scenarios. + +### Match Existing Patterns + +- Before generating new code in an existing file, read how similar features are already implemented in that same file. Match the pattern. +- Do not introduce new patterns when established ones already exist. Search the codebase first. +- Match existing naming conventions, import styles, and file organization. +- Use `@aoagents/ao-core` for cross-package imports. +- Use the `workspace:*` protocol in `package.json`. + +### TypeScript Strict Mode + +- No `any` types unless they are in test files, where `any` and `console.log` are allowed. +- Use `import type { Foo }` for type-only imports. +- Prefix unused variables with `_`. +- Do not use `eval`, `new Function`, or `require()`; use ES module imports. + +### Web / UI Specific + +- Use Tailwind utility classes only. Do not use inline `style=` attributes. +- Use CSS custom properties via `var(--color-*)` from the `globals.css` `@theme` block. Never hardcode hex colors. +- Do not use external UI component libraries such as Radix, shadcn, or Headless UI. +- Preserve the dark theme at all times. +- Border radius must be `0px` everywhere except status dots and avatar circles. Hard edges are part of the visual identity. +- Mark client components with `"use client"`. Use server components for pages. +- SSE updates run at a 5-second interval via the `useSessionEvents` hook. Do not change this interval. +- Keep component files under 400 lines. + +--- + +## PR Review Instructions + +### What to Focus On + +These are the areas where Copilot review adds the most value: issues CI cannot catch. + +**1. Design over implementation.** A perfectly coded bad design is worse than a messy good one. Question: + +- Side-channel communication, such as hidden flags or dynamic attribute setting +- Boolean parameters that switch between fundamentally different behaviors and should be separate code paths +- New internal contracts between components without interface documentation +- Missing migration paths for behavioral changes + +**2. Pattern consistency.** If a file uses one pattern and the PR introduces a different one, flag it. Common violations: + +- Using class attributes in one place and instance properties in another for the same concept +- Mixing callback styles when the file uses one style consistently +- Introducing a new error-handling pattern when the file uses `throw new Error("msg", { cause: err })` + +**3. State machine safety.** Changes to `SessionStatus`, `ActivityState`, or lifecycle transitions require extra scrutiny: + +- Verify that no invalid state transitions are introduced +- Check that `isTerminalSession()` and `TERMINAL_STATUSES` are updated if new statuses are added +- Flag any change that could cause a session to be incorrectly marked `killed` or `exited` + +**4. Plugin interface stability.** Any change to interfaces in `types.ts` is potentially breaking: + +- New required methods on plugin interfaces break all existing plugins +- Changed method signatures break all existing plugins +- New optional methods are acceptable +- Flag any non-optional interface change as "breaking — requires updating all N plugins implementing this slot" + +**5. Backward compatibility.** Flag changes to: + +- CLI flags or arguments in `packages/cli/` +- Config schema, including `agent-orchestrator.yaml` structure and Zod validation in `packages/core/src/config.ts` +- Exported types from `packages/core/src/index.ts`, which are a stable public API and should not break +- Default config values or behavior + +**6. Plugin isolation.** Plugins must never import each other directly. They communicate through: + +- The `Session` object +- The `LifecycleManager` event system +- Core utilities exported from `@aoagents/ao-core` + +**7. Resource cleanup.** Check that: + +- File handles, subprocesses, and runtime sessions (tmux on Unix, ConPTY pty-host processes on Windows) are cleaned up on all exit paths: success, error, and early return +- `destroy()` methods exist and use best-effort semantics +- There are no resource leaks in error paths + +**8. Shell safety.** Any command construction must use `shellEscape()` from `@aoagents/ao-core` for all dynamic arguments. Flag raw string interpolation in shell commands. + +### What to Ignore + +These are handled by automated tooling and should not be raised in review: + +- Formatting, whitespace, and trailing commas; Prettier handles them +- Import ordering; ESLint handles it +- Type errors; TypeScript strict mode and CI catch them +- Lint rule violations; ESLint and CI catch them +- Conventional commit format; CI validates it +- Test file style, including `any` types and `console.log`; relaxed rules apply there + +### High-Risk Files + +These files have a wide blast radius and deserve extra scrutiny: + +| File | Why it's risky | +| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `packages/core/src/types.ts` | All 8 plugin interfaces live here. Changes can break every plugin. | +| `packages/core/src/lifecycle-manager.ts` | State machine and polling loop with subtle state dependencies. | +| `packages/core/src/session-manager.ts` | Session CRUD + stale runtime reconciliation. `list()` persists `runtime_lost` to disk when enrichment detects dead runtimes. Invariant violations can cause phantom `killed` or `exited` sessions. | +| `packages/core/src/lifecycle-state.ts` | Canonical lifecycle → legacy status mapping. New terminal reasons (e.g. `runtime_lost`) must be added to `deriveLegacyStatus()`. | +| `packages/cli/src/commands/start.ts` | ao start/stop + Ctrl+C shutdown. Cross-project scoping logic is subtle — `ao stop ` must not kill parent process. On Windows, also calls `sweepWindowsPtyHosts()` to gracefully tear down detached ConPTY pty-host processes that `taskkill /T` cannot reach. | +| `packages/core/src/config.ts` | Zod validation schema. Changes affect every `ao` command. | +| `packages/core/src/index.ts` | Stable public API. Do not break it without deprecation. | +| `packages/web/src/app/globals.css` | Design tokens used by 50+ components. Renaming tokens breaks the UI. | +| `packages/cli/src/index.ts` | CLI entry point. Flag and argument changes are user-facing. | +| `agent-orchestrator.yaml.example` | Config reference. It must stay in sync with the Zod schema. | + +### Behavioral Rules for Reviews + +1. **If it is worth mentioning, it is worth fixing.** Do not leave "nits" or minor suggestions. Only raise actionable findings with specific remediation. +2. **Reference file paths and line numbers.** Name the specific function, class, or pattern the author should use instead. Do not give generic advice like "consider using a different approach." +3. **Do not suggest refactoring adjacent code that already works.** Review the diff, not the whole file. +4. **Every finding must trace to a specific line in the diff.** If you cannot point to the line, do not raise it. +5. **Do not repeat points.** Each observation should appear exactly once in the review. +6. **Assume competence.** The author knows the codebase. Explain only non-obvious context: why something is risky, not what it does. +7. **For backward-compatible deprecations, provide the specific pattern:** + - TypeScript: `@deprecated` JSDoc, `console.warn`, and preserved old behavior during the deprecation period + - Config: keep the old key working with a warning and add the new key + - CLI: keep the old flag working and add a deprecation notice to `--help` + +### Review Output Format + +Omit sections where you have no findings. Do not write "No concerns" for empty sections. + +Summary +[1-2 sentence overall assessment] + +Architecture & Design +[Pattern violations, design issues, missing abstractions] + +State Machine / Lifecycle +[Any changes to session status, activity state, or transitions] + +Plugin Interface Stability +[Breaking interface changes, new required methods] + +Backward Compatibility +[Breaking changes to CLI, config, or exported APIs] + +Testing +[Missing edge cases, uncovered error paths, test adequacy] + +Security +[Shell injection, credential exposure, input validation] + +Performance +[Unnecessary allocations, missing cleanup, hot path regressions] + +--- + +## Common Patterns to Use + +### Plugin Implementation + +```typescript +import type { PluginModule, Runtime } from "@aoagents/ao-core"; + +export const manifest = { + name: "tmux", + slot: "runtime" as const, + description: "tmux session runtime", + version: "0.1.0", +}; + +export function create(config?: Record): Runtime { + // Validate config here and store it via closure. + return {/* ... */}; +} + +export function detect(): boolean { + /* ... */ +} + +export default { manifest, create, detect } satisfies PluginModule; +``` + +### Error Handling + +```typescript +// Wrap with cause for debugging. +throw new Error("Failed to create tmux session", { cause: err }); + +// Return null for "not found", throw for unexpected errors. +const issue = await tracker.getIssue("123"); // null if not found +``` + +### Activity Detection + +```typescript +// Always implement the full cascade: +// 1. Process check (exited if not running) +// 2. Actionable states (waiting_input/blocked from JSONL) +// 3. Native signal (agent-specific API) +// 4. JSONL entry fallback (MUST NOT skip — use getActivityFallbackState()) +``` + +### Shell Commands + +```typescript +import { shellEscape } from "@aoagents/ao-core"; + +const cmd = `git checkout ${shellEscape(branchName)}`; +// NEVER: `git checkout ${branchName}` +``` + +--- + +## Common Mistakes to Flag + +- **Cross-plugin imports.** Plugin A importing plugin B directly. It must go through core. +- **Hardcoded secrets.** Use `process.env` and throw if the value is missing. +- **Shell injection.** Dynamic values in shell commands without `shellEscape()`. +- **Missing `setupWorkspaceHooks`.** A new agent plugin without metadata hooks means the dashboard will not show PRs. +- **Skipping JSONL fallback.** An agent plugin's `getActivityState` without `getActivityFallbackState()` means the dashboard shows no activity. +- **New `SessionStatus` without updating `isTerminalSession` / `TERMINAL_STATUSES`.** The session can get stuck in limbo. +- **New session reason without updating `deriveLegacyStatus()`.** Terminal reasons like `runtime_lost` must map to a legacy status (e.g. `killed`), or sessions show wrong status. +- **Scoping `useSessionEvents` with project filter in Dashboard.tsx.** The sidebar must see ALL sessions — only the Kanban filters by project (client-side via `projectSessions`). +- **ao stop killing parent process when targeting a specific project.** `ao stop ` must only kill that project's sessions, not the parent `ao start` process or dashboard. +- **CSS color hardcoding.** Using `#hex` or `rgb()` instead of `var(--color-*)` tokens. +- **Rounded corners.** Using `rounded-md` or `rounded-lg` on cards or buttons. Hard edges only. +- **External UI libraries.** Importing from Radix, shadcn, or Headless UI. Use native HTML and Tailwind. +- **SSE interval changes.** Modifying the 5-second polling interval in `useSessionEvents`. +- **Inline styles.** Using `style={{ ... }}` for theme values. Use Tailwind with `var(--token)` or a CSS class instead. +- **New `package.json` dependencies without justification.** The monorepo should stay lean. diff --git a/.github/scripts/coverage-report.mjs b/.github/scripts/coverage-report.mjs index d7efd16d4e..f4fd0681d0 100644 --- a/.github/scripts/coverage-report.mjs +++ b/.github/scripts/coverage-report.mjs @@ -1,172 +1,165 @@ -/** - * Parses Vitest JSON coverage reports, filters to PR-changed files, - * and writes a Markdown summary to coverage-comment.md. - * - * Expects: - * - changed-files.txt in cwd (one relative path per line) - * - coverage-final.json in each package's coverage/ directory - * - */ - -/* eslint-disable no-undef -- Node.js CI script; process/console are globals */ -import { readFileSync, writeFileSync, existsSync, realpathSync, readdirSync } from "node:fs"; -import { resolve, relative } from "node:path"; -import libCoverage from "istanbul-lib-coverage"; - -const COMMENT_TAG = ""; -const cwd = realpathSync(process.cwd()); - -// ── 1. Read changed files ────────────────────────────────────────── -const changedFiles = readFileSync("changed-files.txt", "utf-8") - .split("\n") - .map((f) => f.trim()) - .filter((f) => f && (f.endsWith(".ts") || f.endsWith(".tsx"))) - .filter((f) => !f.includes("__tests__") && !f.includes(".test.")); - -if (changedFiles.length === 0) { - const comment = `${COMMENT_TAG}\n## Test Coverage Report\n\n_No TypeScript source files changed in this PR._\n`; - writeFileSync("coverage-comment.md", comment); - process.exit(0); -} - -// ── 2. Discover and merge coverage-final.json files ──────────────── -function findCoverageFiles(baseDir) { - const results = []; - const packagesDir = resolve(baseDir, "packages"); - - function walk(dir) { - let entries; - try { - entries = readdirSync(dir, { withFileTypes: true }); - } catch { - return; - } - for (const entry of entries) { - if (entry.name === "node_modules") continue; - const full = resolve(dir, entry.name); - if (entry.isDirectory()) { - if (entry.name === "coverage") { - const jsonFile = resolve(full, "coverage-final.json"); - if (existsSync(jsonFile)) results.push(jsonFile); - } else { - walk(full); - } - } - } - } - - walk(packagesDir); - return results; -} - -const coverageMap = libCoverage.createCoverageMap({}); - -for (const jsonPath of findCoverageFiles(cwd)) { - const raw = JSON.parse(readFileSync(jsonPath, "utf-8")); - coverageMap.merge(raw); -} - -// ── 3. Filter to changed files and collect metrics ───────────────── -let totalLines = 0; -let coveredLines = 0; -const fileReports = []; - -for (const absPath of coverageMap.files()) { - // Normalize to handle symlinks (e.g. /tmp -> /private/tmp on macOS) - const realAbsPath = existsSync(absPath) ? realpathSync(absPath) : absPath; - const relPath = relative(cwd, realAbsPath); - - if (!changedFiles.includes(relPath)) continue; - - const fc = coverageMap.fileCoverageFor(absPath); - const summary = fc.toSummary(); - const lineCoverage = fc.getLineCoverage(); - - const fileTotalLines = summary.lines.total; - const fileCoveredLines = summary.lines.covered; - const uncoveredLineNums = Object.entries(lineCoverage) - .filter(([, hits]) => hits === 0) - .map(([line]) => Number(line)) - .sort((a, b) => a - b); - - totalLines += fileTotalLines; - coveredLines += fileCoveredLines; - - if (fileTotalLines > 0) { - fileReports.push({ - path: relPath, - total: fileTotalLines, - covered: fileCoveredLines, - pct: summary.lines.pct.toFixed(1), - uncoveredLines: uncoveredLineNums, - }); - } -} - -// ── 4. Build Markdown comment ────────────────────────────────────── - -/** Collapse consecutive line numbers into ranges: [1,2,3,7,9,10] -> "L1-L3, L7, L9-L10" */ -function consolidateRanges(lines) { - if (lines.length === 0) return ""; - const ranges = []; - let start = lines[0]; - let end = lines[0]; - - for (let i = 1; i < lines.length; i++) { - if (lines[i] === end + 1) { - end = lines[i]; - } else { - ranges.push(start === end ? `L${start}` : `L${start}-L${end}`); - start = lines[i]; - end = lines[i]; - } - } - ranges.push(start === end ? `L${start}` : `L${start}-L${end}`); - return ranges.join(", "); -} - -let comment = `${COMMENT_TAG}\n## Test Coverage Report\n\n`; - -if (fileReports.length === 0) { - comment += - "_Changed files have no coverage data (not instrumented or no tests ran)._\n"; -} else { - const pct = - totalLines > 0 ? ((coveredLines / totalLines) * 100).toFixed(1) : "0.0"; - const uncoveredTotal = totalLines - coveredLines; - - comment += "| Metric | Value |\n"; - comment += "|--------|-------|\n"; - comment += `| Lines covered | ${coveredLines}/${totalLines} |\n`; - comment += `| Lines not covered | ${uncoveredTotal}/${totalLines} |\n`; - comment += `| Overall coverage | ${pct}% |\n\n`; - - // Per-file breakdown - if (fileReports.length > 1) { - comment += "
\nPer-file breakdown\n\n"; - comment += "| File | Coverage |\n"; - comment += "|------|----------|\n"; - for (const f of fileReports.sort((a, b) => a.path.localeCompare(b.path))) { - comment += `| \`${f.path}\` | ${f.covered}/${f.total} (${f.pct}%) |\n`; - } - comment += "\n
\n\n"; - } - - // Uncovered lines section - const filesWithUncovered = fileReports.filter( - (f) => f.uncoveredLines.length > 0, - ); - if (filesWithUncovered.length > 0) { - comment += "### Uncovered lines\n\n"; - for (const file of filesWithUncovered.sort((a, b) => - a.path.localeCompare(b.path), - )) { - const ranges = consolidateRanges(file.uncoveredLines); - comment += `- \`${file.path}\`: ${ranges}\n`; - } - comment += "\n"; - } - -} - -writeFileSync("coverage-comment.md", comment); +/** + * Parses Vitest JSON coverage reports, filters to PR-changed files, + * and writes a Markdown summary to coverage-comment.md. + * + * Expects: + * - changed-files.txt in cwd (one relative path per line) + * - coverage-final.json in each package's coverage/ directory + * + */ + +/* eslint-disable no-undef -- Node.js CI script; process/console are globals */ +import { readFileSync, writeFileSync, existsSync, realpathSync, readdirSync } from "node:fs"; +import { resolve, relative } from "node:path"; +import libCoverage from "istanbul-lib-coverage"; + +const COMMENT_TAG = ""; +const cwd = realpathSync(process.cwd()); + +// ── 1. Read changed files ────────────────────────────────────────── +const changedFiles = readFileSync("changed-files.txt", "utf-8") + .split("\n") + .map((f) => f.trim()) + .filter((f) => f && (f.endsWith(".ts") || f.endsWith(".tsx"))) + .filter((f) => !f.includes("__tests__") && !f.includes(".test.")); + +if (changedFiles.length === 0) { + const comment = `${COMMENT_TAG}\n## Test Coverage Report\n\n_No TypeScript source files changed in this PR._\n`; + writeFileSync("coverage-comment.md", comment); + process.exit(0); +} + +// ── 2. Discover and merge coverage-final.json files ──────────────── +function findCoverageFiles(baseDir) { + const results = []; + const packagesDir = resolve(baseDir, "packages"); + + function walk(dir) { + let entries; + try { + entries = readdirSync(dir, { withFileTypes: true }); + } catch { + return; + } + for (const entry of entries) { + if (entry.name === "node_modules") continue; + const full = resolve(dir, entry.name); + if (entry.isDirectory()) { + if (entry.name === "coverage") { + const jsonFile = resolve(full, "coverage-final.json"); + if (existsSync(jsonFile)) results.push(jsonFile); + } else { + walk(full); + } + } + } + } + + walk(packagesDir); + return results; +} + +const coverageMap = libCoverage.createCoverageMap({}); + +for (const jsonPath of findCoverageFiles(cwd)) { + const raw = JSON.parse(readFileSync(jsonPath, "utf-8")); + coverageMap.merge(raw); +} + +// ── 3. Filter to changed files and collect metrics ───────────────── +let totalLines = 0; +let coveredLines = 0; +const fileReports = []; + +for (const absPath of coverageMap.files()) { + // Normalize to handle symlinks (e.g. /tmp -> /private/tmp on macOS) + const realAbsPath = existsSync(absPath) ? realpathSync(absPath) : absPath; + const relPath = relative(cwd, realAbsPath); + + if (!changedFiles.includes(relPath)) continue; + + const fc = coverageMap.fileCoverageFor(absPath); + const summary = fc.toSummary(); + const lineCoverage = fc.getLineCoverage(); + + const fileTotalLines = summary.lines.total; + const fileCoveredLines = summary.lines.covered; + const uncoveredLineNums = Object.entries(lineCoverage) + .filter(([, hits]) => hits === 0) + .map(([line]) => Number(line)) + .sort((a, b) => a - b); + + totalLines += fileTotalLines; + coveredLines += fileCoveredLines; + + if (fileTotalLines > 0) { + fileReports.push({ + path: relPath, + total: fileTotalLines, + covered: fileCoveredLines, + pct: summary.lines.pct.toFixed(1), + uncoveredLines: uncoveredLineNums, + }); + } +} + +// ── 4. Build Markdown comment ────────────────────────────────────── + +/** Collapse consecutive line numbers into ranges: [1,2,3,7,9,10] -> "L1-L3, L7, L9-L10" */ +function consolidateRanges(lines) { + if (lines.length === 0) return ""; + const ranges = []; + let start = lines[0]; + let end = lines[0]; + + for (let i = 1; i < lines.length; i++) { + if (lines[i] === end + 1) { + end = lines[i]; + } else { + ranges.push(start === end ? `L${start}` : `L${start}-L${end}`); + start = lines[i]; + end = lines[i]; + } + } + ranges.push(start === end ? `L${start}` : `L${start}-L${end}`); + return ranges.join(", "); +} + +let comment = `${COMMENT_TAG}\n## Test Coverage Report\n\n`; + +if (fileReports.length === 0) { + comment += "_Changed files have no coverage data (not instrumented or no tests ran)._\n"; +} else { + const pct = totalLines > 0 ? ((coveredLines / totalLines) * 100).toFixed(1) : "0.0"; + const uncoveredTotal = totalLines - coveredLines; + + comment += "| Metric | Value |\n"; + comment += "|--------|-------|\n"; + comment += `| Lines covered | ${coveredLines}/${totalLines} |\n`; + comment += `| Lines not covered | ${uncoveredTotal}/${totalLines} |\n`; + comment += `| Overall coverage | ${pct}% |\n\n`; + + // Per-file breakdown + if (fileReports.length > 1) { + comment += "
\nPer-file breakdown\n\n"; + comment += "| File | Coverage |\n"; + comment += "|------|----------|\n"; + for (const f of fileReports.sort((a, b) => a.path.localeCompare(b.path))) { + comment += `| \`${f.path}\` | ${f.covered}/${f.total} (${f.pct}%) |\n`; + } + comment += "\n
\n\n"; + } + + // Uncovered lines section + const filesWithUncovered = fileReports.filter((f) => f.uncoveredLines.length > 0); + if (filesWithUncovered.length > 0) { + comment += "### Uncovered lines\n\n"; + for (const file of filesWithUncovered.sort((a, b) => a.path.localeCompare(b.path))) { + const ranges = consolidateRanges(file.uncoveredLines); + comment += `- \`${file.path}\`: ${ranges}\n`; + } + comment += "\n"; + } +} + +writeFileSync("coverage-comment.md", comment); diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index da49d50428..c22e2a35a9 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -81,15 +81,21 @@ jobs: go-version-file: backend/go.mod cache: false + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 with: node-version: "24" + cache: pnpm + # This job only needs the root openapi-typescript devDependency, but the + # root is a pnpm workspace: `npm ci` here required a second, npm-managed + # lockfile to be kept in sync by hand, which is what let it drift. - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Regenerate API spec and TS types - run: npm run api + run: pnpm run api # openapi.yaml drift is already caught by TestBuild_MatchesEmbedded in # the build-test job (go test -race ./...). Only schema.ts needs checking here. diff --git a/.github/workflows/react-doctor.yml b/.github/workflows/react-doctor.yml index 7c62f244ea..a78c2ee9ce 100644 --- a/.github/workflows/react-doctor.yml +++ b/.github/workflows/react-doctor.yml @@ -20,7 +20,13 @@ jobs: doctor: runs-on: ubuntu-latest steps: + # Full history: react-doctor diffs against main so it reports only what + # this PR introduces. With a shallow checkout there is no merge base, so + # it falls back to listing every finding in the changed files — including + # ones that already exist on main. - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Composite action: installs react-doctor itself, runs the scan against # the landing site, posts a sticky PR summary + inline review comments, diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 4dd03c7c09..b60f9938ea 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -84,6 +84,14 @@ jobs: - name: Dependency Review uses: actions/dependency-review-action@v4 + # Non-blocking. The action hard-fails with "Dependency review is not + # supported on this repository" unless the Dependency graph is enabled + # under Settings → Code security and analysis — it is off by default on + # forks, so every PR here fails on a repo setting rather than on + # anything in the diff. Enabling that setting makes this step start + # reporting real findings again with no change to this file; until then + # it must not gate merges. + continue-on-error: true with: fail-on-severity: moderate diff --git a/.gitleaks.toml b/.gitleaks.toml index fa3d0a8b6d..641cf9223a 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -28,4 +28,17 @@ regexes = [ "your-token-here", "example\\.com", + # Agent session-ID fixtures in the Go adapter table tests. These are the + # value of ports.MetadataKeyAgentSessionID — a session identifier the daemon + # generates, not a credential. Listed individually so any other secret + # appearing in those files still trips the scan. + "019e950e-52e0-7411-961b-d380ca7e610f", + "abcd1234-5678-90ab-cdef-1234567890ab", + + # PostHog *project* API key (`phc_` prefix). Project keys are write-only + # ingest identifiers meant to ship in client code — this one is imported by + # frontend/src/renderer/lib/telemetry.ts and bundled into the renderer, so it + # is already public by design. Personal API keys (`phx_`) are the secret kind + # and are deliberately not allowlisted here. + "phc_uXAqS8nokL2QLSGBZSEMHTUNVXsFeXu3SrcWG7fjEyVH", ] diff --git a/.prettierignore b/.prettierignore index 4be88241fe..42a561f4a7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,9 +9,17 @@ frontend/release frontend/test-results frontend/playwright-report -# Lockfiles +# Lockfiles — generated, and the format job commits its output back, so +# leaving pnpm-lock.yaml in scope means every push rewrites ~2900 lines of a +# file pnpm owns. package-lock.json frontend/package-lock.json +pnpm-lock.yaml # Go uses gofmt, not Prettier backend/ + +# GitHub refuses a GITHUB_TOKEN-authenticated push that touches workflow files, +# so the format job's auto-commit is rejected outright the moment Prettier +# reformats one of these. Formatting them is not worth losing the whole job. +.github/workflows/ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 5e87083f90..fe8fcaf2cc 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,311 +1,311 @@ -# Final Architecture Plan - -## Core Principles - -1. **Convention over configuration** - Auto-derive everything possible -2. **Single source of truth** - Config file in repo, runtime data in `~/.agent-orchestrator/` -3. **Zero path configuration** - All paths determined automatically -4. **Global uniqueness** - Hash-based namespacing prevents collisions - ---- - -## 1. Directory Structure - -``` -Repo (versioned): -~/any/path/to/agent-orchestrator/ - agent-orchestrator.yaml ← Config file (only this matters) - packages/ - ... - -Runtime Data (not versioned): -~/.agent-orchestrator/ ← Single parent directory - a3b4c5d6e7f8-integrator/ ← {hash}-{projectId} - sessions/ - int-1 ← Session metadata files (no hash prefix) - int-2 - worktrees/ - int-1/ ← Git worktrees (no hash prefix) - int-2/ - archive/ - int-3_2026-02-17T10-30-00 - .origin ← Config path reference - - a3b4c5d6e7f8-backend/ ← Same hash (same config!) - sessions/ - be-1 ← No hash prefix (already namespaced) - worktrees/ - be-1/ - .origin -``` - -**Hash Derivation (from config location):** - -```typescript -const configDir = path.dirname(configPath); // /Users/alice/code/agent-orchestrator -const hash = sha256(configDir).slice(0, 12); // a3b4c5d6e7f8 - -// Each project managed by this config gets a directory -// Format: {hash}-{projectId} -const projectId = path.basename(projectPath); // integrator, backend, etc. -const instanceId = `${hash}-${projectId}`; // a3b4c5d6e7f8-integrator - -// Not configurable! -const projectBaseDir = `~/.agent-orchestrator/${instanceId}`; -const sessionsDir = `${projectBaseDir}/sessions`; -const worktreesDir = `${projectBaseDir}/worktrees`; -``` - -**Key insight:** All projects from the same config share the same hash prefix! - ---- - -## 2. Config File (Minimal) - -```yaml -# agent-orchestrator.yaml - -projects: - - path: ~/repos/integrator # Required: where is the repo? - repo: ComposioHQ/integrator # Required: GitHub repo - defaultBranch: next # Required: base branch - - # Optional overrides: - name: Composio Integrator # Display name (default: folder name) - sessionPrefix: int # Override auto-generated prefix -``` - -**Auto-derived:** - -- Project ID: `basename(path)` → `integrator` -- Session prefix: `generatePrefix("integrator")` → `int` -- Worktree path: `{worktreeDir}/integrator/` - -**That's it! No dataDir, no worktreeDir, no explicit IDs.** - ---- - -## 3. Session Naming - -### User-Facing Names (Elegant) - -``` -{sessionPrefix}-{num} - -int-1, int-2 (integrator) -ao-1, ao-2 (agent-orchestrator) -ss-1, ss-2 (safe-split) -``` - -### Runtime Session Names (Globally Unique) - -``` -{hash}-{sessionPrefix}-{num} - -a3b4c5d6e7f8-int-1 -a3b4c5d6e7f8-ao-1 -f1e2d3c4b5a6-int-1 (different checkout, no collision!) -``` - -On Unix this is the tmux session name. On Windows (where the default runtime is `process`, not `tmux`) the same string identifies the named pipe path `\\.\pipe\ao-pty-{sessionId}` and is recorded in `~/.agent-orchestrator/windows-pty-hosts.json`. - -### Prefix Generation (Clean Heuristic) - -```typescript -function generateSessionPrefix(projectId: string): string { - if (projectId.length <= 4) return projectId.toLowerCase(); - - // CamelCase: PyTorch → pt - const uppercase = projectId.match(/[A-Z]/g); - if (uppercase?.length > 1) { - return uppercase.join("").toLowerCase(); - } - - // kebab-case: agent-orchestrator → ao - if (projectId.includes("-") || projectId.includes("_")) { - const sep = projectId.includes("-") ? "-" : "_"; - return projectId - .split(sep) - .map((w) => w[0]) - .join("") - .toLowerCase(); - } - - // Single word: integrator → int - return projectId.slice(0, 3).toLowerCase(); -} -``` - ---- - -## 4. Metadata Storage - -### File Structure (One Directory Per Project) - -``` -~/.agent-orchestrator/a3b4c5d6e7f8-integrator/ - sessions/ - int-1 ← Metadata file (user-facing session name) - int-2 - worktrees/ - int-1/ - int-2/ - archive/ - int-3_2026-02-17T10-30-00 -``` - -### Metadata File Format (key=value) - -``` -project=integrator -issue=INT-100 -branch=feat/INT-100 -status=working -tmuxName=a3b4c5d6e7f8-int-1 # Unix; on Windows the runtime handle is `pipePath=\\.\pipe\ao-pty-` plus `ptyHostPid` -worktree=/Users/alice/.agent-orchestrator/a3b4c5d6e7f8-integrator/worktrees/int-1 -createdAt=2026-02-17T10:30:00Z -pr=https://github.com/ComposioHQ/integrator/pull/123 -``` - -**Key fields:** - -- `project` - Which project this session belongs to (for filtering) -- `issue` - Linear/GitHub issue ID -- `branch` - Git branch name -- `worktree` - Path to git worktree -- `status` - working/idle/pr_open/merged - ---- - -## 5. User Commands (Simple) - -```bash -# List all sessions -ao list - -# List sessions for specific project -ao list integrator - -# Spawn new session -ao spawn integrator INT-100 - -# Attach to session (orchestrator finds the runtime handle: tmux name on Unix, named pipe on Windows) -ao attach int-1 - -# Kill session -ao kill int-1 - -# Show instance info -ao info -``` - -**No config paths in commands! Everything auto-discovered.** - ---- - -## 6. Multi-Instance Support - -### Same Config → Same Hash - -```yaml -# ~/code/my-orchestrator/agent-orchestrator.yaml -projects: - - path: ~/repos/integrator - - path: ~/repos/backend -``` - -Results in: - -``` -~/.agent-orchestrator/ - a3b4c5d6e7f8-integrator/ ← Same hash (same config) - a3b4c5d6e7f8-backend/ ← Same hash (same config) -``` - -### Different Config Locations → Different Hashes - -``` -~/code/orchestrator/ → hash: a3b4c5d6e7f8 -~/code/orchestrator-v2/ → hash: f1e2d3c4b5a6 -~/splitly-orchestrator/ → hash: 9876abcd5432 -``` - -Results in: - -``` -~/.agent-orchestrator/ - a3b4c5d6e7f8-integrator/ ← From ~/code/orchestrator - f1e2d3c4b5a6-integrator/ ← From ~/code/orchestrator-v2 (different checkout!) - 9876abcd5432-safesplit/ ← From ~/splitly-orchestrator - -# Sessions (no collisions): -a3b4c5d6e7f8-int-1 (main checkout) -f1e2d3c4b5a6-int-1 (v2 checkout) -9876abcd5432-ss-1 (splitly) -``` - -**Each orchestrator checkout gets unique hash. Projects within same config share that hash.** - ---- - -## 7. Complete Example - -```yaml -# ~/code/my-orchestrator/agent-orchestrator.yaml -projects: - - path: ~/repos/integrator - repo: ComposioHQ/integrator - defaultBranch: next - - - path: ~/repos/backend - repo: ComposioHQ/backend - defaultBranch: main - sessionPrefix: be # Override auto-generated "bac" -``` - -**Results in:** - -``` -Config location: - ~/code/my-orchestrator/ - → Hash: a3b4c5d6e7f8 - -Runtime data: - ~/.agent-orchestrator/ - a3b4c5d6e7f8-integrator/ ← Project 1 - sessions/ - int-1 - worktrees/ - int-1/ - - a3b4c5d6e7f8-backend/ ← Project 2 (same hash!) - sessions/ - be-1 - worktrees/ - be-1/ - -Session names: - User-facing: int-1, be-1 - Tmux: a3b4c5d6e7f8-int-1, a3b4c5d6e7f8-be-1 - -Commands: - ao spawn integrator INT-100 - ao attach int-1 -``` - ---- - -## Summary: What Users Configure - -**Required (3 fields per project):** - -1. `path` - Where is the repo? -2. `repo` - GitHub owner/repo -3. `defaultBranch` - Base branch name - -**Optional:** - -- `sessionPrefix` - Override auto-generated prefix -- `name` - Display name - -**That's it! Everything else is automatic.** +# Final Architecture Plan + +## Core Principles + +1. **Convention over configuration** - Auto-derive everything possible +2. **Single source of truth** - Config file in repo, runtime data in `~/.agent-orchestrator/` +3. **Zero path configuration** - All paths determined automatically +4. **Global uniqueness** - Hash-based namespacing prevents collisions + +--- + +## 1. Directory Structure + +``` +Repo (versioned): +~/any/path/to/agent-orchestrator/ + agent-orchestrator.yaml ← Config file (only this matters) + packages/ + ... + +Runtime Data (not versioned): +~/.agent-orchestrator/ ← Single parent directory + a3b4c5d6e7f8-integrator/ ← {hash}-{projectId} + sessions/ + int-1 ← Session metadata files (no hash prefix) + int-2 + worktrees/ + int-1/ ← Git worktrees (no hash prefix) + int-2/ + archive/ + int-3_2026-02-17T10-30-00 + .origin ← Config path reference + + a3b4c5d6e7f8-backend/ ← Same hash (same config!) + sessions/ + be-1 ← No hash prefix (already namespaced) + worktrees/ + be-1/ + .origin +``` + +**Hash Derivation (from config location):** + +```typescript +const configDir = path.dirname(configPath); // /Users/alice/code/agent-orchestrator +const hash = sha256(configDir).slice(0, 12); // a3b4c5d6e7f8 + +// Each project managed by this config gets a directory +// Format: {hash}-{projectId} +const projectId = path.basename(projectPath); // integrator, backend, etc. +const instanceId = `${hash}-${projectId}`; // a3b4c5d6e7f8-integrator + +// Not configurable! +const projectBaseDir = `~/.agent-orchestrator/${instanceId}`; +const sessionsDir = `${projectBaseDir}/sessions`; +const worktreesDir = `${projectBaseDir}/worktrees`; +``` + +**Key insight:** All projects from the same config share the same hash prefix! + +--- + +## 2. Config File (Minimal) + +```yaml +# agent-orchestrator.yaml + +projects: + - path: ~/repos/integrator # Required: where is the repo? + repo: ComposioHQ/integrator # Required: GitHub repo + defaultBranch: next # Required: base branch + + # Optional overrides: + name: Composio Integrator # Display name (default: folder name) + sessionPrefix: int # Override auto-generated prefix +``` + +**Auto-derived:** + +- Project ID: `basename(path)` → `integrator` +- Session prefix: `generatePrefix("integrator")` → `int` +- Worktree path: `{worktreeDir}/integrator/` + +**That's it! No dataDir, no worktreeDir, no explicit IDs.** + +--- + +## 3. Session Naming + +### User-Facing Names (Elegant) + +``` +{sessionPrefix}-{num} + +int-1, int-2 (integrator) +ao-1, ao-2 (agent-orchestrator) +ss-1, ss-2 (safe-split) +``` + +### Runtime Session Names (Globally Unique) + +``` +{hash}-{sessionPrefix}-{num} + +a3b4c5d6e7f8-int-1 +a3b4c5d6e7f8-ao-1 +f1e2d3c4b5a6-int-1 (different checkout, no collision!) +``` + +On Unix this is the tmux session name. On Windows (where the default runtime is `process`, not `tmux`) the same string identifies the named pipe path `\\.\pipe\ao-pty-{sessionId}` and is recorded in `~/.agent-orchestrator/windows-pty-hosts.json`. + +### Prefix Generation (Clean Heuristic) + +```typescript +function generateSessionPrefix(projectId: string): string { + if (projectId.length <= 4) return projectId.toLowerCase(); + + // CamelCase: PyTorch → pt + const uppercase = projectId.match(/[A-Z]/g); + if (uppercase?.length > 1) { + return uppercase.join("").toLowerCase(); + } + + // kebab-case: agent-orchestrator → ao + if (projectId.includes("-") || projectId.includes("_")) { + const sep = projectId.includes("-") ? "-" : "_"; + return projectId + .split(sep) + .map((w) => w[0]) + .join("") + .toLowerCase(); + } + + // Single word: integrator → int + return projectId.slice(0, 3).toLowerCase(); +} +``` + +--- + +## 4. Metadata Storage + +### File Structure (One Directory Per Project) + +``` +~/.agent-orchestrator/a3b4c5d6e7f8-integrator/ + sessions/ + int-1 ← Metadata file (user-facing session name) + int-2 + worktrees/ + int-1/ + int-2/ + archive/ + int-3_2026-02-17T10-30-00 +``` + +### Metadata File Format (key=value) + +``` +project=integrator +issue=INT-100 +branch=feat/INT-100 +status=working +tmuxName=a3b4c5d6e7f8-int-1 # Unix; on Windows the runtime handle is `pipePath=\\.\pipe\ao-pty-` plus `ptyHostPid` +worktree=/Users/alice/.agent-orchestrator/a3b4c5d6e7f8-integrator/worktrees/int-1 +createdAt=2026-02-17T10:30:00Z +pr=https://github.com/ComposioHQ/integrator/pull/123 +``` + +**Key fields:** + +- `project` - Which project this session belongs to (for filtering) +- `issue` - Linear/GitHub issue ID +- `branch` - Git branch name +- `worktree` - Path to git worktree +- `status` - working/idle/pr_open/merged + +--- + +## 5. User Commands (Simple) + +```bash +# List all sessions +ao list + +# List sessions for specific project +ao list integrator + +# Spawn new session +ao spawn integrator INT-100 + +# Attach to session (orchestrator finds the runtime handle: tmux name on Unix, named pipe on Windows) +ao attach int-1 + +# Kill session +ao kill int-1 + +# Show instance info +ao info +``` + +**No config paths in commands! Everything auto-discovered.** + +--- + +## 6. Multi-Instance Support + +### Same Config → Same Hash + +```yaml +# ~/code/my-orchestrator/agent-orchestrator.yaml +projects: + - path: ~/repos/integrator + - path: ~/repos/backend +``` + +Results in: + +``` +~/.agent-orchestrator/ + a3b4c5d6e7f8-integrator/ ← Same hash (same config) + a3b4c5d6e7f8-backend/ ← Same hash (same config) +``` + +### Different Config Locations → Different Hashes + +``` +~/code/orchestrator/ → hash: a3b4c5d6e7f8 +~/code/orchestrator-v2/ → hash: f1e2d3c4b5a6 +~/splitly-orchestrator/ → hash: 9876abcd5432 +``` + +Results in: + +``` +~/.agent-orchestrator/ + a3b4c5d6e7f8-integrator/ ← From ~/code/orchestrator + f1e2d3c4b5a6-integrator/ ← From ~/code/orchestrator-v2 (different checkout!) + 9876abcd5432-safesplit/ ← From ~/splitly-orchestrator + +# Sessions (no collisions): +a3b4c5d6e7f8-int-1 (main checkout) +f1e2d3c4b5a6-int-1 (v2 checkout) +9876abcd5432-ss-1 (splitly) +``` + +**Each orchestrator checkout gets unique hash. Projects within same config share that hash.** + +--- + +## 7. Complete Example + +```yaml +# ~/code/my-orchestrator/agent-orchestrator.yaml +projects: + - path: ~/repos/integrator + repo: ComposioHQ/integrator + defaultBranch: next + + - path: ~/repos/backend + repo: ComposioHQ/backend + defaultBranch: main + sessionPrefix: be # Override auto-generated "bac" +``` + +**Results in:** + +``` +Config location: + ~/code/my-orchestrator/ + → Hash: a3b4c5d6e7f8 + +Runtime data: + ~/.agent-orchestrator/ + a3b4c5d6e7f8-integrator/ ← Project 1 + sessions/ + int-1 + worktrees/ + int-1/ + + a3b4c5d6e7f8-backend/ ← Project 2 (same hash!) + sessions/ + be-1 + worktrees/ + be-1/ + +Session names: + User-facing: int-1, be-1 + Tmux: a3b4c5d6e7f8-int-1, a3b4c5d6e7f8-be-1 + +Commands: + ao spawn integrator INT-100 + ao attach int-1 +``` + +--- + +## Summary: What Users Configure + +**Required (3 fields per project):** + +1. `path` - Where is the repo? +2. `repo` - GitHub owner/repo +3. `defaultBranch` - Base branch name + +**Optional:** + +- `sessionPrefix` - Override auto-generated prefix +- `name` - Display name + +**That's it! Everything else is automatic.** diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 73f7e7155c..ccd93fb7eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,361 +1,361 @@ -# Contributing to Agent Orchestrator - -Thanks for your interest in contributing. This guide covers how to report bugs, submit PRs, and build new plugins. - -## Quick Links - -- [Setup and first build](#development-setup) -- [Plugin development](#building-a-plugin) -- [Code conventions](#code-conventions) -- [PR process](#pull-request-process) - ---- - -## Reporting Bugs - -Open an issue at [github.com/ComposioHQ/agent-orchestrator/issues](https://github.com/ComposioHQ/agent-orchestrator/issues). - -Include: - -- `ao --version` output -- OS and Node.js version (`node --version`) -- Steps to reproduce -- What you expected vs. what happened -- Relevant output from `ao doctor` - ---- - -## Development Setup - -**Prerequisites**: Node.js 20+, pnpm 9.15+, Git 2.25+, gh CLI - -- **Unix (macOS/Linux)**: also install `tmux` — it is the default runtime. -- **Windows**: tmux is **not** required. The default runtime on Windows is `process` (ConPTY via `node-pty`), and PowerShell is the default shell. See [docs/CROSS_PLATFORM.md](docs/CROSS_PLATFORM.md) for what's different on Windows when contributing. - -```bash -git clone https://github.com/ComposioHQ/agent-orchestrator.git -cd agent-orchestrator -pnpm install -pnpm build -``` - -Build order matters — `@aoagents/ao-core` must be built before the CLI, web, or plugins can run. `pnpm build` at the root handles this automatically. - -### Running tests - -```bash -pnpm test # all packages -pnpm --filter @aoagents/ao-core test # core only -pnpm --filter @aoagents/ao-core test -- --watch # watch mode -pnpm test:integration # integration tests -``` - -### Running the dashboard locally - -```bash -cp agent-orchestrator.yaml.example agent-orchestrator.yaml -# edit agent-orchestrator.yaml for your setup -pnpm --filter @aoagents/ao-web dev -``` - -### Refreshing a local AO install - -If your local `ao` launcher or built packages seem stale, refresh the install from a clean `main` checkout: - -```bash -git switch main -git status --short --branch # confirm the install repo is clean -ao update -``` - -`ao update` fast-forwards the local install repo, reinstalls dependencies, clean-rebuilds `@aoagents/ao-core`, `@aoagents/ao-cli`, and `@aoagents/ao-web`, refreshes the global launcher with `npm link`, and finishes with CLI smoke tests. Use `ao update --skip-smoke` when you only need the rebuild step, or `ao update --smoke-only` when validating an existing install. - -## Release Architecture (maintainers only) - -AO uses a **two-stage release pipeline**. This public repo handles version bumps, git tags, and GitHub releases. npm publishing runs on a private server (AO cron job) that polls GitHub releases and publishes when a new tag is ahead of the current npm version. Org compliance forbids npm publish credentials in public repositories, so `NPM_TOKEN` never enters this repo. - -### Where things happen - -| Stage | Where | Responsibility | -| ------------------------ | ------------------------------ | ------------------------------------------------------------------------ | -| Versioning + GitHub release | This repo (public, CI) | Changesets version bumps, git tags, `gh release create` | -| npm publish | Private server (AO cron) | Detects new GitHub releases → builds → `pnpm changeset publish` | - -The flow on every release: - -``` -This repo (public CI) Private server (AO cron) -────────────────────── ───────────────────────── -release.yml: Polls gh release list - changeset version Detects new vX.Y.Z tag - push vX.Y.Z tag Compare to npm @latest/@nightly - gh release create vX.Y.Z If behind → checkout tag → build → publish - -canary.yml: Same cron, detects prereleases - changeset version --snapshot Publishes with --tag nightly - commit snapshot bump + tag - gh release create --prerelease -``` - -Each release pushes a single umbrella `vX.Y.Z` git tag pointing at the version-bump commit. We deliberately do **not** run `pnpm changeset tag`, which would emit one tag per publishable package (~27) every release — fine for stable's monthly cadence, noisy on the nightly cadence (~7 000 tags/year). The npm publisher only consumes the umbrella tag, so the per-package tags add no value. - -### Secrets - -This repo requires **no additional secrets** beyond the automatic `GITHUB_TOKEN`. `NPM_TOKEN` lives only on the private server. - -### How releases are cut - -- **Stable**: merge the "chore: version packages" PR opened by `changesets/action`. `release.yml` tags the bumped packages and creates a `vX.Y.Z` GitHub release. The AO cron detects the new release and publishes to npm `@latest`. -- **Nightly**: `canary.yml` runs on cron (23:30 IST Fri–Tue) or via `workflow_dispatch`. It snapshots versions to `X.Y.Z-nightly-` format (e.g., `0.6.1-nightly-7c46dc92`), tags, and creates a prerelease GitHub release. The AO cron detects the new prerelease and publishes to npm `@nightly`. - -There is no path from this repo that calls `npm publish` directly. - -### Idempotency - -`release.yml` is idempotent: each step (tag push, GitHub release creation) is gated on whether that piece of state already exists, so a re-run after a partial failure picks up only the missing steps. - -The AO cron is also idempotent — `pnpm changeset publish` skips packages whose current version is already on the registry, so re-running after a partial publish is safe. - -### Recovery - -If `release.yml` fails after the GitHub release was created, **re-run the failed workflow**: the state-detection step will see that the tag and release already exist and skip those steps. - -If the AO cron fails to publish, it will retry on the next poll cycle (every 15 minutes). No manual intervention needed for transient failures. For persistent issues, check the cron logs on the private server. - -## Testing your changes - -### Latest main at any time - -```bash -npm install -g @aoagents/ao@nightly -``` - -The nightly cron publishes from `main` daily at 23:30 IST (Fri–Tue). The bake window (Wed–Thu) pauses scheduled nightlies; release captains can re-cut a nightly via `workflow_dispatch` if a fix lands during bake. - ---- - -## Building a Plugin - -The plugin system is the primary extension point. You can add support for new agents, runtimes, issue trackers, and notification channels without modifying core code. - -### 1. Understand the interface - -All plugin interfaces are in [`packages/core/src/types.ts`](packages/core/src/types.ts). Pick the slot that matches what you want to build: - -| Slot | Interface | Example use case | -| ----------- | ----------- | ------------------------------------ | -| `runtime` | `Runtime` | Run agents in Docker, SSH, cloud VMs | -| `agent` | `Agent` | Adapt a new AI coding tool | -| `workspace` | `Workspace` | Different code isolation strategies | -| `tracker` | `Tracker` | Jira, Asana, or custom issue systems | -| `scm` | `SCM` | GitLab, Bitbucket support | -| `notifier` | `Notifier` | Email, Discord, custom webhooks | -| `terminal` | `Terminal` | Different terminal UI integrations | - -### 2. Create the package - -```bash -mkdir -p packages/plugins/runtime-myplugin/src -cd packages/plugins/runtime-myplugin -``` - -`package.json`: - -```json -{ - "name": "@aoagents/ao-runtime-myplugin", - "version": "0.1.0", - "type": "module", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "typecheck": "tsc --noEmit", - "test": "vitest" - }, - "dependencies": { - "@aoagents/ao-core": "workspace:*" - } -} -``` - -`tsconfig.json` — copy from an existing plugin like `packages/plugins/runtime-tmux/`. - -### 3. Implement the interface - -```typescript -// src/index.ts -import type { PluginModule, Runtime } from "@aoagents/ao-core"; - -export const manifest = { - name: "myplugin", - slot: "runtime" as const, - description: "My custom runtime", - version: "0.1.0", -}; - -export function create(): Runtime { - return { - name: "myplugin", - async create(config) { - /* start session */ - }, - async destroy(sessionName) { - /* tear down */ - }, - async send(sessionName, text) { - /* send input */ - }, - async isRunning(sessionName) { - return false; - }, - }; -} - -export default { manifest, create } satisfies PluginModule; -``` - -### 4. Register the plugin - -Add it to the CLI's dependencies in `packages/cli/package.json`: - -```json -"@aoagents/ao-runtime-myplugin": "workspace:*" -``` - -Then register it in `packages/core/src/plugin-registry.ts` inside `loadBuiltins()`. - -### 5. Add tests - -```typescript -// src/index.test.ts -import { describe, it, expect } from "vitest"; -import { create } from "./index.js"; - -describe("myplugin runtime", () => { - it("reports not running for unknown session", async () => { - const runtime = create(); - expect(await runtime.isRunning("unknown-session")).toBe(false); - }); -}); -``` - -### 6. Build and test - -```bash -pnpm --filter @aoagents/ao-runtime-myplugin build -pnpm --filter @aoagents/ao-runtime-myplugin test -``` - -### Publishing to the Marketplace Registry - -To list your plugin in the AO marketplace so others can install it with `ao plugin install`, submit a PR that adds an entry to `packages/cli/src/assets/plugin-registry.json`. - -Each entry requires: - -- **`id`** — short kebab-case name (e.g. `tracker-jira`) -- **`package`** — npm package name -- **`slot`** — one of: `runtime`, `agent`, `workspace`, `tracker`, `scm`, `notifier`, `terminal` -- **`description`** — one-line summary -- **`source`** — always `"registry"` -- **`latestVersion`** — semver string - -Optionally include `setupAction` if post-install configuration is needed (e.g. `"openclaw-setup"`). - -Your plugin package must satisfy the contract in [`docs/PLUGIN_SPEC.md`](docs/PLUGIN_SPEC.md) — export a `PluginModule` with a valid manifest and `create()` function. The package must be published to npm before your registry PR is merged so `ao plugin install` can fetch it. - ---- - -## Code Conventions - -See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for the full reference. The short version: - -### Behavioral Guidelines - -Beyond syntax and style, follow these principles: - -- **State assumptions explicitly** - if a task is ambiguous, present interpretations rather than guessing. -- **Minimum viable change** - no speculative features, no unused abstractions, no formatting changes outside your diff. -- **Every changed line traces to the task** - if you can't explain why a line changed, revert it. -- **Write a failing test first** - for bug fixes, reproduce the bug in a test before implementing the fix. -- **Don't refactor unrelated code** - mention dead code you spot, don't delete it. - -These match the "Working Principles" section in CLAUDE.md. AI agents working on this repo are instructed to follow these same rules. - -**TypeScript** - -- ESM modules, `.js` extensions on local imports -- `node:` prefix for builtins -- No `any` — use `unknown` + type guards -- Strict mode, semicolons, double quotes, 2-space indent - -**Shell commands** - -- Always `execFile`, never `exec` -- Always pass args as an array, never interpolate into strings -- Always add timeouts - -**Tests** - -- Unit tests alongside source in `src/__tests__/` -- Mock plugins in tests — don't call real tmux, GitHub, or external services -- Test the interface contract, not internal implementation details - ---- - -## Pull Request Process - -1. **Fork and branch** from `main`: - - ```bash - git checkout -b feat/your-feature - ``` - -2. **Make your changes** — keep PRs focused on one thing. - -3. **Build, test, lint**: - - ```bash - pnpm build - pnpm test - pnpm lint - pnpm typecheck - ``` - -4. **Commit** with [Conventional Commits](https://www.conventionalcommits.org/): - - ``` - feat: add kubernetes runtime plugin - fix: handle missing LINEAR_API_KEY gracefully - docs: add plugin development guide - chore: update vitest to v2 - ``` - -5. **Push and open a PR**. In the PR description: - - What changed and why - - How to test it - - Link to the issue it closes (e.g., `Closes #123`) - -6. **Address review comments** — update the branch and push. Reply to comments when done. - -### What gets reviewed - -- Does the change work as described? -- Are there tests? -- Does it follow the TypeScript and shell conventions in [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)? -- For new features: is it documented? - -### CI checks - -All PRs must pass: - -- `pnpm build` — no TypeScript errors -- `pnpm test` — all tests green -- `pnpm lint` — no lint errors -- Secret scanning — no leaked credentials - ---- - -## License - -By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE). +# Contributing to Agent Orchestrator + +Thanks for your interest in contributing. This guide covers how to report bugs, submit PRs, and build new plugins. + +## Quick Links + +- [Setup and first build](#development-setup) +- [Plugin development](#building-a-plugin) +- [Code conventions](#code-conventions) +- [PR process](#pull-request-process) + +--- + +## Reporting Bugs + +Open an issue at [github.com/ComposioHQ/agent-orchestrator/issues](https://github.com/ComposioHQ/agent-orchestrator/issues). + +Include: + +- `ao --version` output +- OS and Node.js version (`node --version`) +- Steps to reproduce +- What you expected vs. what happened +- Relevant output from `ao doctor` + +--- + +## Development Setup + +**Prerequisites**: Node.js 20+, pnpm 9.15+, Git 2.25+, gh CLI + +- **Unix (macOS/Linux)**: also install `tmux` — it is the default runtime. +- **Windows**: tmux is **not** required. The default runtime on Windows is `process` (ConPTY via `node-pty`), and PowerShell is the default shell. See [docs/CROSS_PLATFORM.md](docs/CROSS_PLATFORM.md) for what's different on Windows when contributing. + +```bash +git clone https://github.com/ComposioHQ/agent-orchestrator.git +cd agent-orchestrator +pnpm install +pnpm build +``` + +Build order matters — `@aoagents/ao-core` must be built before the CLI, web, or plugins can run. `pnpm build` at the root handles this automatically. + +### Running tests + +```bash +pnpm test # all packages +pnpm --filter @aoagents/ao-core test # core only +pnpm --filter @aoagents/ao-core test -- --watch # watch mode +pnpm test:integration # integration tests +``` + +### Running the dashboard locally + +```bash +cp agent-orchestrator.yaml.example agent-orchestrator.yaml +# edit agent-orchestrator.yaml for your setup +pnpm --filter @aoagents/ao-web dev +``` + +### Refreshing a local AO install + +If your local `ao` launcher or built packages seem stale, refresh the install from a clean `main` checkout: + +```bash +git switch main +git status --short --branch # confirm the install repo is clean +ao update +``` + +`ao update` fast-forwards the local install repo, reinstalls dependencies, clean-rebuilds `@aoagents/ao-core`, `@aoagents/ao-cli`, and `@aoagents/ao-web`, refreshes the global launcher with `npm link`, and finishes with CLI smoke tests. Use `ao update --skip-smoke` when you only need the rebuild step, or `ao update --smoke-only` when validating an existing install. + +## Release Architecture (maintainers only) + +AO uses a **two-stage release pipeline**. This public repo handles version bumps, git tags, and GitHub releases. npm publishing runs on a private server (AO cron job) that polls GitHub releases and publishes when a new tag is ahead of the current npm version. Org compliance forbids npm publish credentials in public repositories, so `NPM_TOKEN` never enters this repo. + +### Where things happen + +| Stage | Where | Responsibility | +| --------------------------- | ------------------------ | --------------------------------------------------------------- | +| Versioning + GitHub release | This repo (public, CI) | Changesets version bumps, git tags, `gh release create` | +| npm publish | Private server (AO cron) | Detects new GitHub releases → builds → `pnpm changeset publish` | + +The flow on every release: + +``` +This repo (public CI) Private server (AO cron) +────────────────────── ───────────────────────── +release.yml: Polls gh release list + changeset version Detects new vX.Y.Z tag + push vX.Y.Z tag Compare to npm @latest/@nightly + gh release create vX.Y.Z If behind → checkout tag → build → publish + +canary.yml: Same cron, detects prereleases + changeset version --snapshot Publishes with --tag nightly + commit snapshot bump + tag + gh release create --prerelease +``` + +Each release pushes a single umbrella `vX.Y.Z` git tag pointing at the version-bump commit. We deliberately do **not** run `pnpm changeset tag`, which would emit one tag per publishable package (~27) every release — fine for stable's monthly cadence, noisy on the nightly cadence (~7 000 tags/year). The npm publisher only consumes the umbrella tag, so the per-package tags add no value. + +### Secrets + +This repo requires **no additional secrets** beyond the automatic `GITHUB_TOKEN`. `NPM_TOKEN` lives only on the private server. + +### How releases are cut + +- **Stable**: merge the "chore: version packages" PR opened by `changesets/action`. `release.yml` tags the bumped packages and creates a `vX.Y.Z` GitHub release. The AO cron detects the new release and publishes to npm `@latest`. +- **Nightly**: `canary.yml` runs on cron (23:30 IST Fri–Tue) or via `workflow_dispatch`. It snapshots versions to `X.Y.Z-nightly-` format (e.g., `0.6.1-nightly-7c46dc92`), tags, and creates a prerelease GitHub release. The AO cron detects the new prerelease and publishes to npm `@nightly`. + +There is no path from this repo that calls `npm publish` directly. + +### Idempotency + +`release.yml` is idempotent: each step (tag push, GitHub release creation) is gated on whether that piece of state already exists, so a re-run after a partial failure picks up only the missing steps. + +The AO cron is also idempotent — `pnpm changeset publish` skips packages whose current version is already on the registry, so re-running after a partial publish is safe. + +### Recovery + +If `release.yml` fails after the GitHub release was created, **re-run the failed workflow**: the state-detection step will see that the tag and release already exist and skip those steps. + +If the AO cron fails to publish, it will retry on the next poll cycle (every 15 minutes). No manual intervention needed for transient failures. For persistent issues, check the cron logs on the private server. + +## Testing your changes + +### Latest main at any time + +```bash +npm install -g @aoagents/ao@nightly +``` + +The nightly cron publishes from `main` daily at 23:30 IST (Fri–Tue). The bake window (Wed–Thu) pauses scheduled nightlies; release captains can re-cut a nightly via `workflow_dispatch` if a fix lands during bake. + +--- + +## Building a Plugin + +The plugin system is the primary extension point. You can add support for new agents, runtimes, issue trackers, and notification channels without modifying core code. + +### 1. Understand the interface + +All plugin interfaces are in [`packages/core/src/types.ts`](packages/core/src/types.ts). Pick the slot that matches what you want to build: + +| Slot | Interface | Example use case | +| ----------- | ----------- | ------------------------------------ | +| `runtime` | `Runtime` | Run agents in Docker, SSH, cloud VMs | +| `agent` | `Agent` | Adapt a new AI coding tool | +| `workspace` | `Workspace` | Different code isolation strategies | +| `tracker` | `Tracker` | Jira, Asana, or custom issue systems | +| `scm` | `SCM` | GitLab, Bitbucket support | +| `notifier` | `Notifier` | Email, Discord, custom webhooks | +| `terminal` | `Terminal` | Different terminal UI integrations | + +### 2. Create the package + +```bash +mkdir -p packages/plugins/runtime-myplugin/src +cd packages/plugins/runtime-myplugin +``` + +`package.json`: + +```json +{ + "name": "@aoagents/ao-runtime-myplugin", + "version": "0.1.0", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "typecheck": "tsc --noEmit", + "test": "vitest" + }, + "dependencies": { + "@aoagents/ao-core": "workspace:*" + } +} +``` + +`tsconfig.json` — copy from an existing plugin like `packages/plugins/runtime-tmux/`. + +### 3. Implement the interface + +```typescript +// src/index.ts +import type { PluginModule, Runtime } from "@aoagents/ao-core"; + +export const manifest = { + name: "myplugin", + slot: "runtime" as const, + description: "My custom runtime", + version: "0.1.0", +}; + +export function create(): Runtime { + return { + name: "myplugin", + async create(config) { + /* start session */ + }, + async destroy(sessionName) { + /* tear down */ + }, + async send(sessionName, text) { + /* send input */ + }, + async isRunning(sessionName) { + return false; + }, + }; +} + +export default { manifest, create } satisfies PluginModule; +``` + +### 4. Register the plugin + +Add it to the CLI's dependencies in `packages/cli/package.json`: + +```json +"@aoagents/ao-runtime-myplugin": "workspace:*" +``` + +Then register it in `packages/core/src/plugin-registry.ts` inside `loadBuiltins()`. + +### 5. Add tests + +```typescript +// src/index.test.ts +import { describe, it, expect } from "vitest"; +import { create } from "./index.js"; + +describe("myplugin runtime", () => { + it("reports not running for unknown session", async () => { + const runtime = create(); + expect(await runtime.isRunning("unknown-session")).toBe(false); + }); +}); +``` + +### 6. Build and test + +```bash +pnpm --filter @aoagents/ao-runtime-myplugin build +pnpm --filter @aoagents/ao-runtime-myplugin test +``` + +### Publishing to the Marketplace Registry + +To list your plugin in the AO marketplace so others can install it with `ao plugin install`, submit a PR that adds an entry to `packages/cli/src/assets/plugin-registry.json`. + +Each entry requires: + +- **`id`** — short kebab-case name (e.g. `tracker-jira`) +- **`package`** — npm package name +- **`slot`** — one of: `runtime`, `agent`, `workspace`, `tracker`, `scm`, `notifier`, `terminal` +- **`description`** — one-line summary +- **`source`** — always `"registry"` +- **`latestVersion`** — semver string + +Optionally include `setupAction` if post-install configuration is needed (e.g. `"openclaw-setup"`). + +Your plugin package must satisfy the contract in [`docs/PLUGIN_SPEC.md`](docs/PLUGIN_SPEC.md) — export a `PluginModule` with a valid manifest and `create()` function. The package must be published to npm before your registry PR is merged so `ao plugin install` can fetch it. + +--- + +## Code Conventions + +See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for the full reference. The short version: + +### Behavioral Guidelines + +Beyond syntax and style, follow these principles: + +- **State assumptions explicitly** - if a task is ambiguous, present interpretations rather than guessing. +- **Minimum viable change** - no speculative features, no unused abstractions, no formatting changes outside your diff. +- **Every changed line traces to the task** - if you can't explain why a line changed, revert it. +- **Write a failing test first** - for bug fixes, reproduce the bug in a test before implementing the fix. +- **Don't refactor unrelated code** - mention dead code you spot, don't delete it. + +These match the "Working Principles" section in CLAUDE.md. AI agents working on this repo are instructed to follow these same rules. + +**TypeScript** + +- ESM modules, `.js` extensions on local imports +- `node:` prefix for builtins +- No `any` — use `unknown` + type guards +- Strict mode, semicolons, double quotes, 2-space indent + +**Shell commands** + +- Always `execFile`, never `exec` +- Always pass args as an array, never interpolate into strings +- Always add timeouts + +**Tests** + +- Unit tests alongside source in `src/__tests__/` +- Mock plugins in tests — don't call real tmux, GitHub, or external services +- Test the interface contract, not internal implementation details + +--- + +## Pull Request Process + +1. **Fork and branch** from `main`: + + ```bash + git checkout -b feat/your-feature + ``` + +2. **Make your changes** — keep PRs focused on one thing. + +3. **Build, test, lint**: + + ```bash + pnpm build + pnpm test + pnpm lint + pnpm typecheck + ``` + +4. **Commit** with [Conventional Commits](https://www.conventionalcommits.org/): + + ``` + feat: add kubernetes runtime plugin + fix: handle missing LINEAR_API_KEY gracefully + docs: add plugin development guide + chore: update vitest to v2 + ``` + +5. **Push and open a PR**. In the PR description: + - What changed and why + - How to test it + - Link to the issue it closes (e.g., `Closes #123`) + +6. **Address review comments** — update the branch and push. Reply to comments when done. + +### What gets reviewed + +- Does the change work as described? +- Are there tests? +- Does it follow the TypeScript and shell conventions in [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)? +- For new features: is it documented? + +### CI checks + +All PRs must pass: + +- `pnpm build` — no TypeScript errors +- `pnpm test` — all tests green +- `pnpm lint` — no lint errors +- Secret scanning — no leaked credentials + +--- + +## License + +By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE). diff --git a/SECURITY.md b/SECURITY.md index 4c970511db..f04b53f18e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,228 +1,228 @@ -# Security Policy - -## Reporting Security Issues - -**Please do not report security vulnerabilities through public GitHub issues.** - -Instead, please report them via email to security@composio.dev. - -You should receive a response within 48 hours. If for some reason you do not, please follow up via email to ensure we received your original message. - -Please include the following information: - -- Type of issue (e.g., secret leak, code injection, authentication bypass) -- Full paths of source file(s) related to the issue -- Location of the affected source code (tag/branch/commit or direct URL) -- Step-by-step instructions to reproduce the issue -- Proof-of-concept or exploit code (if possible) -- Impact of the issue - -## Security Audit History - -### Known Issues - -#### OpenClaw Notifier Token (Resolved) - -**Status**: Removed from codebase -**Severity**: Medium -**Date**: 2026-02-15 -**Commit**: 0393ab70a83e090883895d2168aa39a76f997ec8 - -An OpenClaw notifier token (`1af5c4f...872` - redacted) was accidentally committed in `agent-orchestrator.yaml` and later removed. This token was: - -- Used for local development/testing only -- Never used in production -- Removed in subsequent commits -- Still present in git history - -**Action Required**: If this token is still in use, it should be rotated immediately. - -**Lesson**: All tokens and API keys must use environment variables. The `agent-orchestrator.yaml` file is now in `.gitignore` to prevent future accidental commits. - -## Security Measures - -### Automated Secret Scanning - -This repository uses [Gitleaks](https://github.com/gitleaks/gitleaks) to prevent accidental commits of secrets: - -1. **Pre-commit Hook** — Scans staged files before every commit -2. **CI Pipeline** — Scans full git history on every push/PR -3. **Scheduled Scans** — Weekly scans to catch new vulnerability patterns - -### Dependency Security - -- **Dependency Review** — GitHub Action scans PRs for vulnerable dependencies -- **npm audit** — Runs in CI to detect known vulnerabilities in dependencies -- **Automated Updates** — Dependabot (or similar) for security patches - -## Best Practices for Developers - -### Never Commit Secrets - -❌ **Bad** — Hardcoded secret: - -```yaml -notifiers: - slack: - webhook: https://hooks.slack.com/services/T123/B456/abc123 -``` - -✅ **Good** — Environment variable: - -```yaml -notifiers: - slack: - webhook: ${SLACK_WEBHOOK_URL} -``` - -### Use Environment Variables - -Store all secrets in environment variables: - -```bash -# .env.local (ignored by git) -LINEAR_API_KEY=lin_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -SLACK_WEBHOOK_URL=https://hooks.slack.com/services/... -``` - -Then reference in config: - -```yaml -notifiers: - slack: - webhook: ${SLACK_WEBHOOK_URL} -``` - -### Naming Conventions - -Use consistent environment variable names: - -- `*_API_KEY` — API keys (e.g., `LINEAR_API_KEY`) -- `*_TOKEN` — Authentication tokens (e.g., `GITHUB_TOKEN`) -- `*_SECRET` — Secret keys (e.g., `JWT_SECRET`) -- `*_URL` — URLs that may contain credentials (e.g., `DATABASE_URL`) - -### Example Config Files - -When creating example config files: - -1. Use placeholder values: `your-api-key-here`, `your-token-here` -2. Use environment variable references: `${ENV_VAR}` -3. Never copy real credentials, even "temporarily" -4. Document which environment variables are required - -### Files to Never Commit - -The `.gitignore` excludes these patterns: - -- `.env`, `.env.local`, `.env.*.local` -- `*.key`, `*.pem`, `*.p12`, `*.pfx` -- `secrets.yaml`, `credentials.json` -- `agent-orchestrator.yaml` (local config) - -### Checking for Secrets Locally - -Before committing: - -```bash -# Scan current files -gitleaks detect --no-git - -# Scan staged files (automatic in pre-commit hook) -gitleaks protect --staged - -# Scan full git history -gitleaks detect -``` - -### What to Do If You Commit a Secret - -If you accidentally commit a secret: - -1. **Rotate the secret immediately** — Assume it's compromised -2. **Remove from git history** — Use `git filter-repo` or similar (dangerous!) -3. **Update `.gitleaks.toml`** — Add pattern to prevent similar leaks -4. **Report internally** — Document in SECURITY.md - -**Never** just delete the file and commit — the secret remains in git history! - -### Code Review - -When reviewing PRs: - -- ✅ Check for hardcoded tokens, passwords, API keys -- ✅ Verify environment variables are documented but not hardcoded -- ✅ Ensure example configs use placeholders -- ✅ Confirm CI security check passed - -## Best Practices for Users - -### Secure Configuration - -When setting up Agent Orchestrator: - -1. **Copy example config**: `cp agent-orchestrator.yaml.example agent-orchestrator.yaml` -2. **Add real secrets**: Edit `agent-orchestrator.yaml` with your actual tokens -3. **Never commit local config**: It's in `.gitignore` — keep it there! -4. **Use secret management**: Consider 1Password, AWS Secrets Manager, etc. - -### Required Secrets - -Agent Orchestrator may require these secrets: - -| Service | Environment Variable | Where to Get | -| --------- | -------------------- | ---------------------------------------- | -| GitHub | `GITHUB_TOKEN` | https://github.com/settings/tokens | -| Linear | `LINEAR_API_KEY` | https://linear.app/settings/api | -| Slack | `SLACK_WEBHOOK_URL` | https://api.slack.com/messaging/webhooks | -| Anthropic | `ANTHROPIC_API_KEY` | https://console.anthropic.com/ | - -### Setting Environment Variables - -**macOS/Linux**: - -```bash -# In ~/.zshrc or ~/.bashrc -export GITHUB_TOKEN="ghp_xxxxx" -export LINEAR_API_KEY="lin_api_xxxxx" -``` - -**Or use `.env.local`**: - -```bash -# In your project directory -echo 'GITHUB_TOKEN=ghp_xxxxx' >> .env.local -echo 'LINEAR_API_KEY=lin_api_xxxxx' >> .env.local -``` - -### Protecting Your Secrets - -- ✅ Use strong, unique tokens for each service -- ✅ Rotate tokens regularly (every 90 days) -- ✅ Use minimal permissions (read-only when possible) -- ✅ Store in a password manager -- ❌ Never share tokens in chat, email, or screenshots -- ❌ Never commit to git (public or private repos) -- ❌ Never hardcode in shell scripts - -## Supported Versions - -| Version | Supported | -| ------- | ------------------ | -| 0.1.x | :white_check_mark: | - -Security updates are provided for the latest version only. - -## Security Tools - -This project uses: - -- [Gitleaks](https://github.com/gitleaks/gitleaks) — Secret scanning -- [GitHub Dependency Review](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review) — Dependency vulnerability scanning -- [npm audit](https://docs.npmjs.com/cli/v8/commands/npm-audit) — Dependency vulnerability detection -- [Husky](https://typicode.github.io/husky/) — Git hooks for pre-commit validation - -## License - -This security policy is part of the Agent Orchestrator project and is licensed under the MIT License. +# Security Policy + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them via email to security@composio.dev. + +You should receive a response within 48 hours. If for some reason you do not, please follow up via email to ensure we received your original message. + +Please include the following information: + +- Type of issue (e.g., secret leak, code injection, authentication bypass) +- Full paths of source file(s) related to the issue +- Location of the affected source code (tag/branch/commit or direct URL) +- Step-by-step instructions to reproduce the issue +- Proof-of-concept or exploit code (if possible) +- Impact of the issue + +## Security Audit History + +### Known Issues + +#### OpenClaw Notifier Token (Resolved) + +**Status**: Removed from codebase +**Severity**: Medium +**Date**: 2026-02-15 +**Commit**: 0393ab70a83e090883895d2168aa39a76f997ec8 + +An OpenClaw notifier token (`1af5c4f...872` - redacted) was accidentally committed in `agent-orchestrator.yaml` and later removed. This token was: + +- Used for local development/testing only +- Never used in production +- Removed in subsequent commits +- Still present in git history + +**Action Required**: If this token is still in use, it should be rotated immediately. + +**Lesson**: All tokens and API keys must use environment variables. The `agent-orchestrator.yaml` file is now in `.gitignore` to prevent future accidental commits. + +## Security Measures + +### Automated Secret Scanning + +This repository uses [Gitleaks](https://github.com/gitleaks/gitleaks) to prevent accidental commits of secrets: + +1. **Pre-commit Hook** — Scans staged files before every commit +2. **CI Pipeline** — Scans full git history on every push/PR +3. **Scheduled Scans** — Weekly scans to catch new vulnerability patterns + +### Dependency Security + +- **Dependency Review** — GitHub Action scans PRs for vulnerable dependencies +- **npm audit** — Runs in CI to detect known vulnerabilities in dependencies +- **Automated Updates** — Dependabot (or similar) for security patches + +## Best Practices for Developers + +### Never Commit Secrets + +❌ **Bad** — Hardcoded secret: + +```yaml +notifiers: + slack: + webhook: https://hooks.slack.com/services/T123/B456/abc123 +``` + +✅ **Good** — Environment variable: + +```yaml +notifiers: + slack: + webhook: ${SLACK_WEBHOOK_URL} +``` + +### Use Environment Variables + +Store all secrets in environment variables: + +```bash +# .env.local (ignored by git) +LINEAR_API_KEY=lin_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +SLACK_WEBHOOK_URL=https://hooks.slack.com/services/... +``` + +Then reference in config: + +```yaml +notifiers: + slack: + webhook: ${SLACK_WEBHOOK_URL} +``` + +### Naming Conventions + +Use consistent environment variable names: + +- `*_API_KEY` — API keys (e.g., `LINEAR_API_KEY`) +- `*_TOKEN` — Authentication tokens (e.g., `GITHUB_TOKEN`) +- `*_SECRET` — Secret keys (e.g., `JWT_SECRET`) +- `*_URL` — URLs that may contain credentials (e.g., `DATABASE_URL`) + +### Example Config Files + +When creating example config files: + +1. Use placeholder values: `your-api-key-here`, `your-token-here` +2. Use environment variable references: `${ENV_VAR}` +3. Never copy real credentials, even "temporarily" +4. Document which environment variables are required + +### Files to Never Commit + +The `.gitignore` excludes these patterns: + +- `.env`, `.env.local`, `.env.*.local` +- `*.key`, `*.pem`, `*.p12`, `*.pfx` +- `secrets.yaml`, `credentials.json` +- `agent-orchestrator.yaml` (local config) + +### Checking for Secrets Locally + +Before committing: + +```bash +# Scan current files +gitleaks detect --no-git + +# Scan staged files (automatic in pre-commit hook) +gitleaks protect --staged + +# Scan full git history +gitleaks detect +``` + +### What to Do If You Commit a Secret + +If you accidentally commit a secret: + +1. **Rotate the secret immediately** — Assume it's compromised +2. **Remove from git history** — Use `git filter-repo` or similar (dangerous!) +3. **Update `.gitleaks.toml`** — Add pattern to prevent similar leaks +4. **Report internally** — Document in SECURITY.md + +**Never** just delete the file and commit — the secret remains in git history! + +### Code Review + +When reviewing PRs: + +- ✅ Check for hardcoded tokens, passwords, API keys +- ✅ Verify environment variables are documented but not hardcoded +- ✅ Ensure example configs use placeholders +- ✅ Confirm CI security check passed + +## Best Practices for Users + +### Secure Configuration + +When setting up Agent Orchestrator: + +1. **Copy example config**: `cp agent-orchestrator.yaml.example agent-orchestrator.yaml` +2. **Add real secrets**: Edit `agent-orchestrator.yaml` with your actual tokens +3. **Never commit local config**: It's in `.gitignore` — keep it there! +4. **Use secret management**: Consider 1Password, AWS Secrets Manager, etc. + +### Required Secrets + +Agent Orchestrator may require these secrets: + +| Service | Environment Variable | Where to Get | +| --------- | -------------------- | ---------------------------------------- | +| GitHub | `GITHUB_TOKEN` | https://github.com/settings/tokens | +| Linear | `LINEAR_API_KEY` | https://linear.app/settings/api | +| Slack | `SLACK_WEBHOOK_URL` | https://api.slack.com/messaging/webhooks | +| Anthropic | `ANTHROPIC_API_KEY` | https://console.anthropic.com/ | + +### Setting Environment Variables + +**macOS/Linux**: + +```bash +# In ~/.zshrc or ~/.bashrc +export GITHUB_TOKEN="ghp_xxxxx" +export LINEAR_API_KEY="lin_api_xxxxx" +``` + +**Or use `.env.local`**: + +```bash +# In your project directory +echo 'GITHUB_TOKEN=ghp_xxxxx' >> .env.local +echo 'LINEAR_API_KEY=lin_api_xxxxx' >> .env.local +``` + +### Protecting Your Secrets + +- ✅ Use strong, unique tokens for each service +- ✅ Rotate tokens regularly (every 90 days) +- ✅ Use minimal permissions (read-only when possible) +- ✅ Store in a password manager +- ❌ Never share tokens in chat, email, or screenshots +- ❌ Never commit to git (public or private repos) +- ❌ Never hardcode in shell scripts + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 0.1.x | :white_check_mark: | + +Security updates are provided for the latest version only. + +## Security Tools + +This project uses: + +- [Gitleaks](https://github.com/gitleaks/gitleaks) — Secret scanning +- [GitHub Dependency Review](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review) — Dependency vulnerability scanning +- [npm audit](https://docs.npmjs.com/cli/v8/commands/npm-audit) — Dependency vulnerability detection +- [Husky](https://typicode.github.io/husky/) — Git hooks for pre-commit validation + +## License + +This security policy is part of the Agent Orchestrator project and is licensed under the MIT License. diff --git a/SETUP.md b/SETUP.md index d0254efcf6..4d050235bb 100644 --- a/SETUP.md +++ b/SETUP.md @@ -1,852 +1,852 @@ -# Agent Orchestrator Setup Guide - -Comprehensive guide to installing, configuring, and troubleshooting Agent Orchestrator. - -## Prerequisites - -### Required - -- **Node.js 20+** - Runtime for the orchestrator and CLI - - ```bash - node --version # Should be v20.0.0 or higher - ``` - -- **Git 2.25+** - For repository management and worktrees - - ```bash - git --version - ``` - -- **Terminal runtime** — varies by OS: - - **On macOS / Linux:** `tmux` is required (it's the default runtime). - - ```bash - tmux -V - - # Install on macOS - brew install tmux - - # Install on Ubuntu/Debian - sudo apt install tmux - - # Install on Fedora/RHEL - sudo dnf install tmux - ``` - - **On Windows:** tmux is **not** required. AO uses native ConPTY via the `runtime-process` plugin (the default on Windows). PowerShell 7+ is recommended; if you have Git Bash and prefer bash semantics for shell-out commands, set `AO_SHELL=bash` in your environment. WSL is not required. - -- **GitHub CLI** (for GitHub integration) - Required for PR creation, issue management - - ```bash - gh --version - - # Install on macOS - brew install gh - - # Install on Linux - # See: https://github.com/cli/cli/blob/trunk/docs/install_linux.md - ``` - -### Optional - -- **Linear API Key** - If using Linear for issue tracking - - Get it from: https://linear.app/settings/api - - Set environment variable: `export LINEAR_API_KEY="lin_api_..."` - -- **Slack Webhook** - If using Slack notifications - - Create incoming webhook: https://api.slack.com/messaging/webhooks - - Set environment variable: `export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."` - -## Installation - -### Install via npm (recommended) - -```bash -npm install -g @aoagents/ao - -# Verify -ao --version -``` - -This installs the `ao` CLI globally along with all default plugins and the web dashboard. - -**Permission denied (EACCES)?** This is common on macOS. Three options: - -```bash -# Option 1: Use sudo -sudo npm install -g @aoagents/ao - -# Option 2: Use npx (no global install needed) -npx @aoagents/ao start - -# Option 3: Fix npm permissions permanently (recommended) -mkdir -p ~/.npm-global -npm config set prefix '~/.npm-global' -echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc -source ~/.zshrc -npm install -g @aoagents/ao -``` - -### Build from Source (for contributors) - -If you want to develop or contribute to Agent Orchestrator: - -```bash -# Clone the repository -git clone https://github.com/ComposioHQ/agent-orchestrator -cd agent-orchestrator - -# Run the setup script (installs deps, builds, links CLI) -bash scripts/setup.sh - -# Verify -ao --version -``` - -The setup script handles pnpm installation, dependency resolution, building all packages, and linking the `ao` command globally (with automatic permission handling on macOS). - -## First-Time Setup - -### `ao start` — the only command you need - -`ao start` handles everything: auto-detecting your project, generating config, and launching the dashboard + orchestrator. There are three ways to use it: - -**From a URL (fastest for any repo):** - -```bash -ao start https://github.com/your-org/your-repo -``` - -This clones the repo, auto-detects language/framework/branch, generates `agent-orchestrator.yaml`, and starts everything. Supports GitHub, GitLab, and Bitbucket (HTTPS and SSH): - -```bash -ao start https://github.com/owner/repo -ao start https://gitlab.com/org/project -ao start git@github.com:owner/repo.git -``` - -**From a local repo (zero prompts):** - -```bash -cd ~/your-project -ao start -``` - -Auto-detects git remote, default branch, language, and available agent runtimes. Generates config and starts. - -**Adding more projects:** - -```bash -ao start ~/path/to/another-repo -``` - -If a config already exists, the new project is appended. If not, one is created first. - -### What `ao start` detects automatically - -- **Git remote** — parses `owner/repo` from origin -- **Default branch** — checks symbolic-ref, GitHub API, then common names (main/master) -- **Project type** — language, framework, test runner, package manager -- **Agent runtime** — which AI agents are installed (Claude Code, Codex, Aider, OpenCode) -- **Free port** — if configured port is busy, auto-finds the next available -- **tmux** — warns if not installed (skipped on Windows; AO uses ConPTY there and tmux is not required) -- **GitHub CLI** — checks `gh auth status` - -### Manual Configuration - -If you prefer to write the config by hand: - -```bash -cp agent-orchestrator.yaml.example agent-orchestrator.yaml -nano agent-orchestrator.yaml -``` - -Or start from an example: - -```bash -cp examples/simple-github.yaml agent-orchestrator.yaml -nano agent-orchestrator.yaml -``` - -## Configuration Reference - -### Minimal Configuration - -The absolute minimum needed (everything else has sensible defaults): - -```yaml -projects: - my-app: - repo: owner/my-app - path: ~/my-app - defaultBranch: main -``` - -`ao start` generates this automatically — you only need to write it manually if you want full control. - -### Full Configuration Schema - -See [agent-orchestrator.yaml.example](./agent-orchestrator.yaml.example) for a fully commented example with all options. - -### Plugin Slots - -Agent Orchestrator has 8 plugin slots. All are swappable: - -| Slot | Purpose | Default | Alternatives | -| ------------- | -------------------- | ------------- | ----------------------------------------------- | -| **Runtime** | How sessions run | `tmux` (macOS/Linux) / `process` (Windows; ConPTY via node-pty) | `process`, `docker`, `kubernetes`, `ssh`, `e2b` | -| **Agent** | AI coding assistant | `claude-code` | `codex`, `aider`, `goose`, custom | -| **Workspace** | Workspace isolation | `worktree` | `clone`, `copy` | -| **Tracker** | Issue tracking | `github` | `linear`, `jira`, custom | -| **SCM** | Source control | `github` | GitLab, Bitbucket (future) | -| **Notifier** | Notifications | `desktop` | `slack`, `discord`, `webhook`, `email` | -| **Terminal** | Terminal integration | `iterm2` | `web`, custom | -| **Lifecycle** | Session lifecycle | (core) | Non-pluggable | - -### Reactions - -Reactions are auto-responses to events. Configure how the orchestrator handles common scenarios: - -#### CI Failed - -```yaml -reactions: - ci-failed: - auto: true # Enable auto-handling - action: send-to-agent # Send failure logs to agent - retries: 2 # Retry up to 2 times - escalateAfter: 2 # Notify human after 2 failures -``` - -#### Changes Requested (Review Comments) - -```yaml -reactions: - changes-requested: - auto: true - action: send-to-agent - escalateAfter: 30m # Notify human if not resolved in 30 minutes -``` - -#### Approved and Green (Auto-merge) - -```yaml -reactions: - approved-and-green: - auto: true # Enable auto-merge - action: auto-merge # Merge when approved + CI passes - priority: action # Notification priority -``` - -**Warning:** Only enable auto-merge if you trust your CI pipeline and agents! - -#### Agent Stuck - -```yaml -reactions: - agent-stuck: - threshold: 10m # Consider stuck after 10 minutes of inactivity - action: notify - priority: urgent -``` - -### Notification Routing - -Route notifications by priority: - -```yaml -notificationRouting: - urgent: [desktop, slack] # Agent stuck, needs input, errored - action: [desktop, slack] # PR ready to merge - warning: [slack] # Auto-fix failed - info: [slack] # Summary, all done -``` - -### Agent Rules - -Inline rules included in every agent prompt: - -```yaml -projects: - my-app: - agentRules: | - Always run tests before pushing. - Use conventional commits (feat:, fix:, chore:). - Link issue numbers in commit messages. -``` - -Or reference an external file: - -```yaml -projects: - my-app: - agentRulesFile: .agent-rules.md -``` - -### Per-Project Overrides - -Override defaults per project: - -```yaml -projects: - frontend: - runtime: tmux # default on macOS/Linux; on Windows use `process` - agent: claude-code - workspace: worktree - - backend: - runtime: docker # Use Docker for backend - agent: codex # Use Codex instead of Claude -``` - -## Integration Guides - -### GitHub Issues - -**Authentication:** - -```bash -gh auth login -``` - -**Required scopes:** - -- `repo` - Full repository access -- `read:org` - Read organization membership (for team mentions) - -**Verification:** - -```bash -gh auth status -``` - -### Linear - -**Setup:** - -1. Get your API key: https://linear.app/settings/api -2. Add to environment: - - ```bash - echo 'export LINEAR_API_KEY="lin_api_..."' >> ~/.zshrc - source ~/.zshrc - ``` - -3. Find your team ID: - - Go to https://linear.app/settings/api - - Click "Create new key" or use existing key - - Team ID is visible in your Linear workspace URL or via API - -4. Configure in `agent-orchestrator.yaml`: - ```yaml - projects: - my-app: - tracker: - plugin: linear - teamId: "your-team-id" - ``` - -**Branch names:** On `ao spawn ` with the Linear tracker, AO **prefers** Linear’s branch name (same as **Copy git branch name**, API field `branchName`). If that value is missing, it **falls back** to the previous convention: `feat/` (e.g. `feat/INT-123`). To change how Linear generates `branchName`, use **Linear → Settings → Integrations → GitHub → Branch format**. - -**Verification:** - -```bash -echo $LINEAR_API_KEY # Should print your key -``` - -### Slack - -**Setup:** - -1. Create incoming webhook: https://api.slack.com/messaging/webhooks -2. Add to environment: - - ```bash - echo 'export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."' >> ~/.zshrc - source ~/.zshrc - ``` - -3. Configure in `agent-orchestrator.yaml`: - ```yaml - notifiers: - slack: - plugin: slack - webhook: ${SLACK_WEBHOOK_URL} - channel: "#agent-updates" - ``` - -**Verification:** - -```bash -# Send test message -curl -X POST -H 'Content-type: application/json' \ - --data '{"text":"Agent Orchestrator test"}' \ - $SLACK_WEBHOOK_URL -``` - -### Custom Trackers - -To add a custom tracker (Jira, Asana, etc.), create a plugin: - -1. See plugin examples in `packages/plugins/tracker-*/` -2. Implement the `Tracker` interface from `@aoagents/ao-core` -3. Register your plugin in the config - -See [Development Guide](./docs/DEVELOPMENT.md) for plugin development guidelines. - -## Troubleshooting - -### Run `ao doctor` - -Use the built-in doctor before debugging a broken install by hand: - -```bash -ao doctor -ao doctor --fix -``` - -`ao doctor` reports deterministic PASS/WARN/FAIL checks for PATH and launcher resolution, required binaries, terminal-runtime health (tmux on Unix; PowerShell / `runtime-process` on Windows), GitHub CLI health, stale AO temp files, config support directories, and core build/runtime sanity. It runs and is supported on Windows. `--fix` only applies safe fixes such as creating missing AO support directories, refreshing the local launcher link, and removing stale AO temp files. - -### Run `ao update` - -When you installed AO from this repository and want to refresh that local install: - -```bash -git switch main -ao update -``` - -`ao update` is intentionally conservative: it requires a clean working tree on `main`, fast-forwards from `origin/main`, reinstalls dependencies, clean-rebuilds the critical core/CLI/web packages, refreshes the launcher with `npm link`, and runs CLI smoke tests. Works on macOS, Linux, and Windows (Windows uses the bundled `ao-update.ps1` script automatically). Use `ao update --skip-smoke` to stop after rebuild, or `ao update --smoke-only` to rerun just the smoke checks. - -### "No agent-orchestrator.yaml found" - -**Problem:** The orchestrator can't find your config file. - -**Solution:** - -```bash -# ao start auto-creates the config if none exists -ao start - -# Or copy an example and edit manually -cp examples/simple-github.yaml agent-orchestrator.yaml -``` - -### "tmux not found" - -**Problem:** tmux is not installed (required for the tmux runtime — the default on macOS and Linux). - -**Solution:** - -```bash -# macOS -brew install tmux - -# Ubuntu/Debian -sudo apt install tmux - -# Fedora/RHEL -sudo dnf install tmux -``` - -**On Windows:** this error should not appear in normal use. If it does, your config has `runtime: tmux` set explicitly. Switch to `runtime: process` (or remove the override — `process` is the Windows default), and AO will use ConPTY natively without tmux. - -### "gh auth failed" - -**Problem:** GitHub CLI is not authenticated. - -**Solution:** - -```bash -gh auth login - -# Select: -# - GitHub.com (not Enterprise) -# - HTTPS (recommended) -# - Authenticate with browser -# - Include repo scope -``` - -**Verify:** - -```bash -gh auth status -``` - -### "LINEAR_API_KEY not found" - -**Problem:** Linear API key is not set in environment. - -**Solution:** - -```bash -# Get your key from: https://linear.app/settings/api - -# Add to shell profile -echo 'export LINEAR_API_KEY="lin_api_..."' >> ~/.zshrc -source ~/.zshrc - -# Verify -echo $LINEAR_API_KEY -``` - -### "Port already in use" - -**Problem:** Another service is using the dashboard port (default 3000). - -**Note:** `ao start` automatically finds the next free port if the configured port is busy. You'll see a message like "Port 3000 is busy — using 3001 instead." If you still need to fix it manually: - -```bash -# Option 1: Change port in agent-orchestrator.yaml -port: 3001 - -# Option 2: Find and kill the process using the port -lsof -ti:3000 | xargs kill -``` - -### "Workspace creation failed" - -**Problem:** Orchestrator can't create worktrees or clones. - -**Solution:** - -```bash -# AO stores runtime data under ~/.agent-orchestrator/ -ls -la ~/.agent-orchestrator - -# Create the base directory if missing -mkdir -p ~/.agent-orchestrator - -# Check disk space -df -h -``` - -### "Session not found" - -**Problem:** Session ID doesn't exist or was already destroyed. - -**Solution:** - -```bash -# List active sessions -ao session ls - -# Check status dashboard -ao status -``` - -### "Agent not responding" - -**Problem:** Agent session is stuck or frozen. - -**Solution:** - -```bash -# Check session status -ao status - -# Attach to session to investigate -ao open - -# Send message to agent -ao send "Please report your current status" - -# Kill and respawn if necessary -ao session kill -ao spawn -``` - -### "Permission denied" when spawning - -**Problem:** Agent doesn't have permissions for git operations. - -**Solution:** - -```bash -# Check SSH keys are added -ssh -T git@github.com - -# Add SSH key if needed -ssh-add ~/.ssh/id_ed25519 - -# Or use HTTPS and authenticate gh CLI -gh auth login -``` - -### "YAML parse error" - -**Problem:** Syntax error in `agent-orchestrator.yaml`. - -**Solution:** - -```bash -# Validate YAML syntax online: https://www.yamllint.com/ - -# Common issues: -# - Incorrect indentation (use 2 spaces, not tabs) -# - Missing quotes around strings with special characters -# - Typo in field names -``` - -### "Node version too old" - -**Problem:** Node.js version is below 20. - -**Solution:** - -```bash -# Check version -node --version - -# Upgrade with nvm (recommended) -nvm install 20 -nvm use 20 -nvm alias default 20 - -# Or download from: https://nodejs.org/ -``` - -## Advanced Configuration - -### Multi-Project Setup - -Manage multiple repositories: - -```yaml -projects: - frontend: - repo: org/frontend - path: ~/frontend - sessionPrefix: fe - - backend: - repo: org/backend - path: ~/backend - sessionPrefix: api - - docs: - repo: org/docs - path: ~/docs - sessionPrefix: doc -``` - -See [examples/multi-project.yaml](./examples/multi-project.yaml) for full example. - -### Custom Plugin Development - -Create custom plugins for: - -- Different runtimes (Docker, Kubernetes, SSH, cloud VMs) -- Different agents (custom AI assistants) -- Different trackers (Jira, Asana, custom systems) -- Different notifiers (email, webhooks, custom integrations) - -See [Development Guide](./docs/DEVELOPMENT.md) for plugin development guidelines. - -### Docker Runtime - -Run agents in Docker containers: - -```yaml -defaults: - runtime: docker - -# Plugin will use official images or build from Dockerfile -``` - -### Kubernetes Runtime - -Run agents in Kubernetes pods: - -```yaml -defaults: - runtime: kubernetes - -# Requires kubectl configured with cluster access -``` - -### Custom Notifiers - -Send notifications to custom webhooks: - -```yaml -notifiers: - webhook: - plugin: webhook - url: https://your-service.com/webhook - method: POST - headers: - Authorization: "Bearer ${WEBHOOK_TOKEN}" -``` - -## FAQ - -### What's a session? - -A session is an isolated workspace where an agent works on a single issue. Each session has: - -- Its own git worktree or clone -- Its own runtime session — a tmux session on macOS/Linux, a ConPTY pty-host process on Windows (or a Docker container, etc.) -- Its own metadata (branch, PR, status) -- Its own event log - -Sessions are ephemeral — they're created for an issue and destroyed when merged. - -### What's a worktree vs clone? - -**Worktree** (default): - -- Shares `.git` directory with main repo -- Fast to create (no cloning) -- Efficient disk usage -- Best for local development - -**Clone**: - -- Full independent repository clone -- Slower to create -- More disk space -- Better for isolation, remote work - -### How do reactions work? - -Reactions are event handlers that run automatically: - -1. Event occurs (CI fails, review comment added, PR approved) -2. Orchestrator checks reaction config -3. If `auto: true`, performs the action automatically -4. If escalation threshold reached, notifies human - -Actions can be: - -- `send-to-agent` - Forward event to agent to handle -- `auto-merge` - Merge PR automatically -- `notify` - Send notification to human - -### When should I enable auto-merge? - -Enable auto-merge if: - -- ✅ You have comprehensive CI/CD tests -- ✅ You require code review approval -- ✅ You trust your agents to write correct code -- ✅ You want maximum automation - -Don't enable auto-merge if: - -- ❌ You have incomplete test coverage -- ❌ You want manual review of every change -- ❌ You're still evaluating agent quality -- ❌ You work on critical systems (finance, healthcare, etc.) - -Start with `auto: false` and enable after building confidence. - -### How do I add custom agent rules? - -**Inline:** - -```yaml -projects: - my-app: - agentRules: | - Always run tests before pushing. - Use conventional commits. -``` - -**External file:** - -```yaml -projects: - my-app: - agentRulesFile: .agent-rules.md -``` - -Rules are included in every agent prompt for that project. - -### Can I use multiple trackers? - -Yes! Different projects can use different trackers: - -```yaml -projects: - frontend: - tracker: - plugin: github - - backend: - tracker: - plugin: linear - teamId: "..." -``` - -### How do I monitor agent progress? - -Three ways: - -1. **Dashboard** - `ao start` then visit http://localhost:3000 (or your configured `port:`) -2. **CLI status** - `ao status` (text-based dashboard) -3. **Attach to session** - `ao open ` (live terminal) - -### What if an agent gets stuck? - -```bash -# Check status -ao status - -# Send message -ao send "What's your current status?" - -# Attach to investigate -ao open - -# Kill and respawn if necessary -ao session kill -ao spawn -``` - -Agents also send "stuck" notifications automatically after inactivity threshold. - -### How do I clean up old sessions? - -```bash -# List all sessions -ao session ls - -# Kill specific session -ao session kill - -# Cleanup script (example) -ao session ls --json --include-terminated | jq -r '.data[] | select(.status == "merged") | .id' | xargs -I{} ao session kill {} -``` - -> **Note:** `ao session ls --json` and `ao status --json` emit `{ data: [...], meta: { hiddenTerminatedCount } }`. By default terminated sessions (`killed`, `terminated`, `done`, `merged`, `errored`, `cleanup`) are hidden — pass `--include-terminated` to include them in `data`. - -### Can I run multiple orchestrators? - -Yes! Each orchestrator instance should have: - -- Different dashboard port (`port`) — e.g., 3000 for project A, 3001 for project B -- Different config location or project paths - -AO derives runtime directories from the config location, so separate config locations already produce separate hash-scoped runtime paths under `~/.agent-orchestrator/`. Terminal WebSocket ports are auto-detected by default, so you typically only need to set `port:` differently. If you need explicit control, you can also set `terminalPort:` and `directTerminalPort:` per config. - -Useful for: - -- Separating projects -- Different teams -- Testing new configs - -## Next Steps - -1. **Start the orchestrator** — `ao start` (auto-creates config on first run) -2. **Spawn an agent** — `ao spawn 123` (project auto-detected from cwd) -3. **Monitor progress** — `ao status` or dashboard at http://localhost:3000 -4. **Read [Development Guide](./docs/DEVELOPMENT.md)** — Code conventions and architecture -5. **Explore examples** — See [examples/](./examples/) for more configs -6. **Join the community** — Report issues, share configs, contribute plugins - ---- - -**Need help?** Open an issue at: https://github.com/ComposioHQ/agent-orchestrator/issues +# Agent Orchestrator Setup Guide + +Comprehensive guide to installing, configuring, and troubleshooting Agent Orchestrator. + +## Prerequisites + +### Required + +- **Node.js 20+** - Runtime for the orchestrator and CLI + + ```bash + node --version # Should be v20.0.0 or higher + ``` + +- **Git 2.25+** - For repository management and worktrees + + ```bash + git --version + ``` + +- **Terminal runtime** — varies by OS: + + **On macOS / Linux:** `tmux` is required (it's the default runtime). + + ```bash + tmux -V + + # Install on macOS + brew install tmux + + # Install on Ubuntu/Debian + sudo apt install tmux + + # Install on Fedora/RHEL + sudo dnf install tmux + ``` + + **On Windows:** tmux is **not** required. AO uses native ConPTY via the `runtime-process` plugin (the default on Windows). PowerShell 7+ is recommended; if you have Git Bash and prefer bash semantics for shell-out commands, set `AO_SHELL=bash` in your environment. WSL is not required. + +- **GitHub CLI** (for GitHub integration) - Required for PR creation, issue management + + ```bash + gh --version + + # Install on macOS + brew install gh + + # Install on Linux + # See: https://github.com/cli/cli/blob/trunk/docs/install_linux.md + ``` + +### Optional + +- **Linear API Key** - If using Linear for issue tracking + - Get it from: https://linear.app/settings/api + - Set environment variable: `export LINEAR_API_KEY="lin_api_..."` + +- **Slack Webhook** - If using Slack notifications + - Create incoming webhook: https://api.slack.com/messaging/webhooks + - Set environment variable: `export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."` + +## Installation + +### Install via npm (recommended) + +```bash +npm install -g @aoagents/ao + +# Verify +ao --version +``` + +This installs the `ao` CLI globally along with all default plugins and the web dashboard. + +**Permission denied (EACCES)?** This is common on macOS. Three options: + +```bash +# Option 1: Use sudo +sudo npm install -g @aoagents/ao + +# Option 2: Use npx (no global install needed) +npx @aoagents/ao start + +# Option 3: Fix npm permissions permanently (recommended) +mkdir -p ~/.npm-global +npm config set prefix '~/.npm-global' +echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc +source ~/.zshrc +npm install -g @aoagents/ao +``` + +### Build from Source (for contributors) + +If you want to develop or contribute to Agent Orchestrator: + +```bash +# Clone the repository +git clone https://github.com/ComposioHQ/agent-orchestrator +cd agent-orchestrator + +# Run the setup script (installs deps, builds, links CLI) +bash scripts/setup.sh + +# Verify +ao --version +``` + +The setup script handles pnpm installation, dependency resolution, building all packages, and linking the `ao` command globally (with automatic permission handling on macOS). + +## First-Time Setup + +### `ao start` — the only command you need + +`ao start` handles everything: auto-detecting your project, generating config, and launching the dashboard + orchestrator. There are three ways to use it: + +**From a URL (fastest for any repo):** + +```bash +ao start https://github.com/your-org/your-repo +``` + +This clones the repo, auto-detects language/framework/branch, generates `agent-orchestrator.yaml`, and starts everything. Supports GitHub, GitLab, and Bitbucket (HTTPS and SSH): + +```bash +ao start https://github.com/owner/repo +ao start https://gitlab.com/org/project +ao start git@github.com:owner/repo.git +``` + +**From a local repo (zero prompts):** + +```bash +cd ~/your-project +ao start +``` + +Auto-detects git remote, default branch, language, and available agent runtimes. Generates config and starts. + +**Adding more projects:** + +```bash +ao start ~/path/to/another-repo +``` + +If a config already exists, the new project is appended. If not, one is created first. + +### What `ao start` detects automatically + +- **Git remote** — parses `owner/repo` from origin +- **Default branch** — checks symbolic-ref, GitHub API, then common names (main/master) +- **Project type** — language, framework, test runner, package manager +- **Agent runtime** — which AI agents are installed (Claude Code, Codex, Aider, OpenCode) +- **Free port** — if configured port is busy, auto-finds the next available +- **tmux** — warns if not installed (skipped on Windows; AO uses ConPTY there and tmux is not required) +- **GitHub CLI** — checks `gh auth status` + +### Manual Configuration + +If you prefer to write the config by hand: + +```bash +cp agent-orchestrator.yaml.example agent-orchestrator.yaml +nano agent-orchestrator.yaml +``` + +Or start from an example: + +```bash +cp examples/simple-github.yaml agent-orchestrator.yaml +nano agent-orchestrator.yaml +``` + +## Configuration Reference + +### Minimal Configuration + +The absolute minimum needed (everything else has sensible defaults): + +```yaml +projects: + my-app: + repo: owner/my-app + path: ~/my-app + defaultBranch: main +``` + +`ao start` generates this automatically — you only need to write it manually if you want full control. + +### Full Configuration Schema + +See [agent-orchestrator.yaml.example](./agent-orchestrator.yaml.example) for a fully commented example with all options. + +### Plugin Slots + +Agent Orchestrator has 8 plugin slots. All are swappable: + +| Slot | Purpose | Default | Alternatives | +| ------------- | -------------------- | --------------------------------------------------------------- | ----------------------------------------------- | +| **Runtime** | How sessions run | `tmux` (macOS/Linux) / `process` (Windows; ConPTY via node-pty) | `process`, `docker`, `kubernetes`, `ssh`, `e2b` | +| **Agent** | AI coding assistant | `claude-code` | `codex`, `aider`, `goose`, custom | +| **Workspace** | Workspace isolation | `worktree` | `clone`, `copy` | +| **Tracker** | Issue tracking | `github` | `linear`, `jira`, custom | +| **SCM** | Source control | `github` | GitLab, Bitbucket (future) | +| **Notifier** | Notifications | `desktop` | `slack`, `discord`, `webhook`, `email` | +| **Terminal** | Terminal integration | `iterm2` | `web`, custom | +| **Lifecycle** | Session lifecycle | (core) | Non-pluggable | + +### Reactions + +Reactions are auto-responses to events. Configure how the orchestrator handles common scenarios: + +#### CI Failed + +```yaml +reactions: + ci-failed: + auto: true # Enable auto-handling + action: send-to-agent # Send failure logs to agent + retries: 2 # Retry up to 2 times + escalateAfter: 2 # Notify human after 2 failures +``` + +#### Changes Requested (Review Comments) + +```yaml +reactions: + changes-requested: + auto: true + action: send-to-agent + escalateAfter: 30m # Notify human if not resolved in 30 minutes +``` + +#### Approved and Green (Auto-merge) + +```yaml +reactions: + approved-and-green: + auto: true # Enable auto-merge + action: auto-merge # Merge when approved + CI passes + priority: action # Notification priority +``` + +**Warning:** Only enable auto-merge if you trust your CI pipeline and agents! + +#### Agent Stuck + +```yaml +reactions: + agent-stuck: + threshold: 10m # Consider stuck after 10 minutes of inactivity + action: notify + priority: urgent +``` + +### Notification Routing + +Route notifications by priority: + +```yaml +notificationRouting: + urgent: [desktop, slack] # Agent stuck, needs input, errored + action: [desktop, slack] # PR ready to merge + warning: [slack] # Auto-fix failed + info: [slack] # Summary, all done +``` + +### Agent Rules + +Inline rules included in every agent prompt: + +```yaml +projects: + my-app: + agentRules: | + Always run tests before pushing. + Use conventional commits (feat:, fix:, chore:). + Link issue numbers in commit messages. +``` + +Or reference an external file: + +```yaml +projects: + my-app: + agentRulesFile: .agent-rules.md +``` + +### Per-Project Overrides + +Override defaults per project: + +```yaml +projects: + frontend: + runtime: tmux # default on macOS/Linux; on Windows use `process` + agent: claude-code + workspace: worktree + + backend: + runtime: docker # Use Docker for backend + agent: codex # Use Codex instead of Claude +``` + +## Integration Guides + +### GitHub Issues + +**Authentication:** + +```bash +gh auth login +``` + +**Required scopes:** + +- `repo` - Full repository access +- `read:org` - Read organization membership (for team mentions) + +**Verification:** + +```bash +gh auth status +``` + +### Linear + +**Setup:** + +1. Get your API key: https://linear.app/settings/api +2. Add to environment: + + ```bash + echo 'export LINEAR_API_KEY="lin_api_..."' >> ~/.zshrc + source ~/.zshrc + ``` + +3. Find your team ID: + - Go to https://linear.app/settings/api + - Click "Create new key" or use existing key + - Team ID is visible in your Linear workspace URL or via API + +4. Configure in `agent-orchestrator.yaml`: + ```yaml + projects: + my-app: + tracker: + plugin: linear + teamId: "your-team-id" + ``` + +**Branch names:** On `ao spawn ` with the Linear tracker, AO **prefers** Linear’s branch name (same as **Copy git branch name**, API field `branchName`). If that value is missing, it **falls back** to the previous convention: `feat/` (e.g. `feat/INT-123`). To change how Linear generates `branchName`, use **Linear → Settings → Integrations → GitHub → Branch format**. + +**Verification:** + +```bash +echo $LINEAR_API_KEY # Should print your key +``` + +### Slack + +**Setup:** + +1. Create incoming webhook: https://api.slack.com/messaging/webhooks +2. Add to environment: + + ```bash + echo 'export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."' >> ~/.zshrc + source ~/.zshrc + ``` + +3. Configure in `agent-orchestrator.yaml`: + ```yaml + notifiers: + slack: + plugin: slack + webhook: ${SLACK_WEBHOOK_URL} + channel: "#agent-updates" + ``` + +**Verification:** + +```bash +# Send test message +curl -X POST -H 'Content-type: application/json' \ + --data '{"text":"Agent Orchestrator test"}' \ + $SLACK_WEBHOOK_URL +``` + +### Custom Trackers + +To add a custom tracker (Jira, Asana, etc.), create a plugin: + +1. See plugin examples in `packages/plugins/tracker-*/` +2. Implement the `Tracker` interface from `@aoagents/ao-core` +3. Register your plugin in the config + +See [Development Guide](./docs/DEVELOPMENT.md) for plugin development guidelines. + +## Troubleshooting + +### Run `ao doctor` + +Use the built-in doctor before debugging a broken install by hand: + +```bash +ao doctor +ao doctor --fix +``` + +`ao doctor` reports deterministic PASS/WARN/FAIL checks for PATH and launcher resolution, required binaries, terminal-runtime health (tmux on Unix; PowerShell / `runtime-process` on Windows), GitHub CLI health, stale AO temp files, config support directories, and core build/runtime sanity. It runs and is supported on Windows. `--fix` only applies safe fixes such as creating missing AO support directories, refreshing the local launcher link, and removing stale AO temp files. + +### Run `ao update` + +When you installed AO from this repository and want to refresh that local install: + +```bash +git switch main +ao update +``` + +`ao update` is intentionally conservative: it requires a clean working tree on `main`, fast-forwards from `origin/main`, reinstalls dependencies, clean-rebuilds the critical core/CLI/web packages, refreshes the launcher with `npm link`, and runs CLI smoke tests. Works on macOS, Linux, and Windows (Windows uses the bundled `ao-update.ps1` script automatically). Use `ao update --skip-smoke` to stop after rebuild, or `ao update --smoke-only` to rerun just the smoke checks. + +### "No agent-orchestrator.yaml found" + +**Problem:** The orchestrator can't find your config file. + +**Solution:** + +```bash +# ao start auto-creates the config if none exists +ao start + +# Or copy an example and edit manually +cp examples/simple-github.yaml agent-orchestrator.yaml +``` + +### "tmux not found" + +**Problem:** tmux is not installed (required for the tmux runtime — the default on macOS and Linux). + +**Solution:** + +```bash +# macOS +brew install tmux + +# Ubuntu/Debian +sudo apt install tmux + +# Fedora/RHEL +sudo dnf install tmux +``` + +**On Windows:** this error should not appear in normal use. If it does, your config has `runtime: tmux` set explicitly. Switch to `runtime: process` (or remove the override — `process` is the Windows default), and AO will use ConPTY natively without tmux. + +### "gh auth failed" + +**Problem:** GitHub CLI is not authenticated. + +**Solution:** + +```bash +gh auth login + +# Select: +# - GitHub.com (not Enterprise) +# - HTTPS (recommended) +# - Authenticate with browser +# - Include repo scope +``` + +**Verify:** + +```bash +gh auth status +``` + +### "LINEAR_API_KEY not found" + +**Problem:** Linear API key is not set in environment. + +**Solution:** + +```bash +# Get your key from: https://linear.app/settings/api + +# Add to shell profile +echo 'export LINEAR_API_KEY="lin_api_..."' >> ~/.zshrc +source ~/.zshrc + +# Verify +echo $LINEAR_API_KEY +``` + +### "Port already in use" + +**Problem:** Another service is using the dashboard port (default 3000). + +**Note:** `ao start` automatically finds the next free port if the configured port is busy. You'll see a message like "Port 3000 is busy — using 3001 instead." If you still need to fix it manually: + +```bash +# Option 1: Change port in agent-orchestrator.yaml +port: 3001 + +# Option 2: Find and kill the process using the port +lsof -ti:3000 | xargs kill +``` + +### "Workspace creation failed" + +**Problem:** Orchestrator can't create worktrees or clones. + +**Solution:** + +```bash +# AO stores runtime data under ~/.agent-orchestrator/ +ls -la ~/.agent-orchestrator + +# Create the base directory if missing +mkdir -p ~/.agent-orchestrator + +# Check disk space +df -h +``` + +### "Session not found" + +**Problem:** Session ID doesn't exist or was already destroyed. + +**Solution:** + +```bash +# List active sessions +ao session ls + +# Check status dashboard +ao status +``` + +### "Agent not responding" + +**Problem:** Agent session is stuck or frozen. + +**Solution:** + +```bash +# Check session status +ao status + +# Attach to session to investigate +ao open + +# Send message to agent +ao send "Please report your current status" + +# Kill and respawn if necessary +ao session kill +ao spawn +``` + +### "Permission denied" when spawning + +**Problem:** Agent doesn't have permissions for git operations. + +**Solution:** + +```bash +# Check SSH keys are added +ssh -T git@github.com + +# Add SSH key if needed +ssh-add ~/.ssh/id_ed25519 + +# Or use HTTPS and authenticate gh CLI +gh auth login +``` + +### "YAML parse error" + +**Problem:** Syntax error in `agent-orchestrator.yaml`. + +**Solution:** + +```bash +# Validate YAML syntax online: https://www.yamllint.com/ + +# Common issues: +# - Incorrect indentation (use 2 spaces, not tabs) +# - Missing quotes around strings with special characters +# - Typo in field names +``` + +### "Node version too old" + +**Problem:** Node.js version is below 20. + +**Solution:** + +```bash +# Check version +node --version + +# Upgrade with nvm (recommended) +nvm install 20 +nvm use 20 +nvm alias default 20 + +# Or download from: https://nodejs.org/ +``` + +## Advanced Configuration + +### Multi-Project Setup + +Manage multiple repositories: + +```yaml +projects: + frontend: + repo: org/frontend + path: ~/frontend + sessionPrefix: fe + + backend: + repo: org/backend + path: ~/backend + sessionPrefix: api + + docs: + repo: org/docs + path: ~/docs + sessionPrefix: doc +``` + +See [examples/multi-project.yaml](./examples/multi-project.yaml) for full example. + +### Custom Plugin Development + +Create custom plugins for: + +- Different runtimes (Docker, Kubernetes, SSH, cloud VMs) +- Different agents (custom AI assistants) +- Different trackers (Jira, Asana, custom systems) +- Different notifiers (email, webhooks, custom integrations) + +See [Development Guide](./docs/DEVELOPMENT.md) for plugin development guidelines. + +### Docker Runtime + +Run agents in Docker containers: + +```yaml +defaults: + runtime: docker + +# Plugin will use official images or build from Dockerfile +``` + +### Kubernetes Runtime + +Run agents in Kubernetes pods: + +```yaml +defaults: + runtime: kubernetes + +# Requires kubectl configured with cluster access +``` + +### Custom Notifiers + +Send notifications to custom webhooks: + +```yaml +notifiers: + webhook: + plugin: webhook + url: https://your-service.com/webhook + method: POST + headers: + Authorization: "Bearer ${WEBHOOK_TOKEN}" +``` + +## FAQ + +### What's a session? + +A session is an isolated workspace where an agent works on a single issue. Each session has: + +- Its own git worktree or clone +- Its own runtime session — a tmux session on macOS/Linux, a ConPTY pty-host process on Windows (or a Docker container, etc.) +- Its own metadata (branch, PR, status) +- Its own event log + +Sessions are ephemeral — they're created for an issue and destroyed when merged. + +### What's a worktree vs clone? + +**Worktree** (default): + +- Shares `.git` directory with main repo +- Fast to create (no cloning) +- Efficient disk usage +- Best for local development + +**Clone**: + +- Full independent repository clone +- Slower to create +- More disk space +- Better for isolation, remote work + +### How do reactions work? + +Reactions are event handlers that run automatically: + +1. Event occurs (CI fails, review comment added, PR approved) +2. Orchestrator checks reaction config +3. If `auto: true`, performs the action automatically +4. If escalation threshold reached, notifies human + +Actions can be: + +- `send-to-agent` - Forward event to agent to handle +- `auto-merge` - Merge PR automatically +- `notify` - Send notification to human + +### When should I enable auto-merge? + +Enable auto-merge if: + +- ✅ You have comprehensive CI/CD tests +- ✅ You require code review approval +- ✅ You trust your agents to write correct code +- ✅ You want maximum automation + +Don't enable auto-merge if: + +- ❌ You have incomplete test coverage +- ❌ You want manual review of every change +- ❌ You're still evaluating agent quality +- ❌ You work on critical systems (finance, healthcare, etc.) + +Start with `auto: false` and enable after building confidence. + +### How do I add custom agent rules? + +**Inline:** + +```yaml +projects: + my-app: + agentRules: | + Always run tests before pushing. + Use conventional commits. +``` + +**External file:** + +```yaml +projects: + my-app: + agentRulesFile: .agent-rules.md +``` + +Rules are included in every agent prompt for that project. + +### Can I use multiple trackers? + +Yes! Different projects can use different trackers: + +```yaml +projects: + frontend: + tracker: + plugin: github + + backend: + tracker: + plugin: linear + teamId: "..." +``` + +### How do I monitor agent progress? + +Three ways: + +1. **Dashboard** - `ao start` then visit http://localhost:3000 (or your configured `port:`) +2. **CLI status** - `ao status` (text-based dashboard) +3. **Attach to session** - `ao open ` (live terminal) + +### What if an agent gets stuck? + +```bash +# Check status +ao status + +# Send message +ao send "What's your current status?" + +# Attach to investigate +ao open + +# Kill and respawn if necessary +ao session kill +ao spawn +``` + +Agents also send "stuck" notifications automatically after inactivity threshold. + +### How do I clean up old sessions? + +```bash +# List all sessions +ao session ls + +# Kill specific session +ao session kill + +# Cleanup script (example) +ao session ls --json --include-terminated | jq -r '.data[] | select(.status == "merged") | .id' | xargs -I{} ao session kill {} +``` + +> **Note:** `ao session ls --json` and `ao status --json` emit `{ data: [...], meta: { hiddenTerminatedCount } }`. By default terminated sessions (`killed`, `terminated`, `done`, `merged`, `errored`, `cleanup`) are hidden — pass `--include-terminated` to include them in `data`. + +### Can I run multiple orchestrators? + +Yes! Each orchestrator instance should have: + +- Different dashboard port (`port`) — e.g., 3000 for project A, 3001 for project B +- Different config location or project paths + +AO derives runtime directories from the config location, so separate config locations already produce separate hash-scoped runtime paths under `~/.agent-orchestrator/`. Terminal WebSocket ports are auto-detected by default, so you typically only need to set `port:` differently. If you need explicit control, you can also set `terminalPort:` and `directTerminalPort:` per config. + +Useful for: + +- Separating projects +- Different teams +- Testing new configs + +## Next Steps + +1. **Start the orchestrator** — `ao start` (auto-creates config on first run) +2. **Spawn an agent** — `ao spawn 123` (project auto-detected from cwd) +3. **Monitor progress** — `ao status` or dashboard at http://localhost:3000 +4. **Read [Development Guide](./docs/DEVELOPMENT.md)** — Code conventions and architecture +5. **Explore examples** — See [examples/](./examples/) for more configs +6. **Join the community** — Report issues, share configs, contribute plugins + +--- + +**Need help?** Open an issue at: https://github.com/ComposioHQ/agent-orchestrator/issues diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index ea4433c9c5..c8ef0b7e30 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -1,61 +1,61 @@ -# Troubleshooting - -## DirectTerminal: posix_spawnp failed error - -**Symptom**: Terminal in browser shows "Connected" but blank. WebSocket logs show: - -``` -[DirectTerminal] Failed to spawn PTY: Error: posix_spawnp failed. -``` - -**Root Cause**: node-pty prebuilt binaries are incompatible with your system. - -**Fix**: Rebuild node-pty from source: - -```bash -# From the repository root -cd node_modules/.pnpm/node-pty@1.1.0/node_modules/node-pty -npx node-gyp rebuild -``` - -**Verification**: - -```bash -# Test node-pty works -node -e "const pty = require('./node_modules/.pnpm/node-pty@1.1.0/node_modules/node-pty'); \ - const shell = pty.spawn('/bin/zsh', [], {name: 'xterm-256color', cols: 80, rows: 24, \ - cwd: process.env.HOME, env: process.env}); \ - shell.onData((d) => console.log('✅ OK')); \ - setTimeout(() => process.exit(0), 1000);" -``` - -**When this happens**: - -- After `pnpm install` (uses cached prebuilts) -- After copying the repo to a new location -- On some macOS configurations with Homebrew Node - -**Permanent fix**: The postinstall hook automatically rebuilds node-pty: - -```bash -pnpm install # Automatically rebuilds node-pty via postinstall hook -``` - -If you need to manually rebuild: - -```bash -cd node_modules/.pnpm/node-pty@1.1.0/node_modules/node-pty -npx node-gyp rebuild -``` - -## Other Issues - -### Config file not found - -**Symptom**: API returns 500 with "No agent-orchestrator.yaml found" - -**Fix**: Ensure config exists in the directory where you run `ao start`, or symlink it: - -```bash -ln -s /path/to/agent-orchestrator.yaml packages/web/agent-orchestrator.yaml -``` +# Troubleshooting + +## DirectTerminal: posix_spawnp failed error + +**Symptom**: Terminal in browser shows "Connected" but blank. WebSocket logs show: + +``` +[DirectTerminal] Failed to spawn PTY: Error: posix_spawnp failed. +``` + +**Root Cause**: node-pty prebuilt binaries are incompatible with your system. + +**Fix**: Rebuild node-pty from source: + +```bash +# From the repository root +cd node_modules/.pnpm/node-pty@1.1.0/node_modules/node-pty +npx node-gyp rebuild +``` + +**Verification**: + +```bash +# Test node-pty works +node -e "const pty = require('./node_modules/.pnpm/node-pty@1.1.0/node_modules/node-pty'); \ + const shell = pty.spawn('/bin/zsh', [], {name: 'xterm-256color', cols: 80, rows: 24, \ + cwd: process.env.HOME, env: process.env}); \ + shell.onData((d) => console.log('✅ OK')); \ + setTimeout(() => process.exit(0), 1000);" +``` + +**When this happens**: + +- After `pnpm install` (uses cached prebuilts) +- After copying the repo to a new location +- On some macOS configurations with Homebrew Node + +**Permanent fix**: The postinstall hook automatically rebuilds node-pty: + +```bash +pnpm install # Automatically rebuilds node-pty via postinstall hook +``` + +If you need to manually rebuild: + +```bash +cd node_modules/.pnpm/node-pty@1.1.0/node_modules/node-pty +npx node-gyp rebuild +``` + +## Other Issues + +### Config file not found + +**Symptom**: API returns 500 with "No agent-orchestrator.yaml found" + +**Fix**: Ensure config exists in the directory where you run `ao start`, or symlink it: + +```bash +ln -s /path/to/agent-orchestrator.yaml packages/web/agent-orchestrator.yaml +``` diff --git a/artifacts/architecture-design.md b/artifacts/architecture-design.md index 5ff8253fd2..274117281a 100644 --- a/artifacts/architecture-design.md +++ b/artifacts/architecture-design.md @@ -1,783 +1,783 @@ -# Architecture Design — Agent Orchestrator - -_Compiled: 2026-02-13_ - -## Core Philosophy - -**Push, not pull.** The human never polls. The human never checks a dashboard wondering "what's happening?" The system pushes notifications to the human exactly when their attention is needed — and stays silent otherwise. - -The dashboard is a **drill-down tool** you open after receiving a notification, not something you sit and watch. The **Notifier is the primary interface.** - -### Interaction Model - -``` -Human spawns 20 agents → walks away → lives their life - │ - ┌───────────────────────────────┘ - │ - ▼ - Orchestrator runs autonomously: - ├── Agents work on issues - ├── CI fails? → auto-send fix to agent → resolved silently - ├── Review comments? → auto-send to agent → resolved silently - ├── Agent stuck? → NOTIFY HUMAN - ├── Agent needs input? → NOTIFY HUMAN - ├── PR ready to merge? → NOTIFY HUMAN (or auto-merge if configured) - ├── Agent errored? → NOTIFY HUMAN - └── All done? → NOTIFY HUMAN with summary - -Human only intervenes when notified. Everything else is handled. -``` - -### Design Principles - -1. **Push, not pull**: Notifications are the primary interface. Dashboard is secondary drill-down. -2. **Server-centric**: All agents report to a central server. The server coordinates everything. -3. **Plugin everything**: 8 pluggable abstraction slots. Swap any component. -4. **Works out of the box**: Default config (tmux + claude-code + worktree + github) requires zero setup beyond `npx agent-orchestrator init`. -5. **Silence by default, loud when needed**: Auto-handle routine issues (CI failures, review comments). Only notify the human when their judgment or action is truly required. -6. **Runtime agnostic**: tmux is just one way to run agents. Docker, K8s, cloud, SSH, child processes — all through the same interface. - ---- - -## Nomenclature - -| Term | Definition | Examples | -| ---------------- | ------------------------------------------ | -------------------------------- | -| **Orchestrator** | The central server that manages everything | The Next.js app | -| **Project** | A configured repository to work on | `my-app`, `backend-api` | -| **Session** | A running agent instance working on a task | `my-app-1`, `my-app-2` | -| **Runtime** | Where/how the session executes | tmux, docker, k8s, process | -| **Agent** | The AI coding tool being used | claude-code, codex, aider | -| **Workspace** | Isolated code copy for a session | git worktree, clone, volume | -| **Tracker** | Issue/task tracking system | github, linear, jira | -| **SCM** | Source code management platform | github, gitlab, bitbucket | -| **Notifier** | Communication/alert channel | slack, discord, desktop, webhook | -| **Terminal** | Human interaction interface | iterm2, web terminal, none | - ---- - -## System Architecture - -``` - ┌──────────────────────────────────────┐ - CLI ───REST───► │ Orchestrator Server │ - │ (Next.js) │ - Web ───REST/───► │ │ - SSE │ ┌────────────┐ ┌────────────────┐ │ - │ │ Session │ │ Plugin │ │ - Agents ────────► │ │ Manager │ │ Registry │ │ - (heartbeat/ │ └──────┬─────┘ └───────┬────────┘ │ - webhook) │ │ │ │ - │ ┌──────┴─────┐ ┌───────┴────────┐ │ - │ │ Lifecycle │ │ Config │ │ - │ │ Manager │ │ Manager │ │ - │ └──────┬─────┘ └────────────────┘ │ - │ │ │ - │ ┌──────┴──────────────────────────┐ │ - │ │ Event Bus │ │ - │ │ (pub/sub + persistence) │ │ - │ └──┬──────┬──────┬──────┬────────┘ │ - └─────┼──────┼──────┼──────┼──────────┘ - │ │ │ │ - ┌───────┘ │ │ └───────┐ - ▼ ▼ ▼ ▼ - ┌─────────┐ ┌────────┐ ┌────────┐ ┌─────────┐ - │ SSE → │ │Notifier│ │Reaction│ │ Event │ - │ Web UI │ │Plugins │ │ Engine │ │ Log │ - └─────────┘ └────────┘ └────────┘ └─────────┘ -``` - -### Data Flow - -1. **Agent → Server**: Heartbeats, status updates, "need input" signals -2. **Server → Dashboard**: SSE stream of session state changes -3. **Server → Notifiers**: Alerts when human attention is needed -4. **Server → Agents**: Commands via runtime-specific channels (tmux send-keys, docker exec, HTTP POST, etc.) -5. **CLI → Server**: REST API calls for spawn, kill, send, status -6. **SCM → Server**: PR state, CI checks, review comments (polled or webhooks) - ---- - -## The 8 Plugin Slots - -### 1. Runtime — Where sessions execute - -```typescript -interface Runtime { - readonly name: string; - - // Lifecycle - create(session: SessionConfig): Promise; - destroy(handle: RuntimeHandle): Promise; - - // Communication - sendMessage(handle: RuntimeHandle, message: string): Promise; - getOutput(handle: RuntimeHandle, lines?: number): Promise; - - // Health - isAlive(handle: RuntimeHandle): Promise; - getMetrics(handle: RuntimeHandle): Promise; - - // Optional: interactive access - attach?(handle: RuntimeHandle): Promise; -} -``` - -| Implementation | How it works | Best for | -| ---------------- | ------------------------------ | ------------------------------ | -| `tmux` (default) | tmux sessions + send-keys | Local development, interactive | -| `process` | Child processes + stdin/stdout | Headless, CI/CD, scripting | -| `docker` | Docker containers + exec | Isolation, reproducibility | -| `kubernetes` | K8s pods/jobs | Scale, enterprise | -| `ssh` | SSH to remote + tmux/process | Remote machines | -| `e2b` | E2B SDK (Firecracker microVMs) | Cloud sandboxes | -| `fly` | Fly.io Machines API | Cost-effective cloud | -| `modal` | Modal Sandboxes | GPU, autoscaling | - -### 2. Agent — AI coding tool - -```typescript -interface Agent { - readonly name: string; - readonly processName: string; // for detection - - // Launch - getLaunchCommand(session: SessionConfig, project: ProjectConfig): string; - getEnvironment(session: SessionConfig): Record; - - // Activity detection - detectActivity(session: Session): Promise; - isProcessRunning(runtimeHandle: RuntimeHandle): Promise; - - // Introspection - introspect(session: Session): Promise; - - // Optional - postLaunchSetup?(session: Session): Promise; - estimateCost?(session: Session): Promise; -} -``` - -| Implementation | Launch command | Activity detection | -| ----------------------- | --------------------------------------- | -------------------------- | -| `claude-code` (default) | `claude --dangerously-skip-permissions` | JSONL mtime + process tree | -| `claude-headless` | `claude -p --output-format stream-json` | stdout parsing | -| `codex` | `codex` | Process detection | -| `aider` | `aider --no-auto-commits` | Process detection | -| `goose` | `goose session` | Process detection | -| `custom` | User-defined command | Configurable | - -### 3. Workspace — Code isolation - -```typescript -interface Workspace { - readonly name: string; - - create(project: ProjectConfig, session: SessionConfig): Promise; - destroy(path: WorkspacePath): Promise; - list(project: ProjectConfig): Promise; - - // Optional hooks - postCreate?(path: WorkspacePath, project: ProjectConfig): Promise; -} -``` - -| Implementation | How | Tradeoff | -| -------------------- | ------------------------ | ---------------------------------------- | -| `worktree` (default) | `git worktree add` | Fast, shared objects, requires same repo | -| `clone` | `git clone` | Full isolation, slower, more disk | -| `copy` | `cp -r` | No git dependency, heaviest | -| `volume` | Docker/K8s volume mounts | For container runtimes | - -### 4. Tracker — Issue/task tracking - -```typescript -interface Tracker { - readonly name: string; - - getIssue(identifier: string): Promise; - isCompleted(identifier: string): Promise; - issueUrl(identifier: string): string; - branchName(identifier: string): string; - generatePrompt(identifier: string, project: ProjectConfig): string; - - // Optional - listIssues?(filters?: IssueFilters): Promise; - updateIssue?(identifier: string, update: IssueUpdate): Promise; - createIssue?(input: CreateIssueInput): Promise; -} -``` - -| Implementation | API | Auth | -| ------------------ | ----------- | -------------- | -| `github` (default) | `gh` CLI | GitHub token | -| `linear` | GraphQL API | Linear API key | -| `jira` | REST API | Jira token | -| `plain` | Local files | None | - -### 5. SCM — Source code platform (PR, CI, Reviews) - -```typescript -interface SCM { - readonly name: string; - - // PR lifecycle - detectPR(session: Session): Promise; - getPRState(pr: PRInfo): Promise; - createPR(session: Session, title: string, body: string): Promise; - mergePR(pr: PRInfo, method?: MergeMethod): Promise; - closePR(pr: PRInfo): Promise; - - // CI tracking - getCIChecks(pr: PRInfo): Promise; - getCISummary(pr: PRInfo): Promise; - - // Review tracking - getReviews(pr: PRInfo): Promise; - getReviewDecision(pr: PRInfo): Promise; - getPendingComments(pr: PRInfo): Promise; - getAutomatedComments(pr: PRInfo): Promise; - - // Merge readiness - getMergeability(pr: PRInfo): Promise; -} -``` - -| Implementation | API | Features | -| ------------------ | ------------------- | -------------------------- | -| `github` (default) | `gh` CLI + REST API | Full PR/CI/review support | -| `gitlab` | REST API | MR/pipeline/review support | -| `bitbucket` | REST API | PR/pipeline support | - -### 6. Notifier — THE PRIMARY INTERFACE - -The notifier is not a nice-to-have — it is the primary way the system communicates with humans. The human walks away after spawning agents. Notifications bring them back only when needed. - -```typescript -interface Notifier { - readonly name: string; - - // Core: push a notification to the human - notify(event: OrchestratorEvent): Promise; - - // Optional: actionable notifications (buttons/links) - notifyWithActions?(event: OrchestratorEvent, actions: NotifyAction[]): Promise; - - // Optional: richer communication (post to channel) - post?(message: string, context?: NotifyContext): Promise; -} - -// Notifications can include actions the human can take directly -interface NotifyAction { - label: string; // "Merge PR", "Open Dashboard", "Kill Session" - url?: string; // Deep link to dashboard action - callback?: string; // API endpoint to call -} -``` - -| Implementation | Channel | Best for | Actionable? | -| ------------------- | ---------------------------- | ------------------- | ----------------------------- | -| `desktop` (default) | OS notifications (clickable) | Solo developer | Click → opens dashboard | -| `slack` | Slack messages with buttons | Teams | Buttons → merge, review, kill | -| `discord` | Discord messages | Communities | Links | -| `webhook` | HTTP POST | Custom integrations | Custom | -| `email` | Email digest | Async | Links | - -**Multiple notifiers can be active simultaneously.** E.g., desktop for immediate alerts + Slack for team visibility + email for daily digest. - -### 7. Terminal — Human interaction interface - -```typescript -interface Terminal { - readonly name: string; - - openSession(session: Session): Promise; - openAll(sessions: Session[]): Promise; - - // Optional - isSessionOpen?(session: Session): Promise; -} -``` - -| Implementation | How | Platform | -| ---------------- | ------------------------- | ------------- | -| `auto` (default) | Detect best available | Any | -| `iterm2` | AppleScript API | macOS | -| `web` | xterm.js in browser | Any | -| `tmux-attach` | `tmux attach` in terminal | Any with tmux | -| `none` | Headless | CI/CD | - -### 8. Lifecycle Manager (Core — not pluggable) - -The Lifecycle Manager is the orchestrator's brain. It: - -- Polls SCM + Agent plugins on configurable intervals -- Maintains state machine per session -- Emits events on state transitions -- Runs configured reactions -- Feeds real-time data to dashboard via SSE - ---- - -## Session Lifecycle State Machine - -``` - ┌──────────┐ - │ SPAWNING │ - └────┬─────┘ - │ runtime.create() + agent launched - ▼ - ┌──────────┐ - ┌─────│ WORKING │◄─────────────────────────┐ - │ └────┬─────┘ │ - │ │ PR detected │ - │ ▼ │ - │ ┌──────────────┐ │ - │ │ PR_OPEN │ │ - │ └────┬─────────┘ │ - │ │ │ - │ ┌────┴────────────┐ │ - │ ▼ ▼ │ - │ ┌──────────┐ ┌─────────────────┐ │ - │ │ CI_FAILED│ │ REVIEW_PENDING │ │ - │ └────┬─────┘ └────┬────────────┘ │ - │ │ │ │ - │ │ ┌──────────┴──────┐ │ - │ │ ▼ ▼ │ - │ │ ┌──────────────┐ ┌──────────┐ │ - │ │ │CHANGES_REQ'D │ │ APPROVED │ │ - │ │ └──────┬───────┘ └────┬─────┘ │ - │ │ │ │ │ - │ └────────┼───────────────┘ │ - │ │ agent fixes │ - │ └──────────────────────────┘ - │ - │ When approved + CI green + no conflicts: - │ ▼ - │ ┌──────────┐ - │ │MERGEABLE │──► auto-merge or notify human - │ └────┬─────┘ - │ │ - │ ▼ - │ ┌──────────┐ - │ │ MERGED │ - │ └────┬─────┘ - │ │ - │ ▼ - │ ┌──────────┐ - │ │ CLEANUP │──► destroy workspace + archive metadata - │ └──────────┘ - │ - │ At any point: - │ ┌───────────────┐ - ├────►│ NEEDS_INPUT │──► notify human - │ └───────────────┘ - │ ┌───────────────┐ - ├────►│ STUCK/IDLE │──► notify human after threshold - │ └───────────────┘ - │ ┌───────────────┐ - ├────►│ ERRORED │──► notify human - │ └───────────────┘ - │ ┌───────────────┐ - └────►│ KILLED │──► cleanup - └───────────────┘ -``` - ---- - -## Human Attention Optimization - -**The system notifies the human. The human never polls.** - -The orchestrator operates on a simple principle: handle everything you can automatically, and push a notification to the human only when their judgment or action is truly required. The human spawns agents, walks away, and gets notified. - -### Two-Tier Event Handling - -**Tier 1: Auto-handled (human never sees these)** -The orchestrator resolves these silently. The human is only notified if auto-resolution fails. - -| Event | Auto-Response | Escalation | -| ---------------------- | -------------------------------- | -------------------------------- | -| CI failed | Send fix prompt to agent | Notify after 2 failed attempts | -| Review comments | Send "address comments" to agent | Notify if unresolved after 30min | -| Bugbot/linter comments | Send fix prompt to agent | Notify if unresolved after 30min | -| Merge conflicts | Send "rebase" to agent | Notify if unresolved after 15min | - -**Tier 2: Notify human (requires human judgment)** -These always push a notification. The human's phone buzzes, Slack pings, etc. - -| Event | Priority | Notification | -| -------------------------------------------------------------- | -------- | ----------------------------------------------------- | -| **Agent needs input** (permission, question, stuck) | URGENT | "Session X needs your input" + deep link | -| **Agent errored** (crashed, unrecoverable) | URGENT | "Session X crashed" + error context | -| **PR ready to merge** (approved + CI green) | ACTION | "PR #42 ready to merge" + merge button | -| **Agent idle too long** (no PR, no progress) | WARNING | "Session X idle for 15min, may need help" | -| **Auto-fix failed** (CI fix failed 2x, comments not addressed) | WARNING | "Session X couldn't resolve CI/review — needs you" | -| **All work complete** | INFO | "All 20 sessions done. 18 PRs merged, 2 need review." | - -### Escalation Chains - -Events start at auto-handle and escalate through notification tiers: - -``` -Event detected - │ - ▼ -Can auto-handle? ──yes──► Auto-respond (send to agent) - │ │ - no Resolved? ──yes──► Done (silent) - │ │ - ▼ no (retry N times) -NOTIFY HUMAN │ - │ ▼ - │ NOTIFY HUMAN - │ "Tried to auto-fix, couldn't resolve" - ▼ -Human acts via: - ├── Notification action button (merge, kill, open) - ├── Dashboard deep link - ├── CLI command - └── Direct tmux attach -``` - -### Notification Channels (Priority-Based Routing) - -Different priorities route to different channels: - -```yaml -notifications: - routing: - urgent: [desktop, slack, sms] # Agent stuck, errored, needs input - action: [desktop, slack] # PR ready to merge - warning: [slack] # Auto-fix failed, idle too long - info: [slack] # Summary, all done -``` - -### Reactions (configurable auto-responses) - -```yaml -# agent-orchestrator.yaml -reactions: - ci-failed: - auto: true - action: send-to-agent - message: "CI is failing. Run `gh pr checks` to see failures, fix them, and push." - retries: 2 - escalate-after: 2 # notify human after 2 failed auto-fix attempts - - changes-requested: - auto: true - action: send-to-agent - message: "Review comments on your PR. Check with `gh pr view --comments` and address each one." - escalate-after: 30m - - bugbot-comments: - auto: true - action: send-to-agent - message: "Automated review comments found. Fix the issues flagged by the bot." - escalate-after: 30m - - merge-conflicts: - auto: true - action: send-to-agent - message: "Your branch has merge conflicts. Rebase on the default branch and resolve them." - escalate-after: 15m - - approved-and-green: - auto: false # require human confirmation by default - action: notify - priority: action - message: "PR is ready to merge" - # Set auto: true + action: auto-merge for full automation - - agent-stuck: - threshold: 10m - action: notify - priority: urgent - - agent-needs-input: - action: notify - priority: urgent - - agent-exited: - action: notify - priority: urgent - - all-complete: - action: notify - priority: info - message: "All sessions complete" - include-summary: true # PRs merged, pending, failed - - agent-idle-no-pr: - threshold: 30m # working for 30min with no PR - action: notify - priority: warning - message: "Agent has been working for 30min without creating a PR" -``` - -### Dashboard (Secondary — Drill-Down Tool) - -The dashboard exists for when you get a notification and need to drill down. It's organized by attention priority: - -- **Red zone** (top): URGENT — sessions needing human input RIGHT NOW -- **Orange zone**: ACTION — PRs ready to merge, decisions needed -- **Yellow zone**: WARNING — auto-fix failed, agents idle too long -- **Green zone**: Sessions working normally (collapsed by default) -- **Grey zone**: Completed/merged (collapsed by default) - -Clicking a notification deep-links directly to the relevant session/PR in the dashboard. - ---- - -## Configuration - -### Minimal Config (works out of the box) - -```yaml -# agent-orchestrator.yaml -projects: - my-app: - repo: org/repo - path: ~/my-app -``` - -Everything else uses sensible defaults: - -- Runtime: tmux -- Agent: claude-code -- Workspace: worktree -- Tracker: github (inferred from repo) -- SCM: github (inferred from repo) -- Notifier: desktop -- Terminal: auto-detect - -### Full Config - -```yaml -# agent-orchestrator.yaml -dataDir: ~/.agent-orchestrator # metadata storage -worktreeDir: ~/.worktrees # workspace root -port: 3000 # web dashboard port - -defaults: - runtime: tmux - agent: claude-code - workspace: worktree - notifiers: [desktop] - -projects: - my-app: - name: My App - repo: org/repo - path: ~/my-app - defaultBranch: main - sessionPrefix: app - - # Override defaults per project - agent: claude-code - runtime: tmux - - # Issue tracker - tracker: - plugin: linear - teamId: "abc-123" - - # SCM (usually inferred from repo) - scm: - plugin: github - - # Symlinks to copy into workspaces - symlinks: [.env, .claude] - - # Commands to run after workspace creation - postCreate: - - "pnpm install" - - "claude mcp add rube --transport http https://rube.app/mcp" - - # Agent-specific config - agentConfig: - permissions: skip # --dangerously-skip-permissions - model: opus - - # Reaction overrides - reactions: - approved-and-green: - auto: true # enable auto-merge for this project - -# Notification channels -notifiers: - slack: - plugin: slack - webhook: ${SLACK_WEBHOOK_URL} - channel: "#agent-updates" - desktop: - plugin: desktop - -# Reaction defaults (can be overridden per project) -reactions: - ci-failed: - auto: true - retries: 2 - escalate-after: 2 - changes-requested: - auto: true - escalate-after: 30m - approved-and-green: - auto: false - agent-stuck: - threshold: 10m - agent-needs-input: - priority: high -``` - ---- - -## Tech Stack - -| Segment | Choice | Why | -| ------------------- | --------------------------------------- | ---------------------------------------------------- | -| **Core library** | TypeScript | Shared types across all packages | -| **Web + API** | Next.js 15 (App Router) | SSR + API routes in one process | -| **Styling** | Tailwind CSS | Dark theme, responsive | -| **Real-time** | Server-Sent Events | One-way push, auto-reconnect, simpler than WebSocket | -| **CLI** | TypeScript + Commander.js | Shares types with core | -| **Config** | YAML + Zod validation | Human-readable, type-safe | -| **State** | Flat metadata files + Event log (JSONL) | Stateless orchestrator, crash recovery | -| **Package manager** | pnpm workspaces | Fast, monorepo-native | -| **Distribution** | npm (`npx agent-orchestrator`) | Zero install | - -### Why TypeScript Throughout - -1. **One language** — Plugin authors only need TypeScript/JavaScript -2. **Shared types** — No serialization boundaries between core, web, CLI, plugins -3. **npm distribution** — `npx agent-orchestrator` works everywhere -4. **Next.js** — Web + API server in one process, great DX -5. **Largest ecosystem** — More packages on npm than any other registry -6. **Performance is fine** — Bottleneck is AI agents, not orchestrator. We shell out to tmux/git/docker anyway. - ---- - -## Directory Structure - -``` -agent-orchestrator/ -├── package.json -├── pnpm-workspace.yaml -├── tsconfig.base.json -├── agent-orchestrator.yaml.example -│ -├── packages/ -│ ├── core/ # @aoagents/ao-core -│ │ └── src/ -│ │ ├── types.ts # All interfaces + types -│ │ ├── config.ts # YAML config loader + Zod validation -│ │ ├── session-manager.ts # Session CRUD -│ │ ├── lifecycle-manager.ts # State machine + reactions -│ │ ├── event-bus.ts # Pub/sub + JSONL persistence -│ │ ├── plugin-registry.ts # Plugin discovery + loading -│ │ ├── metadata.ts # Flat-file read/write -│ │ └── index.ts -│ │ -│ ├── cli/ # @aoagents/ao-cli → `ao` binary -│ │ └── src/ -│ │ ├── index.ts # Commander.js setup -│ │ └── commands/ -│ │ ├── init.ts # ao init -│ │ ├── status.ts # ao status -│ │ ├── spawn.ts # ao spawn [issue] -│ │ ├── batch-spawn.ts # ao batch-spawn -│ │ ├── session.ts # ao session [ls|kill|cleanup] -│ │ ├── send.ts # ao send -│ │ ├── review-check.ts # ao review-check [project] -│ │ ├── dashboard.ts # ao dashboard (starts web) -│ │ └── open.ts # ao open [session|all] -│ │ -│ ├── web/ # @aoagents/ao-web -│ │ ├── next.config.ts -│ │ └── src/ -│ │ ├── app/ -│ │ │ ├── layout.tsx -│ │ │ ├── page.tsx # Dashboard (attention-prioritized) -│ │ │ └── sessions/[id]/ -│ │ │ └── page.tsx # Session detail -│ │ ├── api/ -│ │ │ ├── sessions/ # CRUD + actions -│ │ │ ├── spawn/ # POST spawn -│ │ │ ├── events/ # SSE stream -│ │ │ └── health/ # Server health -│ │ └── components/ -│ │ ├── SessionCard.tsx -│ │ ├── AttentionZone.tsx -│ │ ├── PRStatus.tsx -│ │ ├── CIBadge.tsx -│ │ └── Terminal.tsx # xterm.js -│ │ -│ └── plugins/ # Built-in plugins -│ ├── runtime-tmux/ -│ ├── runtime-process/ -│ ├── runtime-docker/ -│ ├── agent-claude-code/ -│ ├── agent-codex/ -│ ├── agent-aider/ -│ ├── workspace-worktree/ -│ ├── workspace-clone/ -│ ├── tracker-github/ -│ ├── tracker-linear/ -│ ├── scm-github/ -│ ├── notifier-desktop/ -│ ├── notifier-slack/ -│ ├── terminal-iterm2/ -│ └── terminal-web/ -│ -├── artifacts/ # Research + design docs -│ ├── competitive-research.md -│ └── architecture-design.md -│ -├── scripts/ # Original bash scripts (reference) -│ -└── CLAUDE.md -``` - ---- - -## Implementation Phases - -### Phase 1: Foundation (Dog-food ready) - -- Monorepo scaffolding -- Core types + interfaces -- Config loader -- Session manager + lifecycle manager + event bus -- tmux runtime, claude-code agent, worktree workspace -- GitHub SCM (PR/CI/review tracking) -- GitHub tracker -- Desktop notifier -- CLI (init, status, spawn, session, send, dashboard) -- Web dashboard with attention-prioritized view -- SSE real-time updates -- Reaction engine (CI failed, changes requested, agent stuck) - -### Phase 2: Multi-Runtime + More Plugins - -- Process runtime (headless claude -p) -- Docker runtime -- Codex + Aider agent adapters -- Linear + Jira trackers -- Slack notifier -- Web terminal (xterm.js) - -### Phase 3: Cloud + Scale - -- Kubernetes runtime -- E2B / Fly.io runtimes -- Cost tracking -- Webhook-triggered spawning - -### Phase 4: Team + Enterprise - -- Dashboard auth -- Role-based access -- Remote session support -- Audit log +# Architecture Design — Agent Orchestrator + +_Compiled: 2026-02-13_ + +## Core Philosophy + +**Push, not pull.** The human never polls. The human never checks a dashboard wondering "what's happening?" The system pushes notifications to the human exactly when their attention is needed — and stays silent otherwise. + +The dashboard is a **drill-down tool** you open after receiving a notification, not something you sit and watch. The **Notifier is the primary interface.** + +### Interaction Model + +``` +Human spawns 20 agents → walks away → lives their life + │ + ┌───────────────────────────────┘ + │ + ▼ + Orchestrator runs autonomously: + ├── Agents work on issues + ├── CI fails? → auto-send fix to agent → resolved silently + ├── Review comments? → auto-send to agent → resolved silently + ├── Agent stuck? → NOTIFY HUMAN + ├── Agent needs input? → NOTIFY HUMAN + ├── PR ready to merge? → NOTIFY HUMAN (or auto-merge if configured) + ├── Agent errored? → NOTIFY HUMAN + └── All done? → NOTIFY HUMAN with summary + +Human only intervenes when notified. Everything else is handled. +``` + +### Design Principles + +1. **Push, not pull**: Notifications are the primary interface. Dashboard is secondary drill-down. +2. **Server-centric**: All agents report to a central server. The server coordinates everything. +3. **Plugin everything**: 8 pluggable abstraction slots. Swap any component. +4. **Works out of the box**: Default config (tmux + claude-code + worktree + github) requires zero setup beyond `npx agent-orchestrator init`. +5. **Silence by default, loud when needed**: Auto-handle routine issues (CI failures, review comments). Only notify the human when their judgment or action is truly required. +6. **Runtime agnostic**: tmux is just one way to run agents. Docker, K8s, cloud, SSH, child processes — all through the same interface. + +--- + +## Nomenclature + +| Term | Definition | Examples | +| ---------------- | ------------------------------------------ | -------------------------------- | +| **Orchestrator** | The central server that manages everything | The Next.js app | +| **Project** | A configured repository to work on | `my-app`, `backend-api` | +| **Session** | A running agent instance working on a task | `my-app-1`, `my-app-2` | +| **Runtime** | Where/how the session executes | tmux, docker, k8s, process | +| **Agent** | The AI coding tool being used | claude-code, codex, aider | +| **Workspace** | Isolated code copy for a session | git worktree, clone, volume | +| **Tracker** | Issue/task tracking system | github, linear, jira | +| **SCM** | Source code management platform | github, gitlab, bitbucket | +| **Notifier** | Communication/alert channel | slack, discord, desktop, webhook | +| **Terminal** | Human interaction interface | iterm2, web terminal, none | + +--- + +## System Architecture + +``` + ┌──────────────────────────────────────┐ + CLI ───REST───► │ Orchestrator Server │ + │ (Next.js) │ + Web ───REST/───► │ │ + SSE │ ┌────────────┐ ┌────────────────┐ │ + │ │ Session │ │ Plugin │ │ + Agents ────────► │ │ Manager │ │ Registry │ │ + (heartbeat/ │ └──────┬─────┘ └───────┬────────┘ │ + webhook) │ │ │ │ + │ ┌──────┴─────┐ ┌───────┴────────┐ │ + │ │ Lifecycle │ │ Config │ │ + │ │ Manager │ │ Manager │ │ + │ └──────┬─────┘ └────────────────┘ │ + │ │ │ + │ ┌──────┴──────────────────────────┐ │ + │ │ Event Bus │ │ + │ │ (pub/sub + persistence) │ │ + │ └──┬──────┬──────┬──────┬────────┘ │ + └─────┼──────┼──────┼──────┼──────────┘ + │ │ │ │ + ┌───────┘ │ │ └───────┐ + ▼ ▼ ▼ ▼ + ┌─────────┐ ┌────────┐ ┌────────┐ ┌─────────┐ + │ SSE → │ │Notifier│ │Reaction│ │ Event │ + │ Web UI │ │Plugins │ │ Engine │ │ Log │ + └─────────┘ └────────┘ └────────┘ └─────────┘ +``` + +### Data Flow + +1. **Agent → Server**: Heartbeats, status updates, "need input" signals +2. **Server → Dashboard**: SSE stream of session state changes +3. **Server → Notifiers**: Alerts when human attention is needed +4. **Server → Agents**: Commands via runtime-specific channels (tmux send-keys, docker exec, HTTP POST, etc.) +5. **CLI → Server**: REST API calls for spawn, kill, send, status +6. **SCM → Server**: PR state, CI checks, review comments (polled or webhooks) + +--- + +## The 8 Plugin Slots + +### 1. Runtime — Where sessions execute + +```typescript +interface Runtime { + readonly name: string; + + // Lifecycle + create(session: SessionConfig): Promise; + destroy(handle: RuntimeHandle): Promise; + + // Communication + sendMessage(handle: RuntimeHandle, message: string): Promise; + getOutput(handle: RuntimeHandle, lines?: number): Promise; + + // Health + isAlive(handle: RuntimeHandle): Promise; + getMetrics(handle: RuntimeHandle): Promise; + + // Optional: interactive access + attach?(handle: RuntimeHandle): Promise; +} +``` + +| Implementation | How it works | Best for | +| ---------------- | ------------------------------ | ------------------------------ | +| `tmux` (default) | tmux sessions + send-keys | Local development, interactive | +| `process` | Child processes + stdin/stdout | Headless, CI/CD, scripting | +| `docker` | Docker containers + exec | Isolation, reproducibility | +| `kubernetes` | K8s pods/jobs | Scale, enterprise | +| `ssh` | SSH to remote + tmux/process | Remote machines | +| `e2b` | E2B SDK (Firecracker microVMs) | Cloud sandboxes | +| `fly` | Fly.io Machines API | Cost-effective cloud | +| `modal` | Modal Sandboxes | GPU, autoscaling | + +### 2. Agent — AI coding tool + +```typescript +interface Agent { + readonly name: string; + readonly processName: string; // for detection + + // Launch + getLaunchCommand(session: SessionConfig, project: ProjectConfig): string; + getEnvironment(session: SessionConfig): Record; + + // Activity detection + detectActivity(session: Session): Promise; + isProcessRunning(runtimeHandle: RuntimeHandle): Promise; + + // Introspection + introspect(session: Session): Promise; + + // Optional + postLaunchSetup?(session: Session): Promise; + estimateCost?(session: Session): Promise; +} +``` + +| Implementation | Launch command | Activity detection | +| ----------------------- | --------------------------------------- | -------------------------- | +| `claude-code` (default) | `claude --dangerously-skip-permissions` | JSONL mtime + process tree | +| `claude-headless` | `claude -p --output-format stream-json` | stdout parsing | +| `codex` | `codex` | Process detection | +| `aider` | `aider --no-auto-commits` | Process detection | +| `goose` | `goose session` | Process detection | +| `custom` | User-defined command | Configurable | + +### 3. Workspace — Code isolation + +```typescript +interface Workspace { + readonly name: string; + + create(project: ProjectConfig, session: SessionConfig): Promise; + destroy(path: WorkspacePath): Promise; + list(project: ProjectConfig): Promise; + + // Optional hooks + postCreate?(path: WorkspacePath, project: ProjectConfig): Promise; +} +``` + +| Implementation | How | Tradeoff | +| -------------------- | ------------------------ | ---------------------------------------- | +| `worktree` (default) | `git worktree add` | Fast, shared objects, requires same repo | +| `clone` | `git clone` | Full isolation, slower, more disk | +| `copy` | `cp -r` | No git dependency, heaviest | +| `volume` | Docker/K8s volume mounts | For container runtimes | + +### 4. Tracker — Issue/task tracking + +```typescript +interface Tracker { + readonly name: string; + + getIssue(identifier: string): Promise; + isCompleted(identifier: string): Promise; + issueUrl(identifier: string): string; + branchName(identifier: string): string; + generatePrompt(identifier: string, project: ProjectConfig): string; + + // Optional + listIssues?(filters?: IssueFilters): Promise; + updateIssue?(identifier: string, update: IssueUpdate): Promise; + createIssue?(input: CreateIssueInput): Promise; +} +``` + +| Implementation | API | Auth | +| ------------------ | ----------- | -------------- | +| `github` (default) | `gh` CLI | GitHub token | +| `linear` | GraphQL API | Linear API key | +| `jira` | REST API | Jira token | +| `plain` | Local files | None | + +### 5. SCM — Source code platform (PR, CI, Reviews) + +```typescript +interface SCM { + readonly name: string; + + // PR lifecycle + detectPR(session: Session): Promise; + getPRState(pr: PRInfo): Promise; + createPR(session: Session, title: string, body: string): Promise; + mergePR(pr: PRInfo, method?: MergeMethod): Promise; + closePR(pr: PRInfo): Promise; + + // CI tracking + getCIChecks(pr: PRInfo): Promise; + getCISummary(pr: PRInfo): Promise; + + // Review tracking + getReviews(pr: PRInfo): Promise; + getReviewDecision(pr: PRInfo): Promise; + getPendingComments(pr: PRInfo): Promise; + getAutomatedComments(pr: PRInfo): Promise; + + // Merge readiness + getMergeability(pr: PRInfo): Promise; +} +``` + +| Implementation | API | Features | +| ------------------ | ------------------- | -------------------------- | +| `github` (default) | `gh` CLI + REST API | Full PR/CI/review support | +| `gitlab` | REST API | MR/pipeline/review support | +| `bitbucket` | REST API | PR/pipeline support | + +### 6. Notifier — THE PRIMARY INTERFACE + +The notifier is not a nice-to-have — it is the primary way the system communicates with humans. The human walks away after spawning agents. Notifications bring them back only when needed. + +```typescript +interface Notifier { + readonly name: string; + + // Core: push a notification to the human + notify(event: OrchestratorEvent): Promise; + + // Optional: actionable notifications (buttons/links) + notifyWithActions?(event: OrchestratorEvent, actions: NotifyAction[]): Promise; + + // Optional: richer communication (post to channel) + post?(message: string, context?: NotifyContext): Promise; +} + +// Notifications can include actions the human can take directly +interface NotifyAction { + label: string; // "Merge PR", "Open Dashboard", "Kill Session" + url?: string; // Deep link to dashboard action + callback?: string; // API endpoint to call +} +``` + +| Implementation | Channel | Best for | Actionable? | +| ------------------- | ---------------------------- | ------------------- | ----------------------------- | +| `desktop` (default) | OS notifications (clickable) | Solo developer | Click → opens dashboard | +| `slack` | Slack messages with buttons | Teams | Buttons → merge, review, kill | +| `discord` | Discord messages | Communities | Links | +| `webhook` | HTTP POST | Custom integrations | Custom | +| `email` | Email digest | Async | Links | + +**Multiple notifiers can be active simultaneously.** E.g., desktop for immediate alerts + Slack for team visibility + email for daily digest. + +### 7. Terminal — Human interaction interface + +```typescript +interface Terminal { + readonly name: string; + + openSession(session: Session): Promise; + openAll(sessions: Session[]): Promise; + + // Optional + isSessionOpen?(session: Session): Promise; +} +``` + +| Implementation | How | Platform | +| ---------------- | ------------------------- | ------------- | +| `auto` (default) | Detect best available | Any | +| `iterm2` | AppleScript API | macOS | +| `web` | xterm.js in browser | Any | +| `tmux-attach` | `tmux attach` in terminal | Any with tmux | +| `none` | Headless | CI/CD | + +### 8. Lifecycle Manager (Core — not pluggable) + +The Lifecycle Manager is the orchestrator's brain. It: + +- Polls SCM + Agent plugins on configurable intervals +- Maintains state machine per session +- Emits events on state transitions +- Runs configured reactions +- Feeds real-time data to dashboard via SSE + +--- + +## Session Lifecycle State Machine + +``` + ┌──────────┐ + │ SPAWNING │ + └────┬─────┘ + │ runtime.create() + agent launched + ▼ + ┌──────────┐ + ┌─────│ WORKING │◄─────────────────────────┐ + │ └────┬─────┘ │ + │ │ PR detected │ + │ ▼ │ + │ ┌──────────────┐ │ + │ │ PR_OPEN │ │ + │ └────┬─────────┘ │ + │ │ │ + │ ┌────┴────────────┐ │ + │ ▼ ▼ │ + │ ┌──────────┐ ┌─────────────────┐ │ + │ │ CI_FAILED│ │ REVIEW_PENDING │ │ + │ └────┬─────┘ └────┬────────────┘ │ + │ │ │ │ + │ │ ┌──────────┴──────┐ │ + │ │ ▼ ▼ │ + │ │ ┌──────────────┐ ┌──────────┐ │ + │ │ │CHANGES_REQ'D │ │ APPROVED │ │ + │ │ └──────┬───────┘ └────┬─────┘ │ + │ │ │ │ │ + │ └────────┼───────────────┘ │ + │ │ agent fixes │ + │ └──────────────────────────┘ + │ + │ When approved + CI green + no conflicts: + │ ▼ + │ ┌──────────┐ + │ │MERGEABLE │──► auto-merge or notify human + │ └────┬─────┘ + │ │ + │ ▼ + │ ┌──────────┐ + │ │ MERGED │ + │ └────┬─────┘ + │ │ + │ ▼ + │ ┌──────────┐ + │ │ CLEANUP │──► destroy workspace + archive metadata + │ └──────────┘ + │ + │ At any point: + │ ┌───────────────┐ + ├────►│ NEEDS_INPUT │──► notify human + │ └───────────────┘ + │ ┌───────────────┐ + ├────►│ STUCK/IDLE │──► notify human after threshold + │ └───────────────┘ + │ ┌───────────────┐ + ├────►│ ERRORED │──► notify human + │ └───────────────┘ + │ ┌───────────────┐ + └────►│ KILLED │──► cleanup + └───────────────┘ +``` + +--- + +## Human Attention Optimization + +**The system notifies the human. The human never polls.** + +The orchestrator operates on a simple principle: handle everything you can automatically, and push a notification to the human only when their judgment or action is truly required. The human spawns agents, walks away, and gets notified. + +### Two-Tier Event Handling + +**Tier 1: Auto-handled (human never sees these)** +The orchestrator resolves these silently. The human is only notified if auto-resolution fails. + +| Event | Auto-Response | Escalation | +| ---------------------- | -------------------------------- | -------------------------------- | +| CI failed | Send fix prompt to agent | Notify after 2 failed attempts | +| Review comments | Send "address comments" to agent | Notify if unresolved after 30min | +| Bugbot/linter comments | Send fix prompt to agent | Notify if unresolved after 30min | +| Merge conflicts | Send "rebase" to agent | Notify if unresolved after 15min | + +**Tier 2: Notify human (requires human judgment)** +These always push a notification. The human's phone buzzes, Slack pings, etc. + +| Event | Priority | Notification | +| -------------------------------------------------------------- | -------- | ----------------------------------------------------- | +| **Agent needs input** (permission, question, stuck) | URGENT | "Session X needs your input" + deep link | +| **Agent errored** (crashed, unrecoverable) | URGENT | "Session X crashed" + error context | +| **PR ready to merge** (approved + CI green) | ACTION | "PR #42 ready to merge" + merge button | +| **Agent idle too long** (no PR, no progress) | WARNING | "Session X idle for 15min, may need help" | +| **Auto-fix failed** (CI fix failed 2x, comments not addressed) | WARNING | "Session X couldn't resolve CI/review — needs you" | +| **All work complete** | INFO | "All 20 sessions done. 18 PRs merged, 2 need review." | + +### Escalation Chains + +Events start at auto-handle and escalate through notification tiers: + +``` +Event detected + │ + ▼ +Can auto-handle? ──yes──► Auto-respond (send to agent) + │ │ + no Resolved? ──yes──► Done (silent) + │ │ + ▼ no (retry N times) +NOTIFY HUMAN │ + │ ▼ + │ NOTIFY HUMAN + │ "Tried to auto-fix, couldn't resolve" + ▼ +Human acts via: + ├── Notification action button (merge, kill, open) + ├── Dashboard deep link + ├── CLI command + └── Direct tmux attach +``` + +### Notification Channels (Priority-Based Routing) + +Different priorities route to different channels: + +```yaml +notifications: + routing: + urgent: [desktop, slack, sms] # Agent stuck, errored, needs input + action: [desktop, slack] # PR ready to merge + warning: [slack] # Auto-fix failed, idle too long + info: [slack] # Summary, all done +``` + +### Reactions (configurable auto-responses) + +```yaml +# agent-orchestrator.yaml +reactions: + ci-failed: + auto: true + action: send-to-agent + message: "CI is failing. Run `gh pr checks` to see failures, fix them, and push." + retries: 2 + escalate-after: 2 # notify human after 2 failed auto-fix attempts + + changes-requested: + auto: true + action: send-to-agent + message: "Review comments on your PR. Check with `gh pr view --comments` and address each one." + escalate-after: 30m + + bugbot-comments: + auto: true + action: send-to-agent + message: "Automated review comments found. Fix the issues flagged by the bot." + escalate-after: 30m + + merge-conflicts: + auto: true + action: send-to-agent + message: "Your branch has merge conflicts. Rebase on the default branch and resolve them." + escalate-after: 15m + + approved-and-green: + auto: false # require human confirmation by default + action: notify + priority: action + message: "PR is ready to merge" + # Set auto: true + action: auto-merge for full automation + + agent-stuck: + threshold: 10m + action: notify + priority: urgent + + agent-needs-input: + action: notify + priority: urgent + + agent-exited: + action: notify + priority: urgent + + all-complete: + action: notify + priority: info + message: "All sessions complete" + include-summary: true # PRs merged, pending, failed + + agent-idle-no-pr: + threshold: 30m # working for 30min with no PR + action: notify + priority: warning + message: "Agent has been working for 30min without creating a PR" +``` + +### Dashboard (Secondary — Drill-Down Tool) + +The dashboard exists for when you get a notification and need to drill down. It's organized by attention priority: + +- **Red zone** (top): URGENT — sessions needing human input RIGHT NOW +- **Orange zone**: ACTION — PRs ready to merge, decisions needed +- **Yellow zone**: WARNING — auto-fix failed, agents idle too long +- **Green zone**: Sessions working normally (collapsed by default) +- **Grey zone**: Completed/merged (collapsed by default) + +Clicking a notification deep-links directly to the relevant session/PR in the dashboard. + +--- + +## Configuration + +### Minimal Config (works out of the box) + +```yaml +# agent-orchestrator.yaml +projects: + my-app: + repo: org/repo + path: ~/my-app +``` + +Everything else uses sensible defaults: + +- Runtime: tmux +- Agent: claude-code +- Workspace: worktree +- Tracker: github (inferred from repo) +- SCM: github (inferred from repo) +- Notifier: desktop +- Terminal: auto-detect + +### Full Config + +```yaml +# agent-orchestrator.yaml +dataDir: ~/.agent-orchestrator # metadata storage +worktreeDir: ~/.worktrees # workspace root +port: 3000 # web dashboard port + +defaults: + runtime: tmux + agent: claude-code + workspace: worktree + notifiers: [desktop] + +projects: + my-app: + name: My App + repo: org/repo + path: ~/my-app + defaultBranch: main + sessionPrefix: app + + # Override defaults per project + agent: claude-code + runtime: tmux + + # Issue tracker + tracker: + plugin: linear + teamId: "abc-123" + + # SCM (usually inferred from repo) + scm: + plugin: github + + # Symlinks to copy into workspaces + symlinks: [.env, .claude] + + # Commands to run after workspace creation + postCreate: + - "pnpm install" + - "claude mcp add rube --transport http https://rube.app/mcp" + + # Agent-specific config + agentConfig: + permissions: skip # --dangerously-skip-permissions + model: opus + + # Reaction overrides + reactions: + approved-and-green: + auto: true # enable auto-merge for this project + +# Notification channels +notifiers: + slack: + plugin: slack + webhook: ${SLACK_WEBHOOK_URL} + channel: "#agent-updates" + desktop: + plugin: desktop + +# Reaction defaults (can be overridden per project) +reactions: + ci-failed: + auto: true + retries: 2 + escalate-after: 2 + changes-requested: + auto: true + escalate-after: 30m + approved-and-green: + auto: false + agent-stuck: + threshold: 10m + agent-needs-input: + priority: high +``` + +--- + +## Tech Stack + +| Segment | Choice | Why | +| ------------------- | --------------------------------------- | ---------------------------------------------------- | +| **Core library** | TypeScript | Shared types across all packages | +| **Web + API** | Next.js 15 (App Router) | SSR + API routes in one process | +| **Styling** | Tailwind CSS | Dark theme, responsive | +| **Real-time** | Server-Sent Events | One-way push, auto-reconnect, simpler than WebSocket | +| **CLI** | TypeScript + Commander.js | Shares types with core | +| **Config** | YAML + Zod validation | Human-readable, type-safe | +| **State** | Flat metadata files + Event log (JSONL) | Stateless orchestrator, crash recovery | +| **Package manager** | pnpm workspaces | Fast, monorepo-native | +| **Distribution** | npm (`npx agent-orchestrator`) | Zero install | + +### Why TypeScript Throughout + +1. **One language** — Plugin authors only need TypeScript/JavaScript +2. **Shared types** — No serialization boundaries between core, web, CLI, plugins +3. **npm distribution** — `npx agent-orchestrator` works everywhere +4. **Next.js** — Web + API server in one process, great DX +5. **Largest ecosystem** — More packages on npm than any other registry +6. **Performance is fine** — Bottleneck is AI agents, not orchestrator. We shell out to tmux/git/docker anyway. + +--- + +## Directory Structure + +``` +agent-orchestrator/ +├── package.json +├── pnpm-workspace.yaml +├── tsconfig.base.json +├── agent-orchestrator.yaml.example +│ +├── packages/ +│ ├── core/ # @aoagents/ao-core +│ │ └── src/ +│ │ ├── types.ts # All interfaces + types +│ │ ├── config.ts # YAML config loader + Zod validation +│ │ ├── session-manager.ts # Session CRUD +│ │ ├── lifecycle-manager.ts # State machine + reactions +│ │ ├── event-bus.ts # Pub/sub + JSONL persistence +│ │ ├── plugin-registry.ts # Plugin discovery + loading +│ │ ├── metadata.ts # Flat-file read/write +│ │ └── index.ts +│ │ +│ ├── cli/ # @aoagents/ao-cli → `ao` binary +│ │ └── src/ +│ │ ├── index.ts # Commander.js setup +│ │ └── commands/ +│ │ ├── init.ts # ao init +│ │ ├── status.ts # ao status +│ │ ├── spawn.ts # ao spawn [issue] +│ │ ├── batch-spawn.ts # ao batch-spawn +│ │ ├── session.ts # ao session [ls|kill|cleanup] +│ │ ├── send.ts # ao send +│ │ ├── review-check.ts # ao review-check [project] +│ │ ├── dashboard.ts # ao dashboard (starts web) +│ │ └── open.ts # ao open [session|all] +│ │ +│ ├── web/ # @aoagents/ao-web +│ │ ├── next.config.ts +│ │ └── src/ +│ │ ├── app/ +│ │ │ ├── layout.tsx +│ │ │ ├── page.tsx # Dashboard (attention-prioritized) +│ │ │ └── sessions/[id]/ +│ │ │ └── page.tsx # Session detail +│ │ ├── api/ +│ │ │ ├── sessions/ # CRUD + actions +│ │ │ ├── spawn/ # POST spawn +│ │ │ ├── events/ # SSE stream +│ │ │ └── health/ # Server health +│ │ └── components/ +│ │ ├── SessionCard.tsx +│ │ ├── AttentionZone.tsx +│ │ ├── PRStatus.tsx +│ │ ├── CIBadge.tsx +│ │ └── Terminal.tsx # xterm.js +│ │ +│ └── plugins/ # Built-in plugins +│ ├── runtime-tmux/ +│ ├── runtime-process/ +│ ├── runtime-docker/ +│ ├── agent-claude-code/ +│ ├── agent-codex/ +│ ├── agent-aider/ +│ ├── workspace-worktree/ +│ ├── workspace-clone/ +│ ├── tracker-github/ +│ ├── tracker-linear/ +│ ├── scm-github/ +│ ├── notifier-desktop/ +│ ├── notifier-slack/ +│ ├── terminal-iterm2/ +│ └── terminal-web/ +│ +├── artifacts/ # Research + design docs +│ ├── competitive-research.md +│ └── architecture-design.md +│ +├── scripts/ # Original bash scripts (reference) +│ +└── CLAUDE.md +``` + +--- + +## Implementation Phases + +### Phase 1: Foundation (Dog-food ready) + +- Monorepo scaffolding +- Core types + interfaces +- Config loader +- Session manager + lifecycle manager + event bus +- tmux runtime, claude-code agent, worktree workspace +- GitHub SCM (PR/CI/review tracking) +- GitHub tracker +- Desktop notifier +- CLI (init, status, spawn, session, send, dashboard) +- Web dashboard with attention-prioritized view +- SSE real-time updates +- Reaction engine (CI failed, changes requested, agent stuck) + +### Phase 2: Multi-Runtime + More Plugins + +- Process runtime (headless claude -p) +- Docker runtime +- Codex + Aider agent adapters +- Linear + Jira trackers +- Slack notifier +- Web terminal (xterm.js) + +### Phase 3: Cloud + Scale + +- Kubernetes runtime +- E2B / Fly.io runtimes +- Cost tracking +- Webhook-triggered spawning + +### Phase 4: Team + Enterprise + +- Dashboard auth +- Role-based access +- Remote session support +- Audit log diff --git a/artifacts/competitive-research.md b/artifacts/competitive-research.md index 4400180427..981cb2ef48 100644 --- a/artifacts/competitive-research.md +++ b/artifacts/competitive-research.md @@ -1,432 +1,432 @@ -# Competitive Research — Agent Orchestration Tools - -_Compiled: 2026-02-13_ - -## Overview - -Research into 16+ projects that orchestrate AI coding agents. The goal: understand abstractions, architectures, and gaps to build the best, most extensible agent orchestrator. - ---- - -## Tier 1: Direct Competitors (Multi-Agent Orchestrators) - -### Gas Town (Steve Yegge) - -- **GitHub**: https://github.com/steveyegge/gastown -- **Stack**: Go 1.23+ (~189K LOC), SQLite3, Git 2.25+, tmux 3.0+ -- **Stars**: Growing rapidly (released Jan 2026) - -**Architecture — MEOW Stack (Molecular Expression of Work):** - -| Layer | What | How | -| ----------------------------- | ------------------------ | ------------------------------------------------------------------------------ | -| **Beads** | Atomic work units | JSONL files tracked in Git. IDs like `gt-abc12`. Universal data/control plane. | -| **Epics** | Hierarchical collections | Organize beads into tree structures for parallel/sequential execution | -| **Molecules** | Workflow graphs | Sequenced beads with dependencies, gates, loops | -| **Protomolecules & Formulas** | Reusable templates | TOML format workflow definitions | - -**Agent Roles (7 roles, 2 scopes):** - -| Role | Scope | Purpose | -| ------------ | ----- | --------------------------------------------------------- | -| **Mayor** | Town | Chief AI coordinator with full workspace context | -| **Deacon** | Town | Health daemon running patrol loops | -| **Dogs** | Town | Maintenance helpers | -| **Crew** | Rig | Named, persistent agents for sustained design/review work | -| **Polecats** | Rig | Ephemeral "cattle" workers spawned for specific tasks | -| **Refinery** | Rig | Merge queue manager handling conflicts | -| **Witness** | Rig | Supervises polecats, unblocks stuck work | - -**Other Abstractions:** - -- **Town** — Workspace directory (`~/gt/`) housing all projects -- **Rigs** — Project containers wrapping git repositories -- **Hooks** — Git worktree-based persistent storage surviving crashes -- **Convoys** — Work-tracking bundles grouping multiple beads for an agent -- **GUPP** — Agents must execute work on their hooks; scheduling persists across restarts - -**Runtime Backends:** claude, gemini, codex, cursor, auggie, amp (per-rig config) - -**Communication/Isolation:** - -- Git worktrees for filesystem isolation per agent -- Beads/Hooks for coordination (external state, not shared context windows) -- GUPP: deterministic handoffs through version control, not LLM-judged phase gates - -**Strengths:** Most architecturally ambitious. Crash recovery via git-backed Beads. Role-based agent hierarchy. Multi-agent support. -**Weaknesses:** ~$100/hr token burn, auto-merged failing tests, agents causing unexpected deletions. Go-only ecosystem. No web dashboard. Optimized for autonomous, not human-in-the-loop. - ---- - -### Par (Coplane) - -- **GitHub**: https://github.com/coplane/par -- **Stack**: Python 3.12+ -- **Closest to our current approach** - -**Key Abstractions:** - -- **Sessions**: Single-repo isolated branches via git worktrees + tmux sessions -- **Workspaces**: Multi-repo synchronized development contexts -- **Control Center**: Unified tmux session with windows for each context -- **Labels**: Globally unique, human-readable names - -**Features:** - -- `par start my-feature` — creates worktree + branch + tmux session -- `par send