Commit Graph

37 Commits

Author SHA1 Message Date
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
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
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
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
Madhav Kumar 6885af26b2
feat(windows): ConPTY terminal, zellij discovery, agent launcher trampoline, codex shim resolution (#310)
* fix(daemon): self-heal a stale run-file instead of refusing to start

On Windows the desktop supervisor can only TerminateProcess the daemon
(no POSIX signal reaches a detached child), so the daemon's graceful
shutdown never runs and ~/.ao/running.json is never removed. The leaked
file survives into the next launch, and because Windows reuses PIDs
aggressively the recorded PID usually belongs to an unrelated process.
The startup pre-flight trusted PID liveness alone (runfile.CheckStale ->
processalive.Alive), so it concluded a daemon was "already running" and
exited with "refusing to start" on every restart. A dead daemon then
makes the renderer's loopback REST calls (e.g. Spawn Orchestrator) fail
silently.

Verify the recorded port is actually served by an AO daemon with the
recorded PID (a /healthz probe matching service + pid, the same ground
truth inspectDaemon already uses) before refusing. A run-file left by a
crashed, hard-killed, or reused-PID predecessor is treated as stale and
overwritten, so startup is robust to a leaked run-file from any cause.

Fixes #256

* fix(release): build the desktop daemon natively on each target OS

build-daemon.mjs compiles the bundled `ao` daemon with the build host's
GOOS and names it off the host platform (ao.exe only when the builder is
Windows). The release workflow ran only on macos-latest, so a Windows
package would ship a macOS binary named `ao` with no `ao.exe`, and the
app could not launch a valid Windows daemon ("This program cannot be run
in DOS mode" / binary not found).

Run the release as a per-OS matrix (macOS + Windows) so host == target
and each installer bundles a daemon compiled for its own platform, and
pin the Go toolchain with setup-go since build-daemon needs it on every
runner.

Fixes #235

* feat(terminal): Windows ConPTY support for /mux attach

Replaces the Windows stub in internal/terminal/pty_windows.go with a real ConPTY implementation backed by github.com/aymanbagabas/go-pty, so the daemon's /mux attach can stream a live terminal to the renderer on Windows.

PTYSource.AttachCommand now returns (argv, env, err). On Windows the zellij attach is spawned directly (no powershell.exe wrapper) — wrapping ConPTY startup around a shell surfaces as modal application-error dialogs — and the per-session ZELLIJ_SOCKET_DIR is delivered via the spawn's CreateProcess env block instead of an 'env -u NO_COLOR' shim. Unix continues to use the env-shim wrapper and returns nil env.

Adds go-pty v0.2.3 (+ bumps golang.org/x/sys to v0.44.0 transitively). Updates the in-process test fakes (terminal/fakes_test.go, httpd/terminal_mux_test.go) for the new signature.

* feat(zellij): discover zellij binary on Windows and raise command timeout

Defaults the zellij binary to whatever exec.LookPath finds first (preferring zellij.exe on Windows), falling back to LOCALAPPDATA\Programs\zellij\zellij.exe and ProgramFiles{,(x86)}\{zellij,Zellij}\zellij.exe so a fresh-installed Windows user gets a working runtime without setting Options.Binary.

Raises the per-command timeout from 5s to 30s on Windows: the first zellij invocation after install routinely takes longer than 5s on Windows due to filesystem/AV warmup, which was causing benign DeadlineExceeded failures during session create.

* feat(zellij,cli): Windows agent launcher trampoline for codex argv

On Windows, zellij's KDL `args` quoting cannot round-trip codex's --config key=value flags (or any argv with embedded quotes), and shell-wrapping the agent in powershell/cmd quoting is equally unsound. This adds a small launch trampoline so zellij runs a known-fixed argv and the real argv is delivered out-of-band.

How it works on Windows:

1. zellij.Runtime.writeLayout persists cfg.Argv to a temp JSON spec via the new agentlaunch package (AO_LAUNCH_SPEC env var points at the file).

2. The KDL layout runs the trampoline as `<ao.exe> launch` (windowsLaunchArgv); PATH is augmented so the trampoline resolves.

3. The new hidden `ao launch` subcommand reads the spec, deletes the temp file, and execs the real agent with cfg.Argv inside cfg.WorkspacePath.

Also adds:

- runner.Start fire-and-forget path (process_windows.go uses powershell.exe -EncodedCommand + Start-Process -WindowStyle Hidden with CREATE_NEW_CONSOLE so the daemon is not blocked on zellij's --create-background settling).

- powerShellEncodedCommand helper and switch from -Command to -EncodedCommand for the existing powershell shellLaunchSpec (avoids brittle KDL→PowerShell quoting round-trips).

Unix is unchanged: writeLayout passes cfg.Env straight through, createSession stays synchronous via runner.Run, and process_other.go is a stub that returns an error if anyone calls into the background path.

* feat(codex): Windows binary resolution, terminal compat flags, TOML literal strings

Three Windows-targeted refinements to the codex agent plugin so a default Windows install lands in a working state:

1. ResolveCodexBinary now follows .cmd/.ps1 shims to the underlying codex.exe (resolveNativeWindowsCodex + windowsNativeCodexCandidatesForShim). The npm-distributed codex shim cannot be exec'd directly under ConPTY without a shell wrapper; jumping straight to the .exe avoids that wrapper.

2. appendTerminalCompatibilityFlags adds Windows-specific args (e.g. --no-alt-screen) so codex's TUI renders correctly inside zellij's pane without the alternate-screen buffer churn that breaks ConPTY redraws.

3. hooks.go gains codexTOMLLiteralString / codexTOMLConfigString / containsTOMLControl so paths and other values with backslashes and quotes round-trip through codex's --config TOML parser using literal strings ('...') when basic strings would require unsafe escaping.

* fix(lint): paramTypeCombine in pty_unix.go, revive doc comments in agentlaunch, codex test quotes

* fix: stabilize windows zellij sessions

---------

Co-authored-by: harshitsinghbhandari <24b4506@iitb.ac.in>
Co-authored-by: Madhav <madhavkumar@microsoft.com>
Co-authored-by: Vaibhaav <user@example.com>
2026-06-18 21:51:05 +05:30
neversettle 9ae9f08887
fix(session): make worker branch namespace child-safe (#309)
* fix(session): make worker branch namespace child-safe

* test(cli): wait for daemon exit after shutdown
2026-06-18 20:00:15 +05:30
Harshit Singh Bhandari 5fff98087a
feat(import): rewrite-side legacy → rewrite first-boot import (#314)
* feat(import): rewrite-side legacy → rewrite first-boot import

Port the legacy-side TS reader (AgentWrapper #2144/#2129) to Go and run the
migration inside the rewrite as an opt-in import, per the FINAL v2 plan. Reads
the legacy flat-file store (~/.agent-orchestrator) read-only and writes the
rewrite's own SQLite DB via the native storage layer; legacy files are never
touched, and a re-run skips existing rows, so a declined or failed import loses
nothing.

What's included:
- internal/legacyimport: Go reader + field mappers (issue #247). Lifecycle
  double-decode (lifecycle key or statePayload+stateVersion:"2"),
  role/orchestrator detection, sessionPrefix fallback (first 12 chars of id),
  8→4 activity-state map, per-harness resume-id selection, permission/harness
  remap, and the claude transcript slug + relocation to the rewrite's
  orchestrator worktree path ({DataDir}/worktrees/{id}/orchestrator/{prefix}-orchestrator).
- store.ImportSession: verbatim session insert (explicit id/num, ON CONFLICT
  DO NOTHING) so the orchestrator lands at id "{prefix}-orchestrator", num 0.
- `ao import`: explicit, idempotent import with --from/--dry-run/--yes/--json.
  Refuses while a live daemon owns the run-file (the daemon is sole writer; the
  import runs offline, matching the #2129 reference).
- First-boot opt-in: `ao start` offers the import before launching the daemon
  when legacy data is present and the rewrite DB has no projects yet. Declining
  or any failure is non-fatal; a non-interactive boot prints a hint instead of
  auto-importing.

Scope (gist §6): all projects + per-project settings, and the single
non-terminated orchestrator session per project (claude-code/codex/opencode;
aider skipped with a note). Workers are not imported (they respawn fresh).

Resume-id mapping (#247 §2.2): agent_session_id carries claudeSessionUuid /
codexThreadId / opencodeSessionId by harness. codexModel and
restoreFallbackReason have no rewrite column, so they are dropped and surfaced
as import notes — codex resumes from the thread id alone, the rest is forensic.

Gate: `go build ./... && go test -race ./...` green (1423 tests).

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

* fix(import): resolve golangci-lint errcheck/gocritic/nilerr findings

- start.go: check fmt.Fprint* returns in the first-boot import path
- project.go: combine same-typed return params (gocritic paramTypeCombine)
- claude.go: use a pathExists helper so a missing transcript source is a normal
  skip, not an err-then-return-nil (nilerr)
- importer.go: fold best-effort transcript relocation into a switch so the
  non-fatal path no longer returns nil from an error branch (nilerr)

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

* fix(import): resolve transcript dest path like the daemon; harden lifecycle parse

Code-review follow-ups on the legacy importer:

- claude.go: compute the Claude transcript DESTINATION slug from the
  symlink-resolved orchestrator worktree path (new resolvePhysical, mirroring
  gitworktree.physicalAbs), not the literal path. The daemon resolves that cwd
  through physicalAbs before `claude --resume` runs, so a literal-path slug
  missed the resume bucket whenever any component of AO_DATA_DIR was a symlink
  (custom data dir, macOS /tmp→/private/tmp, symlinked $HOME) — the orchestrator
  would have resumed without its prior context. Source slug now uses the same
  resolver for symmetry.
- orchestrator.go: accept a numeric stateVersion (JSON 2 → float64) as well as
  the string "2" when falling back to statePayload, so a V2 record carried only
  in statePayload is not misparsed as stateless.
- orchestrator.go: build the dropped-resume-metadata note as a joined list
  instead of string concatenation.

Tests: added a symlinked-data-dir dest-slug test and a numeric-stateVersion
fallback test. Gate green: `go build ./... && go test -race ./...` (1425).

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:54:51 +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
Ashish Huddar 40bd2dfb2b
fix(sessions): stop AO hook files from making every worktree permanently dirty (#169)
* fix(sessions): stop AO hook files from making every worktree permanently dirty

Agent adapters write hook files (.codex/hooks.json, .opencode/plugins/
ao-activity.ts, .claude/settings.local.json, ...) into fresh session
worktrees as untracked files. `git worktree remove` (deliberately run
without --force) refuses on any untracked file, so Workspace.Destroy
failed for every session of the 12 workspace-writing harnesses:
POST /sessions/{id}/kill returned an unlogged 500 INTERNAL_ERROR and
`ao session cleanup` reported 'Would clean N' then '0 sessions cleaned'
with no reason, leaking workspaces forever.

Three coordinated fixes, none of which force-deletes user/agent work:

- Root cause: every adapter now writes a sentinel-guarded, self-ignoring
  .gitignore next to its hook files (hookutil.EnsureWorkspaceGitignore),
  so AO's own files no longer count as dirt while anything an agent
  drops — even in the same directory — still blocks teardown. A
  registry-wide conformance test enforces the contract for all current
  and future adapters. (Per-worktree .git/worktrees/<name>/info/exclude
  was evaluated first but git does not honor it.)
- Typed refusal: gitworktree.Destroy classifies a still-dirty refusal as
  ports.ErrWorkspaceDirty (git status probe). Kill maps it to success
  with freed=false (session terminated, worktree preserved); Cleanup
  reports it per-session as skipped-with-reason through the API
  (CleanupSessionsResponse.skipped), and the CLI prints
  'Skipped: <id> (workspace has uncommitted changes)' plus a summary.
- Observability: envelope.WriteError records the raw service error into
  a request-scoped slot and the access log attaches it to 5xx lines, so
  any remaining internal error is diagnosable server-side.

Worktrees created before this fix gain the .gitignore on restore (hook
install re-runs); their cleanup is otherwise reported as skipped instead
of erroring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cleanup): address Greptile P2s — surface dirty-probe failures, stop leaking raw errors

Two review findings on this PR:

- gitworktree.Destroy: when the isDirty probe itself failed, the error was
  silently discarded and the refusal looked identical to "registered but not
  dirty". The probe failure now rides the returned error (dirty probe: ...),
  so it reaches the access log via the 5xx error capture.

- Cleanup skip reasons: a non-dirty teardown failure put the raw error —
  including internal filesystem paths — into the public skipped[].reason
  field. The public reason is now the fixed string "workspace teardown
  failed"; the full cause goes to the daemon log (warn, with sessionID and
  path). The dirty-refusal reason is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gitworktree): wrap the dirty-probe error with %w per errorlint

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 11:06:45 +05:30
Ashish Huddar 8d0c53ec1d
fix(codex): reliable activity signals — session-flag hooks, trust bypass, no_signal watchdog (#170)
* fix(codex): deliver activity hooks via -c session flags, trust worktree at launch

Codex (0.136+) never loads hook config from AO's per-session worktrees:
project-local .codex/ layers only load from trusted directories, and for
linked git worktrees codex sources hook declarations from the matching
folder in the root checkout — so the workspace-local .codex/hooks.json AO
wrote was dead config and codex sessions never reported activity.

Deliver the hooks on the launch/resume command instead:
- -c 'hooks.<Event>=[...]' session-flag config for SessionStart,
  UserPromptSubmit, PermissionRequest, and Stop; the session-flags layer
  is not trust-gated and aggregates with the user's own hooks. The
  existing --dangerously-bypass-hook-trust flag lets them run without a
  persisted trust hash.
- -c 'projects={"<worktree>"={trust_level="trusted"}}' (inline-table
  form; the dotted projects."<path>".trust_level key is corrupted by
  codex's naive -c dot-split) so spawns into never-trusted repos don't
  hang invisibly on the interactive directory-trust prompt. Both the
  literal and symlink-resolved worktree paths are trusted.
- -c notice.hide_rate_limit_model_nudge=true so the "switch to a cheaper
  model?" dialog can't hang a headless pane and swallow the spawn prompt.

GetAgentHooks no longer writes workspace files (worktrees stay clean); it
only strips entries older AO versions left in .codex/hooks.json,
preserving user hooks. UninstallHooks/AreHooksInstalled now operate on
those legacy files only.

Verified with a real spawn into a fresh untrusted repo: activity
transitions idle -> active -> idle hands-free, no .codex dir in the
worktree, no hook delivery failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(sessions): activity-signal watchdog + hook delivery hardening

A codex upgrade broke activity tracking silently: sessions showed a
confident "idle" forever while the agent worked. This bundle makes hook
delivery verifiable end to end and makes any future breakage loud
instead of invisible.

Watchdog (no_signal status):
- sessions.first_signal_at (migration 0010) records the FIRST hook
  callback per spawn/restore — raw signal receipt, independent of the
  derived activity state. lifecycle.ApplyActivitySignal stamps it (and
  writes through same-state repeats until stamped, e.g. Codex
  SessionStart reporting idle on an idle-seeded row); MarkSpawned clears
  it so every relaunch re-proves its hook pipeline.
- deriveStatus downgrades a live session with no receipt to the new
  no_signal display status after a 90s grace, instead of idle.
  Terminated/PR-derived statuses still win. The sessions CDC update
  trigger now also fires on first-signal receipt so the dashboard
  transition is pushed live.
- frontend maps no_signal -> needs_you (a human should look at the pane).

Hook callback hardening (re-landed from the closed redesign PR #156):
- the session manager pins each spawned session's PATH with the daemon
  executable's directory first, so the bare `ao` in hook commands
  resolves to the daemon that installed them, with a spawn-time warning
  when the pin cannot apply.
- `ao hooks` failures append to $AO_DATA_DIR/hooks.log (size-capped);
  `ao doctor` gains a hooks-log check that warns on failures from the
  last 24h, and an ao-binary identity check.

Codex launch-surface canary:
- `ao doctor` gains codex-launch-flags: it runs probes exported by the
  codex adapter (built from the same flag builders as the real spawn
  argv) against the installed binary, warning when codex rejects the
  hook-trust bypass flag or AO's -c session-flag overrides.
- codex hook callback timeout drops 30s -> 5s so a hung daemon cannot
  stall the agent's turn.

Docs: the agent PRD callback section now describes the implemented flow
(derive state, POST /sessions/{id}/activity, hooks.log) instead of the
unbuilt SQLite/metadata merge, and notes that hook-derived metadata
persistence (codex resume) is still not implemented.

Frontend note: main's renderer test suite has 7 pre-existing failing
files and a vite-config typecheck error unrelated to this change;
workspace.test.ts (the only frontend file touched) passes 26/26.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(store): restore TestSessionWorktreesRoundTrip lost in the re-landing port

The branch ported store_test.go wholesale from the closed redesign
branch, whose copy predates #165 — silently dropping the
session-worktrees round-trip test #165 added. Restore main's file and
re-apply only this branch's addition (TestSessionFirstSignalRoundTrip).
No other ported file lost main-side content (audited per-file against
main; the remaining deletions are this branch's intended refactors).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(status): only derive no_signal for harnesses that have a hook pipeline

Review finding: the no_signal downgrade had no harness-capability gate, but
first_signal_at can only ever be stamped by an `ao hooks` callback. Ten
spawnable harnesses (amp, aider, crush, grok, kimi, devin, auggie, continue,
vibe, pi) install no hooks at all, so every live session of theirs would have
flipped from idle to a permanent no_signal -> needs_you after the 90s grace.

The session service now takes a SignalCapable predicate; daemon wiring injects
activitydispatch.SupportsHarness (the deriver registry is the source of truth
for "this harness can signal"). Left nil, the service never claims no_signal.
A new dispatch test pins that every deriver token is a known harness name.

Also from the same review:
- lifecycle/manager.go and the 0010 migration claimed Codex's SessionStart
  reports idle as the first signal; both codex and claude-code derivers
  deliberately return no signal for session-start, so the comments now cite a
  real case (a lost "active" POST followed by a Stop hook landing idle).
- docs/agent/README.md documents the gate and the restore caveat: a restored
  session the user never prompts has nothing to signal, so it shows no_signal
  after the grace until a receipt-only session-start signal exists.
- 0010 migration uses DROP TRIGGER IF EXISTS per house style.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:36:45 +05:30
Harshit Singh Bhandari 5244015802
feat: add workspace project registration foundation (#165)
* feat: add workspace project registration schema

* fix: satisfy workspace registration lint

* fix: harden workspace registration edge paths

- Reject linked-worktree and bare parents via validateWorkspaceParent before any mutation
- Roll back git init/.gitignore on failure in initWorkspaceParent so retries are clean
- Reject child repos named __root__ (reserved PK in session_worktrees)
- Serialise Service.Add with addMu to eliminate TOCTOU on concurrent same-path calls
- Fix ensureWorkspaceGitignore permission 0o600 -> 0o644
- Improve guardNoGitlinks suggestedFix with actionable git rm --cached guidance
- Remove dead CASE/__root__ ordering from ListWorkspaceRepos SQL (regenerated via sqlc)
- Resolve RepoOriginURL once per code path in Add (workspace vs single-repo)
- Add 7 tests covering the new edge paths
2026-06-10 16:10:14 +05:30
Harshit Singh Bhandari 5071364f91
fix(sessions): remove agent rules spawn path (#159) 2026-06-09 00:10:28 +05:30
neversettle 7698c24931
feat(config): persist per-project agent config and resolve it at spawn (#154)
* feat(config): persist per-project agent config and resolve it at spawn

Each project can now carry its own agent config (model, permissions,
adapter-specific keys) that survives daemon restart and is resolved into
the launch command when a session spawns.

- storage: add nullable projects.agent_config JSON column (migration 0008);
  marshal/unmarshal in the store so the domain carries map[string]any
- resolution: session manager loads the project row and populates
  LaunchConfig.Config before GetLaunchCommand
- validation: claude-code declares a ConfigSpec (model, permissions) and
  rejects unknown keys / bad types / bad enums at spawn; it applies the
  model override and config-driven permission mode (explicit Permissions
  still wins)
- surface: PUT /projects/{id}/agent-config + `ao project set-config`
  (--set/--config-json/--clear), config shown in `ao project get`

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

* fix(claudecode): validate string-list/required config keys and unhandled types

Address review on per-project agent config validation:
- handle ConfigFieldStringList (list of strings) explicitly
- reject unhandled ConfigFieldType via a default case rather than
  silently passing
- enforce Required fields are present

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

* refactor(config): make per-project agent config a typed struct

Replace the free-form map[string]any agent config with a typed
domain.AgentConfig{Model, Permissions} so values are validated when set
(CLI/API) instead of silently dropped at spawn, and the OpenAPI/TS schema
and UI get real typed fields.

- domain: AgentConfig struct + Validate(); PermissionMode moves to domain
  and ports re-exports it as a type alias (zero adapter churn)
- storage: marshal/unmarshal the typed struct (IsZero → SQL NULL)
- service: validate on Add and SetAgentConfig; read-model exposes a typed
  *AgentConfig
- claudecode: read typed cfg.Config.Model/.Permissions; drop the
  map/spec-based validateConfig in favor of the typed Validate()
- cli: typed `ao project set-config --model/--permission/--clear`
- docs: add docs/design/per-project-config.md blueprint sequencing the
  remaining # Projects fields toward fully typed per-project config

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

* feat(config): full typed per-project ProjectConfig (store, resolve, surface)

Expand per-project config from agentConfig-only to the full legacy
`projects.<id>` surface, modeled as one typed domain.ProjectConfig
persisted in a single projects.config JSON column.

Wired end-to-end at spawn:
- defaultBranch  → base branch for the session worktree (ports.WorkspaceConfig.BaseBranch)
- env            → merged into the runtime env (AO-internal vars still win)
- symlinks       → repo files linked into the workspace
- postCreate     → commands run in the workspace (OS-agnostic shell)
- agentRules / agentRulesFile / orchestratorRules → merged into the prompt
- worker/orchestrator role overrides → harness + agent-config resolution

Stored + validated + surfaced now, consumption deferred (no consumer yet):
tracker, scm(+webhook), opencodeIssueSessionStrategy; sessionPrefix feeds
the display prefix only (session-id generation unchanged).

Validation lives on domain.ProjectConfig.Validate() and runs when config is
set (CLI/API). PermissionMode/AgentConfig stay typed; harness names validated
via domain.AgentHarness.IsKnown().

Surface: PUT /projects/{id}/config (replaces /agent-config) + typed
`ao project set-config` flags (--default-branch/--env/--symlink/--post-create/
--agent-rules/--worker-agent/… or --config-json). OpenAPI + TS regenerated.

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

* fix(lint): tighten symlink dir perms to 0o750 (gosec G301)

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

* feat(config): centralize default project config + tests

Add domain.DefaultProjectConfig / ProjectConfig.WithDefaults with a single
DefaultBranchName ("main") source of truth, replacing the literal "main"
scattered in the read-model and the gitworktree adapter. Unconfigured
projects now resolve the default branch through one path; every other field
defaults to its zero value.

Tests: defaults present for all fields (DefaultProjectConfig/WithDefaults),
and an unconfigured project reports the default branch + derived session
prefix while omitting the empty config object.

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

* feat(config): encode documented defaults (branch=main, tracker=github)

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

* fix(config): fail-safe paths for missing/corrupt per-project config

Address review on default-config / fail-safe spawning:
- projectRules: a missing AgentRulesFile is optional context, skipped
  rather than aborting every spawn (only a real read error surfaces)
- store: a corrupt config JSON column degrades to a zero config instead
  of failing GetProject/ListProjects/FindProjectByPath for that row
- restore: re-apply the project's resolved AgentConfig so a configured
  model/permissions carry across a restore (matches fresh spawn)

Tests: missing rules file skips, corrupt config degrades to zero, restore
applies the project agent config.

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

* refactor(config): trim per-project config to consumer-backed fields

Drop config that has no live consumer yet, so this PR lands only the
fields actually read at spawn/display:

- Remove prompt rules (agentRules, agentRulesFile, orchestratorRules)
  from ProjectConfig. Project/agent instructions belong on the system
  prompt path or repo-local AGENTS.md, not another rules family.
- Remove future-only integration config with no consumer: tracker, scm,
  scm.webhook, and opencodeIssueSessionStrategy (plus their types,
  constants, the github tracker default, CLI flags, and spec schemas).
  These return in focused PRs alongside the code that reads them.

Kept: defaultBranch, sessionPrefix, env, symlinks, postCreate,
agentConfig (model/permissions), and worker/orchestrator role
overrides. Cross-agent model/permissions support stays follow-up (#157).

Regenerated openapi.yaml + frontend schema.ts.

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

* fix(config): reject unknown config JSON keys; confine symlink paths

Two review hardenings on the now-trimmed per-project config surface:

- Project add/set-config endpoints decode with DisallowUnknownFields, so
  a misspelled or removed config field surfaces as a clear 400 instead
  of being silently dropped. Locks the removals from e213b68 (and any
  future trims) at the API gate. Covered by new controllers test.
- applySymlinks now refuses absolute paths and any ".." segment via a
  safeRelPath guard, so a project config cannot escape the project or
  workspace tree via a malicious symlinks entry. Covered by new
  session_manager test.

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

* fix(config): reject symlink path traversal at config write time

greptile flagged ProjectConfig.Symlinks as a write-time path-traversal
gap on PR #154 — the runtime guard in applySymlinks catches a malicious
entry on every spawn, but the config itself accepted it. Move the check
into ProjectConfig.Validate so a bad symlinks entry surfaces as
INVALID_PROJECT_CONFIG when set (CLI/API) instead of silently sitting in
the row until the next spawn. The runtime guard stays as
defense-in-depth.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: harshitsinghbhandari <24b4506@iitb.ac.in>
2026-06-08 21:35:29 +05:30
Harshit Singh Bhandari c343c55c14
fix: 7 bugs from discussion #149 smoke walk (envelope, spawn, CDC, observer) (#153)
* fix(cdc): emit pr_review_thread_resolved on replace polls (#152 bug 5)

writePRRows was DELETE-then-UPSERT on the Replace path, so every poll's
upserts hit the INSERT branch and the AFTER UPDATE trigger that emits
pr_review_thread_resolved never fired in production. Replaces the
blanket delete with a set-diff: upsert observed threads first (so
unchanged thread_ids go through ON CONFLICT DO UPDATE and fire the
UPDATE trigger when resolved flips), then delete orphans whose
thread_id is not in the observed set, all inside the existing tx.

Adds DeletePRReviewThread query (sqlc-generated form hand-edited; no
sqlc binary available locally — sqlc generate from backend/ produces an
identical file).

Tests: TestPRReviewThreadsCDC_EmitsResolvedOnReplacePoll (regression —
fails without fix) and TestPRReviewThreadsReplace_PrunesOrphansWithoutReinserting.

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

* fix(observe): emit scm-disabled log on startup with no subjects (#152 bug 7)

checkCredentials lived only inside Poll, which short-circuits when
discoverSubjects is empty. On a fresh daemon with no tracked PRs the
documented "scm observer disabled: provider credentials unavailable"
warn was unreachable, leaving users with no signal that the SCM
observer was a no-op.

Calls checkCredentials once in Observer.loop before the first Poll.
The existing credentialsChecked guard preserves once-per-process
semantics; provider construction still uses SkipTokenPreflight so
daemon readiness doesn't block on gh.

Test: TestStart_LogsDisabledWarningWhenNoTokenAndNoSubjects with a
race-safe syncBuffer for capturing slog from the observer goroutine.

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

* fix(api,spawn): typed errors + project/branch/binary preflight (#152 bugs 1-4,6)

Closes the long tail of opaque-500-and-orphan-row failures that
discussion #149's smoke walk surfaced. The common shape: spawn created
the session row before validating preconditions, and the underlying
errors weren't typed, so toAPIError defaulted to INTERNAL_ERROR.

Bug 1 (orphan row + opaque 500 on unknown projectId):
Service.Spawn / SpawnOrchestrator now call store.GetProject first and
return apierr.NotFound("PROJECT_NOT_FOUND", ...) before manager.Spawn,
eliminating the create-row-then-fail-workspace ordering.

Bug 2 (Restore opaque 500 on half-spawned/terminated session):
Manager.Restore gained the ErrIncompleteHandle guard that Kill has at
manager.go:189-193. toAPIError now maps both restore and kill to the
same SESSION_INCOMPLETE_HANDLE 409 envelope.

Bug 3 (--branch unfetched / checked-out-elsewhere → opaque 500):
gitworktree pre-checks listRecords for branch-in-other-worktree, falls
back to refs/tags on missing local/remote head, and emits two new port
sentinels (ErrWorkspaceBranchCheckedOutElsewhere,
ErrWorkspaceBranchNotFetched) mapped to BRANCH_CHECKED_OUT_ELSEWHERE
(409) and BRANCH_NOT_FETCHED (400).

Bug 4 (orphan terminated row on claim-pr rollback):
Adds Store.DeleteSession gated to seed-state rows only (preserves the
no-resurrection guarantee for live sessions), transactional change_log
cleanup, Manager.RollbackSpawn (delete-then-fallback-to-kill), a new
POST /sessions/{id}/rollback endpoint, and rewires
cli/spawn.rollbackSpawnedSession to use it. The exit-0 sub-symptom was
unreproducible from current source and is left unaddressed.

Bug 6 (agent binary not on PATH → silent idle session):
Drops the "return name, nil" anti-pattern from all 21 agent adapters
and returns the new ports.ErrAgentBinaryNotFound on exec.LookPath miss.
Manager.Spawn gained a validateAgentBinary pre-flight (with injectable
LookPath so tests don't need real binaries on PATH) that aborts before
runtime.Create. Mapped to AGENT_BINARY_NOT_FOUND (400). Integration
tests in internal/integration/ stub LookPath to /usr/bin/true.

Tests cover each bug end-to-end. OpenAPI regenerated for /rollback.

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

* chore: gofmt + regen frontend schema.ts for /rollback

CI fixes for #153:
- gofmt/goimports on kilocode and kiro adapters that the bug 6 audit
  left mis-grouped.
- openapi-typescript regen against the new /rollback endpoint added in
  the Lane A commit.

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

* fix(store): guard change_log delete behind seed probe + regen sqlc (#152, PR #153 review)

Addresses @greptile-apps P1 and P2 review feedback on PR #153.

P1 (CDC events deleted for live sessions in rollback fallback):
DeleteChangeLogForSession ran unconditionally inside the transaction
before DeleteSeedSession's seed-state predicates filtered the session
delete to a no-op. For a live session reaching DeleteSession (the
delete-then-kill fallback path inside RollbackSpawn), the seed delete
returned 0 rows but the session_created/session_updated CDC events
had already been purged. Now probes via a new SessionIsSeed query
first and short-circuits the whole tx — including the change_log
cleanup — when the row is not in seed state.

P2 (regen sqlc): installed sqlc 1.31.1 and ran `sqlc generate` from
backend/, replacing the hand-edited pr_review_threads.sql.go (and
producing minor format-only churn in models.go, pr.sql.go,
sessions.sql.go, changelog.sql.go).

The regen surfaced two issues:

1. GetPR / ListPRsBySession had their return types hand-changed to
   gen.PR by the previous PR; sqlc actually emits GetPRRow /
   ListPRsBySessionRow when queries enumerate columns. Fixed by
   collapsing those two queries to `SELECT * FROM pr` so sqlc returns
   gen.PR (which is what the store's prRowFromGen converter expects),
   and pr.last_nudge_signature now lands in the result alongside the
   existing 37 columns.

2. sqlc 1.31.1's SQLite parser silently strips trailing `?`
   placeholders and string literals from DELETE statements (reproduced
   with sqlc.arg, IFNULL, rowid subquery, and second-predicate
   workarounds — all eaten). DeleteSeedSession and
   DeleteChangeLogForSession both tripped it. They are now run as
   plain tx.ExecContext calls inside Store.DeleteSession, inside the
   same write transaction as SessionIsSeed; both queries are removed
   from the queries/ directory and the workaround context is
   documented inline in queries/sessions.sql and queries/changelog.sql
   to keep future contributors from re-adding them.

Verified: go build ./... clean, go test -race ./... 1097/1097 pass.
2026-06-07 07:35:46 +05:30
yyovil 3152cdc948
feat(agents): agent platform — registry, activity hooks, harness allowlist (#119)
Introduces the shared platform that per-agent adapters plug into, wired for the
three shipped harnesses (claude-code, codex, opencode):

- adapters/agent/registry: single source of truth for shipped adapters
  (Constructors), consumed by the daemon to resolve a session's harness.
- adapters/agent/activitydispatch + 'ao hooks' command: maps an agent's native
  hook callbacks onto AO activity states (active/idle/waiting/...).
- claudecode/codex/opencode: emit SessionStart/UserPromptSubmit/Stop activity.
- HTTP + OpenAPI: report session activity state.
- db: single migration widening sessions.harness to all shipped harnesses, so
  adding an adapter needs no further migration.
- domain: harness constants + --agent alias for 'ao spawn'.

Adding a new agent is now one adapter package plus a line in Constructors().

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Harshit Singh Bhandari <claudeagain@pkarnal.com>
2026-06-07 00:52:40 +05:30
yyovil 3c7344b233
[codex] add ao hooks activity command (#113)
* feat: add ao hooks activity command

* fix(activity): address review nits

- lcm: sameActivity ignores LastActivityAt so same-state repeats no-op
  and don't churn UpdatedAt / CDC events.
- cli/hooks: surface stdin read errors to stderr for parity with the
  daemon-error path; still exit 0 so a failed hook can't break the agent.
- claudecode: GetAgentHooks docstring covers Notification + SessionEnd
  (the slice already included them; only the comment was stale).

---------

Co-authored-by: harshitsinghbhandari <dev@theharshitsingh.com>
2026-06-06 20:29:00 +05:30
Harshit Singh Bhandari 3413acca33
feat: ao session claim-pr + spawn --claim-pr wiring (#101)
* feat: add session PR claiming CLI and API

* fix: tighten PR claim rollback and CDC facts

* fix: align PR claim branch with latest main
2026-06-06 00:01:03 +05:30
neversettle 7880f59cf5
refactor(backend): LLD maintainability fixes in controllers/service layers (#95) (#96)
* refactor(backend): LLD maintainability fixes in controllers/service layers

Addresses the high + medium severity findings from the LLD review of
backend/internal/httpd and backend/internal/service (#95):

1. Controllers no longer import internal/session_manager. Session sentinel
   errors are now *domain.ServiceError values carrying their own HTTP mapping,
   so the controller translates them with one generic errors.As — no
   cross-package sentinel imports.
2. One error pattern across services: project.Error is now an alias of the
   shared domain.ServiceError, and session_manager sentinels use it too. A
   single writeServiceError replaces the per-resource error switches.
3. Clean-orchestrator business logic moved out of the controller into
   session.Service.SpawnOrchestrator(ctx, projectID, clean).
4. isGitRepo no longer treats case-different paths as equal on case-sensitive
   filesystems; case-insensitive compare is gated to darwin/windows via samePath.
5. Project repo check sits behind an injectable GitChecker, so the service is
   testable without a real git binary.
6. httpd exports only the production constructors (NewWithDeps,
   NewRouterWithControl); the 3 test-only wrappers are removed and the
   "router with empty deps" convenience moved to an unexported test helper.

Closes #95

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

* refactor(backend): standardize service errors on internal/httpd/errors

Replace the domain.ServiceError approach with a REST-API-scoped error package
and a single envelope renderer, per review feedback:

- Add internal/httpd/errors (package errors, aliased apierr): one structured
  Error type with semantic Kinds (Internal/Invalid/NotFound/Conflict) and
  constructors. Imports nothing, so any layer can depend on it.
- envelope.WriteError is now the single path from a service error to the wire
  APIError, and the only place a Kind becomes an HTTP status/word. The
  per-resource writeProjectError/writeSessionError translators are gone.
- Delete domain/errors.go (keeps domain pure of HTTP-flavored kinds) and
  service/project/errors.go (no per-service error files); services build
  errors inline via apierr constructors.
- session_manager sentinels are apierr.Error values (pointer identity still
  works with errors.Is).

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

* revert(backend): drop GitChecker seam and isGitRepo case-sensitivity change

Defer findings #4 (isGitRepo case-sensitivity) and #5 (GitChecker seam) out
of this PR. Restores the original exec-based isGitRepo and the New(store)
constructor; removes git.go, git_test.go, and the test-only export shims. The
error-standardization and other findings are unaffected.

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

* refactor(session): translate engine errors to API errors at the facade

The session_manager is the internal command engine and must not depend on the
REST API error vocabulary. Revert its sentinels to plain errors.New values and
move the engine→API translation into the service/session facade (toAPIError),
which is the correct boundary. Controllers still see apierr.Error and never
import the engine; the engine no longer imports internal/httpd/errors.

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

* docs(session): tighten error comments to state what the code does

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

* style(envelope): make KindInternal an explicit case in httpStatus

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

* refactor(apierr): rename package, test SpawnOrchestrator, parity fixes

Address review feedback on PR #96:
- Rename internal/httpd/errors → internal/httpd/apierr (package apierr) so
  importers no longer alias around the stdlib errors package.
- Add a commander seam to session.Service and unit-test the relocated
  clean-orchestrator rule: clean=true kills all active orchestrators before
  spawning; clean=false spawns without kills.
- project.Add: wrap the UpsertProject store error in apierr.Internal for parity
  with its sibling paths (was a raw 500).
- Document that KindInternal is iota's zero value, so a zero-value Error
  defaults to 500.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:09:02 +05:30
Harshit Singh Bhandari 210c9df758
feat(cli): enrich ao doctor (#90) (#99)
* feat(cli): enrich ao doctor

* fix(cli): address doctor review feedback
2026-06-03 16:50:54 +05:30
Harshit Singh Bhandari bab0d2d167
feat: add light backend CLI commands (#98) 2026-06-03 16:18:00 +05:30
Harshit Singh Bhandari 010b422bb5
feat(cli): add ao session ls/get/kill/restore (#90) (#92)
* feat(cli): add session commands

* test(cli): cover session json output

* chore(cli): trim unused session response fields
2026-06-03 04:50:45 +05:30
Harshit Singh Bhandari ae9fa0e341
feat(cli): add ao project ls/get/rm (#90) (#91)
* feat(cli): add project ls get rm

* fix(cli): satisfy project confirmation lint

* chore(ci): remove agent-ci dockerfile

* test(cli): cover project json output

* fix(cli): label project agent as default harness
2026-06-03 04:46:55 +05:30
Harshit Singh Bhandari 9058017439
fix: prefix ao send messages with sender session (#85)
* fix: prefix ao send messages with sender session

* feat: add orchestrator-aware spawn prompts

* fix(session): return first active orchestrator
2026-06-02 21:39:21 +05:30
Harshit Singh Bhandari 5435246c9a
feat(cli): add minimal ao send (#83)
* feat(messenger): ao send + live zellij pane ping (live agent nudges)

Replace the daemon's noopMessenger stub with a composite AgentMessenger
that writes a durable inbox file (primary) and types a live pointer into
the running zellij pane (best-effort secondary), plus the `ao send` CLI
that drives the existing POST /api/v1/sessions/{id}/send route.

- composite: fans Send to inbox then panep, pinning one timestamp so both
  derive the same filename; a secondary failure is logged at WARN and
  swallowed (the file is on disk), a primary failure aborts the call.
- inbox: writes <workspace>/.ao/inbox/<rfc3339nano>_<hash>.md.
- panep: types "new message at .ao/inbox/<file>" + Enter via a new narrow
  zellij WriteChars seam (RuntimePaneWriter), kept off ports.Runtime.
- wiring: newSessionMessenger composes inbox+panep over the shared store;
  startSession takes the messenger instead of the noop stub.

Carries across @aa-43's work from PR #74 (staging), adapted to main's
post-#65/#77 daemon wiring shape.

Closes #79

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

* fix(inbox): use O_EXCL so a filename collision errors instead of clobbering

os.WriteFile opens with O_CREATE|O_WRONLY|O_TRUNC, which silently overwrites
an existing file. The doc comment already stated the intent ("we do not retry
on EEXIST"), but O_TRUNC never yields EEXIST — two identical messages sent on
the same composite-pinned nanosecond would produce the same filename and the
second Send would silently lose the first message. Switch to
O_CREATE|O_EXCL|O_WRONLY so a collision surfaces as an error; O_EXCL also
refuses to follow a symlink at the final path component. Add a regression test.

Addresses greptile review on PR #83.

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

* fix(inbox): remove the freshly-created file when write or close fails

The O_EXCL switch creates the inbox file before writing its body; if
WriteString or Close then fails, the empty/partial .md was left on disk and
the agent's next inbox scan would pick up a truncated ghost message. Remove
the file on those error paths. O_EXCL guarantees the file did not exist before
this call, so the cleanup can only delete our own partial write, never a
legitimate earlier message.

Addresses greptile review on PR #83.

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

* fix(messenger): reduce ao send to live pane delivery

* fix(send): preserve messages and map lookup errors

* fix(send): reject terminated sessions
2026-06-02 20:02:47 +05:30
yyovil 57bb63701d
Add `ao spawn` + `ao project add` (spawn a real worker end-to-end) (#77)
* Add `ao spawn` and `ao project add`; resolve project repos for worktrees

Make a registered project spawnable end-to-end from the CLI:

- DB-backed RepoResolver: the daemon resolves a project's on-disk repo
  path from the projects table (replacing the empty StaticRepoResolver
  that failed every lookup), so a session's worktree is cut from the
  right repo.
- session_manager defaults an empty spawn branch to ao/<session-id> — a
  fresh, unique branch per session, since gitworktree can't reuse a
  branch already checked out elsewhere (e.g. main).
- `ao project add --path <repo>`: register a local git repo (POST /api/v1/projects).
- `ao spawn --project <id> [--harness] [--branch] [--prompt] [--issue]`:
  spawn a worker session (POST /api/v1/sessions); harness defaults to the
  daemon's AO_AGENT.
- Shared postJSON daemon client (reads the run-file for the port, surfaces
  the API error envelope).

Stacked on #65, which lands the agent-adapter + session-manager wiring
this depends on.

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

* Address Copilot review on #77

- `ao spawn` no longer prints a branch the sessions API doesn't return
  (session metadata is json:"-"), so the output is no longer misleading.
- Unregistered/archived/no-path projects now surface a 400
  PROJECT_NOT_RESOLVABLE with an actionable message instead of a generic
  500: a new sessionmanager.ErrProjectNotResolvable sentinel the resolver
  wraps and writeSessionError maps.
- postJSON reuses the injected Deps.HTTPClient (cloned, with a longer
  timeout) instead of a fresh client, keeping HTTP behaviour stubbable.
- postJSON treats a stale run-file (dead PID) as "not running" via
  ProcessAlive, matching its docstring.

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

* Assert the project-not-resolvable sentinel in the resolver test

Greptile review: harden TestProjectRepoResolver to verify the unregistered
-project error wraps ErrProjectNotResolvable, so a future regression in the
sentinel wrapping (which the HTTP 400 mapping relies on) is caught.

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

* Fix ao spawn 500 on long session ids (zellij socket-path overflow)

Root cause: the daemon built the zellij runtime with an empty SocketDir,
so zellij fell back to its $TMPDIR-based default (long on macOS). That
left almost none of the ~103-byte unix-socket-path budget for the session
name, so a long session id (e.g. "aoagents-agent-orchestrator-1", derived
from a long project id) was rejected by zellij with "session name must be
less than 0 characters". runtime.Create failed, the spawn 500'd, and the
worktree was rolled back (leaving an orphan ao/ branch).

- New zellij.DefaultSocketDir(): a short, stable per-user socket dir
  (/tmp/ao-zellij-<uid>); the daemon uses it (and MkdirAll's it).
- ao spawn's attach hint now prefixes ZELLIJ_SOCKET_DIR so it stays
  copy-pasteable against the daemon's socket dir.
- Regression test guards that the socket dir leaves >= 48 bytes for the
  session name within the 103-byte limit.

Verified: ao spawn against a long-id project now succeeds (session live,
worktree created) where it previously 500'd.

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

* test(cli): guard CLI/daemon DTO drift with an e2e round-trip

The CLI keeps its own request structs (spawnRequest, addProjectRequest)
separate from the daemon's canonical DTOs (controllers.SpawnSessionRequest,
project.AddInput). Nothing verified the JSON field names agreed, so a renamed
tag on either side would compile but break at runtime.

Drive `ao spawn` and `ao project add` through the real httpd router and
controllers (fakes only at the service layer) over a real loopback round trip
via postJSON, asserting each field decodes into the right SpawnConfig/AddInput
field. Runs in the normal test lane (no extra ports/processes).

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

* fix(cli,daemon): address review findings on ao spawn

- spawn: print the sanitised zellij session name (zellij.SessionName) in the
  attach hint; a long/non-conforming session id is registered under a different
  name, so the raw id sent users to a missing session.
- client: surface the daemon error envelope's requestId so a failed command can
  be correlated with daemon logs.
- daemon: don't swallow the zellij socket-dir MkdirAll error — log it, since a
  failure otherwise surfaces later as an opaque socket-bind error on every spawn.
- project: reject an embedded ".." in a project id up front; it passed the id
  pattern but yielded an invalid branch (ao/a..b-1) and an opaque 500 at spawn.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: harshitsinghbhandari <24b4506@iitb.ac.in>
2026-06-02 18:39:13 +05:30
prateek a34094e7d8
refactor: simplify session lifecycle and zellij runtime (#62)
* refactor: remove canonical lifecycle state

* refactor: move sqlite stores into subpackage (#62)

* refactor: strengthen sqlite generated model types (#62)

* refactor: remove lifecycle notifications (#62)

* docs: remove notification cleanup leftovers (#62)

* refactor: narrow lifecycle manager scope (#62)

* refactor: keep PR nudges in lifecycle (#62)

* refactor: trim unused storage and lifecycle contracts (#62)

* refactor: align storage and runtime observation surfaces (#62)

* refactor: remove stale daemon and adapter bloat (#62)

* test: fix terminal ring race assertion (#62)

* refactor: trim lifecycle and http boilerplate (#62)

* refactor: expose sqlite CDC source directly (#62)

* refactor: share process liveness checks (#62)

* test: trim lifecycle store fake surface (#62)

* refactor: separate PR observations from storage rows (#62)

* refactor: trim remaining cleanup surfaces (#62)

* refactor: narrow observation and PR display APIs (#62)

* refactor: move PR write DTOs out of domain (#62)

* refactor: normalize PR domain storage types (#62)

* refactor: remove unused session port interface (#62)

* fix: reject unexpected CLI arguments (#62)

* refactor: use session metadata for spawn completion (#62)

* refactor: narrow session runtime dependency (#62)

* fix: validate zellij version in doctor (#62)

* refactor: split observation port DTOs (#62)

* chore: add sqlc generation script (#62)

* refactor: clarify terminal mux naming (#62)

* fix: tolerate nil loggers (#62)

---------

Co-authored-by: itrytoohard <ayetrytoohard@gmail.com>
2026-06-01 08:42:49 +05:30
prateek 8df074b1c9 chore(backend): add golangci-lint with a strong ruleset and clear the tree
Introduces backend/.golangci.yml (27 linters across correctness, dead-code/
boilerplate, style, and security), wires it into CI as a blocking job, and
fixes every finding so the tree starts at zero.

Config:
- 27 linters: errcheck, govet, staticcheck, errorlint, bodyclose,
  sqlclosecheck, rowserrcheck, nilerr, makezero, unused, unparam, unconvert,
  wastedassign, copyloopvar, prealloc, dupl, revive (incl. exported-symbol doc
  comments), gocritic, misspell, usestdlibvars, predeclared, nakedret, gosec, …
- Tuned for signal over noise: govet/shadow and gocritic hugeParam/rangeValCopy/
  unnamedResult disabled (idiomatic-Go false positives); sqlc-generated code and
  tests get scoped exclusions; gosec G304 excluded (paths are config/run-file/
  worktree-derived, not user input); nilerr excluded in cli/status.go (probe
  failures are the reported status, not a command error).

CI:
- New blocking lint job (golangci-lint-action, latest binary for Go-version
  compatibility).
- go-version now read from go.mod (was pinned 1.22 while go.mod declares 1.25).

Cleanup to reach zero (no behavior change):
- errcheck: wrap deferred/inline Close()/Remove()/Rollback() with `_ =`.
- gosec: tighten dir/file perms (0755->0750, 0644->0600).
- unparam: drop always-nil error return from startLifecycle; drop unused
  shellPath param (zellij PowerShell) and always-500 fallbackStatus param
  (writeProjectError).
- gocritic: regexp \d, s != "", switch->if, combined appends.
- revive: doc comments on all exported symbols; rename project.ProjectRow ->
  project.Row (stutter); rename `max` locals shadowing the builtin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 04:58:41 +05:30
itrytoohard c0bf99eb22 test(cli): port the E2E suite to cross-platform Go; slim the Docker harness
Replaces the growing bash smoke test with a Go os/exec suite behind the `e2e`
build tag (backend/internal/cli/e2e_test.go). It builds the real binary and
drives start/status/doctor/stop + the daemon-control HTTP surface against
isolated state (temp dir + OS-assigned free port), and now runs natively on
ubuntu + macOS + WINDOWS in CI — finally covering the Windows
CREATE_NEW_PROCESS_GROUP detach path and per-OS os.UserConfigDir resolution
that a Linux container can't observe. `go test -tags e2e -v` logs every command
and its output, replacing the bash -v flag.

- backend/internal/cli/e2e_test.go: 8 table-style TestE2E_* cases; strips any
  inherited AO_* env so a real daemon's AO_PORT can't leak in.
- test/cli/install-check.sh: small, linear fresh-install proof the Dockerfile
  runs (binary on PATH, no toolchain) — kept as the hardening tier.
- test/cli/Dockerfile: run install-check.sh instead of the full bash suite.
- .github/workflows/cli-e2e.yml: `native` is now a go test matrix over
  ubuntu+macos+windows; `container` builds the image and runs it with --init.
- Removes test/cli/smoke.sh and test/cli/run-local.sh (superseded by `go test`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 03:14:08 +05:30
itrytoohard e6661e3f3b test(cli): pin ExitCode mapping (usage=2, runtime=1, nil=0)
Closes the one nit from the regression audit: the exit-code wiring was correct
and covered end-to-end by the smoke test, but not pinned by a unit test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 02:38:25 +05:30
itrytoohard 2d00e4675d fix(cli): harden daemon control surface and stop CLI from writing the store
Addresses review findings on PR #53 (on top of the rebase onto main).

- doctor: stop opening/migrating SQLite. The daemon is the sole store
  writer/migrator (architecture.md §7); the CLI must not run migrations or
  open a second writer against a DB a live daemon owns. doctor now reports
  database-file presence and gains --json.
- stop: only remove running.json when it still belongs to the PID we
  stopped, so a concurrent `ao start` that wrote a new run-file is not
  clobbered into looking stopped.
- httpd: gate POST /shutdown to loopback callers with no Origin header,
  closing the CSRF / DNS-rebinding vector against an unauthenticated,
  state-changing endpoint.
- start: detach the spawned daemon into its own session/process group so a
  Ctrl-C while `ao start` waits for readiness doesn't also kill it.
- cli: exit 2 for usage errors (bad flag / arg count) vs 1 for runtime
  failures.
- daemon: unexport newLogger (only used in-package).
- tests: /shutdown guard (cross-origin + rebinding) and stop run-file
  ownership guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 01:55:14 +05:30
Dhruv Sharma 2f4662b470 fix(cli): handle stale start and graceful shutdown 2026-06-01 01:45:59 +05:30
Dhruv Sharma a614462d38 fix(cli): preserve live daemon state on probe failures 2026-06-01 01:45:04 +05:30
Dhruv Sharma 925e70763d fix(cli): verify daemon ownership before stop signal 2026-06-01 01:45:04 +05:30
Dhruv Sharma f72facb9e5 fix(cli): address greptile review comments 2026-06-01 01:44:43 +05:30
Dhruv Sharma 4671d27307 feat(backend): add cobra cli foundation 2026-06-01 01:44:43 +05:30