Commit Graph

118 Commits

Author SHA1 Message Date
suraj-markup 433189a79b perf(agent-codex): cache findCodexSessionFile to avoid double scan
Add a 30-second TTL cache for session file lookups so that
getActivityState, getSessionInfo, and getRestoreCommand called in the
same refresh cycle reuse the result instead of re-scanning
~/.codex/sessions/ independently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:25:24 +05:30
suraj-markup c307ae30e0 feat(agent-codex): implement mtime-based activity state detection
Use the Codex session file's mtime as a proxy for agent activity.
Codex continuously appends to its rollout JSONL while working, so:
- recently modified file → active
- stale file (past threshold) → idle
- process not running → exited

This replaces the previous null return with real activity detection,
using the same threshold constant (DEFAULT_READY_THRESHOLD_MS = 5min)
as the Claude Code plugin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 15:58:25 +05:30
suraj-markup b6eb4d8888 fix(agent-codex): catch approval emit throw, remove dead wasClosedBefore guard
- Wrap entire handleApprovalRequest body in try/catch so a throwing
  "approval" listener doesn't cause an unhandled promise rejection
- Remove wasClosedBefore variable that was always false (the closed
  check on entry guarantees it), simplifying the connect catch block

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 13:52:52 +05:30
suraj-markup 9f1614dd0d refactor(agent-codex): extract shared CLI flag helpers
Extract duplicated approval-policy and model/reasoning flag logic from
getLaunchCommand and getRestoreCommand into shared appendApprovalFlags
and appendModelFlags helpers. Single source of truth for flag mapping.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 12:28:50 +05:30
suraj-markup 3f81936467 fix(agent-codex): reject pending requests before emitting error event
Move this.emit("error", err) after the pending-rejection loop in
handleProcessError, matching handleProcessExit's order. Without this,
emit("error") with no listeners throws synchronously, skipping
cleanup of pending requests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 12:08:00 +05:30
suraj-markup 89d2b5ead3 test(agent-codex): add missing setupMockStream calls to edge-case tests
Three tests relied on the empty default stream coincidentally matching
expected values instead of actually exercising the streaming parser.
Added setupMockStream(content) and stronger assertions to verify the
parser correctly handles missing model, missing token events, and
missing threadId.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:51:09 +05:30
suraj-markup da7d3ec80e fix(agent-codex): stream JSONL, shell-escape binary, guard close()
- Replace full-file readFile with streaming createReadStream + readline
  for potentially huge Codex session files (100MB+)
- Shell-escape resolved binary path in launch/restore commands
- Prevent connect() failure from resetting an explicit close() call
- Remove dead helper functions replaced by streamCodexSessionData

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 09:23:15 +05:30
suraj-markup 3f76338bb4 fix(agent-codex): add jsonrpc 2.0 field and fix resume flag ordering
- Add "jsonrpc": "2.0" to all JSON-RPC requests, notifications, and
  approval responses for spec compliance
- Place flags before positional threadId in resume command for CLI
  parser compatibility (codex resume --flags <threadId>)
- Add test assertions for jsonrpc field and flag ordering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 01:41:47 +05:30
suraj-markup e7033048f2 fix(agent-codex): prevent symlink cycle in recursive session scan
- Use lstat instead of stat in collectJsonlFiles so symlinks to
  directories are never followed (isDirectory returns false for symlinks)
- Add MAX_SESSION_SCAN_DEPTH=4 cap as defense-in-depth
  (Codex uses YYYY/MM/DD structure, max 3 levels)
- Update tests to mock lstat for directory checks separately from stat

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 01:29:41 +05:30
suraj-markup 2dfbf8a778 fix(agent-codex): fix token double-counting and spawn error handling
- Remove additive cached_tokens/reasoning_tokens from cost calculation
  (they are subsets of input_tokens/output_tokens per OpenAI API convention)
- Wrap entire connect() body in try/catch so connecting flag resets if
  spawn() throws synchronously (EMFILE, ENOMEM)
- Add test for spawn-throws-synchronously recovery
- Update cost assertions to reflect correct non-additive token counting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 01:19:36 +05:30
suraj-markup c0105476da fix(agent-codex): address follow-up review — retry after failed connect, binary guard recovery, lint fixes
- Reset closed flag after failed connect() so client can retry
- Wrap binary resolve guard in try/finally to clear on rejection
- Fix duplicate import and unused variable lint errors in tests
- Add test: client retries connect after transient handshake failure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 01:00:44 +05:30
suraj-markup 4e7129d50d fix(agent-codex): address review comments — resource cleanup, concurrency, and memory
Fixes 6 issues flagged by Cursor Bugbot on PR #188:

1. sessionFileMatchesCwd reads only first 4 KB via open()+read() instead of
   loading entire rollout file into memory (Medium)
2. Binary resolution race condition: added promise guard to prevent
   concurrent resolveCodexBinary() calls (Low)
3. Concurrent connect() calls: added connecting guard to prevent
   orphaned child processes (High)
4. connect() failure cleanup: wrap initialize() in try/catch that
   calls close() on failure (Medium)
5. readline cleanup on process exit/error: close readline in both
   handleProcessExit and handleProcessError (Medium)
6. stderr pipe drain: call stderr.resume() to prevent child blocking
   when stderr buffer fills (High)

All 177 tests pass, typecheck clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 00:38:58 +05:30
suraj-markup 82317cbdeb fix(agent-codex): use correct Codex CLI flags, native resume, and date-sharded sessions
Verified all CLI flags against actual Codex documentation and fixed 6 issues:
- --approval-mode → --ask-for-approval (untrusted/on-request/never)
- --reasoning → -c model_reasoning_effort=high (config override)
- Fake text re-injection → codex resume <threadId> (native subcommand)
- Flat readdir → recursive collectJsonlFiles() for YYYY/MM/DD sharding
- ThreadStartParams values → correct Codex enum values
- Added /opt/homebrew/bin/codex and ~/.cargo/bin/codex fallback paths

Closes #176, #177, #178, #179, #183, #184

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 23:47:28 +05:30
suraj-markup 2e09330980 feat(agent-codex): add token tracking, approval policies, reasoning flag, binary detection, conversation resume, and app-server client
Implements six Codex-specific improvements:
- getSessionInfo() parses JSONL session files for token usage and cost estimation
- Configurable approval/sandbox policies (--approval-mode, --dangerously-bypass-approvals-and-sandbox)
- Auto-detect o-series models for --reasoning flag
- resolveCodexBinary() discovers codex via which + fallback paths
- getRestoreCommand() extracts thread context for conversation resume via re-injection
- CodexAppServerClient JSON-RPC client for Codex app-server mode (thread/turn management, approval handling, model discovery)

Closes #176, #177, #178, #179, #183, #184

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 22:37:13 +05:30
suraj_markup ae508c90ee
Merge pull request #154 from suraj-markup/suraj/codex-maturity
feat(agent-codex): mature Codex plugin to match Claude Code support
2026-02-24 09:27:49 +05:30
suraj-markup 0a5fea19e3 test(agent-codex): update tests for correct Codex CLI flags
Update test expectations to match the corrected Codex CLI flags:
- --full-auto instead of --dangerously-bypass-approvals-and-sandbox
- -c developer_instructions=... instead of --system-prompt (inline)
- -c model_instructions_file=... instead of --system-prompt $(cat) (file)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 08:56:03 +05:30
suraj-markup 584ca1daa2 fix(agent-codex): use correct Codex CLI flags and remove dead code
- Replace --dangerously-bypass-approvals-and-sandbox with --full-auto
  (keeps workspace sandbox, auto-approves — safer default for automation)
- Replace nonexistent --system-prompt flag with Codex config overrides:
  -c developer_instructions=... for inline prompts
  -c model_instructions_file=... for file-based prompts
- Remove redundant "active" pattern checks that duplicate the default
  return value (addresses BugBot dead-code finding)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 08:25:42 +05:30
suraj-markup 89fc3369d5 fix: add agent field to readMetadata return mapping
readMetadata() was not returning the `agent` field even though
writeMetadata() persisted it. This meant typed consumers got
undefined for session.agent while raw readers worked fine.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:54:52 +05:30
suraj-markup 46b0d1a541 fix: persist agent name in metadata so lifecycle resolves correct plugin
When --agent override is used at spawn time, the agent name was not stored
in session metadata. The lifecycle manager and session enrichment always
resolved the agent from project config defaults, causing mismatched process
detection (e.g. looking for "claude" instead of "codex") and incorrect
"killed" status on the first poll cycle.

- Add `agent` field to SessionMetadata type and writeMetadata
- Store `plugins.agent.name` in metadata during spawn
- Pass stored agent name to resolvePlugins in list/get/restore paths
- Read `session.metadata["agent"]` in lifecycle manager's determineStatus
- Add tests for metadata persistence with and without override

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:47:43 +05:30
suraj-markup 2bdc6bb59c test: add tests for --agent override and codex plugin registration
- Session manager: agent override uses correct agent, unknown agent throws, default used without override
- CLI spawn: --agent flag passthrough with and without issue ID
- Plugin registry: multiple agent plugins registered via loadBuiltins importFn

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:26:54 +05:30
suraj-markup b0965693c2 fix(agent-codex): suppress no-useless-escape lint for bash template literals
The \$ escapes in bash script template literals are intentional to
prevent JS template interpolation. Added eslint-disable/enable block.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 00:32:51 +05:30
suraj-markup 71ee3a2144 feat(cli): add --agent flag to override agent plugin at spawn time
Allows starting sessions with a different agent without changing config:
  ao spawn ao --agent codex
  ao spawn ao --agent codex INT-1234

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 23:23:54 +05:30
suraj-markup e410079026 feat(agent-codex): mature Codex plugin to match Claude Code support
Brings the Codex agent plugin to feature parity with Claude Code by
fixing four key gaps and hardening the shell wrapper infrastructure.

