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>
* 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>
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>
* 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>
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.
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>
* fix(ui): use popper position for SelectContent to prevent scroll reset
* fix(ui): use React.memo to prevent scroll resets without breaking popper position
* 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>
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>
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>
* 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>
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>
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>
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>
* 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>
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>
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>
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>
* 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>
* 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>
* 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>
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>
* 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>
* 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>
* 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>
* fix(browser): clip preview view to its panel column so it can't paint out of bounds
The inspector's Browser tab hosts a native WebContentsView positioned from the
slot's getBoundingClientRect. That overlay is a window-level layer, so DOM
overflow:hidden never clips it. The slot sits inside the inspector's
min-w-[280px] reflow guard, so on a narrow panel (small window, or mid
collapse/expand) the slot's box spills past its resizable-panel column and the
view paints over the terminal/sidebar, "out of the box" (#2202).
Two fixes in useBrowserView:
- Intersect the measured slot rect with its [data-panel] column before sending
bounds, so the view can only ever paint inside that column.
- Observe the panel column (not just the slot) with the ResizeObserver. During
the flex-grow collapse/expand animation the slot's width stays pinned by
min-w, so a slot-only observer never re-fires and the bounds go stale; the
column's width changes every frame, so observing it re-measures throughout.
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>
* fix(daemon): do not tear down live sessions on shutdown; adopt them on boot
Remove SaveAndTeardownAll from the graceful shutdown path. Live tmux/ConPTY
sessions survive daemon exit; Reconcile on the next boot adopts them via
reconcileLive, preserving session IDs and preventing the id-increment bug.
Add runShutdownSessionLifecycle as a testable seam and narrow the
sessionLifecycle interface to Reconcile+RestoreAll only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(daemon): make shutdown-teardown regression test falsifiable
Re-add SaveAndTeardownAll to the sessionLifecycle interface and to
fakeSessionLifecycle so TestShutdown_DoesNotCallSaveAndTeardownAll is
genuinely falsifiable: the flag flips if runShutdownSessionLifecycle
ever calls sl.SaveAndTeardownAll, making the assertion meaningful.
Name the sl parameter (was discarded with _) so the seam is visible.
RED: with a temporary sl.SaveAndTeardownAll call, test fails.
GREEN: without it, test passes. go test ./internal/daemon/... -race: 23/23.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(daemon): guard shutdown-teardown at compile time via narrowed interface
Remove SaveAndTeardownAll from sessionLifecycle so daemon.Run physically
cannot call teardown on shutdown. Delete the no-op runShutdownSessionLifecycle
seam and its test. The narrowed interface is the guard: re-introducing teardown
requires a visible, reviewable interface change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(session): make ensure-orchestrator idempotent so POST cannot mint a duplicate
When clean=false, SpawnOrchestrator now checks for an existing active orchestrator
and returns it directly instead of always calling Spawn. Adds RED/GREEN tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(session): drop redundant NoCleanSkipsKills, covered by SpawnsWhenNoneExists
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(daemon): supervisor watchdog
Adds backend/internal/daemon/supervisor: a transport-agnostic watchdog
that fires onLastClientGone() exactly once when the live connection count
drops to zero and stays zero for a configurable grace period. Arms only
after the first accepted connection (headless safety: CLI ao start never
self-stops). Reconnect before grace elapses cancels the pending timer.
Mutex guards liveCount/armed/pendingTimer; sync.Once guards the callback.
Tests use net.Pipe + a fake listener (no OS sockets); all three behavior
contracts verified with -race: never fires pre-connect, fires once on
last disconnect, reconnect within grace cancels and re-arms correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(daemon): supervisor watchdog review fixes (data race, ErrClosed, leak, cleanup)
- Fix data race: capture liveCount into local inside lock before logging
- Replace io.EOF with errors.Is(err, net.ErrClosed) for correct production behavior; update fakeListener to match
- Fix goroutine leak: derive cancellable child context in Serve so watcher always unblocks on return
- Simplify makePipe: drop dead error return, update 3 call sites
- Remove dead defensive pendingTimer nil-check in armGrace
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(daemon): OS-native supervisor listener triggers clean shutdown
Creates platform-split Listen() in supervisor package (Unix UDS sibling
of run-file; Windows named pipe via go-winio). Wires it into daemon.Run
before srv.Run: listener failure is non-fatal so headless ao-start works.
Adds RequestShutdown() on Server and three unit tests for listen_unix.go.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(daemon): log supervisor Serve error instead of discarding it
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(desktop): supervisor link; daemon self-stops (clean) on frontend exit
Add connectSupervisor() in frontend/src/main/supervisor-link.ts: holds one
client connection to the daemon's OS-native supervisor socket (Unix domain
socket on macOS/Linux, named pipe on Windows). Retries with bounded backoff
until the daemon is up; reconnects automatically on drop.
In main.ts, connect the link from reportBoundPort (once per daemon ready
transition). Remove the quit-time daemon teardown: the before-quit handler
now only disposes the browser view. The process.on("exit") killDaemon call
is also removed. When Electron exits for any reason the OS closes the fd,
the daemon detects EOF, and self-stops after its 5s grace period. tmux and
ConPTY sessions survive and are adopted on the next boot.
killDaemon and stopDaemon are kept for the explicit user-stop path
(ipcMain.handle("daemon:stop", ...)).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(desktop): guard against daemon orphan when supervisor link is down; tidy test setup
Expose a live `connected` getter on SupervisorLinkHandle and add a
last-resort process.on("exit") kill that fires only when the link is
not actually connected (UDS never bound or addr was null), preventing
orphan daemons while keeping the OS-fd teardown path for the normal
case. Log a warning when addr is null so the skip is diagnosable.
Invert the setup.ts guard to the natural form, dropping the empty
if/else skeleton.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(core): restore promptless sessions in place (reboot recovery, no increment)
Drop the empty-prompt early return in restoreArgv that returned ErrNotResumable
when the adapter could not resume and no prompt was saved. Now control falls
through to GetLaunchCommand unconditionally: a saved prompt is replayed, an
empty prompt (orchestrator) launches fresh with the system prompt only, same id,
same workspace. Removes the only producer of ErrNotResumable and deletes the
now-dead sentinel, its service mapping, and the corresponding tests.
Frontend reference to SESSION_NOT_RESUMABLE in TerminalPane.tsx is left intact
(the handler becomes harmlessly dead; the API simply stops returning that code).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(integration): dead-live session is restored, not abandoned, after reconcile
Task 5 made promptless sessions relaunch fresh instead of hitting ErrNotResumable.
The reconcile crash-recovery path (documented in reconcileLive) terminates a
dead-live session then RestoreAll relaunches it on the same boot. This test
asserted the old promptless-stays-terminated artifact; update it to the intended
restored end state (live again, same id, one fresh runtime Create).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(session): replace em dashes in service.go messages and comment
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(desktop): scope supervisor link to spawn path; dispose link on explicit stop
Document that the liveness link is established only when the app spawned the
daemon: the attach path intentionally does not link, to keep an `ao start`
daemon persistent (headless safety). Also dispose the link on an explicit
daemon:stop so its reconnect loop does not retry a deliberately-stopped daemon.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: format with prettier [skip ci]
* docs: add daemon-lifecycle adopt-on-shutdown implementation plan
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: format with prettier [skip ci]
* fix(daemon): keep supervisor watchdog alive across transient Accept errors
Review of #2185: Serve previously returned on the first unexpected Accept error,
silently disabling the watchdog (the 'restart is caller's job' comment described
a contract the caller never fulfilled). Back off and keep accepting instead, so
a transient error (e.g. EMFILE) cannot leave the daemon unable to self-stop on
frontend death. Also drop the stale Task 3/4 planning comment.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(core): leave promptless workers terminated on restore (orchestrators still relaunch fresh)
A promptless, unresumable KindWorker session had no prompt to replay and
no native session id to resume from. Blank-relaunching it via GetLaunchCommand
would silently drop its task. restoreArgv now returns ErrNotResumable for this
case, gated on (meta.Prompt == "" && kind != domain.KindOrchestrator). Orchestrators
are promptless by design and continue to relaunch fresh with the system prompt only.
Re-introduces ErrNotResumable sentinel and its Conflict API mapping.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(desktop): re-link supervisor on attach for app-owned daemons (close lingering-daemon gap)
- runfile.Info gets Owner field (omitempty): "app" when Electron spawned,
empty for headless `ao start`. server.go reads AO_OWNER env to set it.
- daemonEnv() injects AO_OWNER=app so spawned daemons self-identify.
- Extracted establishSupervisorLink() from inline reportBoundPort code.
Spawn path calls it unconditionally; attach paths call it only when
shouldLinkOnAttach(owner) is true (owner === "app").
- Both attach paths (inspectExistingDaemon, resolveDaemonFromPort) now
read the owner from the run-file and re-link when app-owned, preventing
a lingering app-spawned daemon from self-stopping mid-session.
- Headless ao start daemons stay unlinked: persistent across app quit.
- New daemon-owner.ts + daemon-owner.test.ts (4 vitest cases, all pass).
- Go: 9 tests pass with -race; vet clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: quiet expected ErrNotResumable log in RestoreAll; note attach TOCTOU
Review polish on #2185: a promptless worker left terminated on boot is expected,
not an operational error, so log it at Warn not Error. Document the narrow
run-file re-read TOCTOU on the port-attach re-link path (worst case is linking a
headless daemon; the dispose-idempotency guard prevents any leak).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style: gofmt the review-fix files (CI format/lint check)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>