Commit Graph

1627 Commits

Author SHA1 Message Date
suraj-markup c53099923d feat(mobile): add React Native app for monitoring AO sessions
Adds `packages/mobile` — an Expo SDK 53 React Native app for monitoring
agent sessions from a phone.

Features:
- Home screen: Kanban-style session list sorted by attention level
- Session detail: metadata, PR link, CI status, branch info
- Terminal screen: full xterm.js terminal via WebView (port 14801)
- Settings screen: configurable backend URL (LAN IP or ngrok)
- Push notifications: background polling for attention-level changes

Improvements over PR #235:
- Fixed terminal onmessage handler to filter JSON control messages
  (resize echoes no longer render as garbage text)
- Removed duplicate terminal.html (single source of truth in
  terminal-html.ts)
- Fixed isDone check to include "cleanup" status using isTerminal()

Tech: Expo SDK 53, React Navigation 6, React Native WebView.
Two server connections: REST API (port 3000) + WebSocket terminal (14801).

The mobile package is excluded from the pnpm workspace to avoid
interfering with the monorepo toolchain.

Closes #265

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:34:45 +05:30
suraj_markup 7aa883c217
Merge pull request #254 from suraj-markup/feat/try-pr-script
feat(scripts): add try-pr.sh for testing PR worktrees locally
2026-03-03 01:18:06 +05:30
suraj_markup 01a6c56e1b
Merge pull request #246 from suraj-markup/feat/issue-245
fix(setup): improve setup.sh prereq validation and add start/spawn pre-flight checks
2026-03-02 03:24:14 +05:30
suraj-markup 6603685a2f feat(scripts): add try-pr.sh for testing PR worktrees locally
Adds scripts/try-pr.sh — a helper to switch the global 'ao' command to
any session's worktree for manual testing, then restore back to main.

Usage:
  bash scripts/try-pr.sh <session-id>            # CLI/core/plugins only
  bash scripts/try-pr.sh <session-id> --with-web # also builds + starts dashboard
  bash scripts/try-pr.sh --restore               # switch back to main

Also fixes isPortAvailable() to use a connect-based probe instead of
bind-based. The old approach had false positives on macOS when Next.js
listens on :: (IPv6 wildcard) — binding 127.0.0.1 succeeded even though
the port was taken. A TCP connect correctly detects any listener
regardless of which address it's bound to.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 03:19:49 +05:30
suraj-markup 18dea011e7 fix: address review feedback and add preflight tests
- Consistent error formatting: single spawn now uses err.message like
  batch-spawn, avoiding redundant "Error:" prefix
- checkBuilt distinguishes missing node_modules ("pnpm install") from
  missing dist ("pnpm build") so users get the right guidance
- Add 13 preflight unit tests covering port, build, tmux, and gh checks
- Add 5 spawn integration tests covering runtime-aware tmux check,
  tracker-aware gh check, and error message formatting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 03:05:00 +05:30
suraj-markup 62cd42b30f fix: resolve checkBuilt from web node_modules and remove unused imports
Check ao-core/dist/index.js relative to webDir's node_modules instead
of using require.resolve which fails under npm link. Also removes
unused existsSync/resolve/createRequire imports that caused lint errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:50:56 +05:30
suraj-markup b43b1556c0 fix: check core dist output instead of .next/BUILD_ID in checkBuilt
.next/BUILD_ID only exists after next build (production) but the
dashboard runs with next dev. Check @composio/ao-core resolve instead,
which is the actual cause of module resolution errors when packages
are not compiled.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:44:19 +05:30
suraj-markup cb071e122d fix: wrap batch-spawn preflight in try-catch
Preflight errors in batch-spawn were unhandled, causing unformatted
rejection output. Now caught with chalk.red formatting and
process.exit(1), matching the single spawn handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:40:12 +05:30
suraj_markup 1d7e6e3c66
Merge pull request #240 from suraj-markup/feat/issue-237
fix(terminal): OSC 52 clipboard, resize frame cap, and hardcoded height
2026-03-02 02:27:39 +05:30
suraj_markup 2d785beac5
Merge pull request #242 from suraj-markup/feat/issue-238
fix(core): atomic metadata writes, restoredAt persistence, and session count/sorting bugs
2026-03-02 02:24:51 +05:30
suraj-markup 96c7ae534a fix: run spawn preflight checks once before batch loop
Extract preflight checks from spawnSession into runSpawnPreflight and
call it once upfront in both registerSpawn and registerBatchSpawn. A
missing prerequisite now fails fast with one clear error instead of
repeating N times across the batch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:01:15 +05:30
suraj-markup 421e443d30 fix(terminal): remove DOM paste handler that could double-paste with xterm.js
xterm.js handles paste natively via its internal textarea — our
container-level paste listener either double-pastes (if the event
bubbles) or is dead code (if xterm calls stopPropagation). Also remove
the dead isPaste key handler block that returned true (same as default).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 01:56:02 +05:30
suraj-markup f14465335d fix: address review feedback on preflight checks
- Consistent error handling: all preflight functions now throw instead
  of mixing process.exit and throw, so callers can catch and clean up
