Commit Graph

1612 Commits

Author SHA1 Message Date
Adil Shaikh 4557146681
fix: render raw activity state in inspector (#2278)
* fix: render raw session activity in inspector

* fix: hide unknown activity state in inspector
2026-07-04 00:29:32 +05:30
Harshit Singh Bhandari 8cadea5b16
fix(ci): gofmt manager_test.go to unbreak main (#2386)
manager_test.go landed from #2350 with a gofmt violation (extra spaces
aligning a struct field), turning main red: build-test's `gofmt -l .`
and lint's goimports both flag it. #2350's merge push never ran the Go
workflow, so it went unnoticed until the next push surfaced it.

Pure `gofmt -w` output, one whitespace line, no behavior change.
2026-07-03 22:42:27 +05:30
Harshit Singh Bhandari c9cff5f22e
feat(sidebar): nightly channel badge on the wordmark (#2379)
* feat(sidebar): purple nightly badge on the wordmark

Add a small purple pill labeled "nightly" next to the "Agent Orchestrator"
wordmark in the sidebar header. The badge:
- is derived from the running app version string (contains "-nightly."),
  not the update-channel setting, so it reflects the actual binary identity
- is hidden in the collapsed icon rail (group-data-[collapsible=icon]:hidden)
- uses a new --purple CSS token added to both dark (#a78bfa) and light
  (#7c3aed) palette blocks in styles.css, styled via color-mix following
  the existing reviewer-status idiom
- is shrink-0 so it does not crowd the flex-1 truncate wordmark span

Stable builds show no badge. Dev builds ("0.0.0-preview") show no badge.

Part of AgentWrapper/agent-orchestrator#2377.

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-03 22:29:07 +05:30
Harshit Singh Bhandari 6f09a1fc78
fix(frontend): show session display name in terminal toolbar header (#2382)
* fix(frontend): show session display name in terminal toolbar header

The terminal toolbar showed the raw session id. Show the display name
(session.title, same field the sidebar renders) instead, and
'Orchestrator' for orchestrator sessions.

Fixes #2380

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

* test(frontend): cover terminal toolbar session label

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 22:23:57 +05:30
Harshit Singh Bhandari f92ff1111a
feat(release): add important flag to nightly feed manifests (#2378)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 22:23:03 +05:30
Vaibhaav Tiwari 52d8c6051a
docs: add contributing guide (#2385)
Co-authored-by: Vaibhaav <user@example.com>
2026-07-03 22:17:02 +05:30
Vaibhaav Tiwari 365c99f705
docs: add first-timer README overview (#2384)
Co-authored-by: Vaibhaav <user@example.com>
2026-07-03 21:59:51 +05:30
Harshit Singh Bhandari a32d507eb8
feat(scm): detect cross-fork PRs by scanning all project remotes (#2368)
The SCM observer only polled a project's push origin for open PRs, so a
PR opened from the fork against an upstream base repo (the standard
fork -> upstream flow) was never discovered: GitHub lists a PR under its
base repo, not its head, so an origin-only scan can't see it. Sessions
that opened such PRs showed no PR on the dashboard.

Scan every GitHub remote in the project checkout (origin + upstreams /
mirrors) for open PRs, and attribute a PR to a session only when its
head branch lives in that session's push origin. This surfaces
cross-fork PRs while preserving the no-misattribution guarantee: a
stranger's fork PR has a head repo no session owns and is dropped.

Tracked cross-fork PRs are keyed for refresh by their own recorded repo
(the upstream base) so the GraphQL refetch targets the right repo.
2026-07-03 20:50:41 +05:30
Harshit Singh Bhandari 3aa8e044b2
test(session_manager): prove agent sessions survive daemon restart/upgrade (#2335) (#2350)
* test(session_manager): prove sessions survive daemon restart and re-adopt (#2335)

Add an end-to-end Reconcile() durability test covering the full mix of
session states a daemon restart/upgrade leaves behind: an alive
orchestrator and worker are adopted in place under their original ids
(no id increment, runtime never torn down), a worker whose runtime died
with the daemon is captured and relaunched under its original id, and a
truly-dead unmarked session is not resurrected.

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-03 19:59:27 +05:30
Harshit Singh Bhandari 57ba468fea
feat(skills): add using-ao skill, install it under the data dir, and wire it into session prompts (#2365)
* feat(prompt): point sessions at using-ao skill for CLI usage

Append a short standing-prompt pointer to skills/using-ao/SKILL.md so
orchestrator and worker sessions read the skill catalog before using the
ao CLI, instead of inlining the whole command surface.

* docs(skills): add using-ao skill cataloging the ao CLI

Full command catalog sourced verbatim from 'ao --help' recursively:
SKILL.md (top-level catalog), commands/*.md (one per command with every
subcommand, flag, and examples), and references.md (natural-language to
command table).

* feat(skills): install using-ao skill under the data dir on daemon boot

A repo-relative skills/ path only resolves when a worker's project is the
AO repo itself; the ao CLI is used in every session regardless of project.
Move the skill into the Go module, embed it, and clobber-install it into
<dataDir>/skills/using-ao at daemon boot (before any session spawns, so no
locking is needed). The embedded copy is the single source of truth: a new
daemon build always refreshes it, so there is no version marker to drift.

Flip the session-prompt pointer to the absolute installed path so it
resolves from any project's worktree.

* fix(skillassets): tighten install perms to satisfy gosec

golangci-lint gosec flagged G301 (dir perms) and G306 (file perms).
Use 0o750 for dirs and 0o600 for files, matching the repo convention
for daemon-owned single-user state.

* Update preview.md with dev server instructions

Added note about using the correct URL for the dev server.

---------

Co-authored-by: Vaibhaav Tiwari <155460282+Vaibhaav-Tiwari@users.noreply.github.com>
2026-07-03 18:11:15 +05:30
Dushyant Singh Hada 7e409d438b
fix: clear stale preview URL from SQLite and prevent stale white screen on preview clear (#2358)
* fix: remove auto-switch-to-summary effect, keep browser tab on clear

* fix: clear browser preview when session is terminated
2026-07-03 15:38:43 +05:30
VenkataSakethDakuri fadbdd26cd
Fix and enrich the orchestrator coordinator system prompt so it matches the current CLI and teaches harness selection + command discovery (#2361)
* Prompt changed for correct agent spawning

* Fix formatting issue
2026-07-03 01:36:14 +05:30
Harshit Singh Bhandari e56248759e
ci(prettier): make workflow check-only instead of auto-committing (#2356)
Convert the Prettier workflow from auto-formatting and pushing commits
back to the PR branch into a check-only job. It now runs
`prettier@3 --check .`, which annotates and fails on unformatted files
but never writes, commits, or pushes anything. Drops the auto-commit
step and narrows permissions to `contents: read`.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 22:49:29 +05:30
Harshit Singh Bhandari 6186458df7
docs(bug-triage): retarget skill at the Go rewrite and upstream repo (#2339)
* docs(bug-triage): retarget skill at the Go rewrite and upstream repo

Rewrite the bug-triage skill for this repository: file issues/PRs on the
upstream AgentWrapper/agent-orchestrator, swap the Zellij runtime notes for
tmux (ConPTY on Windows), refresh the label list to match upstream, and drop
ReverbCode-specific naming. Port 3001, ~/.ao paths, and the CLI/daemon layout
are unchanged and were re-verified against backend/.

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-02 22:03:13 +05:30
Anirudh Sharma e74fb65a85
feat(review): support additional reviewer harnesses (#2306)
* feat(review): support more reviewer harnesses

* fix(review): preserve reviewer daemon context

* fix(review): allow printf-piped review commands in reviewer allowlists

The review prompt now pipes JSON into `gh api` and `ao review submit`
via `printf '%s' ... | cmd` (heredocs break in interactive PTY panes).
Widen the claude-code allowlist with `Bash(printf:*)` and the opencode
permission policy with `printf * | gh api *` / `printf * | ao review
submit *` so those piped commands pass each tool's allowlist. Cover
both with tests that model each tool's matching semantics.

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-02 19:25:18 +05:30
VenkataSakethDakuri c267420632
feat(projects): default worker and orchestrator agents to claude-code (#2321)
Pre-fill both role agents with claude-code in the create-project dialog and project settings instead of requiring a selection; the required-agent validation gate is removed. Users can still change either agent at creation or later in settings.
2026-07-01 21:56:00 +05:30
Laxman f74ebf379f
docs: add telemetry.md with accurate description of PostHog integration (#2308)
Replaces the inaccurate opt-in framing. Documents that telemetry is on
by default in the renderer, covers session recording, privacy redaction,
the persistent install ID, and how to override or disable via build-time
env vars.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 14:15:45 +05:30
Khushi Diwan 6a7ba46078
fix: set terminal type for tmux attach (#2312)
- Force TERM for tmux attach processes
- Cover missing and unsupported TERM values
2026-07-01 03:27:29 +05:30
Harshit Singh Bhandari d302414f52
feat(sidebar): inline-edit session display name via hover pencil (#2307)
* feat(sidebar): inline-edit session display name via hover pencil

Reuse the merged PATCH /sessions/{id} rename endpoint (#2302). A pencil
reveals on session-row hover/focus; clicking it swaps the label for an
inline input capped at 20 chars (same as spawn --name). Enter/blur saves
and invalidates the workspace query so the rename survives reload; Escape
cancels.

Refs #2301

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

* test(sidebar): cover inline session rename save/cancel/cap

Refs #2301

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-01 03:19:27 +05:30
Harshit Singh Bhandari 60b651f20e
fix(session): clear shutdown-saved marker so killed sessions stay dead (#2319) (#2320)
After Cmd+Q quit + reopen, sessions the user killed reappeared as alive.
The session_worktrees "shutdown-saved" restore marker was write-only:
RestoreAll auto-relaunches any terminated session that still has a marker,
but Kill never deleted it and RestoreAll never consumed it. A session that
survived one reopen cycle (gaining a marker via reconcileLive) and was then
killed still carried the marker, so the next boot resurrected it.

Two-layer fix:
- Kill now deletes the marker (best-effort) after MarkTerminated: a user
  kill is explicit terminal intent and must not leave a resurrection marker.
- RestoreAll deletes the marker after consuming preserveRef and attempting
  relaunch, making it one-shot so it never outlives a single restart. A
  still-live session re-acquires a fresh marker at the next quit.

Manual Restore stays marker-independent, so killed sessions remain
restorable on demand, just not auto-resurrected on boot.

Adds DeleteSessionWorktrees to the Manager Store interface (the concrete
store already implements it) and tests covering all three behaviors.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 03:00:09 +05:30
neversettle 010c7e4c1a
feat: default reviewer is always claude-code (#2241) - temporary change 2026-06-30 21:25:26 +05:30
Apoorv Singh 78cbe22928
fix(new-task): use project default agent and expose all agents in spawn dialog (#2291) 2026-06-30 16:36:57 +05:30
Harshit Singh Bhandari 7c4a77d7cc
feat(spawn): add required --name flag for sidebar display name (#2302)
* feat(spawn): add required --name flag for sidebar display name

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

* feat(spawn): require --name for sidebar label; cap at 20 chars

Add a required --name flag to `ao spawn` that sets the session's
sidebar display name. The CLI rejects a missing or >20-character name
before contacting the daemon.

The daemon's POST /sessions keeps displayName optional (the desktop
new-task dialog omits it and the read model falls back to the session
id) but enforces the same 20-character cap when present, so a direct
API call cannot exceed it. The value flows CLI -> SpawnSessionRequest
-> SpawnConfig -> session record, and the existing read-model fallback
(displayName ?? issueId ?? id) renders it in the sidebar unchanged.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:26:48 +05:30
NIKHIL ACHALE 194bb28c8c
feat(icon): apply runtime app icon on macOS (#2296) 2026-06-30 15:27:50 +05:30
Dushyant Singh Hada de9e90df45
fix: resolve duplicate migration version 20 collision (#2294)
Two PRs (#2193, #2200) merged ~2h apart each added a migration
file numbered 0020, since each branch only saw one such file at
CI time and neither was rebased against the other before merge.
Renumbers 0020_pr_reviews.sql to 0021_pr_reviews.sql.

TestMigrationVersionsAreUnique already existed and is correct;
it didn't catch this because it only runs against each PR's
branch state, not the merged result of both PRs together.
2026-06-30 11:23:54 +05:30
swyam sharma 2c4bf55e95
fix(spawn): fail fast when tmux is missing (#2259)
Co-authored-by: Swyam Sharma <cruzer@Swyams-MacBook-Pro.local>
2026-06-30 00:14:46 +05:30
Adil Shaikh a31cf1b582
fix: deep-link PR attention actions (#2200)
* fix: deep-link PR attention actions

* chore: format with prettier [skip ci]

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-29 21:16:35 +05:30
neversettle 8241868398
feat(review): enforce reviewer read-only via tool allowlist (#2194)
The reviewer's read-only guarantee was enforced only by the prompt. Add
AllowedTools/DisallowedTools to ports.LaunchConfig and plumb them through
the claude-code agent adapter to --allowedTools/--disallowedTools (each list
comma-joined into one value so a rule containing spaces like "Bash(git
diff:*)" is not split into separate tool names). Empty lists emit nothing, so
worker sessions are unaffected.

Launch the reviewer off bypassPermissions (which skips the permission system
and ignores allow/deny rules) in the default auto mode, with an allowlist
scoped to Read/Grep/Glob and the few Bash commands a reviewer needs (gh, git
diff/log/show/status, ao review submit) and an explicit deny list for
Edit/Write/NotebookEdit/git push/git commit as defense in depth.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:27:19 +05:30
neversettle cb456bb288
feat: show multi-PR review status (#2193)
* feat: support multi-pr review runs

* fix: avoid review state status stutter

* feat: batch review delivery by trigger

* test: update review inspector mocks

* chore: fold review batch migration into 0020

* fix: submit multi-pr reviews as one batch

* fix: make queued reviews autonomous

* fix: clarify multi-pr review submit prompt

* feat: show multi-pr review status

* chore: format with prettier [skip ci]

* feat: simplify multi-pr review summary

* chore: format with prettier [skip ci]

* fix: match multi-pr review card design

* chore: format with prettier [skip ci]

* fix: remove review session label

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Vaibhaav <user@example.com>
2026-06-29 18:20:28 +05:30
Vaibhaav Tiwari 3535d12f2c
docs: align readme (#2239)
* docs: align readme

* chore: format with prettier [skip ci]

---------

Co-authored-by: Vaibhaav <user@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-29 16:38:33 +05:30
PRADEEP SAHU d87b763e34
UI : use React.Memo position for SelectContent to prevent scroll reset (#2261)
* fix(ui): use popper position for SelectContent to prevent scroll reset

* fix(ui): use React.memo to prevent scroll resets without breaking popper position
2026-06-29 16:16:58 +05:30
Harshit Singh Bhandari 2cd3b88cfa
fix(ci): Windows publish shell + fresh-install smoke check (#2267) (#2269)
* fix(ci): pin publish step to bash so the retry loop runs on Windows

The 3x retry wrapper added in #2266 is bash syntax, but the release
matrix Publish step inherited the runner default shell, which is
PowerShell on windows-latest. That made every Windows publish fail with
a ParserError. Pin shell: bash on all four publish steps.

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

* fix(ci): point fresh-install smoke check at an empty release repo (#2267)

The container check asserts `ao start` fails cleanly on a fresh box with
no published asset. Now that AgentWrapper publishes a linux-x64 AppImage,
an unpinned smoke binary downloads it and exits 0, tripping the
assertion. Build the smoke binary against a release repo with no assets
so the fetch path deterministically 404s and start exits non-zero with a
clear error, preserving the test's intent without depending on what the
real repo publishes.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 15:44:30 +05:30
Harshit Singh Bhandari 92a52f0992
ci: retry transient macOS sign/notary flakes in the publish step (#2266)
Releases fail intermittently on transient macOS issues that hit EITHER
macOS leg, not just Intel: Apple notary -1009 connection-offline (seen on
macos-latest/arm64) and osx-sign keychain races, code object is not signed
at all (seen on macos-15-intel). Both pass on a manual re-run.

Wrap npm run publish in a 3x retry with backoff in all four release/nightly
publish steps so these self-heal in-place instead of failing the run (and,
for nightly, skipping the whole feed). Keeps the Intel leg coupled so the
x64 feed entry stays guaranteed.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 03:58:19 +05:30
Harshit Singh Bhandari 6b56b1c245
fix(update): enable allowPrerelease on the nightly channel (#2265)
Nightly builds publish as GitHub prereleases. configureFeed set channel
and allowDowngrade but never allowPrerelease, which defaults to false, so
electron-updater only inspected the latest NON-prerelease release
(e.g. v0.10.0) and 404d looking for nightly-mac.yml there. The nightly
channel therefore never resolved, regardless of whether a nightly was
published.

Set allowPrerelease = (channel === nightly): nightly scans prereleases
and finds the nightly feed; stable stays on non-prereleases only.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 03:37:49 +05:30
Harshit Singh Bhandari 965e388792
fix(update): bundle app-update.yml so electron-updater can install (forge omits it) (#2244)
* fix(update): generate app-update.yml at build time; drop runtime setFeedURL

electron-forge does not emit app-update.yml; electron-updater requires it at
process.resourcesPath to resolve the GitHub release feed. Without it every
packaged app threw ENOENT on the first download attempt, making updates
detected but never installed.

Two-part fix:
- forge.config.ts: add postPackage hook that writes app-update.yml into
  each platform's Resources dir (baked from AO_RELEASE_REPO so fork builds
  point at the fork, prod at AgentWrapper/agent-orchestrator).
- auto-updater.ts: remove setFeedURL + repo() + DEFAULT_RELEASE_REPO;
  configureFeed now only sets channel + allowDowngrade. electron-updater
  auto-loads the bundled yml on the first checkForUpdates call.

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

* chore: format with prettier [skip ci]

* fix(update): generate app-update.yml before signing, not after

The postPackage hook wrote app-update.yml into Contents/Resources AFTER osxSign
sealed the bundle, so the added file was unsealed and macOS reported the app as
"damaged" (codesign: "a sealed resource is missing or invalid"). Generate it in
a prePackage hook and ship it via packagerConfig.extraResource, so it is copied
into the bundle and signed as part of the seal. The generated app-update.yml is
gitignored.

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

* chore: format with prettier [skip ci]

* ci(release): move Intel leg to macos-15-intel and gate the feed on it

macos-13 is deprecated and has no runner capacity (multi-hour queues),
so the detached release-intel leg never completed. Switch it to the
supported macos-15-intel image in both the release and nightly workflows.

Now that the Intel leg gets a runner and builds + signs the x64 installer
reliably (verified on fork v0.10.10), re-couple it into publish-feed
(needs: [release, release-intel]) so latest-mac.yml / nightly-mac.yml
always carry the x64 entry and Intel macOS users receive auto-updates.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-28 23:36:06 +05:30
Harshit Singh Bhandari ff23e66055
chore(release): stamp desktop app version 0.10.0 (#2248)
Stable release builds read the version from frontend/package.json (the
release workflow does not stamp from the git tag, unlike nightly), so the
0.10.0 prod cut needs this committed at the tagged commit. Otherwise the
app ships as 0.0.0 and the stable update channel never advances.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 23:24:57 +05:30
Harshit Singh Bhandari 2c377dcb7e
ci(release): gate signed release on the release environment (#2249)
The Desktop release fires on any desktop-v* tag push and consumes the
repo-level signing secrets, so any write-access collaborator can cut a
signed prod build. Reference the `release` environment from the build
jobs so a designated reviewer must approve before the secrets are used.
Takes effect once required reviewers are configured on that environment.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 23:24:43 +05:30
Harshit Singh Bhandari 01406eddca
fix(ci): decouple Intel runner from publish-feed (#2237) (#2240)
Remove macos-13 from the release matrix in both frontend-release.yml and
frontend-nightly.yml, replacing it with a standalone release-intel job
that runs on macos-13 independently. publish-feed keeps needs: release
(the three fast legs), so it no longer waits hours for the scarce Intel
runner. The darwin-x64 installer and stable alias are still built and
uploaded; feed.mjs includes the x64 entry on a best-effort basis.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:10:23 +05:30
Harshit Singh Bhandari cfe505eb4d
feat: Global Settings (migration + update channel) + ao start download progress (#2235)
* feat(settings): IPC bridge for global update settings (get/set)

Expose readUpdateSettings/writeUpdateSettings to the renderer via
updateSettings:get/set so the Global Settings page can read and persist
the auto-update channel choice. Refs #2207.

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

* feat(cli): show ao start download + install progress

The fetch path streamed a hundreds-of-MB asset and ran a silent install
with zero output, so a slow download looked like a hang. Print a start
line (asset, ~size, repo), stream a TTY-aware progress reader (live \r
percentage on a terminal, start+done lines off a TTY), and announce the
otherwise-silent unpack/install step. stderr only, so --json stdout stays
clean; no new dependency. Closes #2234.

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

* feat(settings): Updates (channel) + Migration sections in Global Settings

- UpdatesSection: pick Stable vs Nightly auto-update channel, persisted to
  ~/.ao/update-settings.json via the new IPC bridge. Closes #2207.
- MigrationSection: drop-in card showing migration status + last report/error
  and a Run/Re-run button hitting the idempotent POST /api/v1/import; works
  even after Don't Migrate. Closes #2205.
- Wire both into the (previously blank) GlobalSettingsForm; cover both with tests.

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

* chore: format with prettier [skip ci]

* fix(browser): re-measure preview bounds after layout transitions

The native WebContentsView preview is positioned from a renderer-measured
rect, re-measured on ResizeObserver + window resize/scroll. But a
ResizeObserver fires on size changes only, so a position-only layout shift
(entering/leaving pop-out moves the slot to another panel; opening the
inspector, which ao preview does, reflows its x) left the overlay at stale
bounds, visibly spilling over the sidebar/terminal until an unrelated resize
fixed it. Drive a settle re-measure on mount and on active/pop-out
transitions (immediate frame + once the ~240ms panel transition settles) so
the final geometry always wins.

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

* chore(backend): gofmt + goimports pre-existing files (fix red CI)

config.go, project_test.go, importer_test.go and spawn_windows.go were
committed unformatted earlier and fail go.yml's gofmt check and golangci's
goimports formatter on main. Apply `golangci-lint fmt`; formatting only, no
logic change. Unblocks this PR's lint/build-test jobs.

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

* feat(settings): manual Check for updates + Update button

Auto-update only checked at launch, so users with auto-updates off (or who
just want it now) had no way to update from the app. Add an on-demand flow:
checkForUpdatesNow/downloadUpdateNow/quitAndInstallUpdate in auto-updater
(works regardless of the opt-in; not-packaged dev surfaces 'unsupported'),
forwarded to the renderer as a live UpdateStatus over updates:status. The
Updates section gains a Check for updates button and an Update button that
downloads then installs (Restart & install), with checking/available/
downloading%/downloaded/error status. Refs #2207.

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

* chore: format with prettier [skip ci]

* fix(sidebar): always offer Global settings in the footer menu

The footer Settings menu showed either Project settings (with a project
selected) or Global settings (with none), so global settings was
unreachable while inside a project. Always list Global settings; add
Project settings above it when a project is active. Refs #2205.

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

* feat(settings): show current app version in Updates section

Surface app.getVersion() as a 'Current version vX.Y.Z' line above the
update controls so users can see what they're running. Refs #2207.

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-27 23:08:55 +05:30
Harshit Singh Bhandari 982cd62886
ci(gitleaks): allowlist historical false-positive commits from rewrite graft (#2169)
The gitleaks job scans full git history on push to main (gitleaks-action
v1.6.0 runs `gitleaks --path` with no commit filter on push events). The
ReverbCode rewrite graft (#2166) carried 279 commits of prior history, 10 of
which trip 42 findings: example secret-patterns in security docs and
secret-redaction test fixtures. All offending files are already deleted from
the tree, so PRs pass (they scan only their own diff) but every push to main
re-scans history and fails indefinitely.

Add `.github/.gitleaks.toml` (the action's default config-path) carrying the
full gitleaks v7.4.0 default ruleset plus a commit allowlist for the 10
historical commits. Detection on new code is unchanged; only these specific
historical commits are skipped.

Verified with gitleaks v7.4.0: full-history scan goes 42 leaks -> 0.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:07:35 +05:30
Harshit Singh Bhandari 77f33c2864
fix(cli): treat run-file removal as stopped in ao stop (#2215)
waitForStopped removed the run-file and then additionally waited for the
daemon process to fully exit, erroring with "removed run-file but did not
exit within 10s" if it lingered past the stop timeout. The run-file is the
daemon's own liveness marker: once it is gone the daemon has committed to
stopping. When no desktop client is connected the daemon can drain its
background workers slower than the stop timeout, which made ao stop
spuriously report failure (the TestE2E_Lifecycle failure in #2214).

Treat run-file removal as stopped: keep polling for full process exit as a
best effort (so Windows releases inherited handles before callers clean the
data dir) but no longer error when that grace elapses.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:24:02 +05:30
Harshit Singh Bhandari 6d382495da
docs: add Apache License 2.0 (#2233)
Add a verbatim Apache License 2.0 LICENSE file at the repo root, with the
appendix copyright line filled as "Copyright 2026 Untrivial".

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:23:09 +05:30
Vaibhaav Tiwari 4d5b1c365a
docs: aligned readme (#2238)
* docs: align readme

* chore: format with prettier [skip ci]

---------

Co-authored-by: Vaibhaav <user@example.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-27 20:20:34 +05:30
Harshit Singh Bhandari 5fa53ad976
feat: electron-updater feed publishing + macOS signing (#2220) (#2226)
* docs(spec): feed-publishing + macOS signing design (#2220)

Design for the electron-updater feed-publishing workstream plus the macOS
code-signing half of Track B, shipped as one PR so a single review unblocks
auto-update. Sidecar-only post-matrix join job emits latest*.yml / nightly*.yml
+ .blockmap sidecars on all three platforms (no maker changes, no artifact
mutation). macOS signing reproduces the proven local runbook in CI: keychain
provisioning, hardened-runtime entitlements, and notarization via the App Store
Connect API key (.p8) path, with the osxNotarize cast fixed.

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

* docs(plan): feed-publishing + macOS signing implementation plan (#2220)

Six TDD tasks: blockmap wrapper, feed module (selection + yml), osxNotarize
API-key rewire, macOS signing-setup composite action, and the two workflow
wirings (latest + nightly feed jobs). Two evidence-based simplifications vs the
spec: no custom entitlements plist (default osxSign entitlements are proven by
the local runbook) and no Node bump (CI already below the crash ceiling).

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

* feat(feed): blockmap sidecar wrapper over app-builder-lib

Adds frontend/scripts/blockmap.mjs isolating the single fragile internal
import (app-builder-lib/out/targets/blockmap/blockmap.js) behind a thin
writeBlockmap(filePath) wrapper. Returns { sha512, size } only, omitting
blockMapSize to force the sidecar differential path in electron-updater.

Test added with // @vitest-environment node directive (required: project
vitest config defaults to jsdom; same pattern as nightly-version.test.mjs).
TDD: red on missing module, green after implementation.

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

* feat(feed): installer selection + electron-updater yml assembly

Implement selectInstallers, feedFilename, and buildYml pure functions
for packaging versioned installers and generating platform-specific
feed metadata. Excludes ao-start aliases and deb/rpm packages.
Generates blockmap sidecars and yml with deprecated top-level
path/sha512 for compatibility, no blockMapSize (forces sidecar diff).

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

* feat(sign): notarize via App Store Connect API key; drop osxNotarize cast

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

* feat(ci): macOS signing-setup composite action

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

* feat(ci): sign macOS + publish latest feed in the release workflow

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

* feat(ci): sign macOS + publish nightly feed in the nightly workflow

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-27 18:10:20 +05:30
Harshit Singh Bhandari a1789a3a1e
feat(update): channel-aware auto-update runtime + nightly pipeline (#2221)
* docs(spec): auto-update with stable + nightly channels

Design spec for channel-aware auto-update via electron-updater: stable
(manual semver releases) and nightly (daily CI cron, version
X.Y.(Z+1)-nightly.<UTC-ts>+<sha>). Covers runtime wiring, the feed,
the version scheme and its ordering/channel rationale, the nightly
pipeline, in-app UX (opt-in + channel + nightly disclaimer), error
handling, testing, and the deferred/prereq items (CI signing for macOS
updates, stable version stamping, polished UI in #2207).

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

* docs(plan): auto-update channels implementation plan

Bite-sized TDD plan for the in-app channel-aware updater + nightly pipeline:
nightly version compute module, daily cron workflow, macOS feed metadata,
~/.ao update-settings module, electron-updater shell + main.ts wiring, and the
first-run opt-in/channel/nightly-disclaimer prompt. Deferred items and Track-B
prereqs (CI signing, stable stamping) are called out.

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

* feat(release): nightly version compute module

computeNightlyVersion -> X.Y.(Z+1)-nightly.<UTC-ts>+<sha>: next-patch base,
fixed-width UTC timestamp for monotonic prerelease ordering, sha as build
metadata. Pure ESM so CI runs it and vitest tests it.

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

* feat(release): daily nightly build + publish workflow

Cron computes the nightly version from the latest stable tag, stamps it,
builds all platforms, and publishes a prerelease (nightly channel). Skips
when HEAD is already covered by the latest nightly. forge publisher prerelease
flag is now env-driven (AO_RELEASE_PRERELEASE) so stable stays non-prerelease.

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

* fix(release): pass nightly version via env, not inline interpolation

Avoids interpolating ${{ }} into the run script (GitHub Actions injection
hardening); the version now arrives via the NIGHTLY_VERSION env var.

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

* feat(update): persist auto-update settings under ~/.ao

readUpdateSettings/writeUpdateSettings store {enabled, channel, nightlyAck}
with safe defaults and channel coercion, atomic temp+rename like app-state.ts.

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

* feat(update): channel-aware electron-updater wiring

Replace update-electron-app (stable-only, no channels) with electron-updater
driven by the user's ~/.ao settings: channel from settings, allowDowngrade for
channel switches, auto-download gated on opt-in, errors swallowed. Feed
configured via setFeedURL since forge does not emit app-update.yml.

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

* style(update): tabs in auto-updater.ts to match repo prettier config

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

* feat(update): first-run opt-in + channel + nightly disclaimer prompt

Minimal main-process dialog flow: opt into auto-updates, pick stable/nightly,
and acknowledge a nightly instability/data-loss disclaimer. Persists the choice
to ~/.ao. Polished Settings-page selector is tracked in #2207.

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

* docs(spec): correct feed-yml premise (no platform emits it as built)

Final review verified that publish:null in both custom makers makes
electron-builder skip update-info yml generation on Windows and Linux too
(app-builder-lib PublishManager returns early before createUpdateInfoTasks),
not just signing-blocked macOS. The runtime updater is therefore inert on all
platforms until a separate feed-publishing workstream (yml gen+upload across
all 3 platforms, coupled to Track-B signing) lands. Recorded in the Feed
section and the prerequisites table.

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

* fix(release): nightly skip-guard must match the v<version> tag namespace

The forge publisher tags nightly releases v<version> (e.g.
v0.10.4-nightly.<ts>), not desktop-v..., so the no-new-commits guard queried a
namespace that never exists and never fired. Query v*-nightly.* instead.

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

* chore: format with prettier [skip ci]

* chore(release): run nightly at 19:00 IST (13:30 UTC)

Was 03:00 UTC (08:30 AM IST); move to 13:30 UTC = 7:00 PM IST.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-27 02:26:56 +05:30
Harshit Singh Bhandari 572093de9d
feat: dashboard legacy-migration popup + app-state migration marker (#2219)
* docs: design for dashboard legacy-migration popup + app-state marker

Spec for the app-side migration trigger (Approach A): projects-only import
daemon API + a migration marker in ~/.ao/app-state.json, with a launch-time
popup (Proceed / Skip / Don't Migrate). Settings redo path deferred to #2205.
Includes the projects-only import-offer backend plan it consumes.

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

* docs: implementation plan for legacy-migration popup + marker

Part A reuses the projects-only import API (import-offer plan) with an
availability-only Status; Part B adds the app-state migration marker (schema v2),
IPC, the useMigrationOffer gate, and the MigrationPopup (Proceed/Skip/Don't
Migrate). Settings redo path deferred to #2205.

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

* refactor(legacyimport): scope import to projects + settings only

Remove orchestrator/transcript import code (orchestrator.go, claude.go,
session_import_store.go and their tests). Trim Store, Options, Report to
projects-only fields. Drop defaultClaudeProjectsDir and projectSessionsDir
from paths.go. Add yaml.TypeError robustness in config.go. Update cli/import.go
confirm prompt and summary. Update importer_test.go to projects-only assertions.

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

* feat(importer): availability probe + projects-only run

Create service/importer.Manager with Status (physical availability check only,
no DB heuristic) and Run (delegates to legacyimport.Run). The app-state.json
marker governs whether to prompt; this service only answers whether legacy data
is physically present.

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

* feat(httpd): import controller + DTOs (GET/POST /api/v1/import)

Add ImportStatusResponse/ImportRunResponse DTOs to dto.go. Create
ImportController with GET (status probe) and POST (run) handlers, both
returning 501 when Svc is nil. Wire APIDeps.Import + API.imports in api.go.
Add controller tests (status, status error, run, run error).

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

* feat(apispec): describe /api/v1/import; regenerate openapi + schema.ts

Add import tag, importOperations() (GET + POST /api/v1/import), and schema
name mappings (ImportStatusResponse, ImportRunResponse, ImportReport) to
build.go. Regenerate openapi.yaml and frontend/src/api/schema.ts. Route-spec
parity test passes. Restore the nil-svc-501 import controller test now that
the spec includes the operation.

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

* feat(daemon): mount import service on the API

Wire importsvc.New(importsvc.Deps{Store: store}) into APIDeps.Import in
daemon.go so the daemon serves GET/POST /api/v1/import backed by the live
sqlite store. Projects-only; no DataDir.

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

* chore(cli): drop resolved §6.4 first-boot-import TODO

The legacy import is now a daemon API (GET/POST /api/v1/import) served by
the importer service and the desktop app handles the popup prompt via the
app-state.json migration marker. The TODO comment is resolved.

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

* chore(legacyimport): remove dead isDir helper

isDir was only used by the deleted projectSessionsDir function.

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

* chore: update package-lock.json after npm install for api:ts

openapi-typescript was missing from root node_modules; npm install
populated it so npm run api:ts could regenerate schema.ts.

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

* fix(httpd): drop em-dash comment + unused ImportStatusResult alias

Review findings M1/M2 from G1 task review.

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

* feat(app-state): migration marker (schema v2) + updateMigration

Bump SCHEMA_VERSION to 2, add MigrationStatus/MigrationState types and
migration? field to AppStateMarker. Extract atomicWriteMarker helper and
reuse it. writeAppStateMarker now preserves an existing migration block
across launch writes. Add updateMigration (IPC setter) and readMigrationState
(IPC getter) exports. TDD: tests added first (red), then implementation (green).

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

* feat(ipc): expose app-state migration getter/setter to the renderer

Add appState:getMigration / appState:setMigration IPC handlers in main.ts.
Add ao.appState.getMigration / setMigration to preload.ts (typed via AoBridge).
Add appState preview fallback in bridge.ts and test setup so AoBridge stays
satisfied in both browser preview and test environments.

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

* test(app-state): cover corrupt-marker case + clean up temp dirs

G2 review findings I2 (corrupt-JSON branch untested) and m1 (temp dirs not cleaned).

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

* feat(renderer): useMigrationOffer gate (marker + availability)

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

* feat(renderer): MigrationPopup (Proceed / Skip / Don't Migrate)

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

* feat(renderer): surface MigrationPopup on the dashboard

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-27 02:24:36 +05:30
Harshit Singh Bhandari 0bccaacdc9
ci(release): build darwin-x64 on macos-13 for Intel Mac ao start (#2216)
The desktop release only built on macos-latest (Apple Silicon, arm64),
so the darwin-x64 stable alias was never produced and Intel-Mac
ao start 404s fetching agent-orchestrator-darwin-x64.zip.

Add the macos-13 (Intel x64) runner to the job matrix and broaden the
macOS alias-upload step's condition from matrix.os == 'macos-latest' to
startsWith(matrix.os, 'macos') so it runs on both macOS runners. The
step already derives arch via uname -m, so the Intel runner now emits
and uploads the x64 alias. Updated the now-stale gap comment.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:09:09 +05:30
Harshit Singh Bhandari 14030772b9
feat(frontend): add blank Global Settings page (#2218)
* feat(frontend): add blank Global Settings page

Add a Global Settings page to the desktop renderer, reachable from the
sidebar Settings menu when no project is selected. The project-scoped
Settings entry stays when a project is active; otherwise the menu shows
Global settings, which opens the new /settings route. The page body is
intentionally empty for now and will be filled in incrementally.

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

* feat(frontend): regenerate route tree + test Global Settings menu

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:07:42 +05:30
Harshit Singh Bhandari bc84b6d2a3
feat(cli): ao start fetch/open for Windows and Linux (T6/T7) (#2204)
* feat(start): implement ao start fetch/open for Windows and Linux

Fill in the non-darwin branches of the bootstrapper (T6/T7):

- assetName() selects the per-GOOS stable release asset: windows ->
  agent-orchestrator-win32-x64.exe (NSIS installer), linux ->
  agent-orchestrator-linux-x64.AppImage. amd64-only for now via
  requireAMD64(), which returns a clear unsupported-arch error.
- fetchApp() dispatches per GOOS. Windows downloads the NSIS installer and
  runs it silently (/S) to the default per-user dir, then resolves the
  installed exe under %LOCALAPPDATA%\Programs. Linux downloads the AppImage
  to a stable path under ~/.ao (atomic temp+rename), chmods it executable,
  and skips any install step so re-runs resolve without re-fetching.
- knownAppLocations() scans the per-user and per-machine Windows install
  dirs and the stable Linux AppImage path.
- isUsableBundle() treats a win exe / linux AppImage as a regular file
  (darwin stays a directory).
- openApp() launches win/linux detached via the existing StartProcess seam,
  forwarding --installed-via=npm-bootstrap, and falls back to manual-open on
  spawn failure.

The Windows silent-install path is marked ponytail (untestable on the macOS
build host); a wrong install dir surfaces as a clear not-found error. Tests
cover asset naming, arch gating, scan locations, regular-file vs dir, and
the detached-spawn + fallback paths.

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

* feat(release): build + publish a Linux AppImage for ao start (T7)

AppImage is the Linux fetch-and-run artifact for the bootstrapper (spec
§11.3): a single self-contained executable ao start downloads and runs
directly, no system package manager.

- makers/maker-appimage.ts: a MakerBase subclass bridging to
  electron-builder's buildForge (appImage target), mirroring maker-nsis.ts,
  since Forge has no first-party AppImage maker. publish:null so Forge owns
  release uploads.
- forge.config.ts: register MakerAppImage for linux; keep deb/rpm for users
  who want a system package.
- frontend-release.yml: on ubuntu-latest, copy the built AppImage to the
  stable, space-free name agent-orchestrator-linux-x64.AppImage and upload
  it to the v<version> release with --clobber, mirroring the Windows step.

Build-untested on this macOS host: the first ubuntu CI run must confirm the
electron-builder AppImage target token and the out/make/*.AppImage output
glob.

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

* fix(ci): green up ao start Win/Linux PR (lint, cross-OS test, container smoke)

Five golangci-lint findings in start.go, two cross-OS test failures, and the
fresh-install container smoke were broken by the Win/Linux bootstrapper diff.

Go lint (start.go):
- gocritic filepathJoin: build "/Applications/<bundle>" as a literal instead of
  filepath.Join with a separator-bearing arg.
- gocritic httpNoBody: pass http.NoBody, not nil, to NewRequestWithContext.
- gosec G302: annotate the AppImage chmod 0755 with a nolint; an AppImage is a
  self-contained executable and must be executable.
- nilerr: annotate openApp's intentional (false, nil) on launch failure; the
  failure is reported via the bool, not as an error.
- unparam: resolveApp's error result was always nil; drop it and update callers.

Cross-OS tests (start_test.go):
- makeBundle created a directory, which only stats as usable on macOS. Make it a
  regular file on Windows/Linux so the marker/scan resolve tests pass there,
  matching isUsableBundle's per-OS rule.

Container fresh-install smoke (test/cli/install-check.sh, Dockerfile):
- ao start is now the desktop-app launcher and no longer runs a daemon, so the
  old daemon status/shutdown/stop assertions could never pass. Assert instead
  that on a fresh box start reaches the fetch path and exits non-zero with a
  clear error (404 download on amd64, unsupported-arch on arm64). Refresh the
  stale daemon-reaping comments in the Dockerfile.

Verified locally: go build/vet ok, golangci-lint v2.12.2 reports 0 issues,
go test -tags e2e ./internal/cli/... passes (the only remaining failure,
TestE2E_Lifecycle, is a pre-existing daemon-shutdown flake that fails the same
way on upstream/main on this host), and the container smoke passes on both
linux/arm64 and linux/amd64.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:50:30 +05:30
Harshit Singh Bhandari 514946fd8b
feat(cli): ao start fetches + opens the desktop app; deprecate npm as an app channel (Track A) (#2201)
* docs: grounded spec for ao start bootstrapper + npm deprecation

Real-codebase-verified implementation spec (Track A: launcher + app-state
marker + release asset wiring). Replaces the somthing.md draft's aspirational
assumptions with file:line ground truth: correct bundle name (Agent
Orchestrator.app), publish repo (aoagents/agent-orchestrator), draft-release
+ asset-rename gaps, unsigned-build reality, and the already-wired
update-electron-app updater. Includes a dependency-ordered task breakdown
for AO execution.

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

* docs: reframe spec around Go ao start subcommand (not a JS launcher)

The npm package ships the existing Go cobra CLI (backend/cmd/ao); this effort
rewrites the `ao start` subcommand to fetch+open the desktop app. Corrections:
- releases land on AgentWrapper/agent-orchestrator (aoagents was the temporary
  rewrite home; forge publisher must be repointed)
- ao start stops starting the daemon; the frontend owns the daemon
- adds the real Go CLI command surface (1.7), the npm-delivery gap for the Go
  binary (1.6), and the legacy first-boot import decision (6.4)

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

* docs: separate prod vs test release targets in ao start spec

Dev/test loop must never cut a production release. Download repo + forge
publisher + npm scope are now build-time overridable:
- prod: AgentWrapper/agent-orchestrator + real package name
- test: harshitsinghbhandari/agent-orchestrator (fork) + @theharshitsingh/ao
T3/T5 now release+test against the fork and the test scope, with prod cut as a
separate gated step.

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

* feat(cli): rewrite ao start to fetch + open the desktop app (T1)

ao start no longer starts the daemon (the desktop app owns it). It now
resolves the installed app (~/.ao/app-state.json marker -> stat -> known-
location scan), fetches the latest release zip and ditto-unpacks it on macOS
when absent, opens it with --installed-via=npm-bootstrap, and prints an honest
deprecation notice. releaseRepo is build-time overridable (-ldflags) so test
builds fetch from the fork. Windows/Linux fetch/open are stubbed for T6/T7.

Review fixes folded in: download() copies deps.HTTPClient and drops its 2s
loopback-probe timeout (a real release asset is hundreds of MB), and fetchApp
clears ~/.ao/staging before unpacking. Regression test covers the timeout.

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

* feat(desktop): write ~/.ao/app-state.json marker on launch (T4)

The app is the sole writer of the marker ao start reads to locate the bundle.
New frontend/src/main/app-state.ts does an atomic temp+rename write mirroring
the daemon's runfile.Write, preserving installedAt/installSource across launches
and refreshing appPath/version/lastReconciledAt. main.ts hooks it into
app.whenReady ordered relocate (macOS) -> write marker -> createWindow, both
non-fatal. Bundle path is derived from process.execPath (not app.getAppPath,
which is the asar path); JSON keys match start.go's appState reader exactly.

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

* ci(desktop-release): overridable repo, published release, stable asset aliases (T3)

Make the bootstrapper's constant releases/latest/download URL resolve:
- forge publisher repo reads AO_RELEASE_REPO (default AgentWrapper/agent-
  orchestrator); the workflow sets it to github.repository so a fork run
  publishes to the fork and never to prod
- draft:false so the release is immediately live (constant URL needs it)
- add ubuntu-latest to the matrix (issue #2191)
- post-publish steps upload stable space-free aliases
  (agent-orchestrator-darwin-arm64.zip, agent-orchestrator-win32-x64.exe)
  matching exactly what start.go fetches

Review fix: the alias upload targeted GITHUB_REF_NAME (the git tag), but
publisher-github creates the release as v<package.json version>; retargeted to
that and relaxed the guard so workflow_dispatch also produces aliases.

Known gaps (documented inline): macOS x64 needs an Intel runner (macos-latest is
arm64-only); the Linux stable asset name awaits the deb/rpm-vs-AppImage decision.

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

* chore: format with prettier [skip ci]

* feat(npm): deliver Go ao binary as @aoagents/ao via per-platform packages (T2)

Ship the ao CLI to npm with ZERO install scripts using the esbuild model:
- packages/ao: pure-JS @aoagents/ao with a bin/ao.js shim that resolves and
  execs the matching @aoagents/ao-<platform>-<arch> optionalDependency
- four platform packages (darwin-arm64/x64, win32-x64, linux-x64), each os/cpu
  gated so npm installs only the host's; binary cross-compiled CGO-free
  (modernc.org/sqlite), gitignored, shipped via files
- build-binaries.sh cross-compiles all four; releaseRepo keeps its prod default

Not added as root workspaces on purpose: os/cpu-restricted members make root
`npm ci` fail EBADPLATFORM (CI's api-drift job runs it). Packages publish
standalone; the shim was verified in a published-like layout.

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

* chore(npm): set @aoagents/ao packages to 0.10.0 (above existing 0.9.5)

The existing @aoagents/ao on npm is 0.9.5; the launcher must publish a higher
version so npm latest advances and existing users get the new fetch-and-open
binary on update. All five packages plus the four optionalDeps pins set to
0.10.0.

* chore: format with prettier [skip ci]

* fix(start): capture install provenance before macOS relocation

moveToApplicationsFolder() relaunches the app from /Applications without
forwarding the --installed-via arg, and code past a successful move never
runs in the staging instance. The post-move instance therefore wrote
installSource="unknown", and writeAppStateMarker's sticky logic then locked
it there, losing the npm-bootstrap provenance in the exact path it exists for.

Write the marker before relocation when --installed-via is present so the
source is persisted while the arg is still available; the post-move launch
preserves it (sticky installSource) while refreshing appPath to /Applications.

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

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-26 19:31:56 +05:30