Commit Graph

33 Commits

Author SHA1 Message Date
codebanditssss 6c8ad99b06 docs: refresh README 2026-07-05 02:22:07 +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
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 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 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 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 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 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 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 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
Harshit Singh Bhandari e970f72d3e
fix(desktop): package Windows via NSIS instead of Squirrel (#403)
* 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>
2026-06-23 20:44:30 +05:30
Harshit Singh Bhandari 7ac1e2caa2
fix(desktop): handle Squirrel startup events + CI smoke-install the Windows build (#398)
* fix(desktop): handle Squirrel startup events + CI smoke-install the Windows build

Two Windows-install fixes.

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

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

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

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

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

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:51:02 +05:30
Harshit Singh Bhandari 5440d2d1df
ci: consolidate per-OS testing builds into one matrix workflow
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>
2026-06-23 00:40:58 +05:30
Harshit Singh Bhandari 2946212171
ci: add manual/tag macOS testing build (unsigned arm64 zip)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:17:11 +05:30
Harshit Singh Bhandari 665f2a26a6
ci: add manual/tag Windows testing build (Squirrel installer)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:41:10 +05:30
Harshit Singh Bhandari 15e01af7c6
ci(linux-testing): build .deb only, drop failing rpm maker
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:26:34 +05:30
Harshit Singh Bhandari 4c2e1eaa54
ci: add manual/tag Linux testing build; disable desktop-testing tag trigger
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:21:44 +05:30
Harshit Singh Bhandari d9dde9280f
ci: build unsigned desktop artifacts on 0.0.0-testing-* tags (#347)
Add a tag-triggered workflow that builds the Electron desktop app (with the
bundled Go daemon) on macOS, Windows, and Linux runners and attaches the
unsigned artifacts to a GitHub prerelease for end-to-end pipeline validation.

Signing/notarization is intentionally off until the certs and secrets exist,
so these builds are for validating packaging, not distribution.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:19:24 +05:30
Madhav Kumar 6885af26b2
feat(windows): ConPTY terminal, zellij discovery, agent launcher trampoline, codex shim resolution (#310)
* fix(daemon): self-heal a stale run-file instead of refusing to start

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

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

Fixes #256

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

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

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

Fixes #235

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

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

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

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

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

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

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

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

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

How it works on Windows:

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

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

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

Also adds:

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

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

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

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

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

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

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

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

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

* fix: stabilize windows zellij sessions

---------

Co-authored-by: harshitsinghbhandari <24b4506@iitb.ac.in>
Co-authored-by: Madhav <madhavkumar@microsoft.com>
Co-authored-by: Vaibhaav <user@example.com>
2026-06-18 21:51:05 +05:30
Ashish Huddar 785f060b71
ci: run the frontend vitest suite on pull requests (#172)
* ci: run the frontend vitest suite on pull requests

The renderer suite was dead for months with nothing noticing — no workflow
executed it (and until #171 it could not even run: vitest auto-loads only
vite.config.ts / vitest.config.ts, which the repo lacked). This job keeps
the revived suite alive.

Typecheck is intentionally left out until the pre-existing forge.config /
update-electron-app type errors are fixed.

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

* fix(frontend): regenerate package-lock.json in sync with package.json

The committed lock was missing dozens of entries (ansi-regex, error-ex,
minimatch, ...), so `npm ci` under CI's npm 10 hard-fails with "Missing:
<pkg> from lock file" — the new Frontend workflow caught it on its first
run. Newer local npm versions tolerated the drift, which is why it went
unnoticed. Regenerated with npm install (npm 10.9.8, lockfileVersion 3) and
validated with a clean `npm ci` + full vitest run (9/9 files, 99/99 tests).

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:18:55 +05:30
yyovil c2c4404c7d
feat(frontend): scaffold for frontend with complimentary backend changes (#168)
* feat(frontend): rebuild Electron desktop UI as a React + Vite renderer

Replaces the skeleton Electron frontend with a full React 19 + TypeScript
renderer (Vite, electron-forge, contextBridge preload), plus the backend
additions it needs.

Renderer:
- TanStack Query + EventTransport (CDC SSE on /api/v1/events)
- TanStack Router file-system routing (hash history for the file:// origin)
- Tailwind + shadcn/ui, react-resizable-panels, Zustand UI state
- @xterm/xterm per-session PTY over /mux WebSocket + WebGL addon
- openapi-typescript + openapi-fetch types off openapi.yaml
- electron-forge packaging + update-electron-app auto-updater
- Vitest + RTL · Playwright

Backend:
- cors.go — allowlist-only CORS, handles Private Network Access preflight
  for app:// renderer -> loopback daemon
- session.TerminalHandleID exposed in domain + OpenAPI spec
- project.Path added to OpenAPI spec, service, store, and tests

DESIGN.md documents the emdash-matched dark UI (tokens, blue accent, status
glyph spec, orchestrator-led layout).

Co-authored-by: Ashish Huddar <ashish.hudar@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: b1e334c1e54a

* chore: format with prettier [skip ci]

---------

Co-authored-by: Ashish Huddar <ashish.hudar@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-10 11:40:17 +05:30
yyovil 5982051651
chore: add prettier config and CI auto-formatter (#166)
* chore: add prettier config and CI auto-formatter

Adds .prettierrc and .prettierignore (config only, no local enforcement).
Formatting runs in CI via the prettier.yml workflow: on every push to a
non-main branch, Prettier rewrites changed files and commits the result back
using GITHUB_TOKEN. Developers never need to run Prettier locally.

Intentionally excludes husky/lint-staged — local pre-commit hooks are the
wrong layer for a formatter that the whole team doesn't need installed.

Also adds .envrc.local to .gitignore for personal local shell overrides.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 27336650d2ee

* chore: format with prettier [skip ci]

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-10 09:09:17 +05:30
Harshit Singh Bhandari c7e3a0336e
ci(frontend): add react-doctor check for landing site (#151)
* ci(frontend): add react-doctor check for landing site

Adds a CI job that runs react-doctor (https://github.com/millionco/react-doctor)
against the Next.js 15 + React 19 landing site at frontend/src/landing/.
The landing has 29 .tsx components and no existing lint coverage; the only
prior frontend CI was a schema.ts drift check in go.yml.

- New workflow .github/workflows/react-doctor.yml, path-filtered to
  frontend/src/landing/** so backend-only PRs don't pay for it.
- doctor script + react-doctor devDep added to the landing package.
- Default --blocking=error: surfaces security, bugs, perf, a11y, and
  maintainability findings without failing CI on existing warnings
  (current baseline: 0 errors, 58 warnings, ~5s locally).
- Runs with --no-telemetry so CI runners don't ping react.doctor.

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

* ci(frontend): also scope react-doctor push trigger to landing paths

Mirrors the PR path filter on push to main so backend-only merges don't
re-run the landing check. Unlike go.yml/cli-e2e.yml (which trigger on
broad path sets), this workflow only cares about frontend/src/landing/.

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

* ci(frontend): use react-doctor GitHub Action, drop devDep + lockfile churn

Replaces the npm install + npm run doctor approach with the official
millionco/react-doctor@v2 composite action. The action manages its own
Node setup and react-doctor install on the runner, so the landing
package.json and lockfile stay untouched (no transitive-dep bloat from
react-doctor's 479-package tree).

The action also wires up sticky PR summary comments, inline review
comments, and commit statuses out of the box — requires pull-requests
and statuses write perms.
2026-06-07 07:11:35 +05:30
neversettle 6da5d93241
feat: automate OpenAPI spec + frontend TS type generation (issue #102) (#103)
* feat: add npm run api scripts and document API contract change flow

Adds three root package.json scripts mirroring the sqlc convention:
- api:spec — runs go generate to regenerate openapi.yaml
- api:ts   — runs openapi-typescript@7.4.4 to generate frontend/src/api/schema.ts
- api      — runs both in sequence (the single contributor command)

Documents the full flow in a new AGENTS.md "API contract changes" section
so contributors know which files to edit, how to regenerate, and what to
verify. Implements slice 1 of issue #102.

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

* fix: correct paths in AGENTS.md API contract changes section

- api:ts equivalent command: use full relative path
  backend/internal/httpd/apispec/openapi.yaml, not bare openapi.yaml
- verify command: prefix with `cd backend &&` so it works from repo root

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

* feat: add CI drift guard for OpenAPI spec and frontend TS types

- Adds api-drift job to go.yml: regenerates openapi.yaml + schema.ts
  via `npm run api`, then fails if git diff detects any change
- Expands go.yml path filter to trigger on package.json and schema.ts
- Commits initial schema.ts generated from the current spec
- Notes the CI guard in AGENTS.md API contract changes section

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

* fix: address review feedback on api-drift CI job

- Scope git diff to schema.ts only; openapi.yaml drift is already
  caught by TestBuild_MatchesEmbedded in the build-test Go job
- Add npm ci step so openapi-typescript is installed from the lockfile
  rather than re-fetched by npx on every run
- Move openapi-typescript from npx -y to a pinned devDependency
  (exact 7.4.4, no caret) with a committed package-lock.json
- Note in AGENTS.md Verify block that go test does not cover schema.ts drift

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

---------

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 03:14:08 +05:30
itrytoohard a9e83011f1 docs(test): correct --init rationale (suite uses a fabricated dead PID)
The stale-daemon assertion does not depend on container PID-1 reaping — it
writes a fabricated dead PID rather than killing a real process. --init is
still run so the real daemon spawned by the `start` test (detached via setsid)
is reaped after `stop` instead of lingering as a zombie. Reword the README,
Dockerfile, and workflow comments to say that accurately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 02:37:51 +05:30
itrytoohard 3680ac5474 test(cli): add end-to-end smoke test + Docker/CI harness
Adds a fresh-machine, install→use→verify E2E test for the `ao` CLI and wires
it into CI. The suite drives the real binary (start/status/doctor/stop + the
daemon-control HTTP surface) against fully isolated state — its own temp
run-file, data dir, and an auto-picked free loopback port — so it never
collides with a developer's real AO install or daemon.

- test/cli/smoke.sh: 40 assertions covering install resolution, version/help
  (daemon hidden), doctor text+json (and that it does NOT migrate SQLite),
  status stopped/stale/ready, start fresh+idempotent, daemon-created store,
  /healthz identity, the /shutdown CSRF + DNS-rebinding guard (403 + survives),
  graceful/stale/idempotent stop, run-file ownership cleanup, exit codes
  (2 usage / 1 runtime), and completion for all four shells. It deliberately
  ignores an inherited AO_PORT and self-allocates a free port for isolation.
- test/cli/Dockerfile: models installing ao on a fresh machine — builds the
  binary, drops it on PATH in a clean Debian image with only runtime deps
  (git/tmux/curl), runs the suite as a non-root user.
- test/cli/run-local.sh: build-from-source + native run convenience wrapper.
- .github/workflows/cli-e2e.yml: two tiers — `native` runs the suite on a
  ubuntu+macos runner matrix (the real VMs, to cover the unix setsid detach and
  macOS os.UserConfigDir paths a Linux container can't), and `container` runs
  the fresh-machine Docker image with --init (real PID-1 reaper so the
  stale-daemon assertion is reliable).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 02:33:56 +05:30
harshitsinghbhandari d824c4f767 ci: add Go build/test and gitleaks secret-scan workflows
- go.yml: gofmt check, build, vet, and race-enabled tests for backend/,
  triggered on backend changes and pushes to main.
- gitleaks.yml: secret scanning on PRs and main using gitleaks-action v1
  (license-free; v2 requires GITLEAKS_LICENSE for org repos).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 21:02:58 +05:30