- checkGhAuth distinguishes "not installed" (ENOENT) from "not
  authenticated" by checking gh --version first
- Move checkBuilt() after package.json existence check in start.ts so
  missing web package shows "Run: pnpm install" not "Run: pnpm build"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 01:45:39 +05:30
suraj-markup 3fc51b8882 fix(terminal): harden clipboard buffer and resize for production
Three fixes from code review:

1. Clear selection after Cmd+C so the terminal resumes receiving
   output immediately instead of staying frozen up to 5 seconds.

2. Add 1MB byte cap on the write buffer to prevent OOM if a fast
   process dumps output while the user has text selected.

3. Use ws.current instead of a stale captured websocket reference
   in the resize effect, preventing throws if the WebSocket
   reconnected during a fullscreen toggle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 01:17:16 +05:30
suraj-markup 09e2c2b540 fix(terminal): preserve selection highlight by buffering after selection exists
Buffer incoming terminal.write() only after onSelectionChange reports
a non-empty selection — NOT during mousedown. This lets xterm.js
render the selection highlight normally, then prevents incoming data
from clearing it. The buffer flushes when the user clears the
selection (click/keypress) or after a 5s safety timeout.

Previous attempts broke because they buffered during mousedown which
prevented xterm.js from rendering the highlight in the first place.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 01:08:24 +05:30
suraj-markup fb5e0c9646 fix(terminal): remove write buffer that broke selection highlighting
The write buffer intercepted all terminal.write() during mousedown,
which prevented xterm.js from rendering the selection highlight.

Simplified to: auto-copy on selection change + DOM paste event +
keyboard shortcut fallback. The selection highlight may disappear
from incoming data, but the text is already in the clipboard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 01:04:11 +05:30
suraj-markup 2d80a1dd35 fix(terminal): preserve text selection while terminal receives output
Buffer incoming WebSocket data while the user has an active selection.
xterm.js clears the selection on every terminal.write(), so without
buffering the selection disappears instantly in a live terminal.

How it works:
- mousedown: start buffering (user is drag-selecting)
- mouseup: stop tracking mouse, but keep buffering if selection exists
- onSelectionChange(empty): selection cleared → flush buffer
- Safety timeout (5s): flush regardless to prevent unbounded buffering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 00:58:49 +05:30
suraj-markup d8bbf860cd fix(terminal): auto-copy selection and use DOM paste event for clipboard
Two issues with the previous clipboard implementation:

1. Cmd+C failed because incoming terminal.write() clears the xterm.js
   selection before the user can press the shortcut. Fixed by adding
   terminal.onSelectionChange() that auto-copies to clipboard the
   moment text is selected.

2. Cmd+V failed silently because navigator.clipboard.readText() requires
   the "clipboard-read" browser permission which may not be granted.
   Fixed by using the DOM "paste" event (ClipboardEvent.clipboardData)
   which provides clipboard content without needing that permission.

