Commit Graph

90 Commits

Author SHA1 Message Date
Laxman f15021c19d
fix: avoid duplicate terminal paste shortcuts (#400)
* fix: avoid duplicate terminal paste shortcuts

* chore: format with prettier [skip ci]

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-23 18:23:00 +05:30
Harshit Singh Bhandari 7ac1e2caa2
fix(desktop): handle Squirrel startup events + CI smoke-install the Windows build (#398)
* fix(desktop): handle Squirrel startup events + CI smoke-install the Windows build

Two Windows-install fixes.

1. Add electron-squirrel-startup handling. main.ts had no handler for the
   --squirrel-{install,updated,uninstall,obsolete} flags Squirrel runs the exe
   with during install/update/uninstall. Without it the install hook booted the
   full app (window + daemon spawn) and never exited, so the installer hung
   waiting on it. Now we create/remove shortcuts and quit immediately; it is a
   no-op on macOS/Linux.

2. Add a Windows CI smoke-install step. After the build, run Setup.exe --silent
   on the clean native x64 windows-latest runner, assert the install dir is
   created, and upload SquirrelSetup.log as an artifact. This captures the
   install log we otherwise cannot get and gives a build-vs-host verdict: a clean
   install proves the artifact is good, so a failing user machine is host-side
   (AV/disk/signing). The runner has no real-time AV, so it does NOT prove
   SmartScreen/Defender will accept the unsigned binaries on end-user machines;
   code-signing stays the durable fix.

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

* ci(windows): capture SquirrelSetup.log from the app dir, fix misleading warning

Update.exe writes SquirrelSetup.log into %LocalAppData%\AgentOrchestrator, not
SquirrelTemp; the smoke step looked only in SquirrelTemp and so printed
"failed before Update.exe ran" even on a successful install (exit 0, dir
created). Look in the app root dir first, fall back to SquirrelTemp, and drop
the false-failure wording.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:51:02 +05:30
Harshit Singh Bhandari 47e3dddff5
fix(desktop): stop console-window flashing on Windows (#399)
On Windows the daemon shells out to console-subsystem processes without
suppressing the console window. The reaper polls session liveness on a
timer (and the attach loop retries), each call runs a zellij command, and
every invocation pops a console window that instantly closes, so the user
sees rapid blinking.

- Add a platform-split hideWindow(cmd) helper to the zellij package
  (CREATE_NO_WINDOW + HideWindow on Windows, no-op elsewhere) and call it
  in execRunner.Run so list-sessions and friends stop flashing.
- startBackgroundProcess now uses CREATE_NO_WINDOW instead of
  CREATE_NEW_CONSOLE (the latter creates a console then hides it, flashing).
- Pass windowsHide: true to the daemon spawn in main.ts so the daemon's own
  console stays hidden on a Windows GUI launch.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:49:04 +05:30
Adil Shaikh afe0817765
fix: keep terminal input sources explicit (#383) 2026-06-23 04:40:09 +05:30
Harshit Singh Bhandari 14c2c485be
fix(desktop): recover login-shell env so the daemon finds zellij/credentials (#389)
* fix(desktop): recover login-shell env so the daemon finds zellij/credentials

A Finder/Dock launch starts the app via launchd, not a login shell, so
~/.zprofile and ~/.zshrc are never sourced. The daemon then inherits
launchd's minimal env (no /opt/homebrew/bin on PATH, no exported
ANTHROPIC_API_KEY, etc.), cannot exec zellij/git, and its agents cannot
authenticate. Launching from a terminal masked this because the shell had
already populated the env, so it only reproduced on a real Finder/Dock launch.

Resolve the login-shell environment once at startup
($SHELL -ilc "printf sentinel; env -0"), adopt it as the base for
daemonEnv(), and force PATH from the shell with a static floor when the probe
fails (timeout/non-zero exit). The probe never blocks startup (3s SIGKILL
timeout, stdin closed) and degrades to the floor rather than erroring.

Windows keeps its existing behavior: its env comes from the registry/session
and is inherited by GUI-launched apps, so this bug does not exist there.

Pure parse/merge logic lives in shared/shell-env.ts (no node:* imports, per
the daemon-attach.ts convention) with unit tests; main.ts owns the real spawn.

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-23 02:28:12 +05:30
Harshit Singh Bhandari a6ebabda68
fix(desktop): swallow EPIPE on std streams (Windows GUI launch crash)
On Windows Squirrel/GUI launches there is no attached console, so
process.stdout/stderr are dead pipes. The daemon-output console.log/error
calls failed with EPIPE and, lacking an error listener, crashed the main
process with 'A JavaScript error occurred in the main process'. Add an
error listener that ignores broken-pipe writes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 01:17:21 +05:30
Vaibhaav Tiwari 3e3d22c6fa
fix: make preview browser deterministic (#384)
* feat(frontend): add live browser panel

* feat: preserve and auto-open browser previews

* fix: retry browser preview after session updates

* fix: wait for browser view before preview navigation

* fix: reopen preview after session switches

* fix: preserve browser views across session switches

* test: allow project settings form more time

* fix: make preview browser deterministic

* chore: format with prettier [skip ci]

* fix: preserve cleared preview state

---------

Co-authored-by: Vaibhaav <user@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-23 00:33:25 +05:30
Harshit Singh Bhandari ea54f31b24
fix(daemon): fall back to ephemeral port on conflict + harden teardown (#386)
Fixes the packaged desktop app getting permanently stuck on "AO daemon
is not ready" (#385) via two daemon-lifecycle fixes.

1. Port conflict no longer exits the daemon. When the configured port
   (default 127.0.0.1:3001) is held by a non-AO process, NewWithDeps now
   falls back to an OS-assigned ephemeral port instead of returning a bind
   error. A genuine peer AO daemon is already ruled out upstream (the
   running.json + /healthz check in daemon.Run), so a conflict here means a
   foreign holder. The bound port is logged ("daemon listening") and written
   to running.json, both of which the supervisor reads, so the fallback
   propagates to the renderer with no UI changes.

2. Detached daemon is torn down on more exit paths. before-quit already
   group-kills the daemon, but app.exit() and some shutdown routes skip it,
   orphaning the daemon so it keeps holding the port for the next launch. A
   synchronous process 'exit' handler now also signals the daemon's process
   group. A hard SIGKILL/crash still can't run JS, but fix #1 covers the
   orphan that leaves behind.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:05:14 +05:30
Harshit Singh Bhandari e20c23a89e
fix(desktop): don't tear down browser views after window destroyed
mainWindow.on('closed') -> browserViewHost.dispose() ran destroy(), which
touched contentView/child WebContentsViews already torn down by Electron,
crashing the main process with 'Object has been destroyed'. Skip the window
ops when the window reports destroyed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:55:01 +05:30
Harshit Singh Bhandari 851d3a88e6
fix(forge): set deb maker bin to executableName so Linux .deb builds
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:30:54 +05:30
Laxman 4c8f92ad39
fix: normalize terminal keyboard shortcuts (#381)
* fix(frontend): normalize terminal shortcuts

* fix(frontend): preserve ctrl-c interrupt outside windows
2026-06-22 19:14:53 +05:30
Harshit Singh Bhandari 8fa403c480
fix(preview): add clear, reuse defaults, force refresh, local files (#379) (#380)
* fix(preview): add clear, reuse defaults, force refresh, local files (#379)

`ao preview` had four issues that made the desktop browser panel awkward
during sessions. This addresses all four:

1. No way to clear the panel. Adds `ao preview clear` (DELETE
   /sessions/{id}/preview) which empties the stored target; the panel
   loads about:blank and returns to its empty state.

2. Bare `ao preview` always autodetected index.html. It now reuses the
   session's existing preview target (so each agent/context keeps its own
   default), falling back to index.html only when nothing was previewed.

3. Re-running `ao preview <same-url>` never refreshed. The preview_url
   alone could not distinguish a real re-run from a CDC replay of an
   unrelated session update. A new monotonic preview_revision (bumped on
   every set, migration 0018, added to the sessions_cdc_update trigger)
   gives the renderer a per-command identity to key navigation on, so a
   re-run always re-navigates while unrelated updates are ignored.

4. Local files could not be previewed. `ao preview ./dist/index.html`
   (and other workspace-relative paths) now resolve server-side to the
   preview/files proxy URL when the file exists; non-file targets stay
   verbatim.

Backend, CLI, and renderer all covered by tests; OpenAPI spec and the
frontend schema are regenerated for the new DELETE route and field.

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

* fix(cdc): include previewRevision in sessions update event payload

The CDC trigger watched preview_revision changes but didn't include it
in the JSON payload, so the frontend couldn't detect same-URL preview
refreshes via SSE events. This broke the core purpose of the feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(migration): renumber to 0019 to resolve conflict with main

Main branch now has migration 0018 (review_run_delivered_at), causing
a duplicate version conflict when CI merges the PR branch with main.
Renamed 0018_add_session_preview_revision.sql to 0019.

Also fixed the Down migration to properly restore the CDC trigger state
after migration 0017 (with previewUrl but without previewRevision).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Vaibhaav <user@example.com>
2026-06-22 16:04:29 +05:30
neversettle f85b0d2ffe
Refactoring Reviews feature code and idempotency check (#377)
* fix: dedupe AO review change nudges

* fix: satisfy review delivery lint

* fix: renumber review delivery migration

* fix: gate review trigger idempotency on verdict

* fix: preserve running review trigger idempotency

* fix: fail stale running review runs

* Update review.go

* fix: avoid review context import ambiguity

* docs: clarify defensive review idempotency branch

* fix: keep review submit persistence single-sourced

* fix: remove unused review nudge send result

* fix: preserve existing pr review nudge copy

* fix: simplify merge conflict nudge return
2026-06-22 15:40:00 +05:30
Pritom Mazumdar cbd2a1baba
fix(desktop): attach to a serving daemon instead of spawning a doomed child (#373)
* fix(desktop): attach to a serving daemon instead of spawning a doomed child

Launching the Electron app while a standalone `ao daemon` already owns the
port made the Electron-spawned child daemon log "daemon already running …
refusing to start" and exit 1, instead of attaching to the running daemon.

`inspectExistingDaemon` only attaches when ~/.ao/running.json agrees with a
live daemon, so any run-file divergence (missing/stale/unparseable file, dead
PID, or a /healthz pid mismatch) made it return null — and there was no
independent port probe before spawn(), so Electron spawned into an occupied
port and the Go bind guard correctly refused.

Add a defensive direct probe of http://127.0.0.1:<expectedPort>/healthz in
startDaemonInner, after the run-file check and before spawn(): if a genuine
daemon answers, attach to it (the same "ready" DaemonStatus shape the run-file
path returns) instead of spawning. The expected port is resolved the same way
startup does (AO_PORT or the default).

The attach-or-spawn decision is extracted into a pure, dependency-injected
module (shared/daemon-attach.ts) so it can be exercised directly; main.ts
keeps ownership of fs reads, process signals, fetch, and the path identity
check. Covered by 27 tests, including real loopback-server cases that
reproduce the issue scenario end to end.

Fixes #367

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

* chore: format with prettier [skip ci]

* fix(desktop): enforce readiness + identity checks on the port-probe attach path

Address review feedback on #367: the new direct port probe attached to any
service-matching daemon as soon as /healthz returned ok, without the /readyz
and foreign-binary identity checks the run-file path enforces. That reopened
the mismatch daemonIdentityError was built to prevent — Electron could
silently drive a different/older AO build serving the port — and could mark a
still-starting daemon "ready".

Extract the shared post-handshake tail (readinessStatus) and run it from both
paths, anchoring on the PID /healthz reports for the port probe. A serving
daemon that is not ready, or whose binary the identity check refuses, now
yields the same "error" DaemonStatus instead of attaching — strictly safer
than spawning, which would only collide on the occupied port and die.

resolveDaemonFromPort now takes the same identityError dependency
resolveDaemonFromRunFile does; main.ts passes daemonIdentityError(launch, …).
Adds port-path tests for not-ready, foreign-binary identity (unit), and a
real-server foreign-binary scenario (e2e). 30 tests in daemon-attach.

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

* chore: format with prettier [skip ci]

* docs(desktop): note why the port probe uses the expected (not hardcoded) port

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-22 13:14:46 +05:30
Pritom Mazumdar 348fd414d1
test(e2e): guard sidebar brand vs macOS titlebar cluster (#366) (#374)
#366 (brand text overlapped by the fixed macOS TitlebarNav cluster on
board routes) is already fixed on main: #263 made the shell render the
topbar on every route, so the sidebar always hangs below the 56px
titlebar band and the brand never lands in the cluster's lane.

Add an e2e regression guard that locks the invariant in for the routes
the issue named — the brand must not overlap the fixed cluster and the
"Agent Orchestrator" wordmark must stay readable (not truncated) — on the
home board route and the project board route, plus across a board→session
transition (the persistent brand must not jump). Drives the live renderer
under a forced macOS UA. If a topbar-less route is ever reintroduced,
these fail.

Verified: passes against current main with no source changes;
typecheck clean.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 13:14:01 +05:30
Vaibhaav Tiwari c6d9692d37
feat(frontend): add live browser panel (#375)
* feat(frontend): add live browser panel

* chore: format with prettier [skip ci]

* feat: preserve and auto-open browser previews

* fix: retry browser preview after session updates

* fix: wait for browser view before preview navigation

* fix: reopen preview after session switches

* fix: preserve browser views across session switches

* chore: format with prettier [skip ci]

* feat: add `ao preview` command to drive the session browser panel

Replaces the browser panel's auto-detect with an explicit, session-scoped
command. `ao preview [url]` runs inside a session (derives the target from
AO_SESSION_ID; rejects when unset or when the session is unknown):
- with a url, opens it verbatim (file://, http, https; no sanitization for now)
- with no url, autodetects index.html in the workspace as before

The resolved target is persisted as a new `previewUrl` session field and fans
out over the existing CDC /events stream (the sessions update trigger now fires
on preview_url and carries previewUrl in its payload). The desktop browser panel
reflects session.previewUrl: it opens, switches the center pane to the browser,
and navigates, re-navigating only when the target changes.

ponytail: file:// preview targets are accepted unsanitized; agent-trusted for now.

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

* docs(cli): document the `ao preview` command

Add `ao preview` to the CLI command tables in README.md and docs/cli/README.md,
noting it resolves its session from AO_SESSION_ID and its no-arg autodetect vs
explicit-URL behavior.

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

* chore: format with prettier [skip ci]

* fix(frontend): reveal `ao preview` in the inspector Browser tab, not the center pane

`ao preview` set session.previewUrl, and SessionView surfaced it by
popping the browser into the center pane, replacing the terminal. Reveal
it in the inspector rail's Browser tab instead (opening the rail if it is
collapsed); the manual pop-out button still expands it to the center.

Lifts the inspector's active tab to an optional controlled prop so
SessionView can drive it, and adds a regression test asserting the center
pane keeps the terminal while the rail switches to Browser.

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

* docs: instruct agents to `ao preview` when showing frontend changes

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

---------

Co-authored-by: Vaibhaav <user@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Harshit Singh Bhandari <dev@theharshitsingh.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 04:14:18 +05:30
Laxman 7ba860741c
fix: add terminal controls and restore copy/scroll (#372)
* fix(frontend): add terminal controls and reliable copy

* chore: format with prettier [skip ci]

* fix(frontend): preserve terminal mouse scrolling

* fix(backend): enable zellij wheel scrolling

* fix(frontend): forward xterm scroll input

* fix(frontend): restore terminal drag selection copy

* fix(frontend): make terminal wheel scroll zellij scrollback

zellij 0.44.x with mouse-mode true acts on SGR wheel reports written to
its stdin and scrolls the focused pane, but it does not enable host mouse
reporting. xterm therefore never reports the wheel itself (protocol stays
NONE) and, with scrollback:0, converts the wheel into cursor-arrow keys,
which move the agent's cursor/history instead of scrolling.

Synthesize SGR wheel reports from a custom wheel handler and send them
through the existing input pipe; accumulate pixel deltas into line counts
to match xterm's native scroll feel. Ctrl/Cmd wheel is left for the
font-size zoom handler. Drag-copy is unaffected (separate selection path).

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

* chore: format with prettier [skip ci]

* fix(frontend): handle line/page wheel modes for cross-platform scroll

The wheel-to-SGR translation only divided pixel deltas by row height,
which is the deltaMode browsers emit for trackpads and normalized wheels
(macOS). Many Linux/Windows mouse wheels report whole lines (deltaMode 1)
or pages (deltaMode 2) with small deltaY, which truncated to zero lines
and never scrolled. Mirror xterm's getLinesScrolled across all three
modes so scroll works on every platform.

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Harshit Singh Bhandari <dev@theharshitsingh.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 02:26:54 +05:30
Adil Shaikh 8146629ad6
feat: add desktop notifications v1 (#262)
* feat: add desktop notifications v1

* fix: remove duplicate notification icon

* fix: humanize notification type labels

* Revert "fix: humanize notification type labels"

This reverts commit b6ebe6913753c863d6a1f246954f6c66e61f08b5.
2026-06-22 01:14:04 +05:30
Adil Shaikh 6f8112e7b9
feat: surface SCM summaries in desktop (#263)
* feat: surface scm summaries in desktop

* chore: format with prettier [skip ci]

* fix: hydrate PR views from session facts

* chore: format with prettier [skip ci]

* fix: document PR summary DTOs

* fix: clean pr summary attention details

* chore: format with prettier [skip ci]

* fix: default codex sessions to bypass approvals

* fix: refine pr inspector summary

* chore: format with prettier [skip ci]

* fix: color pr diff metadata

* chore: format with prettier [skip ci]

* fix: move diff icon to file count

* fix: keep shell topbar on board routes

* fix: render all session prs on board

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-22 00:35:00 +05:30
neversettle 0e5891df91
fix(review): notify worker on changes_requested instead of relying on SCM poll (#337) (#340)
* fix(review): message worker on changes_requested instead of relying on SCM poll (#337)

review.Engine.Submit previously only persisted the verdict/body and left the
worker to learn about requested changes via the SCM poll loop, which is gated on
GitHub's reviewDecision and never reaches CHANGES_REQUESTED for self-reviews or
COMMENT-state reviews. Submit now nudges the worker's live pane directly via
ports.AgentMessenger (the same mechanism lifecycle uses) whenever the verdict is
changes_requested.

Extended flow: the reviewer reads back the GitHub review id it posted and passes
it through `ao review submit --review-id`; the id is stored on the review_run row
(new column + migration 0016) and included in the worker message so the worker
knows exactly which review to address and reply to.

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

* fix(review): mark worker nudge as AO internal review, ask to reply + resolve

Distinguish the AO internal review nudge from the external GitHub-reviewer
feedback the lifecycle SCM loop relays. For an AO review the worker is now asked,
once it has pushed its fix, to reply on the review referencing its id with what
it changed and resolve the inline review comment threads it addressed (the
reviewer posts inline comments, so the per-finding threads are resolvable via
resolveReviewThread; the top-level review object is not, hence the reply).

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

* fix(review): generalise the changes-requested worker nudge wording

Drop the "not an external GitHub PR reviewer" aside and the assumption that the
worker pushes a fix — it may resolve the feedback without code changes. The nudge
now reads "Review the feedback below and address it" and asks the worker to reply
with how it addressed the review and resolve the threads it addressed.

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

* fix(review): harden the review-id read-back against array order and empty results

The reviewer read the just-posted review id with `--jq '.[-1].id'`, which trusts
the REST API to return reviews in ascending submission order and errors when no
review exists. Review ids are monotonic, so select the highest id instead and
emit nothing when the list is empty: `--jq 'map(.id) | max // empty'`. Update the
matching `--review-id` flag help.

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

* fix(review): post the review via gh api and capture its id from that response

The reviewer must use `gh api --method POST .../reviews` to attach inline
comments anyway (`gh pr review` cannot), and that response already contains the
created review's id. Capture `.id` from that single call instead of a second
read-back, dropping the array-ordering/pagination heuristics entirely — the id is
the exact review just created.

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

* fix(review): send the review as a JSON body so inline comments are a real array

gh api -f/-F cannot build an array of objects: comments[][path] is sent as a
literal key, so the inline comments are dropped — defeating the reason for using
gh api over gh pr review. Post the review via --input JSON instead, keeping the
.id capture and the approve/COMMENT fallback.

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

* chore: format with prettier [skip ci]

* chore(review): drop accidentally committed reviewer scratch, write review out of tree

review.md was the reviewer agent's own writeup, swept onto the worker branch by a
stray `git add -A` in 5df20c9. Remove it, gitignore `/review.md` as a backstop,
and change the reviewer prompt to write its review to a temp file outside the
checkout instead of into the worktree (where it could be committed onto the
worker's branch).

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

* fix(review): message the worker before marking the run complete

If messenger.Send failed after UpdateReviewRunResult had already flipped the run
to complete, a retried `ao review submit` tripped the status='running' guard and
could never record the result. Send first; only mark the run complete once the
worker has been notified, so a failed send leaves the run retryable. A landed
message followed by a failed DB write degrades to one extra nudge on retry — the
same trade lifecycle's sendOnce makes.

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

* feat(review): accept the review body on stdin so the reviewer writes no file

`ao review submit --body -` now reads the review from stdin, and the reviewer
prompt pipes its writeup via a heredoc instead of writing a file. Previously the
reviewer wrote review.md into its checkout to pass as --body, which could be
committed onto the worker's branch (as it just was). A file path is still
accepted for backward compatibility.

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

* fix(review): always post approvals as COMMENT, drop the APPROVE attempt

The reviewer posts from the PR author's own GitHub account, so event=APPROVE
always 422s. Drop it: request changes with REQUEST_CHANGES, approve with a
COMMENT-event review whose body states it is an approval.

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

* fix(review): post every review as event=COMMENT (author can't APPROVE or REQUEST_CHANGES own PR)

The reviewer posts from the PR author's own account, where GitHub rejects both
APPROVE and REQUEST_CHANGES. Always post a COMMENT-event review and state the
verdict in the body; the machine-readable verdict still reaches AO via
`ao review submit --verdict`.

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

* fix(cli): accept underscore flag names on `ao review submit`

Reviewer agents routinely invoke the submit command with --review_id
instead of --review-id, which cobra rejected as an unknown flag and
dropped the GitHub review id from the worker notification. Normalize
underscores to hyphens on the command's flags so both spellings resolve
to the same flag.

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

* fix: sanitize review id in worker notifications

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Vaibhaav <user@example.com>
2026-06-21 19:52:46 +05:30
Harshit Singh Bhandari 2155c3cfaa
fix: pin all app state under ~/.ao, never ~/Library (#369)
Electron's main process set app.setName(...) but never overrode userData,
so Chromium runtime state (cache, cookies, local/session storage, crash
dumps) defaulted to ~/Library/Application Support/<name>. Older dev builds
also wrote the daemon DB there. Pin userData to ~/.ao/electron so the
entire app footprint lives under the canonical AO home alongside the
daemon data dir and running.json; sessionData and crashDumps derive from
userData, so the single override reparents them all.

Document the ~/.ao-only rule as a hard boundary in AGENTS.md and CLAUDE.md.

Closes #368

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 19:46:56 +05:30
Laxman 0e1c5fe54b
Feat/posthog error tracking redaction (#329)
* feat(telemetry): send renderer exceptions to PostHog error tracking

* fix(telemetry): redact local paths and urls before capture

* feat(telemetry): enrich backend error metadata

* fix(telemetry): document telemetry helper exports

* fix(telemetry): satisfy gocritic on helper signature

* chore: format with prettier [skip ci]

* fix(telemetry): redact embedded local urls

* chore: format with prettier [skip ci]

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-21 18:50:47 +05:30
swyam sharma 34a9fdb736
fix: surface session branch in frontend (#358)
Co-authored-by: Swyam Sharma <cruzer@Swyams-MacBook-Pro.local>
2026-06-21 14:57:37 +05:30
Vaibhaav Tiwari 708ec5db56
fix: recover terminal reattach after daemon idle (#354)
* fix: recover terminal reattach after daemon idle

* chore: format with prettier [skip ci]

* fix: harden daemon start recovery

* fix: cancel stale daemon start attempts

* fix: quarantine untrusted daemon base url

* chore: format with prettier [skip ci]

* fix: close daemon status race windows

* chore: format with prettier [skip ci]

* fix: bootstrap daemon trust before shell load

* test: trust mocked API base in PR hydration

---------

Co-authored-by: Vaibhaav <user@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-21 13:59:15 +05:30
Adil Shaikh 72b757b203
feat: require explicit project agents (#355)
* feat: require explicit project agents

* chore: format with prettier [skip ci]

* test: configure integration project agents

* fix: center project agent selection dialog

* fix: keep agent selects neutral before validation

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-21 06:07:53 +05:30
Adil Shaikh 5e8c8defbd
fix: keep terminal mux persistent across navigation (#325)
* fix: keep terminal mux persistent across navigation

* chore: format with prettier [skip ci]

* fix: require configured agent defaults

* chore: format with prettier [skip ci]

* fix: recreate stale orchestrator worktrees

* fix: stop terminal reattach on socket close

* fix: stop terminal attach loop on close

* fix: keep agent default selects controlled

* revert: undo persistent mux branch changes

* fix: harden terminal mux attachment flow

* chore: format with prettier [skip ci]

* chore: remove terminal flow diagnostics

* chore: format with prettier [skip ci]

* fix: satisfy zellij command lint

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-21 06:07:32 +05:30
Harshit Singh Bhandari cdfec7f9d8
fix(desktop): green the tag desktop build on Linux/Windows and add app icon (#348)
The `Desktop testing build` workflow (tag 0.0.0-testing-*) failed on two of
its three runners, and the unsigned macOS artifact has no real app icon.

Linux (rpm): maker-rpm reported "cannot run on linux" because
electron-installer-redhat is only a deeply-nested optional dependency and npm
non-deterministically skipped it on the runner (debian installed, redhat did
not). Promote both electron-installer-debian and electron-installer-redhat to
top-level optionalDependencies so npm reliably installs them on linux/darwin
and still skips them cleanly on win32. Also give the rpm maker an explicit
License (rpmbuild rejects an empty License field) and a maintainer/homepage.

Windows (squirrel): NuGet pack exits 1 when <authors> is empty. package.json
had no author, so add author/license/homepage and set authors + setupIcon on
the squirrel maker.

App icon: generate icon.icns/.ico/.png from src/landing/public/og-image.png
(1024x1024) and wire packagerConfig.icon, the deb/rpm/squirrel makers, and the
runtime BrowserWindow icon (Linux/Windows; macOS uses the bundle .icns).

Verified on macOS: npm run make builds the zip, the packaged app's icns
matches the generated icon, typecheck + 160 vitest tests pass.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:53:11 +05:30
i-trytoohard 83091ceb4c
fix(sidebar): always show project row action icons (#346)
* fix(sidebar): always show project row action icons

The hover-reveal mechanism (opacity-0 → group-hover/menu-item:opacity-100)
was not reliably triggering in the Electron app — icons never appeared on
hover. Instead of debugging CSS :hover group propagation in Chromium, make
the action icons always visible.

Changes:
- Remove opacity-0/opacity-100 hover gating from the action cluster div
- Change button padding from hover-gated pr-[84px] to always pr-[84px]
- Hide the session count badge (it was only shown when icons were hidden)
- Keep z-10 on the action cluster so session rows don't paint over it

Reported by phylolver(vaibhaav).

* fix(sidebar): correct inverted collapsed-count class

The count badge was set to hidden-by-default but shown-as-grid in
collapsed/icon mode — the opposite of correct. In icon mode there's no
room for a count badge and the action cluster is hidden anyway. Since the
action icons are now always visible and permanently replace the count,
the badge should be hidden in all states.

Reported by phylolver(vaibhaav).

---------

Co-authored-by: AO Bot <ao-bot@composio.dev>
2026-06-20 21:17:39 +05:30
i-trytoohard e175da0691
fix(sidebar): make project row hover icons clickable and non-overlapping (#345)
* fix(sidebar): make project row hover icons clickable and non-overlapping

Three fixes for project row hover actions (dashboard, orchestrator, kebab):

1. Count badge pointer-events: The session count span fades to opacity-0 on
   hover but kept pointer-events active, intercepting clicks meant for the
   action icons. Added pointer-events-none on hover/focus/menu-open states.

2. Action icons z-index: The absolutely-positioned action cluster had no
   z-index, so positioned session rows (SidebarMenuSubItem with position:
   relative) in expanded projects painted on top and blocked hover/click on
   the first project's icons. Added z-10 to lift the action cluster.

3. Padding clearance: Bumped pr-[78px] to pr-[84px] to give more room for
   the three 20px action buttons + right offset.

Reported by aditi and phylolver(vaibhaav).

* fix(test): update Sidebar padding assertion from 78px to 84px

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

---------

Co-authored-by: AO Bot <ao-bot@composio.dev>
Co-authored-by: Vaibhaav <user@example.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 19:58:50 +05:30
Pritom Mazumdar b4a8fad215
feat(frontend): surface multiple PRs per session + reviewer in inspector (#335)
* feat(frontend): surface multiple PRs per session

Replace the single optional pullRequest on a session with a prs[] list and
render it across every PR surface: the inspector stacks one card per PR, the
PR board lists one row per attributed PR, and the board card shows a PR count
summary. useWorkspaceQuery now maps the live /api/v1/sessions prs[] into the
query (the frontend surfaced no PRs before this). Ordering is actionable-first
(open, draft, merged, closed).

Adds unit tests (SessionInspector, PullRequestsPage), a Playwright e2e spec,
and a multi-PR mock-data fixture.

* chore: format with prettier [skip ci]

* feat(frontend): swap inspector Changes tab for empty Reviews placeholder

The inspector rail's Summary tab already renders the multi-PR stack from
PR #237 work, so the Changes (Git rail) tab is the next inspector surface
to evolve. Reviews will land on its own backed by PR-scoped review data
(separate workstream); reserve the slot now with an empty placeholder so
the navigation lands before the data does.

- Tabs are now Summary, Reviews, Browser. The InspectorView union and
  the VIEWS array are updated; Reviews gets a message-bubble icon to
  distinguish it from Summary's list icon.
- ChangesView and its lucide imports (GitBranch, GitCommitHorizontal,
  Plus, Square, Trash2) and the unused Button import are removed; a
  small ReviewsView mirrors BrowserView's empty-state shape.
- styles.css drops the orphaned .inspector-changes__* block.
- SessionInspector.test.tsx asserts the new tab labels and that Reviews
  shows the empty placeholder.

* feat(frontend): wire the reviewer feature into the inspector Reviews tab

The multi-PR transplant left the Reviews tab an empty placeholder, which would
have regressed the existing session review feature. Move the reviewer panel
(trigger/re-run a review, open the reviewer terminal, surface verdict + status)
into the Reviews tab, gated on the multi-PR model: it reads the session's prs[]
to decide between the reviewer card and the "no PR yet" empty state, and pulls
review runs + project reviewer config straight from the daemon.

Reconciles the prs[]-from-session-list model across the suite: ShellTopbar and
pr-hydration fixtures carry prs[], useWorkspaceQuery tests drop the obsolete
per-session /pr fetch, and SessionsBoard restores the dropped board title.

Adds a Playwright reviews-tab e2e spec (reviewer card for a session with PRs;
empty state for one without).

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

* test(frontend): adapt multi-PR e2e specs to main's expanded fixture

The rebase pulled in main's larger mock-data fixture (4 workspaces, 13 PRs)
and its renamed refactor-mux title. Rewrite the PR-board assertion to verify
the actionable-first ordering invariant instead of a brittle full-list match,
and update the empty-state selector to the session's current title.

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-20 16:27:53 +05:30
Khushi Diwan a8a3056a6b
feat(frontend): polish board chrome and task actions
Polish the board and task action chrome.

- move board actions into the board header and remove empty board topbar space
- refine session topbar actions with notification, Kill, and Orchestrator controls
- add pointer cursors for clickable controls and clean sidebar child-session styling

Verified with frontend typecheck and targeted renderer tests.
2026-06-20 14:22:05 +05:30
Khushi Diwan 2cb20c23e4
feat(frontend): improve orchestrator board workflows
Improve the frontend board and session workflow presentation.

- refine project sidebar hierarchy and child session display
- clean up kanban task cards and status labels
- add direct task creation from project boards

Verified with frontend typecheck and targeted renderer tests.
2026-06-20 14:19:11 +05:30
Khushi Diwan f98c5e56cc
fix(storage): renumber telemetry migration to 0015 to resolve 0014 version collision (#334)
* fix(storage): renumber telemetry migration to 0015 to resolve goose version collision

* chore: format with prettier [skip ci]

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-20 11:23:13 +05:30
Laxman 43ee6c9b02
Feat/backend telemetry v0 (#307)
* feat(backend): add telemetry event plumbing

* feat(backend): emit session telemetry events

* feat(backend): add http and cli telemetry export

* feat(backend): add onboarding and dwell telemetry

* feat(frontend): add renderer posthog telemetry

* feat(frontend): bundle posthog project defaults

* feat(telemetry): add canonical active-user event

* fix(telemetry): repair cli test expectations

* fix(telemetry): sanitize remote payloads and respect event toggles
2026-06-18 22:00:25 +05:30
Harshit Singh Bhandari b37b6acefe
feat(frontend): refresh dashboard, orchestrator, and AO logos (#317)
* feat(frontend): refresh dashboard, orchestrator, and AO logos

Replaces the dashboard and orchestrator glyphs and the AO brand logo
per issue #315.

- Dashboard buttons (sidebar action + Open Kanban) now use the
  asymmetric LayoutDashboard icon instead of the equal-cell LayoutGrid.
- Orchestrator buttons (sidebar action + every topbar badge/button) use
  a new org-chart OrchestratorIcon (parent fanning to three children),
  authored in lucide's stroke style since lucide has no matching glyph.
- The AO brand logo (sidebar header mark, landing nav, docs header) is
  now the pixel-mascot image; the old ao-logo.svg monogram is removed.

Closes #315

* chore: format with prettier [skip ci]

* fix(landing): use next/image and next/link for the AO logo and docs link

Addresses react-doctor review on #317: the Next.js landing app should use
next/image for the brand logo (optimized formats, responsive srcset, lazy
loading) and next/link for the internal /docs navigation (client-side
routing + prefetch) instead of plain <img>/<a>.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-18 16:23:19 +05:30
Harshit Singh Bhandari 93123edd34
fix(frontend): stop fit loop committing transient cell-box mis-measurements (#313) (#316)
The onRender convergence loop added in #312 recovered the under-counted
rows from #280, but on a HiDPI display it could lock the Codex
orchestrator terminal at half size with a ghosted composer (#313).

FitAddon derives the grid by dividing the pane box by the renderer's
measured css cell box. During the WebGL atlas warm-up that cell box can
emit a one-frame transient (a doubled box on a 2x display), which halves
the proposed cols/rows. The loop committed that single frame's proposal,
resized the grid to half, then detached after a few "stable" frames — so
nothing re-fired the PTY resize that makes zellij repaint, leaving the
grid stuck at half width and the stale composer un-cleared.

Require a differing proposal to repeat identically across two consecutive
renders before applying it, so a one-frame transient only updates the
pending value and is never committed. Add 600ms/1200ms settle fits as a
session-bounded backstop: by then the atlas and font metrics are warm, so
even if the loop detached at a briefly-stable wrong measurement, a late
re-measure corrects the grid and fires the resize zellij needs to repaint
cleanly. fit() is idempotent, so a correct terminal never reflows.

Fixes #313

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 16:09:51 +05:30
Harshit Singh Bhandari 63d488ba29
fix(frontend): recover clipped Codex terminal via onRender convergence re-fit (#312)
The Codex terminal rendered only in the top half of the pane: FitAddon
divided the pane height by a too-tall cell box (measured before the
post-open WebGL renderer and the monospace font's real metrics resolved),
under-counted rows, and sent that short grid to zellij. It never recovered
because every remaining fit trigger after the settle window was the host
ResizeObserver, and the host's height:100% box never changes when only the
row count is wrong.

Add an onRender convergence loop: each renderer repaint re-proposes
dimensions from the current measured cell box and re-fits when they differ,
converging the grid to the true row count once metrics settle, then detaches
once stable (bounded by a re-fit cap). proposeDimensions returns undefined
until the cell box is non-zero, so a fit is never accepted from an unmeasured
cell. Also listen on window resize for OS-window / DPR changes that move the
true cell box without touching the host box.

Fixes #280

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 15:48:51 +05:30
Vaibhaav Tiwari 73b166a015
fix(frontend): make review inspector panels responsive (#300)
* fix(frontend): make review inspector panels responsive

* chore: format with prettier [skip ci]

---------

Co-authored-by: Vaibhaav <user@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-17 23:44:43 +05:30
Harshit Singh Bhandari 4cff3f76eb
feat(frontend): show dashboard and orchestrator buttons on project hover (#293)
* feat(frontend): show dashboard and orchestrator buttons on project hover

Hovering a project row in the sidebar now reveals a dashboard button
(opens the project board), an orchestrator button (opens the running
orchestrator or spawns one), and a vertical three-dot kebab menu,
replacing the lone horizontal kebab. Closes #292.

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-17 23:21:34 +05:30
Harshit Singh Bhandari b8fbbec4ac
feat(frontend): move kill session control to topbar beside Open orchestrator (#298)
* feat(frontend): move kill session control to topbar beside Open orchestrator

Relocate the worker "Kill session" control out of the inspector's Summary
"Danger zone" section and into the app topbar actions row, as a small
danger-tinted icon button next to "Open orchestrator". It only renders for
active worker sessions and keeps the same arm-then-confirm flow and
POST /sessions/{id}/kill behavior.

Move the kill-button tests to ShellTopbar.test.tsx accordingly.

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-17 22:29:22 +05:30
Vaibhaav Tiwari ee044e4edb
feat(frontend): show reviewer worker controls (#255)
* feat(frontend): show reviewer worker controls

* chore: format with prettier [skip ci]

* fix(frontend): surface reviewer config and reused reviews

* chore: format with prettier [skip ci]

* chore: format with prettier [skip ci]

---------

Co-authored-by: Vaibhaav <user@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-17 21:46:09 +05:30
Harshit Singh Bhandari 4bbcf9e925
feat(frontend): add kill button to worker session page (#288)
The worker session inspector had no way to stop a running session from
the UI. Add a "Kill session" action in the Summary view's Danger zone
that arms a one-step confirmation, then POSTs /sessions/{id}/kill and
invalidates the workspace query so the session moves to the terminated
group. The action is hidden for already-terminated/merged sessions.

Closes #287

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 20:50:10 +05:30
neversettle 6ac65d2cd3
fix(frontend): populate session.pullRequest from /pr endpoint (#251) (#279)
* fix(frontend): populate session.pullRequest from /pr endpoint (#251)

WorkspaceSession.pullRequest was declared but never populated, so every
Boolean(session.pullRequest) check was dead: the Summary tab gated its PR
fetch on it (never ran), and the Board card and /prs page read it directly
(always empty).

Hydrate the field centrally in fetchWorkspaces — the single place that
builds session objects for the workspace, board, PR page, and sidebar — by
fetching GET /sessions/{sessionId}/pr per non-terminated session in parallel
and attaching {number, state}. A per-session fetch error degrades to "no PR"
rather than failing the whole workspace query; terminated sessions are
skipped. GET /sessions/{sessionId}/pr stays the single source of truth, so
no new backend endpoint and no changes to the consuming components.

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

* chore: format with prettier [skip ci]

* test(frontend): verify PR hydration end-to-end for a normal project (#251)

Drives the real useWorkspaceQuery + real SessionsBoard / PullRequestsPage
for an ordinary project (from /api/v1/projects) whose session has an open PR,
mocking only the HTTP client and router. Confirms PR facts fetched from
/sessions/{id}/pr flow through the shared workspace cache into both the Board
card ("PR #278 · open") and the PR page row.

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

* test(frontend): move PR hydration integration test into __tests__/integration

Cross-component integration test belongs in a dedicated folder, separate
from the co-located unit tests. No behavior change.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-17 17:48:03 +05:30
Pritom Mazumdar 3986d488c8
feat(session): support multiple PRs per session (#230)
* feat(session): support multiple PRs per session

A session can now own several pull requests (a root plus stacked
children) instead of being capped at one. The SQLite schema was already
1-session->many-PR (pr.url PK, session_id a plain FK), so this is a
behavioural change across the observe -> persist -> derive -> react
pipeline, not a migration.

- observe: the SCM observer discovers every open PR whose source branch
  matches a session branch or descends from it ("branch/..." stacking),
  attributing each to the owning session; the longest matching branch
  wins so a child session claims its own stacked PRs.
- derive: session status is a worst-wins aggregate over all owned PRs,
  with a stack model (B is a child of A iff B.target == A.source and A is
  open) exposed via prs[] on every session read DTO.
- react: per-PR reactions; a stacked child blocked by an open parent is
  exempt from the rebase/merge-conflict nudge (only the bottom of the
  stack is eligible), and the session completes only when no PR is open
  and at least one merged.
- tests: unit coverage across stack/status/observer/lifecycle, a
  real-SQLite ListPRFactsForSession test for the stacked-PR read path,
  and a functional end-to-end integration test driving the real store +
  lifecycle + observer through attribution, completion, and stacked-child
  nudge suppression.

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

* fix(scm): ignore fork heads in PR attribution and persist discovered siblings before completion

Branch-prefix attribution now requires a discovered PR's head branch to live
in the project repo. A fork PR can reuse a session's branch name while its
commits live in the fork, so the previous code could auto-claim foreign work.
Carry head repo full_name from the REST list response and skip any PR whose
head repo is not the base repo.

discoverNewPRs also writes each newly discovered PR as an open baseline row
before the refresh/lifecycle pass runs. A session can own several PRs, and a
terminal observation triggers a completion check that reads all of the
session's PRs from the store. Without the early write, an open sibling found
in the same poll was not yet durable and the session could terminate while
that PR was still open.

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

* fix(session): surface actionable signals from blocked stacked children; clarify worker prompt

Status aggregation previously dropped any open PR blocked by an open parent,
hiding actionable child signals (failing CI, draft, requested changes,
unresolved comments) behind the parent's status. A blocked child still cannot
merge, so its readiness signals (mergeable/approved/review-pending/open) stay
suppressed, but its problem signals now contribute to the worst-wins aggregate.
The all-blocked fallback is preserved so a session never goes dark.

The worker multi-PR prompt said independent PRs could branch off the base
branch as usual, which conflicts with branch-prefix attribution. Clarify that
a PR may target the base branch, but its source branch must stay under the
session branch namespace for AO to track it.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 17:31:28 +05:30
Harshit Singh Bhandari 43ae7ebbc4
fix(frontend): worker session opens orchestrator, not Kanban (#248)
A worker agent session's primary topbar action read "Kanban" and
navigated to the board. Workers now get an "Open orchestrator" button
(Waypoints icon) that navigates to their project's orchestrator, while
orchestrator sessions keep the "Open Kanban" board action.

Closes #234

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:49:16 +05:30
Khushi Diwan d747b57ca6
fix(frontend): exclude nested node_modules from the vitest run (#217)
A bare "node_modules/**" replaces vitest's default "**/node_modules/**"
and only matches the repo root, so the tracked src/landing preview app's
nested node_modules had its vendored third-party test suites (zod, next, ...)
collected and run once those deps were installed — 20+ failures from code
that isn't ours.

Anchor it at any depth with "**/node_modules/**".

Closes #216
2026-06-15 14:51:03 +05:30
neversettle da30da5a45
feat(review): configurable AO code review backend (V1) (#192) (#197)
* feat(review): configurable AO code review backend (V1)

Add per-project configurable code review of a worker's PR. A reviewer
agent runs one-shot over the worker's own worktree and posts its result
to the PR; the worker picks the feedback up through the existing SCM
observer review-nudge path.

- domain: ProjectConfig.reviewers (+ default reviewer harness), Review /
  ReviewRun types and verdict/status vocab.
- storage: review + review_run tables (0011), sqlc queries, store methods.
- service/review: rewrite the in-memory stub as a persisted ReviewService
  (Trigger/Submit/List) with a reviewer Runner over agent resolver +
  runtime; ports.PRReviewPoster implemented on the GitHub adapter.
- http: session-scoped routes POST /sessions/{id}/reviews/trigger,
  POST .../submit, GET .../reviews; regenerated OpenAPI + TS types.
- cli: ao review trigger|submit|list.
- frontend: adapt ReviewDashboard to the per-worker reviews API.

Closes #192

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

* refactor(review): address review — drop submit/poster/CLI, default reviewer to worker harness

Per PR #197 review feedback:
- Reviewer agent posts its review to the PR itself, so remove the
  ports.PRReviewPoster port, the GitHub review poster, the submit HTTP
  route + DTO, and the service Submit method (#1, #4, #7).
- Trigger spawns the reviewer agent over the worker's worktree with its
  own review prompt, mirroring the session launch flow (resolve agent by
  harness -> argv -> runtime.Create) (#8, #9).
- Default reviewer harness reuses the worker's harness when supported,
  falling back to claude-code; reviewer config stays independent of the
  worker override (#5, #6).
- Drop the `ao review` CLI for this PR's scope (#2, #3).

Regenerated OpenAPI + TS types.

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

* feat(review): restore ao review submit (records verdict+body in AO)

Per maintainer request, bring back `ao review submit`. AO records the
reviewer's verdict and body on the review_run and marks the pass complete;
it does not post to GitHub — the reviewer agent posts its review to the PR
itself.

- storage: add review_run.body (0011), persist via Insert/UpdateReviewRunResult.
- service: restore Submit (no SCM poster) storing verdict + body.
- http: restore POST /sessions/{id}/reviews/submit + SubmitReviewInput.
- cli: ao review submit [worker] --verdict --body (worker from arg/--session/$AO_REVIEW_WORKER).
- runner: reviewer prompt instructs posting to GitHub and recording via ao review submit.

Regenerated OpenAPI + TS types.

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

* refactor(review): move reviewer runner to its own package; sharpen prompt

Per PR #197 review:
- Move the concrete reviewer runner out of the service layer into a new
  internal/review_runner package (package reviewrunner), beside other
  orchestration packages like session_manager. The service keeps only the
  Runner interface + RunSpec it depends on; the agent-resolver + runtime
  launch flow lives in review_runner.
- Sharpen the reviewer prompt: tell the agent to diff against the PR base,
  focus on high-confidence findings, post via `gh pr review`, and record
  the result with `ao review submit`; review-only (no commits/edits).
- Add unit tests for the runner.

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

* refactor(review): simplify review_run schema; provider-agnostic reviewer prompt

Per PR #197 review:
- review_run: status default 'running' (drop 'pending'), drop CHECK
  constraints on status/verdict, drop the updated_at column and the
  session/iteration index. Propagated through queries, domain, store,
  service, and tests.
- Reviewer prompt no longer hardcodes GitHub/gh commands — it instructs the
  agent to use whatever review tooling the provider offers, keeping the
  flow extensible across SCM providers.

Regenerated sqlc + OpenAPI/TS.

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

* refactor(review): launch reviewer before persisting the run

Trigger now spawns the reviewer agent first and then writes the review_run
with a status derived from the launch outcome (running on success, failed
if it never started), instead of inserting a running row and correcting it
to failed afterwards.

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

* refactor(review): pluggable reviewer registry distinct from worker harnesses

Reviewers are now their own pluggable adapter set, separate from the worker
agent registry — adding a reviewer (claude-code today, greptile tomorrow) is
a one-line registration that does not widen the worker harness vocabulary,
and a worker harness does not automatically become a valid reviewer.

- domain.ReviewerHarness: a distinct vocabulary (AllReviewerHarnesses) with
  its own IsKnown; ReviewerConfig/Review/ReviewRun use it. ResolveReviewerHarness
  reuses the worker harness only when it is itself a supported reviewer, else
  falls back to claude-code.
- ports.Reviewer: a reviewer-specific contract (ReviewCommand → argv + env)
  that models one-shot / non-prompt CLIs natively instead of forcing every
  reviewer through the worker's interactive GetLaunchCommand(Prompt:...).
- internal/adapters/reviewer: a separate registry + resolver (mirrors the
  worker agent registry) with the claude-code reviewer adapter, which owns the
  review prompt and reuses the worker claude-code launch construction.
- review_runner resolves via the reviewer registry (not the worker
  AgentResolver) and merges AO_REVIEW_WORKER into the adapter's env.
- daemon wires the reviewer resolver. Registry/domain parity is test-enforced.

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

* test(review): cover run-scoped reviewer submit

* fix(api): update generated review submit schema

* refactor(review): split core engine (internal/review) from API service

Move the review orchestration (Trigger/Submit/List, run-id generation,
deps, RunSpec/Runner, sentinels) into a transport-independent core package
internal/review (Engine). internal/service/review is now a thin API-flow
boundary: the controller-facing Manager interface + a Service that delegates
to the engine + error re-exports.

This keeps the service layer to API concerns and lets the same engine back a
future in-process CLI trigger without going through HTTP. review_runner now
depends on the core package; daemon builds the engine and wraps it in the
service. No API/schema changes.

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

* feat(review): commit-aware trigger, reviewer handle for UI, no env vars

Reworks the review trigger lifecycle and drops env-based coupling:

- review_run gains target_sha (the reviewed commit) and drops iteration.
  A repeat trigger for the same PR head short-circuits to the existing run.
- review gains reviewer_handle_id: the live reviewer pane's runtime handle,
  reused across passes and exposed in the reviews API so the UI can attach
  its terminal over /mux.
- Trigger flow: if a live reviewer pane exists and a new commit arrived,
  message it to re-review; otherwise spawn a fresh reviewer. The run is
  recorded only after the reviewer is launched.
- No environment variables: the reviewer adapter embeds the explicit
  `ao review submit --session <w> --run <id>` command in the spawn prompt
  and the re-review message. CLI submit requires --run/--session (no env
  fallbacks).
- Merge review_runner into internal/review as a Launcher (spawn/notify/alive).
- Trigger returns 201 for a new pass, 200 when reusing an existing run.

Regenerated sqlc + OpenAPI/TS.

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

* refactor(review): author the reviewer prompt centrally, not in the adapter

Mirror the worker model (session_manager builds the prompt; adapters just
place it via LaunchConfig.Prompt). The reviewer prompt now lives in
internal/review/prompt.go and is passed through ports.ReviewInvocation.Prompt;
the claude-code reviewer adapter just feeds inv.Prompt to its launch command
and returns it as the re-review message. One-shot CLI reviewers may ignore it.

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

* refactor(review): split reviewer prompt into system+task, mirroring buildSpawnTexts

Mirror session_manager.buildSpawnTexts for the reviewer: a standing role goes
in the system prompt, the per-pass task (PR/commit + exact `ao review submit`
command) goes in the user prompt. internal/review/prompt.go now returns
(prompt, systemPrompt); both flow through ports.ReviewInvocation and the
claude-code adapter places them via LaunchConfig{Prompt, SystemPrompt}. The
re-review message reuses the per-pass prompt (role already established in the
running pane).

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Vaibhaav <user@example.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-15 01:17:17 +05:30
Harshit Singh Bhandari 724b9a2d81
Use ~/.ao as canonical state home (#233)
* Use ~/.ao as canonical state home

* chore: format with prettier [skip ci]

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-14 22:52:25 +05:30
Adil Shaikh 17df922652
feat: add notifications v1 (#181)
* feat: add notifications v1

* fix: address notification review feedback

* fix: require passing CI for merge-ready notifications

* fix: simplify notification listing

* fix: ignore missing sessions for scm notifications

* fix: project notifications from cdc

* fix: stream notifications without cdc
2026-06-14 20:02:32 +05:30
Harshit Singh Bhandari cc29d85f8e
feat(sidebar): restore remove-project functionality (#225)
* feat(sidebar): restore remove-project functionality

#224 deleted the per-project "remove project" feature (and its tests)
instead of completing the wiring #222 left half-applied. Restore it:

- Sidebar: add onRemoveProject to SidebarProps, thread it to
  ProjectItem, re-add the removeError/isRemoving state and the confirm
  handler, render the "Project actions" kebab (MoreHorizontal) with a
  destructive "Remove project" item (Trash2), and re-add the row
  hover/focus/open padding + count-fade classes.
- _shell: re-add the removeProject handler (DELETE /api/v1/projects/{id}
  + drop the project from the workspace cache) and pass it to Sidebar.
- Sidebar.test: restore the confirm-before-remove and cancel-aborts
  coverage alongside the existing class assertions.

Rebuilt on top of #224. Full renderer suite green (127 tests).

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-13 13:48:24 +05:30