Skip to content

Reverbcode port overlay#2166

Merged
AgentWrapper merged 278 commits into
mainfrom
reverbcode-port-overlay
Jun 24, 2026
Merged

Reverbcode port overlay#2166
AgentWrapper merged 278 commits into
mainfrom
reverbcode-port-overlay

Conversation

@harshitsinghbhandari

Copy link
Copy Markdown
Collaborator

No description provided.

harshitsinghbhandari and others added 30 commits May 27, 2026 14:24
fix(session): harden teardown/restore safety + drop dead reaction flag
feat(backend): Lifecycle Manager + Session Manager lane
Add git-worktree Workspace adapter
feat: handle draft PR lifecycle state
fix: handle SCM observer seam facts
* feat(backend): HTTP daemon skeleton — config, health, runfile, graceful shutdown (#10)

Phase 1a of the Go HTTP daemon lane (#10). Stands up the loopback-only
sidecar skeleton the later REST/SSE/WS/static surfaces build on:

- config: env-driven (AO_HOST/PORT/ENV/timeouts/run-file) with zero-config
  defaults; binds 127.0.0.1:3001; validates and fails fast on bad input.
- httpd: chi router with the recoverer → request-id → logger → real-ip
  middleware stack and /healthz + /readyz probes. Per-request timeout is
  carried in config but intentionally not global — it scopes to /api/v1 in
  Phase 1b so it never throttles SSE/WS/health.
- runfile: atomic PID + port handshake (running.json) for the Electron
  supervisor, with a dead-PID stale check so a crashed predecessor doesn't
  block startup while a live one fails fast.
- server: bind-before-publish (port conflict fails fast), graceful shutdown
  on SIGINT/SIGTERM via signal.NotifyContext with a 10s hard timeout, and
  run-file cleanup on exit.

Why: the daemon must be safely supervisable as a child process — the
supervisor needs a discoverable PID/port and the daemon must not leave a
half-started process or stale handshake behind. Locking the lifecycle down
now keeps the future port split a small change rather than a rewrite.

Tests cover config defaults/overrides/validation, run-file round-trip and
live/dead PID detection, health probes, full Run lifecycle, and port-conflict
fail-fast.

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

* refactor(backend): drop Env config field — not needed yet (#10)

Per review on #14: AO_ENV / Config.Env / IsProduction() weren't load-bearing
for Phase 1a — they only switched the slog handler. Removing them now keeps
the surface minimal; the env knob can come back later when a real consumer
needs it.

- config: remove Env field, AO_ENV parsing, and IsProduction helper.
- main: collapse newLogger to a single text-handler path.
- httpd: drop the env field from the listening log line.
- tests: drop the env assertions and AO_ENV fixture.

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

* docs: add backend run + config quick-start to README (#10)

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

* fix(backend): address Phase 1a review comments (#10)

- config: drop AO_HOST entirely — the daemon is loopback-only by design,
  so making the bind host env-configurable was a security footgun
- config: use net.JoinHostPort in Addr() so IPv6 literals stay valid
- config: reject zero/negative AO_REQUEST_TIMEOUT and AO_SHUTDOWN_TIMEOUT
  (time.ParseDuration accepts both; either would silently break the
  daemon — instant request expiry / no graceful drain)
- runfile: split processAlive into unix/windows build-tagged files so
  liveness detection is reliable on both platforms (Windows uses
  OpenProcess; POSIX keeps signal 0)
- runfile: document os.Rename overwrite semantics (atomic on POSIX,
  REPLACE_EXISTING on Windows) so the temp-then-rename pattern's
  cross-platform behaviour is explicit
- httpd tests: give probe/waitForHealth clients an explicit per-request
  timeout so a stalled connect can't hang the test on the outer deadline

* fix(backend): strip trailing blank line from runfile.go (#10)

gofmt CI was failing because removing the orphan processAlive doc
comment left an extra newline at EOF.

* fix(backend): cross-platform run-file replace + AO_HOST rationale (#10)

- runfile: introduce build-tagged atomicReplace — POSIX rename(2) on
  Unix, MoveFileEx with MOVEFILE_REPLACE_EXISTING on Windows. The Go
  runtime happens to do the Windows call internally already, but
  invoking it directly makes the cross-platform contract explicit
  instead of a runtime implementation detail
- runfile: tighten process_unix.go build tag from `!windows` to `unix`
  so plan9/js/wasm fail to build rather than silently using a broken
  signal-0 probe
- runfile: add TestWriteOverwritesExisting covering the stale run-file
  replace path that none of the previous tests exercised
- config: anchor the loopback-only decision in the LoopbackHost doc so
  the next contributor doesn't reintroduce AO_HOST without the security
  rationale

* fix(backend): route chi access logs through slog/stderr (#10)

chi's middleware.Logger writes via stdlib log to stdout, but the
daemon's slog logger writes to stderr — so REST traffic and daemon
logs landed on different streams in different formats. Replace it
with a small slog-backed requestLogger that:

- Wraps the response writer via middleware.NewWrapResponseWriter so
  status/bytes are accurate even when handlers return without an
  explicit WriteHeader.
- Reads the request id off the context set by middleware.RequestID
  (kept mounted just before this middleware so the id is available).
- Emits one structured Info line per request with method, path,
  status, bytes, duration, and remote — same key=value shape as the
  rest of the daemon, one stream for the Electron supervisor to
  capture.

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Four narrowly-scoped fixes against the LCM + Session Manager lane
from an external review of the current backend state. R2 (failed-restore
lifecycle stranding) is intentionally deferred to PR #15, which already
closes it via the new OnSpawnInitiated path; R3 also stays on that PR.

- R1 (BLOCKER): Manager.Spawn never persisted AgentSessionID, so
  Manager.Restore's hard-required metadata key was always missing and
  every restore failed. Persist the assembled launch prompt as
  MetaPrompt at spawn time and add a fresh-launch fallback to Restore
  that uses Agent.GetLaunchCommand with the seeded prompt when the
  captured agent session id is absent (the id-capture hook is a separate
  path that may never have run). Restore still fails fast when neither
  the id nor a prompt is on hand — there is nothing to relaunch from.

- RA (BLOCKER): adapters/workspace/gitworktree/commands.go's
  worktreeRemoveForceArgs passed --force, which deletes uncommitted
  agent work. Renamed to worktreeRemoveArgs and dropped --force so the
  post-prune "still registered" guard in Workspace.Destroy surfaces the
  refusal to Manager.Cleanup, which routes the session to Skipped
  instead of destroying in-progress changes.

- R11 (SHOULD-FIX): reactions.go's two Notifier.Notify call sites
  (executeReaction's notify and escalate) built OrchestratorEvent
  without ProjectID. Captured projectID on the transition (via a
  store.Get in mutate) and on reactionTracker (so TickEscalations can
  still populate it on duration-based escalations), and threaded it
  through executeReaction/sendToAgent/escalate.

- RB (SHOULD-FIX): gitworktree.Workspace.managedPath used filepath.Join
  which cleans .. segments before validateManagedPath ran, so
  session=\"../other\" stayed inside managedRoot while breaking
  per-project isolation. validateConfig now rejects path separators and
  the . / .. components on ProjectID and SessionID at the source.

go build ./..., go vet ./..., and go test -race ./... all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix: address LCM/SM review blockers R1, RA, R11, RB
)

The reaper sits OUTSIDE the LCM's per-session serial loop. On every tick it:
1. Fires lcm.TickEscalations(now) — the duration-based escalation heartbeat
   a non-polling LCM cannot wake itself to drive.
2. Asks lcm.RunningSessions for the snapshot of sessions whose runtime axis is
   alive, then calls runtime.IsAlive(handle) per session via a RuntimeRegistry
   that dispatches by RuntimeHandle.RuntimeName (so a single reaper covers
   tmux + zellij side by side).
3. Reports any non-alive result back as a fact via ApplyRuntimeObservation —
   dead -> RuntimeProbeDead, probe error -> RuntimeProbeFailed (never
   collapsed to alive: failed probe ≠ dead, but it ≠ alive either). Steady-
   state alive is skipped so we don't churn the LCM with no-op load/diff work.

The reaper REPORTS facts; the LCM owns DECIDE (anti-flap Detecting quarantine,
terminal-session rules). The reaper never writes.

Open-question resolution: add RunningSessions(ctx) to ports.LifecycleManager
(option a). The Manager implements it via an injectable session lister
(Manager.WithSessionLister) so the LCM itself does not require a new
LifecycleStore method — Tom's store contract is untouched, daemon wiring (lane
#10) will inject the production lister at startup.

Scope: reaper goroutine + the minimum LCM seam. No activity ingest, no FS
watcher, no daemon wiring, no new schema fields, no store changes.
Address blocker found in self-review (B1 + I1):

- Manager.RunningSessions previously filtered to runtime.State == RuntimeAlive,
  but a session enters Detecting with runtime axis = RuntimeProbeFailed (failed
  probe path, decide_bridge.go:72) or RuntimeMissing (detectingLC in
  manager_test.go:539). The filter silently parked every Detecting session, so
  the recovery path proved by manager_test.go:59 ("healthy probe recovers
  liveness-owned detecting -> working") and the terminal path proved by
  manager_test.go:79 ("dead+dead with no recent activity concludes killed")
  were both unreachable through the reaper. Broaden the predicate to "session
  is not in a terminal state" (mirrors the LCM's existing isTerminal helper)
  and document the wider semantics.

- reaper.probeOne now reports every probe result — including alive — back to
  the LCM as ApplyRuntimeObservation facts. The previous skip-alive
  optimization was a layering violation: the reaper has no business deciding
  what counts as a no-op. The LCM's ApplyRuntimeObservation already diffs
  against canonical and only Upserts on actual change, so steady-state alive
  stays cheap. With the broadened poll set, an alive probe for a Detecting
  session IS the recovery fact.

- Add unit tests for Manager.RunningSessions covering: nil-lister no-op, lister
  error propagation, and the full canonical state matrix (working/idle/
  needs_input/detecting-probefailed/detecting-missing/not_started included;
  terminated/done excluded).

- Update reaper tests: alive case now asserts the alive fact is reported; new
  "detecting session: alive probe reported so LCM can recover from quarantine"
  case locks in the recovery path; multi-runtime case now asserts both runtime
  facts flow through.

- Bump "session in poll set without handle metadata" log from Debug to Warn —
  it is an anomaly (OnSpawnCompleted should have written both keys), not a
  routine event.

- Document WithSessionLister must be called before any reaper attached to the
  Manager starts running (it is a bare field read; concurrent re-injection is
  meaningless anyway).
feat(observe): reaper for liveness probe + TickEscalations heartbeat (#9)
laxmanclo and others added 25 commits June 22, 2026 02:26
* 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>
* 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>
#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>
… 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>
* 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
…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>
* fix(frontend): normalize terminal shortcuts

* fix(frontend): preserve ctrl-c interrupt outside windows
…rigger

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
#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>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* 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>
Replaces linux/windows/macos-testing-build.yml with a single testing-build.yml
(matrix over ubuntu/windows/macos) that publishes all artifacts to one
0.0.0-testing-<sha> prerelease. Manual dispatch + 0.0.0-testing-* tag push.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
* feat(cli): add examples to ao preview --help (#387)

Update the preview command help to include a concise Examples section
covering the four common workflows:

  ao preview
  ao preview file:///home/aoagent/ReverbCode/index.html
  ao preview http://localhost:5173
  ao preview clear

Replace the workspace-relative path (./dist/index.html) in the Long
description with the absolute file:// URL pattern agents actually use.

Command syntax and behavior are unchanged.

Closes #387

* fix(cli): clarify no-arg preview fallback behavior in help text

Address review feedback on #388: the no-argument description now
reflects the daemon's actual behavior — autodetect the workspace
entry point, fall back to the session's existing preview target
when none exists.

---------

Co-authored-by: AO Bot <ao-bot@composio.dev>
…entials (#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>
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>
…indows 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>
* 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>
* fix(desktop): package Windows via NSIS instead of Squirrel (#401)

Squirrel.Windows is a poor fit: per-user install only, no custom install
directory, no proper add/remove-programs uninstaller, and fragile updates.
Replace it with a real NSIS installer (per-user or per-machine, custom
install dir, uninstaller), matching recordly's working Windows setup.

Electron Forge ships no first-party NSIS maker, so add a thin MakerBase
subclass (makers/maker-nsis.ts) that bridges to electron-builder's
buildForge, the same engine electron-builder uses, scoped to win32. The
maker exposes the NSIS knobs the issue calls for (oneClick:false,
allowToChangeInstallationDirectory, per-machine) and defaults to an
assisted installer.

- forge.config.ts: drop maker-squirrel, add the NSIS maker instance.
- testing-build.yml: target "nsis"; smoke-install via /S under out/make;
  drop the Squirrel-specific log capture.
- Rename the package "agent-orchestrator-frontend" -> "agent-orchestrator":
  this repo is the full app, not just a frontend. User-facing naming was
  already "Agent Orchestrator" (productName) / agent-orchestrator.exe.

Deferred (per issue, separate follow-ups): bundling zellij.exe so a fresh
Windows install needs no manual zellij, an actionable "zellij not found"
error at session-create, and Windows code-signing.

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

* chore: format with prettier [skip ci]

* fix(desktop): disable electron-builder publish + drop electron-squirrel-startup

CI fix: the NSIS maker's electron-builder run inferred a GitHub publish
target from package.json `repository` and tried to upload (to emit
auto-update info), failing with "GitHub Personal Access Token is not set".
Forge owns publishing (the workflow uploads via `gh release`), so set
`config.publish = null` to disable electron-builder's upload entirely.

Also remove `electron-squirrel-startup`: it only handled Squirrel.Windows
install/update hooks (--squirrel-* flags) and is dead weight under NSIS.
Drop the dependency, its import, the startup quit-block, the whenReady
guard, and the type shim. The EPIPE std-stream guard stays (it covers any
windowless Windows GUI launch, e.g. from a shortcut).

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

* chore: gitignore electron-builder's builder-debug.yml dump

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>
Joins the ground-up ReverbCode rewrite (this tree) with the prior
agent-orchestrator history as a second parent, preserving both lineages.
Tree content is ReverbCode's; old history remains reachable in main.
@github-actions

Copy link
Copy Markdown
Contributor

React Doctor found 51 issues in 17 files · 51 warnings · score 53 / 100 (Critical) · vs main

51 warnings

components/LandingAbout.tsx

  • ⚠️ L20 Plain anchor reloads internal Next.js links nextjs-no-a-element

components/LandingAgentsBar.tsx

  • ⚠️ L38 Plain img ships unoptimized images nextjs-no-img-element

components/LandingCTA.tsx

  • ⚠️ L15 Plain anchor reloads internal Next.js links nextjs-no-a-element

components/LandingFeatures.tsx

  • ⚠️ L68 State initialized from a mount effect no-initialize-state
  • ⚠️ L81 Repeated inline style writes js-batch-dom-css
  • ⚠️ L99 Repeated inline style writes js-batch-dom-css
  • ⚠️ L169 Static value rebuilt every render prefer-module-scope-static-value
  • ⚠️ L217 Static value rebuilt every render prefer-module-scope-static-value
  • ⚠️ L319 Static value rebuilt every render prefer-module-scope-static-value
  • ⚠️ L362 Static value rebuilt every render prefer-module-scope-static-value
  • ⚠️ L372 Missing effect dependencies exhaustive-deps
  • ⚠️ L381 Array index used as a key no-array-index-as-key
  • ⚠️ L388 Array index used as a key no-array-index-as-key
  • ⚠️ L428 Static value rebuilt every render prefer-module-scope-static-value
  • ⚠️ L444 Chained array iterations js-combine-iterations

components/LandingHero.tsx

  • ⚠️ L26 Plain anchor reloads internal Next.js links nextjs-no-a-element
  • ⚠️ L63 Plain img ships unoptimized images nextjs-no-img-element
  • ⚠️ L83 Large inline style object rebuilds every render no-inline-exhaustive-style

components/LandingHowItWorks.tsx

  • ⚠️ L44 State initialized from a mount effect no-initialize-state
  • ⚠️ L49 Multiple setState calls in one effect no-cascading-set-state
  • ⚠️ L69 Missing effect dependencies exhaustive-deps
  • ⚠️ L98 Role used instead of HTML tag prefer-tag-over-role
  • ⚠️ L159 Animating layout properties no-layout-transition-inline

components/LandingNav.tsx

  • ⚠️ L33 Anchor used as a button anchor-is-valid

components/LandingQuickStart.tsx

  • ⚠️ L43 Plain anchor reloads internal Next.js links nextjs-no-a-element

components/LandingTestimonials.tsx

  • ⚠️ L33 State only used in handlers rerender-state-only-in-handlers
  • ⚠️ L49 Missing effect dependencies exhaustive-deps
  • ⚠️ L87 Button missing explicit type button-has-type
  • ⚠️ L93 Large inline style object rebuilds every render no-inline-exhaustive-style
  • ⚠️ L101 Animating layout properties no-layout-transition-inline
  • ⚠️ L106 Plain img ships unoptimized images nextjs-no-img-element

components/LandingUseCases.tsx

  • ⚠️ L105 Repeated inline style writes js-batch-dom-css
  • ⚠️ L177 Large inline style object rebuilds every render no-inline-exhaustive-style

components/LandingVideo.tsx

  • ⚠️ L10 iframe missing sandbox attribute iframe-missing-sandbox

components/LandingWorkflow.tsx

  • ⚠️ L70 Many related useState calls prefer-useReducer
  • ⚠️ L72 State only used in handlers rerender-state-only-in-handlers
  • ⚠️ L74 State only used in handlers rerender-state-only-in-handlers
  • ⚠️ L75 State only used in handlers rerender-state-only-in-handlers
  • ⚠️ L92 State initialized from a mount effect no-initialize-state
  • ⚠️ L203 Tabindex on non-interactive element no-noninteractive-tabindex
  • ⚠️ L204 Role used instead of HTML tag prefer-tag-over-role
  • ⚠️ L260 Chained array iterations js-combine-iterations
  • ⚠️ L296 Static value rebuilt every render prefer-module-scope-static-value

components/PageConstellation.tsx

  • ⚠️ L148 aria-hidden on focusable element no-aria-hidden-on-focusable

components/docs/Logo.tsx

  • ⚠️ L66 Plain img ships unoptimized images nextjs-no-img-element
  • ⚠️ L82 Large inline style object rebuilds every render no-inline-exhaustive-style

components/docs/PlatformSupport.tsx

  • ⚠️ L34 Large inline style object rebuilds every render no-inline-exhaustive-style

components/docs/PluginCard.tsx

  • ⚠️ L21 Large inline style object rebuilds every render no-inline-exhaustive-style
  • ⚠️ L35 Large inline style object rebuilds every render no-inline-exhaustive-style

components/docs/mdx-components.tsx

  • ⚠️ L12 Non-component export in component file only-export-components

1 more warning not shown.

Reviewed by React Doctor for commit 7a1b303. See inline comments for fixes.

Comment thread frontend/src/landing/components/LandingAbout.tsx
Comment thread frontend/src/landing/components/LandingAgentsBar.tsx
Comment thread frontend/src/landing/components/LandingCTA.tsx
Comment thread frontend/src/landing/components/LandingFeatures.tsx
Comment thread frontend/src/landing/components/LandingFeatures.tsx
Comment thread frontend/src/landing/components/docs/Logo.tsx
Comment thread frontend/src/landing/components/docs/PlatformSupport.tsx
Comment thread frontend/src/landing/components/docs/PluginCard.tsx
Comment thread frontend/src/landing/components/docs/PluginCard.tsx
Comment thread frontend/src/landing/components/docs/mdx-components.tsx
@AgentWrapper AgentWrapper merged commit 2e12a44 into main Jun 24, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.