* 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>
* 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>
* 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>
* 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.
* 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>
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>
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>
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>
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>
- 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>