Also simplified platform detection by checking metaKey/ctrlKey directly
instead of parsing navigator.platform.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 00:49:19 +05:30
suraj-markup 941a99e0c9 fix: resolve merge conflict with upstream WebSocket reconnection
Keep upstream's connectWebSocket() reconnection structure and add our
clipboard shortcuts (Cmd+C/V, Ctrl+Shift+C/V) and OSC 52 handler on
top. Clipboard paste now uses ws.current to match upstream's pattern
where the WebSocket reference can change across reconnects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 00:41:13 +05:30
suraj-markup 54c0a075bc Revert "fix(terminal): preserve text selection while terminal receives output"
This reverts commit 3ac26ba6e8.
2026-03-02 00:35:55 +05:30
suraj-markup 3ac26ba6e8 fix(terminal): preserve text selection while terminal receives output
Buffer incoming WebSocket data while the user is selecting text
(mousedown→mouseup). Without this, terminal.write() clears the
xterm.js selection mid-drag or immediately on mouseup. The buffer
is flushed 150ms after mouseup so the selection stays visible long
enough to copy with Cmd+C.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 00:13:34 +05:30
suraj-markup 783b21dc93 feat(terminal): add Cmd+C/V and Ctrl+Shift+C/V clipboard shortcuts
Intercept keyboard shortcuts in xterm.js so users can copy selected
text with Cmd+C (Mac) or Ctrl+Shift+C (Linux/Win), and paste from
system clipboard with Cmd+V / Ctrl+Shift+V. Without text selected,
Cmd+C still sends SIGINT as expected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 00:03:14 +05:30
suraj_markup ca3286ca37
Merge pull request #241 from suraj-markup/feat/issue-236
fix(cli): ao init missing sessionPrefix, name, and port validation
2026-03-01 23:37:04 +05:30
suraj-markup 42442e007e fix(cli): correct misleading comment on isPortAvailable IPv6 coverage
The comment claimed connect-based detection works for IPv6 listeners,
but the probe only connects to 127.0.0.1 (IPv4). Updated the comment
to accurately document this limitation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:53:11 +05:30
suraj-markup dfcc0841ec fix(cli): remove unused createServer import and fix flaky port test
- Remove unused `createServer` import from web-dir.ts (only `Socket`
  is used) — fixes the lint error in CI