## Changes

**`packages/plugins/agent-codex/src/index.ts`**

- Permission flag: `--approval-mode full-auto` → `--dangerously-bypass-approvals-and-sandbox`
- Terminal-based activity detection with Codex-specific patterns:
  idle (bare prompts), waiting_input (approval prompts), active (spinners, esc to interrupt)
- PATH-based shell wrappers (`~/.ao/bin/gh`, `git`, `ao-metadata-helper.sh`):
  - `gh` wrapper captures `pr/create` and `pr/merge` output to update PR metadata;
    all other commands use `exec` for transparent passthrough (no stderr merging)
  - `git` wrapper captures `checkout -b` / `switch -c` to update branch metadata
  - Metadata helper escapes sed metacharacters (`&`, `|`, `\`) in values
  - Uses `grep -Fxv` (fixed-string) instead of regex for PATH cleaning
  - Validates `AO_DATA_DIR`/`AO_SESSION` paths to prevent arbitrary file overwrites
    (rejects traversal, resolves symlinks, allowlists `~/.ao/` and `/tmp/`)
- `getEnvironment()` prepends `~/.ao/bin` to PATH for wrapper interception
- `setupWorkspaceHooks()` / `postLaunchSetup()` install wrappers atomically
  (temp file + rename) and append AGENTS.md section
- `getActivityState()` returns `{ state: "exited" }` when dead, `null` when running

**`packages/core/src/lifecycle-manager.ts`**

- Branch-based PR fallback: when `metadata.pr` is missing, calls
  `scm.detectPR()` via `gh pr list --head <branch>` to auto-discover PRs
  for agents without hook systems (Codex, Aider, OpenCode)

## Test coverage

- 85 tests in `agent-codex` (up from 27), replicating Claude Code test patterns:
  detectActivity edge cases, isProcessRunning boundaries, setupWorkspaceHooks
  file I/O behavior, shell wrapper content verification, atomic write validation
- 245 tests in core pass
- All 22 packages typecheck clean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:53:33 +05:30
sujayjayjay c820728ec3
docs: README update (#156)
Clearer H1 and tagline.

- H1 now describes what the project is (orchestration layer for parallel agents)
- Tagline calls out git worktree isolation and autonomous CI/review handling

No structural changes.

Co-authored-by: Sujay Choubey <sujaychoubey@Sujays-MacBook-Pro-4.local>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-23 19:37:43 +05:30
prateek 5ffd063550
ci: add workflow_dispatch trigger to release workflow (#153)
Allows manual triggering of the release workflow for testing.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 22:21:44 +05:30
prateek 3c160881e5
chore: remove static CLAUDE.orchestrator.md (#143)
The static file is fully superseded by the auto-generated orchestrator
prompt in packages/core/src/orchestrator-prompt.ts, which gets injected
dynamically via `ao start`. Also clean up stale references in comments
and architecture docs.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 14:29:17 +05:30
prateek ade1322371
docs: redesign README based on competitive research (#132)
* docs: redesign README based on 16-repo competitive research

Restructured README following patterns from lazygit, starship, Aider,
Swarm, LangGraph, and other high-star projects. Key changes:

- Centered hero with one-line tagline and custom metric badges
- Quick Start within first 20 lines (code-first)
- "How It Works" with numbered flow instead of abstract description
- Plugin architecture table (our differentiator) kept prominent
- Config example showing the reactions system (the "aha" feature)
- "Why Agent Orchestrator?" competitive positioning section
- Reduced from 234 to ~130 lines — link to docs for depth

Research artifacts: github.com/ComposioHQ/agent-orchestrator/releases/tag/metrics-v1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use valid HTML structure for README hero section

Replace invalid <h1> inside <p> with <div> + markdown heading.
Drop dynamic stars badge (shields.io intermittently fails).
Use markdown badge syntax instead of raw HTML img tags.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: remove unrelated scm-github changes from PR

These files were accidentally included from pre-existing unstaged
changes on main. Restoring to main state so PR only contains README.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add GitHub stars badge to README

Follow the standard pattern used by popular open-source projects
(CrewAI, lazygit, starship, etc.) using shields.io dynamic badge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 03:23:02 +05:30
prateek 40c1906d41
feat(web): redesign dashboard, session detail, and orchestrator terminal (#125)
* docs: add design research artifacts — briefs, token reference, screenshots

Comprehensive design research package for the ao dashboard, session
detail page, and orchestrator terminal. Produced via competitive analysis
of 14 products (Linear, Vercel, Railway, Fly.io, Inngest, WandB, LangSmith,
Supabase, and more) + Playwright CSS extraction from live sites + full
codebase audit.

Artifacts:
- docs/design/design-brief.md            Main design brief (v2, Playwright-updated)
- docs/design/session-detail-design-brief.md   /sessions/[id] design spec
- docs/design/orchestrator-terminal-design-brief.md  Orchestrator page spec
- docs/design/token-reference.css        Drop-in CSS replacement for globals.css
- docs/design/competitive-analysis-raw.md  Raw research notes, all 14 sites
- docs/design/design-brief-v1.md         Original text-only brief (pre-Playwright)
- docs/design/README.md                  Index + research methods summary
- docs/design/screenshots/linear-homepage.png   Playwright-captured screenshot
- docs/design/screenshots/railway-homepage.png  Playwright-captured screenshot

Key findings:
- Linear CSS token values verified via Playwright (body bg #08090A, accent
  #7070FF, Berkeley Mono monospace, type scale, radius, transitions)
- Recommended palette: #0C0C11 base (blue-cast dark vs current GitHub #0d1117)
- Highest-impact change: load Inter Variable via next/font/google
- Orchestrator terminal needs visual differentiation (violet accent, status strip)
- token-reference.css is ready to drop into packages/web/src/app/globals.css

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): redesign dashboard, session detail, and orchestrator terminal

Implements a cohesive dense dark-mode design system across all three main views.

- New color token palette: #0c0c11 base, #141419 surface, #1c1c25 elevated
- Accent blue #5b7ef8, status semantics (ready/error/attention/working/idle/done)
- Violet accent #a371f7 reserved for orchestrator
- Inter Variable + JetBrains Mono loaded via next/font with CSS variables
- activity-pulse keyframe for live agent dots

- AttentionZone header: dot + label + flex divider + count pill + chevron
- Sessions laid out in responsive 1→2→3 column grid
- Solid green merge button (translateY hover), no confirm() dialog

- Breadcrumb nav: ← Agent Orchestrator / {session-id} [orchestrator badge]
- CSS 8×8px activity dot with pulse animation replaces emoji labels
- Merge-ready state: green-bordered banner with checkmark icon
- Orchestrator sessions show zone counts strip (merge/respond/review counts)

- xterm.js dark theme (#0a0a0f bg, #d4d4d8 fg, full 16-color ANSI palette)
- variant prop: "agent" (blue cursor) vs "orchestrator" (violet cursor)
- Dynamic height prop instead of fixed 600px; fullscreen toggle with SVG icons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(web): strip rainbow stats, clean header, IBM Plex Sans typography

- Replace Inter with IBM Plex Sans (technical tool aesthetic, distinctive numerics)
- Replace 4-color big-number stats bar with a single compact inline status line
  in the header: "35 sessions · 1 working · 9 PRs" — no decorative colors
- Remove the two-tone "Agent (blue) Orchestrator (white)" title — just "Orchestrator"
- Remove ClientTimestamp (useless) — replaced by orchestrator nav link
- Zone headers: colored dot only (semantic), neutral uppercase label, plain count
  — removes the rainbow-colored label text that read as a widget template
- Add subtle radial gradient glow at top of page for depth

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): kanban layout, amber accent, full-width, bigger stats

- Switch accent from blue (#5b7ef8) to amber/gold (#d18616) throughout
- Replace grid layout with horizontal Kanban columns for active zones
  (merge, respond, review, pending, working), Done stays full-width below
- Remove max-w-[1100px] constraint — full viewport width
- Header stats numbers 20px bold (was 12px), orchestrator link is now a
  visible bordered button
- AttentionZone gains variant="column" for Kanban mode (compact header
  with count pill, vertical card stack)
- Update all hardcoded rgba(91,126,248,...) in SessionCard to amber

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): layout, alert sizing, column order, button feel

- Kanban column order: working→pending→review→respond→merge
  (left = in progress, right = ready to ship)
- Columns use flex-1 min-w-[200px] to fill available width
  instead of fixed 260px leaving half the page empty
- Alert badges: inline-flex wrapper prevents stretching to full
  row width when wrapping
- Terminal button: add bg-subtle fill so it reads as a button
- PR number (#91): remove opaque pill background, now plain
  amber text link — clearly a hyperlink
- Merge PR button: pt-0.5 spacer above the action area

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): don't cache rate-limited partial PR data

When GitHub rate limits fire, enrichSessionPR was caching the
bad partial data (0 additions, CI failing) for 60 seconds, causing
the dashboard to show incorrect data for the full TTL window.

- Skip cache write when majority of API calls failed
- Downgrade console.error → console.warn (this is handled/expected)

The next page refresh will retry live API calls, so data recovers
as soon as the rate limit window resets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): graceful GitHub API rate limit handling in UI

When the GitHub plugin hits rate limits, the dashboard now:

- Shows a single amber banner: "GitHub API rate limited — PR data
  (CI status, review state, sizes) may be stale. Will retry on
  next refresh."
- Hides CI badge, review decision, and size pill on PR cards
  (they'd show wrong values: +0 -0 XS, CI failing)
- Shows a subtle "⚠ PR data rate limited" note on affected cards
  instead of misleading alert badges
- Skips CI/review/conflict-based attention zone classification
  for rate-limited PRs (prevents sessions moving to Review due
  to phantom "CI failing" from the fallback value)
- Doesn't cache partial rate-limited data so next refresh retries
  live API calls as soon as the rate limit window resets

What still works when rate limited:
- Session ID, title, branch, PR number/link
- Session activity status (working/spawning/etc.)
- Merge button if mergeability was already cached
- Restore/terminate/send actions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): dashboard redesign — glassmorphism, Kanban, rate-limit handling, perf fix

Design:
- Kanban layout: active zones as flex columns (working→pending→review→respond→merge),
  Done as full-width grid below
- GitHub dark color palette (main's tokens) with glassmorphic card surfaces
  (rgba bg + backdrop-blur) and subtle blue/violet body gradient
- Activity state shown as labeled pill (● active / ● idle etc.) instead of bare dot
- Session card: title on its own row, larger font, inline-flex alert badges (no stretch)
- PR number rendered as plain accent link, not a blue pill badge
- Terminal button has background fill to feel like a button
- Info circle icon replaces alarming warning triangle for rate-limit indicators
- "1 working" → "1 active" in header stats
- PR table constrained to max-w-[900px] and centered
- Orchestrator session no longer uses purple accent

Rate limiting:
- isPRRateLimited() helper; getAttentionLevel() skips PR classification when limited
- Rate-limited banner in Dashboard; suppressed CI/size/review badges in PRStatus
- SessionCard shows subtle "PR data rate limited" indicator; getAlerts() returns []
- serialize.ts: rate-limited enrichment results cached for 5 min (not 60s) to stop
  retrying 168 failing API calls every minute

Performance:
- page.tsx: 4s hard timeout on PR enrichment — serves stale data fast instead of
  blocking SSR for 75s under rate limiting
- cache.ts: TTLCache.set() accepts optional ttl override for per-entry control

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: suppress stale size/CI/review in PR table when rate limited

PRTableRow now shows "—" for size, CI, and review columns when GitHub
API is rate limited, matching the card view which already hides these.
Prevents misleading "+0 -0 XS" size and "needs review" labels from the
default fallback values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: 3D card effect with depth shadow and top-edge shine

Cards now clearly pop against the dark background:
- Solid gradient bg (rgba(28,36,47) → rgba(18,23,31)) instead of
  near-invisible rgba(22,27,34,0.8) surface
- Layered box-shadow: contact shadow + diffuse depth + inset top highlight
  that simulates light hitting the card's top edge (the "shine")
- Hover: card lifts 2px with deeper shadow
- Merge-ready: green-tinted bg with green ambient glow + stronger lift on hover

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: restore text legibility inside session cards

The darker solid card gradient made muted/secondary text nearly
invisible — #484f58 (text-muted) had only ~2:1 contrast on the
new card bg. Override the color tokens locally within .session-card
to GitHub's established dark-mode legibility values:

  --color-text-muted:     #484f58 → #656d76  (3.8:1 on card bg)
  --color-text-secondary: #7d8590 → #8b949e  (6.2:1 on card bg)
  --color-text-tertiary:  #484f58 → #656d76

Scoped to .session-card so the rest of the UI is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address bugbot comments — fonts, review zone, ActivityDot, orchestrator btn

- layout.tsx: add IBM Plex Sans weight 700 (was missing, font-bold falling
  back to 600)
- DirectTerminal.tsx: use "IBM Plex Mono" instead of unloaded "JetBrains Mono"
- SessionDetail.tsx: add review zone to OrchestratorStatusStrip (was omitted,
  sessions with CI failures were invisible in the strip)
- ActivityDot.tsx: extract shared component, remove duplicate implementations
  in SessionCard.tsx and SessionDetail.tsx
- Dashboard.tsx: redesign orchestrator button with 3D glass style matching
  card aesthetic (blue-tinted bg, depth shadow, hover lift)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): lint — eqeqeq, duplicate import, unused var

- ActivityDot.tsx: != → !== (eqeqeq rule)
- PRStatus.tsx: merge duplicate @/lib/types imports into one
- SessionCard.tsx: remove unused activityIcon import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): elevate session detail + orchestrator page design

- Nav: glass backdrop-blur effect with chevron back link
- Header: detail-card 3D treatment with left-border accent keyed to activity color
- Meta chips: bordered pill style with subtle bg instead of flat text
- Status tag: pill badge for status instead of plain text
- PR card: detail-card 3D treatment, border-color reflects PR state
- PR merged badge: purple pill instead of gray text
- Unresolved count: red pill badge in section header
- Blockers section: renamed "Issues" → "Blockers"
- Terminal section: colored bar indicator instead of plain label
- Orchestrator status strip: total agent count + per-zone colored pills
- globals.css: add .nav-glass and .detail-card classes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): fetchZoneCounts parses body.sessions, delayed 2s to avoid contention

The /api/sessions endpoint returns `{ sessions: [...] }` not a bare array.
fetchZoneCounts was treating the whole response object as an array, so
zone counts were always zero on the orchestrator detail page.

Also delays the initial fetchZoneCounts call by 2s so it doesn't contend
with the session fetch on page load (both hit /api/sessions which is slow
when GitHub enrichment is running).

Also includes: Playwright kill-Chrome-for-Testing tip in CLAUDE.md,
toned-down detail-card shadow in globals.css.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* perf+test(web): cache-first PR enrichment, skip exited sessions, fix component tests

Performance improvements:
- enrichSessionPR() now accepts cacheOnly option and returns boolean
- /api/sessions/[id]: serve from cache immediately, only block on first load
- /api/sessions: skip PR enrichment for EXITED sessions (no longer changing)
- cache: increase default TTL from 60s to 5 minutes

Test fixes (match redesigned SessionCard + AttentionZone):
- "restore session" (header) → "restore"; expanded panel still shows "restore session"
- "merge PR #N" → "Merge PR #N" (capital M)
- "CI status unknown" → "CI unknown"
- "ask to fix CI" / "ask to fix CI" → "ask to fix"
- "terminate session" → "terminate"
- Zone labels: RESPOND/WORKING/DONE → Respond/Working/Done (CSS uppercase is visual only)
- "working" zone no longer collapsed by default; collapse tests now use "done" zone

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(core): ActivityDetection with timestamp propagation

- Add ActivityDetection interface { state, timestamp? } to types.ts
- Agent getActivityState() returns ActivityDetection | null instead of
  ActivityState | null, allowing timestamp from JSONL mtime to propagate
- session-manager updates session.lastActivityAt when detected.timestamp
  is more recent — fixes "active 22h ago" showing stale timestamps
- Update all agent plugins (claude-code, aider, codex, opencode) to
  return ActivityDetection objects

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): dismissible rate limit banner + 60min rate-limit cache TTL

- Add X dismiss button to GitHub API rate limit banner in Dashboard.tsx
  so it can be closed during demos
- Extend rate-limited PR cache TTL from 5min to 60min — GitHub GraphQL
  rate limits reset hourly, no point retrying every 5 minutes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): address Cursor Bugbot review comments on PR #125

- Dashboard StatusLine: active sessions count now uses var(--color-status-working)
  (blue) instead of neutral text color, matching the design system semantics
- SessionCard: isReadyToMerge now guards against rate-limited state — a card
  with stale cached mergeability data won't show green merge-ready styling
- DirectTerminal: add `variant` to useEffect dependency array (was missing,
  causing stale cursor/selection colors if variant changed after mount)
- agent-aider: include `timestamp: chatMtime` in all ActivityDetection returns,
  matching the pattern used by agent-claude-code (enables accurate lastActivityAt)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): resolve lint, typecheck, and test failures

Lint:
- Remove unused parseJsonlFile function (superseded by parseJsonlFileTail)
- Remove dead lastLogModified stat() call in getSessionInfo (field was
  removed from AgentSessionInfo but the filesystem read was left behind)

Typecheck + Tests (ActivityDetection):
- session-manager.test.ts: update mocks to return { state: "active" } /
  { state: "idle" } instead of bare strings — getActivityState() returns
  ActivityDetection | null, not ActivityState | null
- integration tests (aider, claude-code, codex, opencode): update imports
  from ActivityState → ActivityDetection, variable types, comparisons
  (activityState?.state !== "exited"), and assertions (?.state).toBe()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: parseJsonlFileTail uses readFile for small files; enrich exited sessions with PRs

- parseJsonlFileTail now calls stat() then readFile() for files smaller than
  maxBytes, falling back to open()/handle.read() only for large files. This
  fixes the test infrastructure (which mocks readFile but not open) and also
  fixes a scope bug where `offset` was declared inside an inner try block but
  referenced outside both try blocks.
- Math.max(0, NaN) returns NaN not 0, so size must default to 0 when stat
  returns a mock without a size field: `const { size = 0 } = await stat(...)`.
- Update activity-detection.test.ts: getActivityState() now returns
  ActivityDetection objects, so tests use (await ...)?.state comparisons.
- Remove stale lastLogModified test (field was removed from AgentSessionInfo).
- Remove EXITED skip guard from api/sessions/route.ts: exited sessions can
  still have open, merge-ready PRs that need enrichment on the dashboard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: comprehensive code review fixes — tests, timestamps, UI correctness

Address gaps identified in code review of the ActivityDetection PR:

Core / Session Manager:
- Add `timestamp` to all `{ state: "exited" }` returns in all 4 agent plugins
  (claude-code, aider, codex, opencode) using consistent `exitedAt = new Date()` pattern
- Add 2 new session-manager tests: timestamp propagation when detection timestamp
  is newer, and no-downgrade when detection timestamp is older
- Fix `parseJsonlFileTail` lint error: remove useless `= 0` initializer (value was
  always overwritten before use; catch block returns early)

Web package — tests:
- Fix 3 `api-routes.test.ts` failures: `sessionsGET()` needs a Request object since
  the route reads `request.url` for `?active=true` query param
- Fix `serialize.test.ts` rate-limit test: spy on `console.warn` (what the code uses)
  not `console.error`
- Add 5 `ActivityDot` component tests covering all activity states, unknown states,
  null activity, and dotOnly mode

Web package — UI correctness:
- Fix `relativeTime()` in SessionDetail to guard against invalid/empty ISO strings
- Fix timer Map leak: add `timersRef.current.clear()` in cleanup effect after forEach
- Add `encodeURIComponent` to sessionId in message fetch URL

Server — race condition fix:
- Guard `activeSessions.delete` in pty.onExit, ws.on("close"), and ws.on("error")
  against stale handlers deleting a newly-registered session with the same ID.
  Fixes flaky integration test where afterEach's pty.kill() fired asynchronously
  after the next test had already set up a new session with the same session ID.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(web): narrow PREnrichmentData types to eliminate unsafe casts in serialize

PREnrichmentData.ciStatus and .reviewDecision were typed as string,
requiring unsafe `as` casts when reading from cache into DashboardPR.
Narrow them to the same literal union types used by DashboardPR, making
the casts unnecessary. Also narrow ciChecks[].status to match CoreCICheck.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 18:43:57 +05:30
prateek 0e2ca70b0b
feat: session title fallback chain for PR-less sessions (#105)
* feat: session title fallback chain — PR title → summary → issue title → branch

Sessions without PRs now always show a meaningful title on the dashboard
instead of just the status text. The fallback chain is:

1. PR title (already worked)
2. Agent summary (now fetched from JSONL via getSessionInfo())
3. Issue title (now fetched via tracker.getIssue())
4. Humanized branch name (e.g., "feat/infer-project-id" → "Infer Project ID")

Key changes:
- Enrich agent summaries by calling getSessionInfo() for sessions
  without summaries (local file I/O, not API calls)
- Enrich issue titles via tracker.getIssue() with 5-min TTL cache
- Add humanizeBranch() utility for last-resort branch name display
- Add issueTitle field to DashboardSession type
- Show issue title in expanded detail panel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: extract humanizeBranch to separate module to avoid client-side timer leaks

Moves humanizeBranch() from serialize.ts to format.ts — a pure utility
module with no side effects. This prevents the client bundle from pulling
in TTLCache instantiations (which create setInterval timers) when
SessionCard.tsx imports the function.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove dead re-export of humanizeBranch from serialize.ts

No consumer imports humanizeBranch from serialize — SessionCard imports
directly from format.ts. The re-export was unused surface area.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add missing first-project fallback in summary enrichment block

Matches the pattern used by all other enrichment blocks in page.tsx
(issue labels, issue titles, PR enrichment) which fall back to the
first configured project when projectId and sessionPrefix both miss.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: smarter title heuristic — skip prompt excerpts, prefer issue titles

The agent summary fallback from extractSummary() often returns truncated
spawn prompts ("You are working on GitHub issue #42: Add auth...") which
make poor titles. The new heuristic detects these prompt excerpts and
prefers the issue title when available.

Updated fallback chain:
  PR title → quality summary → issue title → any summary → humanized branch → status

Changes:
- Add looksLikePromptExcerpt() to detect spawn prompt patterns
- Add getSessionTitle() to encapsulate the smart fallback logic
- Expand humanizeBranch() with more prefix patterns (release, hotfix, etc.)
- SessionCard now uses getSessionTitle() instead of inline ?? chain
- Add 25 unit tests covering all functions and edge cases
- Fix missing issueTitle field in serialize.test.ts fixture

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract shared resolveProject() to eliminate duplication

Moves resolveProject() from route.ts into serialize.ts as a shared
export. Both page.tsx and route.ts now use the same function instead
of duplicating the 3-step project resolution logic (projectId →
sessionPrefix → first project fallback) inline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: replace looksLikePromptExcerpt heuristic with summaryIsFallback metadata

Instead of fragile string matching to detect truncated spawn prompts,
the agent plugin now sets summaryIsFallback: true when the summary is
a first-message fallback rather than a real agent-generated summary.

- Add summaryIsFallback to AgentSessionInfo (core/types.ts)
- extractSummary() returns { summary, isFallback } in claude-code plugin
- Add summaryIsFallback to DashboardSession, propagate in serialize.ts
- Replace looksLikePromptExcerpt() with !session.summaryIsFallback
- Fix .js extension in format.ts import (review feedback)
- Add thorough tests for all layers of propagation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add resolveProject and enrichSessionIssueTitle coverage

- resolveProject: 5 tests covering direct match, prefix fallback,
  first-project fallback, empty projects, and priority ordering
- enrichSessionIssueTitle: 7 tests covering enrichment, # prefix
  stripping, Linear-style labels, skip conditions, error handling,
  and cross-call caching

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: extract shared enrichSessionsMetadata, fix session detail route

- Extract duplicated enrichment orchestration (issue labels, agent
  summaries, issue titles) from page.tsx and route.ts into a single
  enrichSessionsMetadata() function in serialize.ts
- Fix /api/sessions/[id] route: was missing agent summary and issue
  title enrichment, and had hand-rolled project resolution instead of
  using resolveProject() (also missing the first-project fallback)
- Optimize: resolve projects once per session instead of 3x
- Add 8 tests for enrichSessionsMetadata covering full pipeline, skip
  conditions, missing plugins, no-tracker config, multiple sessions,
  and default agent fallback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove dead getAgent and getTracker exports from services.ts

These helpers became unused when enrichSessionsMetadata was extracted
to serialize.ts with inline registry.get() calls (to avoid coupling
serialize.ts to services.ts and pulling plugin packages into webpack).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 19:02:02 +05:30
prateek 450507193e
docs: update port references to reflect configurability (#122)
All docs now mention that the dashboard port (default 3000) is
configurable via `port:` in agent-orchestrator.yaml. Fixes incorrect
port 9847 references in SETUP.md, adds multi-project port guidance,
and documents terminal port auto-detection.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 07:37:00 +05:30
prateek 9822aba4e0
fix: auto-detect free port in `ao init` instead of hardcoding 3000 (#120)
When port 3000 is occupied, `ao init` now scans upward (3001, 3002, ...)
until a free port is found. Applies to both interactive and --auto modes.
Uses the existing isPortAvailable() from web-dir.ts (now exported).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 07:27:39 +05:30
prateek 0e533840ba
fix: tab title followups — empty name guard, dedupe truncation (#121)
- Guard against empty project name in getProjectName() and icon.tsx
  (falls back to config key or "A" initial)
- Extract branch truncation into shared `truncate()` helper in
  session detail page
- Addresses bugbot comments from PR #111

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 07:12:32 +05:30
prateek b605ee8ed4
feat: dynamic tab titles and health-aware favicons (#111)
* feat: dynamic browser tab titles and health-aware favicons

Tabs now show contextual titles so multiple dashboard instances are
distinguishable at a glance. Favicons reflect system health (green/
yellow/red) and display the project initial for visual identification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address review — dedupe project name, handle merge level, add activity emoji

- Extract getProjectName() to shared lib/project-name.ts (used by
  layout, page, icon) — fixes bugbot duplication comment
- computeHealth now treats "merge" attention level as yellow (needs
  human action) instead of silently mapping to green — fixes bugbot
  merge-ignored comment
- Add activity status emoji to session tab titles (🟢💤🚧💀)
  that updates live as session state changes
- Special-case orchestrator sessions: "ao-orchestrator | Orchestrator Terminal"
- Extract activityIcon map to shared lib/activity-icons.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use absolute title to avoid layout template duplication

The layout template `%s | project` was wrapping the page title
`project | Agent Orchestrator`, producing `project | Agent Orchestrator | project`.
Use `title.absolute` to opt out of the template on the root page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use "ao | <project>" format for dashboard title

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: dedupe config reads with React.cache() on getProjectName

Wraps getProjectName with React.cache() so layout, page, and icon
share a single loadConfig() call per server render pass instead of
reading the YAML file three times.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 04:21:49 +05:30
prateek 520010d5a2
feat: configurable terminal server ports for multi-dashboard support (#113)
* feat: make terminal server ports configurable to fix multi-dashboard EADDRINUSE

When multiple ao dashboards run simultaneously (e.g., ao on port 3000,
integrator on port 3002), both try to start terminal WebSocket servers
on hardcoded ports 3001/3003, causing EADDRINUSE. Add terminalPort and
directTerminalPort to config schema so each instance can use unique ports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use optional() instead of default() for terminal port schema

Zod .default() always fills in the value, making config.terminalPort
never undefined and the env var fallback in buildDashboardEnv dead code.
Switch to .optional() so the priority chain works correctly:
config value > TERMINAL_PORT env var > hardcoded default.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: move terminal server defaults from 3001/3003 to 14800/14801

The 3000-3009 range is the most contested in dev tooling (Next.js
auto-increments, BrowserSync, Grafana, Rails, Express all default to
3000+). Port 14800-14899 has zero IANA registrations, zero known dev
tool conflicts, and is safely below OS ephemeral ranges.

Updated all hardcoded fallbacks, .env.local.example, docker-compose
port mappings, and documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: auto-detect available terminal ports for zero-config multi-dashboard

When no terminal ports are configured (no config, no env vars),
buildDashboardEnv now probes for an available port pair starting at
14800. The second `ao start` automatically gets 14802/14803 (or the
next free pair), eliminating EADDRINUSE without any user configuration.

Port detection scans in steps of 2 to keep the pair consecutive.
Explicit config/env values bypass auto-detection entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: update onboarding test to use new default terminal port (14801)

The onboarding integration test had port 3003 hardcoded for the
WebSocket health check. Updated to read from DIRECT_TERMINAL_PORT
env var with 14801 as the default, matching the new port defaults.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 04:00:19 +05:30
prateek 1a3cad900f
fix: restore archived sessions that were killed/cleaned up (#110)
* fix: restore archived sessions that were killed/cleaned up

restore() only searched active metadata files, so sessions that had
been killed (and archived to the archive/ subdirectory) could not be
restored. Now restore() falls back to the archive directory, picks the
latest archived version, and recreates the active metadata file before
proceeding.

- Add readArchivedMetadataRaw() to metadata.ts
- Update restore() to search archive as fallback
- Add 4 unit tests for readArchivedMetadataRaw
- Add 2 integration tests for archive restore in session-manager

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent archive prefix collision for underscore-containing session IDs

readArchivedMetadataRaw used startsWith(sessionId + "_") which would
cause "app" to false-match archive files belonging to "app_v2". Now
verifies the character after the prefix is a digit (start of ISO
timestamp) to disambiguate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 02:19:44 +05:30
prateek 1e304110f7
fix: destroy old runtime before restore, cleanup clone on failure (#109)
Two bugbot follow-ups from PR #104:

1. Destroy old runtime handle before creating new one in restore().
   When an agent crashes, the tmux session survives. Creating a new
   tmux session with the same name fails. Now we destroy the old
   handle first (best-effort, non-blocking).

2. Wrap git clone in try-catch in workspace-clone restore(). On clone
   failure (network error, disk full), clean up the partial directory
   so future restore attempts aren't blocked.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 01:35:49 +05:30
prateek 65fa811b3b
feat: implement session restore for crashed/exited agents (#104)
* feat: implement session restore for crashed/exited agents

Add true in-place session restore: same session ID, same worktree, same
metadata — optionally resuming the Claude Code conversation via --resume.

Core changes:
- Add TERMINAL_STATUSES, TERMINAL_ACTIVITIES, NON_RESTORABLE_STATUSES sets
  and isTerminalSession/isRestorable helpers to types.ts
- Add SessionNotRestorableError and WorkspaceMissingError error classes
- Add restore() to SessionManager with 9-step flow: find metadata →
  validate restorability → check/recreate workspace → get restore or
  launch command → create runtime → update metadata
- Add restoredAt field to Session and SessionMetadata

Plugin extensions:
- workspace-worktree: exists() + restore() (git worktree prune + re-add)
- workspace-clone: exists() + restore() (git clone + checkout)
- scm-github: branchExists() via git rev-parse
- agent-claude-code: getRestoreCommand() finds latest JSONL session file
  and builds claude --resume command

CLI + Web:
- Add `ao session restore <id>` subcommand
- Web restore API route uses sessionManager.restore() instead of spawn()
- SessionCard uses centralized TERMINAL_STATUSES/TERMINAL_ACTIVITIES
- Web types re-export core constants with sync tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add "merged" to TERMINAL_STATUSES

The old inline isTerminal check included "merged" but when refactored
to use the TERMINAL_STATUSES set, "merged" was omitted. This caused
merged sessions (whose activity is not "exited") to incorrectly show
the "terminal" link and "terminate session" button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: enrich runtime state before restore check, remove dead branchExists

- Add enrichSessionWithRuntimeState() call before isRestorable() in
  restore() so crashed sessions (status "working", agent exited) are
  correctly detected as terminal and eligible for restore.
- Remove dead branchExists from SCM interface and scm-github plugin
  (defined but never called anywhere in the codebase).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: allow restore of crashed working sessions

Remove "working" from NON_RESTORABLE_STATUSES. The isTerminalSession()
gate already prevents restoring truly active sessions (activity is not
"exited"). This fix allows crashed agents (status "working", activity
"exited") to be restored, aligning core behavior with the UI which
already shows the restore button for this case.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: distinguish missing branch from missing restore support

Split the compound condition so workspace restore gives an accurate
error message when branch metadata is null ("branch metadata is
missing") vs when the workspace plugin lacks a restore method.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 01:12:57 +05:30
prateek de6653e258
feat: first-class orchestrator session + file-based system prompt (#101)
* feat: first-class orchestrator session + file-based system prompt

Make the orchestrator a first-class managed session that flows through
the same SessionManager pipeline as worker sessions, and fix a blocking
bug where long system prompts get truncated by tmux/zsh.

Changes:
- Add OrchestratorSpawnConfig type and spawnOrchestrator() to
  SessionManager interface
- Implement spawnOrchestrator() in session-manager.ts: proper
  hash-based tmuxName, runtimeHandle, plugin lifecycle — no workspace
  creation (uses project.path directly)
- Refactor `ao start` to use SessionManager.spawnOrchestrator()
  instead of manual tmux calls + metadata writes (~80 lines removed)
- Refactor `ao stop` to use SessionManager.kill() instead of manual
  tmux kill + metadata delete
- Update `ao init` next steps: guide users to `ao start` before
  `ao spawn`
- Add systemPromptFile to AgentLaunchConfig for file-based system
  prompts (avoids tmux truncation of 2000+ char inline prompts)
- Update agent-claude-code, agent-codex, agent-aider plugins to use
  shell command substitution "$(cat '/path')" when systemPromptFile
  is set
- Update runtime-tmux create() to use load-buffer/paste-buffer for
  launch commands >200 chars
- Add 8 tests for spawnOrchestrator
- Fix SessionManager mock in 8 test files (add spawnOrchestrator)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use hash-based tmux name in orchestrator attach hint

The tmux attach hint after `ao start` printed the user-facing session
ID (e.g. app-orchestrator) instead of the hash-based tmux session name
(e.g. a3b4c5d6e7f8-app-orchestrator), causing "session not found"
errors. Now captures the runtimeHandle.id from spawnOrchestrator's
return value for the correct tmux target.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 19:32:35 +05:30
prateek 767558fed6
fix: three spawn regressions from PR #88 session-manager refactor (#100)
1. No-issue spawn: use session/{sessionId} branch instead of defaultBranch
   to avoid git worktree conflicts with the main repo's checked-out branch.

2. Plugin resolution: accept importFn parameter in loadBuiltins/loadFromConfig
   so CLI can pass its own import() context for pnpm strict resolution. Added
   all plugin packages as CLI workspace dependencies.

3. Ad-hoc issue IDs: gracefully handle IssueNotFoundError by continuing
   without tracker context instead of throwing. Non-issue errors (auth,
   network) still fail fast.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 17:53:33 +05:30
prateek 59c490a3af
fix: dashboard config discovery + CLI service layer refactoring (#70)
* fix: config discovery, activity detection, and metadata port storage

- findConfigFile() checks AO_CONFIG_PATH env var (resolved to absolute path)
- loadConfig() delegates to findConfigFile() for consistent validation
- Pure Node.js readLastJsonlEntry (no external tail binary), safe for
  multi-byte UTF-8 at chunk boundaries
- Added "ready" activity state to agent plugins
- Store dashboardPort, terminalWsPort, directTerminalWsPort in session
  metadata so ao stop targets the correct processes
- Zod schema port default aligned with TypeScript interface

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: dashboard config discovery + CLI service layer refactoring

- Config discovery via AO_CONFIG_PATH env var
- Auto port detection with PortManager
- Activity detection with ready state, pure Node.js readLastLine
- 5 CLI services: ConfigService, PortManager, DashboardManager, MetadataService, ProcessManager
- Store all service ports in metadata for ao stop
- Set NEXT_PUBLIC_ env vars for frontend terminal components
- Multi-byte UTF-8 safe readLastJsonlEntry
- Tests for all new services and utils

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address bugbot review comments (port fallback + systemPrompt)

1. Align port fallback to 3000 everywhere (matching Zod schema default):
   - start.ts: config.port ?? 3000
   - dashboard.ts: config.port ?? 3000
   - types.ts JSDoc: "defaults to 3000"
   - orchestrator-prompt.ts: already correct at 3000

2. Add --append-system-prompt to Claude Code plugin's getLaunchCommand
   so orchestrator context is actually passed to the Claude agent.
   Previously systemPrompt was generated but silently dropped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove dead ConfigService mock from status test

The vi.mock for ConfigService.js referenced a deleted module.
Config mocking is already handled by the @composio/ao-core mock.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: extract shared buildDashboardEnv to eliminate duplication

Dashboard env construction (AO_CONFIG_PATH, PORT, NEXT_PUBLIC_*) was
duplicated between start.ts and dashboard.ts. Extracted into
buildDashboardEnv() in web-dir.ts (already shared by both commands).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 17:08:48 +05:30
prateek b75c6b04ea
refactor: delegate CLI commands to core SessionManager (#88)
* refactor: delegate CLI commands to core SessionManager

Replace direct tmux/metadata manipulation in CLI commands with calls to
core's SessionManager (spawn, list, kill, cleanup, send). This removes
~700 lines of reimplemented session logic and makes the CLI a thin layer
over the core service.

- Add create-session-manager.ts factory (cached PluginRegistry + SM)
- spawn: delegate to sm.spawn() instead of manual worktree/tmux setup
- session ls/kill/cleanup: delegate to sm.list()/kill()/cleanup()
- status: use sm.list() for session discovery and activity from Session
- send: resolve tmux target from session.runtimeHandle
- review-check: use sm.list() for session discovery
- dashboard/start: add --rebuild flag for stale .next cache cleanup
- Update all tests to mock create-session-manager.js

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add regression tests for spawn delegation and dashboard stale cache

- spawn.test.ts: verify spawn delegates to sm.spawn() with correct args,
  shows hash-based tmux name in attach hint (regression for flat-naming bug)
- dashboard.test.ts: verify stale .next cache detection patterns match the
  actual error (Cannot find module vendor-chunks/xterm@5.3.0.js) and that
  rebuildDashboard cleans .next directory

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: ao dashboard --rebuild properly kills and restarts running server

Before: --rebuild deleted .next under the running dev server, leaving it
in a broken state (500s, missing chunks). Couldn't recover without manual
intervention.

Now: --rebuild detects the running dashboard via lsof, kills it, cleans
.next, then starts a fresh dev server on the same port. Works correctly
from any worktree since findWebDir resolves to the same web package.

- Add findRunningDashboardPid/findProcessWebDir/waitForPortFree utilities
- Split cleanNextCache from rebuildDashboard (cache-only vs full rebuild)
- Update tests for new dashboard-rebuild exports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address bugbot review comments on PR #88

1. Cleanup dry-run now delegates to sm.cleanup({dryRun: true}) instead
   of checking local status fields — ensures dry-run uses same live
   checks (PR state, runtime alive) as actual cleanup.

2. Remove module-level mutable config in status.ts — pass config as
   parameter to gatherSessionInfo() to avoid stale state bugs.

3. Batch-spawn duplicate detection uses sm.list() instead of broken
   findSessionForIssue() which relied on flat tmux naming incompatible
   with hash-based architecture.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove dead exports getPluginRegistry and rebuildDashboard

Both functions were exported but never imported outside test mocks.
Clean up test mocks and unused imports accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: deduplicate config/session lookups in send, hoist sm.list() in batch-spawn

1. Send command: merge resolveTmuxTarget() and resolveAgentForSession()
   into a single resolveSessionContext() that loads config and calls
   sm.get() once instead of twice.

2. Batch-spawn: move sm.list() call before the loop and build a Map for
   O(1) duplicate lookups, avoiding repeated metadata reads + runtime
   enrichment on every iteration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove unused project parameter from spawnSession

After delegating to sm.spawn(), the ProjectConfig parameter is no
longer referenced — sm.spawn() resolves it internally from the
projectId. Remove the parameter and simplify both callers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: handle process.kill race, waitForPortFree timeout, dry-run errors

1. Wrap process.kill() in try-catch to handle ESRCH when the target
   process exits between detection and kill (race condition).

2. waitForPortFree() now throws on timeout instead of silently
   returning, preventing the dashboard from starting on a busy port.

3. Dry-run cleanup now displays errors from PR/issue checks, matching
   the non-dry-run branch behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: hoist session manager in cleanup, skip dead sessions in batch-spawn

1. Hoist getSessionManager() before the dry-run if/else in cleanup
   since both branches create the same instance.

2. Batch-spawn duplicate detection now excludes dead/killed/exited
   sessions so crashed sessions don't block respawning the same issue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: lsof flags, dry-run error guard, remove unused --regenerate

1. Fix lsof flag from -Fn to -Ffn so findProcessWebDir() actually
   gets the file descriptor field needed to match "fcwd" markers.

2. Dry-run cleanup now guards "no sessions" message with both
   killed.length === 0 AND errors.length === 0, matching the
   non-dry-run branch behavior.

3. Remove unused --regenerate CLI option from ao start (defined
   but never referenced in the action body).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: cache registry promise and cap stderr buffer

- Cache the Promise instead of the resolved PluginRegistry to prevent
  concurrent callers from racing past the null check and creating
  multiple registries before loadFromConfig completes.
- Cap stderrChunks to 100 entries since only early startup errors
  (stale build detection) are checked — unbounded growth wastes memory
  for long-running dashboard processes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 14:30:17 +05:30
prateek 0691c20d4c
fix: decouple activity detection from runtimeHandle (#89)
* fix: decouple activity detection from runtimeHandle

Activity detection reads JSONL files on disk — it only needs
workspacePath, not a runtime handle. Gating on runtimeHandle caused
sessions created by external scripts to always show "unknown".

Two fixes:
1. enrichSessionWithRuntimeState now runs activity detection
   independently of runtime handle presence
2. list() and get() construct a fallback handle from session ID
   when runtimeHandle is missing in metadata (same pattern
   sendMessage already uses)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: don't let fabricated runtime handles override session status

Bugbot correctly identified that constructing a fallback runtimeHandle
for sessions without one causes isAlive() → false → status = "killed",
clobbering meaningful statuses like "pr_open". Track whether the handle
came from metadata and only run liveness checks on real handles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract ensureHandleAndEnrich to deduplicate list/get

Bugbot flagged the identical fallback handle construction in list() and
get(). Extracted into ensureHandleAndEnrich() so the logic lives in one
place.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 14:30:03 +05:30
prateek adc17c8ae0
feat: overhaul orchestrator prompt with comprehensive CLI reference (#90)
* feat: overhaul orchestrator prompt with comprehensive CLI reference

The generated CLAUDE.orchestrator.md now teaches the orchestrator agent
everything it needs out of the box: identity/role, complete CLI reference
for every `ao` command with flags/options, behavioral guidelines (do/don't),
session lifecycle, workflows, and anti-patterns.

- Rewrite generateOrchestratorPrompt() with detailed CLI docs
- Add "Never Do" section (no legacy scripts, no raw tmux, no coding)
- Add session lifecycle ASCII diagram adapted to project config
- Document ao send flags (--no-wait, --timeout, -f) and mechanics
- Update static CLAUDE.orchestrator.md to use ao CLI exclusively
- Add 35 unit tests for orchestrator prompt generation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: move orchestrator prompt injection into Agent plugin interface

The orchestrator prompt was being injected by directly writing
CLAUDE.local.md and CLAUDE.orchestrator.md in start.ts — Claude Code-
specific logic that doesn't work for other agents (Codex, Aider, OpenCode).

- Add `injectSystemPrompt()` to the Agent interface in types.ts
- Implement in agent-claude-code: writes CLAUDE.{name}.md + @import
- Implement in agent-codex/opencode: writes AGENTS.md
- Implement in agent-aider: writes .aider.conventions.md
- Remove ensureOrchestratorPrompt/ensureOrchestratorImport from start.ts
- start.ts now calls agent.injectSystemPrompt() (agent-agnostic)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: pass orchestrator prompt via CLI flags instead of file injection

Replace the file-based injectSystemPrompt() approach with native agent CLI
flags. Each agent plugin now handles systemPrompt in getLaunchCommand():
- Claude Code: --append-system-prompt
- Codex: --system-prompt
- Aider: --system-prompt
- OpenCode: no flag yet (ignored)

This removes the need to write CLAUDE.orchestrator.md / CLAUDE.local.md /
AGENTS.md files, making the implementation truly agent-agnostic. Also removes
the unused --regenerate flag from `ao start`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 11:32:33 +05:30
prateek 73957182f7
fix: activity detection — fix path encoding bug, add ready state (#71)
* fix: activity detection — fix path encoding, use tail -1 for JSONL

Two tightly coupled infrastructure fixes:

- Fix toClaudeProjectPath(): leading `/` becomes `-` (not stripped),
  matching Claude Code's actual project directory naming convention.
- Replace manual 4KB buffer read in readLastJsonlEntry() with
  `tail -1` + JSON.parse — handles any file size, any line length,
  and eliminates the truncated-line edge case entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add "ready" state, return null when unknown, remove dead code

Behavioral changes to activity detection:

- Add "ready" to ActivityState — separates "alive at prompt" from
  "idle/stale". Configurable via readyThresholdMs (default 5 min).
- Agent plugins return null when they can't determine activity
  (no workspace, no JSONL, no per-session tracking). Session manager
  preserves existing activity instead of overwriting with a guess.
- Remove isProcessing() from Agent interface — zero callers in
  production code, fully superseded by getActivityState().
- Remove extractLastMessageType() from claude-code — the field it
  populated (lastMessageType) was only consumed by the old inline
  CLI mapping, which is now replaced by plugin delegation.
- CLI status delegates to agent.getActivityState() (single source
  of truth) with metadata fallback when plugin returns null.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: comprehensive activity detection coverage

- activity-detection.test.ts: 42+ tests covering path encoding,
  getActivityState edge cases (exited/null/fallback), real Claude Code
  JSONL types, agent interface spec types, staleness thresholds,
  JSONL file selection, and realistic session sequences.
- status.test.ts: plugin delegation tests — verifies CLI uses
  agent.getActivityState() as single source of truth, passes
  readyThresholdMs from config, falls back to metadata on null/throw.
- Integration tests: updated type expectations for null returns from
  codex, opencode, and aider; added "ready" to valid state lists.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: flaky Linear integration test + missing ready label in SessionDetail

Linear API has eventual consistency — updateIssue state changes don't
propagate instantly. Poll with retries instead of asserting immediately.

Also adds "ready" entry to SessionDetail activityLabel map (was missing,
causing fallback to dim/unstyled rendering).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pure Node.js readLastJsonlEntry, use pollUntilEqual for Linear test

Replace `tail -1` with pure Node.js implementation that reads backwards
from end of file in 4KB chunks. No external binary dependency — works
on any platform.

Fix flaky Linear integration test by using the existing pollUntilEqual
helper instead of an inline retry loop. Linear API has eventual
consistency; pollUntilEqual retries for up to 5s with 500ms intervals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use tail -1 for readLastJsonlEntry, add real-data integration test

Replace over-engineered pure Node.js backward-reading implementation with
simple `tail -1` via execFile. The codebase already shells out to tmux,
git, and ps everywhere — tail is no different.

Add integration test that validates toClaudeProjectPath() and
readLastJsonlEntry() against real ~/.claude/projects/ data on disk.
No API key needed — just requires Claude to have been run once.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 03:48:19 +05:30
prateek dcfee04e1b
fix: terminal servers compatible with hash-based architecture (#87)
* fix: terminal servers compatible with hash-based architecture

The terminal WebSocket servers (direct-terminal-ws and terminal-websocket)
used config.dataDir to validate sessions, which no longer exists in the
hash-based architecture. Also fixed node-pty failing to find tmux via
posix_spawnp.

Changes:
- Remove config.dataDir dependency, validate via `tmux has-session` instead
- Add resolveTmuxSession() to map user-facing IDs (ao-15) to hash-prefixed
  tmux names (8474d6f29887-ao-15)
- Use explicit tmux path discovery (findTmux) since node-pty's posix_spawnp
  doesn't reliably inherit PATH
- Include /opt/homebrew/bin in fallback PATH for macOS ARM

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add session resolution to ttyd server and use exact tmux matching

- Add findTmux() and resolveTmuxSession() to terminal-websocket.ts
  (previously only in direct-terminal-ws.ts), fixing hash-prefixed
  session lookup for the ttyd-based terminal server
- Use tmux exact match prefix (=sessionId) in has-session checks
  to prevent ao-1 from matching ao-15 via prefix matching
- Add server compatibility tests that verify both servers handle
  hash-based architecture correctly (14 tests)
- Include server/ in tsconfig and vitest config for typecheck coverage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract tmux-utils and add proper unit tests

- Extract findTmux(), resolveTmuxSession(), validateSessionId() into
  shared server/tmux-utils.ts — eliminates duplication between
  direct-terminal-ws.ts and terminal-websocket.ts
- Add 20 real unit tests with injected mocks that test actual behavior:
  - findTmux: candidate priority, fallback to bare name
  - resolveTmuxSession: exact match, hash-prefix resolution,
    = prefix for preventing tmux prefix matching (ao-1 vs ao-15),
    null when no session found, tmux not running
  - validateSessionId: path traversal, shell injection, whitespace
- Slim down server-compatibility.test.ts to 10 structural checks
  (imports tmux-utils, no loadConfig, no config.dataDir, no existsSync)

Total: 30 tests — all pass on fix branch, 8 fail on main

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add real integration tests for direct-terminal-ws

- Refactor direct-terminal-ws to export createDirectTerminalServer()
  factory so tests can control server lifecycle without side effects
- Add 10 integration tests that create real tmux sessions, start the
  real server, connect via WebSocket, and verify the full flow:
  - Health endpoint returns 200
  - Missing session parameter → close 1008
  - Path traversal in session ID → close 1008
  - Shell injection in session ID → close 1008
  - Nonexistent tmux session → close 1008
  - Real tmux session → connects and receives terminal output
  - Hash-prefixed session resolution works end-to-end
  - Can send input and receive echoed output
  - Resize messages don't crash the connection
  - Unknown HTTP path → 404
- Tests create/destroy tmux sessions in beforeAll/afterAll
- Server runs on random port (port 0) to avoid conflicts
- Total test suite: 40 tests (20 unit + 10 compatibility + 10 integration)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add web server tests to CI pipeline

The web package was explicitly excluded from CI test runs
(pnpm -r --filter '!@composio/ao-web' test). Add a test-web job
that installs tmux, starts the tmux server, and runs the web
package tests (unit + integration).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address CI failures and bugbot review comments

- Fix lint: replace require() with ESM import in integration test
- Fix base-path mismatch: ttyd now uses user-facing sessionId for
  --base-path/URL and actual tmux name for attach-session
- Fix bare "tmux" in ttyd spawn args: use TMUX constant (full path)
- Scope CI test-web job to server/__tests__/ to avoid pre-existing
  failures in src/__tests__/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: comprehensive unit and integration test coverage

Unit tests (77): validateSessionId covers all injection vectors (shell,
path traversal, command substitution, special chars, unicode, control
chars), findTmux covers all candidate paths and error types,
resolveTmuxSession covers exact match, hash-prefix resolution, suffix
matching precision, edge cases (single char, long lists, multiple
hyphens, different tmux paths).

Integration tests (45): health endpoint lifecycle (active count tracks
connections/disconnections), HTTP routing (404s for all non-health
paths), WebSocket validation (11 injection/traversal vectors), terminal
connection (resize, multi-resize, invalid JSON, non-resize JSON),
hash-prefixed resolution (suffix match, command passthrough, session key
tracking, cross-match prevention), terminal I/O (Ctrl-C, Tab, Enter,
empty messages, rapid keystrokes, multi-line), connection lifecycle
(cleanup, rapid connect/disconnect, error recovery), server creation
(independent instances).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: validate 12-char hex prefix in hash-prefixed session resolution

The previous endsWith("-{sessionId}") suffix match was ambiguous:
"hash-my-app-1" would falsely match a lookup for "app-1". Now validates
that the prefix matches the exact format generated by generateConfigHash
(12-char lowercase hex) before comparing the remainder.

Added unit tests for the ambiguity case and invalid prefix formats.
Updated integration test session names to use proper 12-char hex prefixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 03:28:55 +05:30
prateek 9c5927a7f6
fix: clean up hash-based test directories in afterEach (#86)
Fixes bugbot issue: "Tests leak directories under home directory without cleanup"

The problem:
- Tests call the real getSessionsDir() which creates ~/.agent-orchestrator/{hash}/
  directories based on hashing the tmpDir path
- Each test run uses a new random tmpDir, creating a unique hash
- afterEach only cleaned up tmpDir, leaving orphaned directories in ~/.agent-orchestrator/
- These directories accumulated indefinitely

The fix:
- Added cleanup for hash-based directories in afterEach for all affected tests
- Uses getProjectBaseDir() to calculate the directory path
- Removes the hash-based directory before cleaning tmpDir
- All 61 tests pass (13 CLI + 48 core)

Files fixed:
- packages/cli/__tests__/commands/session.test.ts
- packages/core/src/__tests__/session-manager.test.ts
- packages/core/src/__tests__/lifecycle-manager.test.ts

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 01:38:48 +05:30
prateek 599710296d
fix: migrate to hash-based project isolation architecture
Complete migration to hash-based directory structure for project isolation. All bugbot issues resolved.
2026-02-18 00:19:55 +05:30
prateek 5fe476774d
fix: clean Next.js build artifacts in setup script (#68)
Add cleanup step to remove packages/web/.next directory before building.
This prevents "Cannot find module" errors from stale webpack artifacts
that can occur after rebasing or switching branches.

The cleanup step ensures a clean build every time the setup script runs.

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 00:24:04 +05:30
prateek eaea131af9
feat: seamless onboarding with enhanced documentation (#66)
* feat: implement seamless onboarding with enhanced documentation

- Add comprehensive README.md (18KB) with quick start, core concepts, and FAQ
- Add detailed SETUP.md (16.5KB) with prerequisites, integration guides, and troubleshooting
- Add examples/ directory with 5 ready-to-use config templates:
  - simple-github.yaml: Minimal GitHub setup
  - linear-team.yaml: Linear integration
  - multi-project.yaml: Multiple repos
  - auto-merge.yaml: Aggressive automation
  - codex-integration.yaml: Using Codex agent

- Add environment detection (git repo, remote, branch, auth status)
- Auto-fill prompts with smart defaults from detected environment
- Add prerequisite validation (git, tmux, gh CLI)
- Show actionable next steps and warnings
- Parse owner/repo from git remote automatically
- Detect LINEAR_API_KEY and SLACK_WEBHOOK_URL in environment
- Prompt for Linear team ID when Linear tracker selected

- Format all files with Prettier for consistency

Reduces onboarding time from 30+ minutes to ~5 minutes:
1. Install CLI: `npm install -g @composio/ao-cli`
2. Run init: `ao init` (auto-detects everything)
3. Spawn agent: `ao spawn my-project ISSUE-123`

Users no longer need to:
- Manually parse git remote URLs
- Look up current branch names
- Remember YAML syntax
- Search for Linear team IDs
- Debug missing prerequisites

-  pnpm build - All packages compile
-  pnpm typecheck - No TypeScript errors
-  pnpm lint - No new linting issues
-  pnpm format - All files formatted

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* docs: update installation instructions to reflect npm not yet published

Package is not published to npm yet, so users must build from source.
Updated README.md and SETUP.md to:
- Make 'build from source' the primary installation method
- Add note that npm publishing is coming soon
- Include pnpm as a prerequisite

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: add ao init --auto --smart for zero-config setup

Implements intelligent config generation with project type detection.

## What's New

### ao init --auto
- Zero prompts - auto-generates config with smart defaults
- Detects: git repo, remote, branch, languages, frameworks, tools
- Generates project-specific agentRules based on detected tech stack

### Project Detection
- Languages: TypeScript, JavaScript, Python, Go, Rust
- Frameworks: React, Next.js, Vue, Express, FastAPI, Django, Flask
- Tools: pnpm workspaces, test frameworks
- Package managers: pnpm, yarn, npm

### Rule Templates
Created templates for:
- base.md - Universal best practices
- typescript.md - TS strict mode, ESM, type imports
- javascript.md - Modern ES6+ patterns
- react.md - Hooks, composition, best practices
- nextjs.md - App Router, Server Components
- python.md - Type hints, PEP 8
- go.md - Error handling, defer patterns
- pnpm-workspaces.md - Monorepo commands

### Example Output

```bash
ao init --auto

# Detects:
# ✓ TypeScript + pnpm workspaces
# ✓ React + Next.js
# ✓ Vitest

# Generates:
agentRules: |
  Always run tests before pushing.
  Use TypeScript strict mode.
  Use ESM modules with .js extensions.
  Use React best practices (hooks, composition).
  Before pushing: pnpm build && pnpm typecheck && pnpm lint && pnpm test
```

## Benefits

- **5 seconds** instead of 5 minutes
- **Zero config knowledge** required
- **Context-aware rules** tailored to your stack
- **Still customizable** - edit the generated config

## Future: --smart (AI-powered)

Flag added but not yet implemented. Will use Claude Code to:
- Analyze CLAUDE.md, CONTRIBUTING.md
- Read CI/CD config
- Generate custom rules based on project patterns

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: detect repo default branch instead of current branch

Fixes Bugbot issue: "Current branch wrongly suggested as default base branch"

## Problem

detectEnvironment was using `git branch --show-current` to suggest
defaultBranch in the config. If a user ran `ao init` while on a feature
branch like `feat/my-work`, the wizard would suggest that feature branch
as the default, causing agents to branch from the wrong base.

## Solution

Added detectDefaultBranch() function with 3 fallback methods:
1. git symbolic-ref refs/remotes/origin/HEAD (most reliable)
2. GitHub API via gh CLI (if ownerRepo known)
3. Check common branch names: main, master, next, develop

Now EnvironmentInfo tracks both:
- currentBranch: The checked-out branch (for display only)
- defaultBranch: The repo's base branch (for config)

## Testing

Tested on feat/seamless-onboarding branch:
- Current branch: feat/seamless-onboarding (displayed)
- Default branch: main (correctly detected for config)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: prevent duplicate framework detection in Python projects

Fixes Bugbot issue: "Duplicate frameworks when multiple Python config files exist"

## Problem

When both requirements.txt and pyproject.toml exist and mention the same
framework (e.g., FastAPI), the detection loop added it to the frameworks
array twice, causing duplicate rules in the generated config.

## Solution

Added addFramework() helper that checks if framework already exists before
adding to the array. Also prevents pytest from being set multiple times as
testFramework.

## Testing

Verified with test repo containing both files with FastAPI:
- Before: Would add 'fastapi' twice
- After: Only adds 'fastapi' once ✓

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: address Bugbot review comments

- Remove redundant conditional in --smart flag (both branches were identical)
- Include templates directory in npm package files

* fix: add existence check for base.md template file

Add existsSync guard before reading base.md to handle missing templates gracefully, consistent with other template file reads.

* fix: use direct tool invocation instead of which command

Replace 'which' with direct tool invocation (tmux -V, gh --version)
for better portability on minimal Linux systems where 'which' may
not be installed.

* fix: address Bugbot review comments

- Simplify gh auth status check to rely on exit code instead of output string
- Remove async from synchronous functions (detectProjectType, generateRulesFromTemplates)

* feat: add setup script for one-command installation

Add scripts/setup.sh that:
- Installs pnpm if not present
- Installs dependencies
- Builds all packages
- Links CLI globally

Updated README with simplified setup instructions using the script.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: correct npm link command in setup script

Remove incorrect -g flag from npm link command. The correct syntax is to cd into the package directory and run npm link without flags.

* fix: address Bugbot review comments on init command

- Validate --smart flag requires --auto (prevents silent ignore)
- Fix path validation to check user-specified path (not CWD)

These fixes address medium and low severity issues found by Cursor Bugbot
in PR #66 review.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* docs: add DirectTerminal troubleshooting and fix setup script

- Add TROUBLESHOOTING.md documenting node-pty posix_spawnp error
- Update setup.sh to rebuild node-pty from source (fixes DirectTerminal)
- Ensures seamless onboarding with working terminal out-of-the-box

Resolves DirectTerminal WebSocket failures from incompatible prebuilt binaries.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: resolve variable scope issue in init command validation

- Move path variable outside if block to fix TypeScript scope error
- Only validate path existence if projectId is provided
- Use inline tilde expansion instead of missing expandHome import

Fixes build error that prevented setup.sh from completing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: automate node-pty rebuild to eliminate terminal issues

- Add postinstall hook to automatically rebuild node-pty after pnpm install
- Create scripts/rebuild-node-pty.js for automatic rebuild with error handling
- Remove manual node-pty rebuild from setup.sh (now automatic)

This ensures DirectTerminal works correctly on every installation without
manual intervention. Fixes posix_spawnp errors from incompatible prebuilt
binaries across different systems and installations.

Resolves issue where users would encounter blank terminals after setup.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* docs: update TROUBLESHOOTING with automatic node-pty rebuild

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* docs: add comprehensive README with quick start guide

- 3-line magical setup: clone → setup → init → start
- Architecture overview with plugin slots table
- Usage examples and auto-reaction configuration
- Links to detailed docs (SETUP.md, TROUBLESHOOTING.md, examples/)
- Philosophy: push not pull, amplify judgment

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: resolve ESLint errors in rebuild-node-pty script

- Add scripts directory configuration to eslint.config.js
- Configure Node.js globals (console, process) for scripts
- Remove unused error variable from catch block

Fixes lint CI failure.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: warn when auto mode uses placeholder repo value

- Detect when 'owner/repo' placeholder is used in --auto mode
- Show warning: 'Could not detect GitHub repository'
- Update next steps to emphasize editing config when placeholder used
- Prevents silent failures when spawning agents with invalid repo

Addresses Bugbot review comment about silent placeholder values.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 22:22:13 +05:30
prateek dbeb8d9fb2
fix: increase delays in spawn prompt submission to ensure Enter is processed (#62)
Fixes issue where spawned sessions would send the prompt text but the Enter
keystroke would not be submitted, requiring manual Enter press.

Changes:
- Increase delay after paste-buffer from 300ms to 1000ms in tmux.ts sendKeys()
  to ensure tmux processes the paste before receiving Enter keystroke
- Add 100ms delay after Escape key to ensure it's processed before pasting
- Increase spawn wait time from 1s to 2s before sending initial prompt to
  allow Claude to fully initialize (permission prompts, startup, etc.)

These longer delays account for:
1. tmux paste buffer processing time
2. Claude permission prompt interactions
3. Agent initialization and readiness

Closes: FIX-SPAWN-PROMPT-SUBMIT
2026-02-16 20:31:07 +05:30