- Mock `isPortAvailable` in the "uses port 3000" test so it doesn't
  depend on port 3000 actually being free on the host machine

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:41:08 +05:30
suraj-markup 06ba99eeb4 fix(cli): check both IPv4 and IPv6 in isPortAvailable to prevent false positives
pnpm dev and Next.js listen on :: (dual-stack IPv6) which doesn't conflict
with a 127.0.0.1-only bind check, causing isPortAvailable to return true
even when the port is in use. Now checks both 127.0.0.1 and ::1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 21:31:44 +05:30
suraj-markup 7128a9d469 fix: handle corepack permission errors in setup.sh
Skip pnpm install if already available. When it's not, try corepack
first and fall back to npm install -g pnpm if corepack fails (e.g.
non-root user in Docker can't write to /usr/local/bin).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:56:15 +05:30
suraj-markup 34cf42616f fix: skip interactive prompts in non-interactive environments
Guard `read` prompts behind `[ -t 0 ]` check so setup.sh works in
CI/Docker without hanging or failing on stdin. Soft warnings still
print but skip the interactive offer to fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:52:36 +05:30
suraj-markup c0b998a445 fix: scope pre-flight checks to when they are relevant
- Move port/build checks inside the `--no-dashboard` guard so they only
  run when the dashboard is actually being started
- Only check for tmux when the resolved runtime is "tmux", respecting
  per-project runtime overrides (e.g. "process")

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:47:02 +05:30
prateek 128b94a932
fix: cache ps output across sessions to fix 51s dashboard load (#244)
* fix: cache ps output across sessions to fix 51s dashboard load

findClaudeProcess() was calling `ps -eo pid,tty,args` once per session.
On machines with ~1150 processes, each `ps` call takes 30-35s. With 18
sessions enriched in parallel, this caused /api/sessions to take 51+
seconds despite the 2s per-session timeout.

Add a module-level TTL cache (5s) with in-flight deduplication so `ps`
is called at most once regardless of session count. Also reduce timeouts
from 30s to 5s — stale process data isn't useful.

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

* fix: guard ps cache callbacks against stale overwrites

The .then() and catch callbacks in getCachedProcessList() could clobber
a newer in-flight cache entry if the TTL expired and a new request
replaced psCache while the old one was still pending. Both now check
`psCache?.promise === promise` before writing.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:41:03 +05:30
prateek 3cb03f6222
fix: WebSocket reconnection in DirectTerminal (#248)
* fix: add WebSocket reconnection with exponential backoff in DirectTerminal

A single network hiccup permanently killed the terminal with no retry.
Now transient disconnects trigger automatic reconnection (1s→2s→4s…15s max)
while permanent errors (4001 auth, 4004 not-found) still fail immediately.

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

* fix: reset reconnection state when sessionId changes

Reset permanentErrorRef and reconnectAttemptRef at the start of the
useEffect so switching sessions doesn't inherit stale error/retry state.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:18:07 +05:30
prateek 2108620ee9
feat(web): subscribe Dashboard to SSE for real-time session updates (#249)
Wire the Dashboard component to the existing /api/events SSE endpoint
so session status, activity, and lastActivityAt update in real-time
without requiring a full page refresh. SSE snapshots are lightweight
patches merged into the full server-rendered session objects.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:04:57 +05:30
prateek 48e5110778
fix: use JSONL-based activity detection in lifecycle manager (#247)
* fix: use JSONL-based activity detection in lifecycle manager

Replace terminal-output-based detectActivity() with JSONL-based
getActivityState() as the primary activity detection in determineStatus().
Falls back to terminal parsing when getActivityState returns null.

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

* fix: address bugbot review comments on lifecycle JSONL detection

- Pass config.readyThresholdMs to getActivityState() for consistency
- Fix idle-but-running test to exercise fallback path (getActivityState → null)
- Deduplicate identical exited tests; replace duplicate with fallback-path test

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:04:31 +05:30
suraj-markup e40275b53c fix(setup): improve setup.sh with prereq validation and add ao start/spawn pre-flight checks
- Rewrite setup.sh with hard stops for Node<20 and git<2.25, soft warns
  with interactive fix for tmux/gh-auth/claude, corepack-based pnpm
  install, and PATH verification after npm link
- Add pre-flight checks to `ao start`: verify dashboard port is free and
  packages are built before proceeding
- Add pre-flight checks to `ao spawn`: verify tmux is installed and gh
  is authenticated (when using github tracker) before session creation
- Extract shared preflight utilities into packages/cli/src/lib/preflight.ts

Closes #245

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:39:29 +05:30
suraj-markup 308d9dcf0d fix(cli): remove unused Server import to fix lint error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:23:58 +05:30
suraj-markup 899e47784f fix(web): rename stats label from "active" to "working" to match broadened filter
The workingSessions stat now includes idle/ready sessions (not just
active), so the UI label should say "working" rather than "active"
to accurately reflect what's being counted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:22:47 +05:30
suraj-markup b96eafeb31 fix(terminal): use height stabilisation instead of fixed threshold for resize
Replace the hardcoded `expectedHeight < 700` heuristic with a
frame-to-frame height comparison. The loop now exits as soon as the
container height stops changing (within 1px tolerance), which works
correctly on all screen sizes. Previously, on viewports taller than
~1140px the non-fullscreen height exceeded 700px so the condition was
never satisfied and the loop exhausted all attempts, introducing a
~1s delay on large screens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:22:44 +05:30
suraj-markup b027f5686e fix(cli): tell user when default port is busy and suggest alternative
In interactive mode: "Port 3000 is busy — suggesting 3001 instead.
Press Enter to accept, or type a different port."

In auto mode: "Port 3000 is busy — using 3001 instead."

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:15:11 +05:30
suraj-markup b894b31a5a test: add tests for atomic writes, restoredAt, computeStats, and attention level fixes
- BUG-28: Verify no .tmp files left behind after writeMetadata/updateMetadata,
  and rapid sequential writes produce correct final state
- BUG-29: Verify restoredAt roundtrips through writeMetadata/readMetadata,
  persists in key=value file, and can be set via updateMetadata
- BUG-12: Verify computeStats counts active/idle/ready as working,
  excludes exited and null activity
- BUG-13: Verify errored/needs_input/stuck sessions with activity=active
  land in respond zone, not working

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:10:57 +05:30
suraj-markup 8eff44aa3f test(cli): add unit tests for init sessionPrefix, name, port, and --smart fixes
- Verify auto mode writes `name` and `sessionPrefix` to project config
- Verify warning is shown when no free port is found in range
- Verify `--smart` flag description includes "coming soon"
- Verify `sessionPrefix` matches core `generateSessionPrefix` output

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:04:50 +05:30
suraj-markup c52caec441 fix(cli): use generateSessionPrefix from core instead of naive slice
Address review feedback: use the core utility `generateSessionPrefix`
which produces proper short-form prefixes (kebab-case initials,
CamelCase extraction, etc.) instead of `projectId.slice(0, 8)`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:00:00 +05:30
suraj-markup eaf18015af fix(core): populate restoredAt from metadata in metadataToSession
The session reconstruction path (readMetadataRaw → metadataToSession)
was not mapping the restoredAt field, so session.restoredAt was always
undefined after a server restart despite the value existing in the
metadata file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 18:59:33 +05:30
suraj-markup dc3b864a67 fix(terminal): UTF-8 safe OSC 52 decode, cancel RAF loop on cleanup
- Use TextDecoder to properly decode UTF-8 from base64 clipboard data
  instead of raw atob() which only handles Latin-1
- Track requestAnimationFrame IDs and cancel them on effect cleanup
  to prevent callbacks firing on disposed terminal/WebSocket

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 18:59:17 +05:30
suraj-markup 1f062201fb fix(core): atomic metadata writes, restoredAt persistence, and session count/sorting bugs
- BUG-28: Use atomic write-then-rename for metadata files to prevent
  race condition data loss between concurrent TS and bash writers
- BUG-29: Read and write restoredAt field in metadata so it persists
  across server restarts
- BUG-12: Count all non-exited sessions (idle, ready, active) as
  "working" instead of only active ones
- BUG-13: Check status-based error conditions (errored, needs_input,
  stuck) before activity-based checks in getAttentionLevel() so errored
  sessions immediately appear in the Respond Kanban zone

Closes #238

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 18:47:29 +05:30
suraj-markup 5f9481bc01 fix(cli): add missing sessionPrefix, name, and port validation in ao init
- Add `sessionPrefix` (derived from projectId.slice(0, 8)) to project
  config in both interactive and auto modes so session IDs are valid
  instead of `undefined-1`
- Add `name` field to project config so `ao start` prints the actual
  project name instead of "undefined"
- Mark `--smart` flag as "(coming soon)" in help text since it is a
  no-op stub
- Return `null` from `findFreePort` when no port is available and warn
  the user to set the port manually

Closes #236

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 18:47:09 +05:30
suraj-markup 0814f9515c fix(terminal): wire OSC 52 clipboard, increase resize cap, fix hardcoded height
- Register OSC 52 handler in DirectTerminal to decode base64 clipboard
  data from tmux and write it to the system clipboard
- Increase resize maxAttempts from 10 to 60 so fullscreen toggle works
  on slow machines with CSS transitions >166ms
- Replace hardcoded h-[600px] in Terminal.tsx with responsive
  max(440px, calc(100vh - 440px)) to prevent overflow on small screens

Closes #237

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 18:46:08 +05:30
Karan Vaidya 4cda43795b
fix: handle ad-hoc spawn with free-text issue strings (#155)
* fix: handle "invalid issue format" error for ad-hoc spawn

gh CLI returns "invalid issue format" when the issue argument is free-text
rather than a valid issue number/URL. This error was not matched by
isIssueNotFoundError, causing ad-hoc spawns to fail instead of gracefully
falling through to ad-hoc mode.

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

* fix: sanitize ad-hoc issue text into valid git branch names

When tracker lookup fails (ad-hoc mode), the raw free-text was used
directly as a branch name, causing git to reject it. Now:
- Only use tracker.branchName() when the issue was actually resolved
- Slugify ad-hoc text (lowercase, replace non-alphanumeric with hyphens,
  trim, cap at 60 chars) to produce valid branch names

e.g. "fix the cold start issue" → feat/fix-the-cold-start-issue

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

* fix: preserve casing for branch-safe issue IDs, only slugify free-text

The slug sanitization was lowercasing all issue IDs without a resolved
issue (e.g. INT-9999 → int-9999). Now only applies sanitization when
the issueId contains characters invalid for git branch names.

Adds tests for slug sanitization, casing preservation, truncation,
empty-slug fallback, and isIssueNotFoundError patterns.

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

* test: assert tracker.branchName not called for unresolved issues

Adds assertion to existing test that tracker.branchName is NOT called
when the issue wasn't found. Adds end-to-end test for "invalid issue
format" error flowing through spawn with free-text slugification.

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

* fix: reject '..' in branch-safe check, verify generatePrompt skipped

The isBranchSafe regex allowed '..' which is invalid in git refs.
Also asserts that tracker.generatePrompt is not called when the
issue wasn't resolved, and adds a test for the '..' edge case.

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

* fix: trim dashes after truncation, not before

Bugbot correctly noted that .slice(0, 60) after dash-trim can
reintroduce a trailing dash. Reorder so trim runs after slice.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Prateek <karnalprateek@gmail.com>
2026-02-27 19:10:03 +05:30
prateek e5105133c5
Default dangerouslySkipPermissions to true (#226)
* docs: add demo video and article links to README

Add tweet screenshot for the video demo prominently after the intro,
and link to the full article thread. Replaces the TODO placeholder.

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

* docs: use article screenshot for README article link

Replace text-only article link with clickable screenshot showing
the article title, preview image, and engagement stats.

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

* docs: replace demo video screenshot with higher quality version

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

* Default dangerouslySkipPermissions to true for autonomous agent operation

Spawned agents need to run CLI commands without permission prompts. Changed
the agentConfig.permissions schema default from undefined to "skip" so
--dangerously-skip-permissions is included by default. The orchestrator
session is hardcoded to "skip" regardless of config since it must always
run ao CLI commands autonomously.

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

* Default agentConfig to {} so permissions default propagates

When agentConfig is absent from YAML, .optional() leaves it undefined,
so project.agentConfig?.permissions evaluates to undefined instead of
"skip". Change to .default({}) so Zod always constructs the object and
applies the inner permissions default.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:04:50 +05:30
prateek 5540d90290
fix: skip orchestrator sessions during cleanup (#144)
* fix: skip orchestrator sessions during cleanup

The cleanup command could kill the orchestrator's own tmux session
(named {prefix}-orchestrator) because it has no PR or issue, making
it eligible for the "dead runtime" cleanup heuristic.

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

* fix: add role metadata for orchestrator session identification

Store role=orchestrator in metadata during spawnOrchestrator() and
check it in cleanup() for explicit identification. Keeps the name
fallback (endsWith -orchestrator) for pre-existing sessions.

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

* fix: preserve role in restore path, add integration test

- restore() now preserves the role field when recreating metadata
  from archive (previously dropped, breaking role-based detection)
- Add integration test: orchestrator skipped while sibling session
  with same closed issue gets killed (verifies real tracker plugin)
- Verified: both unit and integration tests fail without the guard

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

* fix: persist role field in metadata, isolate role test from name fallback

Bugbot caught two real issues:
- writeMetadata() and readMetadata() never serialized the role field,
  so role=orchestrator was silently dropped on disk
- The role-based test used "app-orchestrator" as session name, so the
  name fallback caught it — role check was never actually exercised

Fix: add role to metadata read/write, rename test session to "app-99"
so only the role metadata path can protect it.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 18:17:03 +05:30
prateek 91dd7cc15f
fix: keep Claude Code interactive after initial prompt (#145)
* fix: keep Claude Code interactive after initial prompt

Claude Code's -p flag runs in one-shot mode (exits after responding),
which prevents follow-up messages via `ao send`. Instead, launch Claude
interactively and deliver the initial prompt post-launch via
runtime.sendMessage().

Adds `promptDelivery` property to the Agent interface so each agent
plugin can declare whether prompts should be inlined in the launch
command or sent after the agent starts.

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

* fix: make post-launch prompt delivery non-fatal and add test coverage

- Move sendMessage call outside the try/catch that destroys the session.
  A prompt delivery failure should not kill a running agent — user can
  retry with `ao send`.
- Add tests: no-prompt + post-launch agent, sendMessage failure resilience,
  5s delay verification, systemPrompt/systemPromptFile alongside omitted -p.

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

* test: add integration test for prompt delivery (proves bug and fix)

Two real-Claude integration tests that contrast:
1. `-p` mode: Claude exits after responding (the bug)
2. Interactive + sendMessage: Claude stays alive, follow-up works (the fix)

Runs in CI with ANTHROPIC_API_KEY. Skips when prerequisites missing.

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

* fix(test): skip interactive test without auth, improve TUI readiness detection

The integration test failed in CI because interactive Claude requires
full login auth (not just ANTHROPIC_API_KEY). Skip the interactive suite
when `claude auth status` reports not logged in.

Also fix local flakiness: replace blind 5s sleep with polling for
Claude's TUI prompt character (❯) before sending the first message,
and increase scrollback capture from 200 to 500 lines.

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

* fix(test): skip interactive test in CI, fix TUI readiness detection

The interactive test ran in CI despite hasInteractiveAuth() — Claude
reports logged in when ANTHROPIC_API_KEY is set, but interactive mode
requires OAuth. Use `!process.env.CI` as the skip condition instead.

Also fix waitForTuiReady false positive: the OAuth screen's
"Paste code here if prompted >" matched the `>` regex. Now checks
the last non-empty line for Claude's specific ❯ prompt character,
and bails early if the OAuth/login screen is detected.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 18:11:06 +05:30