Skip to content

feat(release): weekly release train — channels, onboarding, dashboard banner, cron#1781

Open
suraj-markup wants to merge 9 commits intomainfrom
feat/release-train-ao174
Open

feat(release): weekly release train — channels, onboarding, dashboard banner, cron#1781
suraj-markup wants to merge 9 commits intomainfrom
feat/release-train-ao174

Conversation

@suraj-markup
Copy link
Copy Markdown
Collaborator

Summary

Implements the full release pipeline described in release-process.html. Combines what would otherwise be five separate PRs into one cohesive landing.

Supersedes #1525 — incorporates that PR's release infrastructure (canary + release workflows, changeset config, publishConfig on every package) with the cron / no-stale-SHA-guard / no-merged-PR-comment modifications called out in the design doc.

Sections (mapped to the design doc)

A. Cron tweaks + release infrastructure

  • .github/workflows/canary.ymlcron: '30 17 * * 5,6,0,1,2' (23:00 IST Fri–Tue) plus workflow_dispatch. No stale-SHA guard (cron uses ref: main); no merged-PR-comment step (no merged-PR context from cron).
  • .github/workflows/release.ymlchangesets/action opens a "version packages" PR; merging it publishes @latest.
  • .changeset/config.json — adds snapshot.prereleaseTemplate: '{tag}-{commit}', moves the now-private @aoagents/ao-web to ignore[], includes all currently-shipped plugins in the linked group.
  • packages/web/package.json"private": true so changeset publish never tries to push the dashboard.
  • All publishable packages get publishConfig: { access: "public", provenance: true }.

B. Channel awareness — packages/cli/src/lib/update-check.ts

  • New updateChannel field in GlobalConfigSchema (stable | nightly | manual, default manual). Schema is .optional().catch(undefined) so legacy / typo'd values don't break config load.
  • fetchLatestVersion(channel) now reads dist-tags[channel] from the registry (full package doc, not the per-tag URL) and falls back to latest if nightly isn't published yet.
  • isVersionOutdated compares prerelease segments numerically + lexically per semver — SHA-suffixed nightlies (0.5.0-nightly-abc < 0.5.0-nightly-def) sort correctly.
  • maybeShowUpdateNotice and scheduleBackgroundRefresh skip entirely on manual so opted-out users see no traffic and no nudges.
  • Cache file gains a channel field; entries from a different channel are ignored.

C. Active-session guard — packages/cli/src/commands/update.ts

  • ensureNoActiveSessions() runs before any install. Lists sessions, refuses with N session(s) active. Run ao stop first. if any are in working/idle/needs_input/stuck. Never auto-stops.
  • Same guard duplicated in POST /api/update so the dashboard returns a structured 409 { activeSessions, message }.

D. Onboarding question + ao config set

  • packages/cli/src/lib/update-channel-onboarding.ts — prompts once on first ao start. Ask-once gate keyed on the absence of updateChannel in the global config; dismissal persists manual so we don't re-ask.
  • ao config set updateChannel <stable|nightly|manual> — also handles installMethod. New packages/cli/src/commands/config.ts.
  • runStartup calls maybePromptForUpdateChannel() after preflight.

E. Dashboard banner

  • GET /api/version (packages/web/src/app/api/version/route.ts) reads the same cache file the CLI writes ($XDG_CACHE_HOME/ao/update-check.json). Cache-only; never makes a network call inside a request handler.
  • POST /api/update (packages/web/src/app/api/update/route.ts) runs the active-session guard, then spawns ao update detached.
  • UpdateBanner.tsx — top-of-page strip wired into Dashboard.tsx. Visible only when isOutdated. Click POSTs to /api/update. Dismissal persists per-version in localStorage; a newer version reappears the banner. Tailwind only, var(--color-*) tokens, no inline styles.

F. Bun + Homebrew detection

  • classifyInstallPath adds /Cellar/ao/ (homebrew, notice-only — brew upgrade ao, never auto-install because brew owns the symlinks) and ~/.bun/install/global/ (auto-installs bun add -g @aoagents/ao@<channel>). Cellar check runs first because brew nests the npm tree under lib/node_modules/.
  • New installMethod global-config field overrides path detection for users with custom prefixes.

Test plan

New tests added (155 total, all green):

  • update-check.test.ts — channel resolution, prerelease compare (rc.1 < rc.2, nightly SHA suffixes), dist-tags fetch, channel-mismatched cache, manual-channel skip, bun/homebrew classifiers, installMethod override, channel-aware getUpdateCommand.
  • update.test.ts — active-session guard (refuse on working/idle/needs_input/stuck, allow terminal statuses), soft auto-install (skip prompt on stable/nightly, prompt on manual), homebrew notice-only.
  • update-channel-onboarding.test.ts — ask-once gate, dismissal persists manual, no re-prompt after persistence.
  • version-update-api.test.ts — channel-aware cache reading, channel-mismatched entries ignored, 409 active-session refusal, 202 happy path.
  • UpdateBanner.test.tsx — hides on up-to-date, hides on manual, hides when dismissed for current latest, re-appears for new version, POST + 409 surface.

CI:

  • pnpm typecheck clean
  • pnpm lint clean (0 errors; 50 pre-existing warnings, none in changed code)
  • pnpm --filter @aoagents/ao-cli test — 92 update-check + 34 update-command + 11 onboarding tests pass; same 10 pre-existing failures observed on main still present (environment-dependent: /private path canonicalization, ~/.agent-orchestrator/ state).
  • pnpm --filter @aoagents/ao-web test — 18 new tests pass; same 2 pre-existing projects-route failures still present.

Manual verification needed

The release workflows themselves can only be exercised by merging — the cron schedule won't trigger from a feature branch and the release environment requires NPM_TOKEN. The dashboard banner can be exercised locally by writing a stub cache file:

mkdir -p ~/.cache/ao && cat > ~/.cache/ao/update-check.json <<JSON
{"latestVersion":"99.0.0","checkedAt":"$(date -Iseconds)","currentVersionAtCheck":"$(node -p "require('@aoagents/ao/package.json').version")","channel":"stable"}
JSON
ao config set updateChannel stable
ao start

Closes #1525

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 10, 2026

Test Coverage Report

Metric Value
Lines covered 2739/3398
Lines not covered 659/3398
Overall coverage 80.6%
Per-file breakdown
File Coverage
packages/cli/src/commands/config.ts 19/89 (21.3%)
packages/cli/src/commands/start.ts 853/1225 (69.6%)
packages/cli/src/commands/update.ts 244/259 (94.2%)
packages/cli/src/lib/update-channel-onboarding.ts 55/63 (87.3%)
packages/cli/src/lib/update-check.ts 310/329 (94.2%)
packages/cli/src/program.ts 57/58 (98.3%)
packages/core/src/global-config.ts 302/366 (82.5%)
packages/core/src/update-cache.ts 0/16 (0.0%)
packages/core/src/version-compare.ts 34/34 (100.0%)
packages/web/src/app/api/update/route.ts 56/64 (87.5%)
packages/web/src/app/api/version/route.ts 31/33 (93.9%)
packages/web/src/components/Dashboard.tsx 678/753 (90.0%)
packages/web/src/components/UpdateBanner.tsx 100/109 (91.7%)

Uncovered lines

  • packages/cli/src/commands/config.ts: L28-L30, L32-L39, L41-L53, L55-L69, L71-L80, L91-L104, L111-L117
  • packages/cli/src/commands/start.ts: L116-L117, L128-L130, L133-L135, L137, L139-L143, L146-L147, L149, L151-L155, L157-L159, L199-L200, L205-L223, L225-L232, L234-L235, L241-L247, L249, L280-L281, L295-L297, L306-L307, L310-L334, L379-L393, L395-L398, L400-L414, L442, L453, L477-L478, L481-L482, L490-L491, L498-L503, L541-L542, L566-L569, L583-L584, L591-L601, L604-L605, L629-L630, L633-L637, L645-L652, L659-L660, L697-L700, L715-L720, L737-L739, L741-L748, L750-L752, L754-L759, L761-L762, L861-L864, L911, L933-L941, L969-L977, L985-L989, L998-L1000, L1033-L1034, L1050-L1051, L1057-L1058, L1060-L1061, L1065, L1120-L1121, L1139-L1144, L1177-L1178, L1184-L1185, L1256, L1276-L1277, L1361-L1362, L1414-L1431, L1488, L1522-L1524, L1547-L1554, L1581-L1589, L1600-L1601, L1635-L1636, L1644-L1655, L1658, L1674, L1680, L1683-L1691, L1701-L1702, L1743-L1746, L1749, L1751-L1753, L1758-L1759, L1768-L1772, L1774-L1775, L1784-L1785, L1795-L1800, L1836-L1842
  • packages/cli/src/commands/update.ts: L147-L148, L156-L162, L177-L178, L191-L192, L201-L202
  • packages/cli/src/lib/update-channel-onboarding.ts: L51-L52, L123-L128
  • packages/cli/src/lib/update-check.ts: L97-L98, L107-L108, L125-L126, L146-L148, L150-L151, L222-L223, L242-L243, L339-L340, L405-L406
  • packages/cli/src/program.ts: L59
  • packages/core/src/global-config.ts: L26, L38, L97, L381-L382, L404, L427, L447-L449, L452, L471, L478, L486-L487, L489, L492, L522-L524, L537-L539, L541-L543, L545-L546, L548-L550, L600, L617, L625-L631, L634, L661, L671, L720-L721, L733, L742, L881, L889, L978, L989, L991, L995, L997, L999, L1003, L1110, L1143, L1155-L1156, L1212-L1214, L1223
  • packages/core/src/update-cache.ts: L53-L55, L60-L64, L66, L82-L87, L92
  • packages/web/src/app/api/update/route.ts: L91-L98
  • packages/web/src/app/api/version/route.ts: L37-L38
  • packages/web/src/components/Dashboard.tsx: L62-L63, L88-L89, L136, L230, L309-L335, L346-L348, L353-L355, L371-L385, L396-L398, L404-L406, L478, L483-L488, L615, L705-L706, L810, L827-L830
  • packages/web/src/components/UpdateBanner.tsx: L35, L67, L92, L95-L97, L125-L127

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 10, 2026

Greptile Summary

This PR implements the full release pipeline: weekly stable releases via changesets, nightly canary publishes via cron, and a CLI/dashboard update experience with channel awareness (stable/nightly/manual), session guards, and an onboarding prompt.

  • Release infrastructure: Adds canary.yml (cron Fri–Tue, 23:00 IST) and release.yml (changesets/action Version Packages PR flow); configures the changeset linked group, snapshot template, and marks @aoagents/ao-web private.
  • CLI update pipeline: Introduces updateChannel + installMethod global-config fields, channel-aware registry fetches, prerelease semver comparison in @aoagents/ao-core, active-session guard in ao update and POST /api/update, and the ao config set command.
  • Dashboard banner: UpdateBanner.tsx reads the CLI-maintained cache via GET /api/version (cache-only, never network) and offers a one-click POST /api/update that refuses when sessions are active (409).

Confidence Score: 5/5

Safe to merge. All previously flagged issues are confirmed fixed; the single new observation is a minor style-guide deviation that does not affect correctness.

The release infrastructure, channel-aware update pipeline, active-session guard, and dashboard banner are all well-structured. All previously flagged issues from prior review rounds are confirmed fixed. The core version comparison logic lives in a single tested location in @aoagents/ao-core. The remaining observation is a cosmetic alignment with the internal spawn convention.

No files require special attention. The only note is the detached: true value in packages/web/src/app/api/update/route.ts, which is a style-guide alignment rather than a functional concern.

Important Files Changed

Filename Overview
packages/web/src/app/api/update/route.ts Active-session guard + detached spawn of ao update. Windows PATHEXT fix applied; detached: true always violates cross-platform guide's detached: !isWindows() rule.
packages/cli/src/commands/update.ts Adds active-session guard and channel-aware npm/pnpm/bun update routing. shell: isWindows() + windowsHide: true correctly applied to runNpmInstall.
packages/cli/src/lib/update-check.ts Channel-aware version checking, cache management, homebrew/bun path classifier; isVersionOutdated re-exported from core.
packages/core/src/version-compare.ts New shared isVersionOutdated implementation with prerelease segment comparison; correct SHA-suffix ordering via lexical fallback.
packages/web/src/app/api/version/route.ts Cache-only GET for banner; correctly handles git installs via cached.isOutdated fallback, channel mismatch, and legacy cache entries.
packages/web/src/components/UpdateBanner.tsx Banner hides on manual channel, dismissed version, and after update starts. handleDismiss resets phase so the hide condition fires from blocked/error state.
packages/cli/src/lib/update-channel-onboarding.ts Ask-once gate for update channel; uses createDefaultGlobalConfig() (platform-aware runtime default) when no config file exists yet.
.github/workflows/canary.yml New cron-driven nightly canary workflow; creates a minimal temp changeset when none exist, publishes under @nightly tag.
.github/workflows/release.yml Stable release via changesets/action on CI pass on main; Version Packages PR flow; correctly gates on push event and successful conclusion.

Sequence Diagram

sequenceDiagram
    participant CLI as ao CLI (startup)
    participant Cache as ~/.cache/ao/update-check.json
    participant Registry as npm registry
    participant Dashboard as Next.js Dashboard
    participant Banner as UpdateBanner.tsx

    CLI->>Registry: fetchLatestVersion(channel) [background]
    Registry-->>CLI: latestVersion
    CLI->>Cache: "writeCache({ latestVersion, channel, installMethod, isOutdated })"

    Dashboard->>Banner: mount
    Banner->>Dashboard: GET /api/version
    Dashboard->>Cache: readUpdateCheckCacheRaw()
    Cache-->>Dashboard: cached version info
    Dashboard-->>Banner: current, latest, channel, isOutdated

    alt isOutdated and channel not manual
        Banner->>Banner: show update strip
        Banner->>Dashboard: POST /api/update
        Dashboard->>Dashboard: ensureNoActiveSessions()
        alt sessions active
            Dashboard-->>Banner: 409 activeSessions message
            Banner->>Banner: phase blocked show error
        else no active sessions
            Dashboard->>CLI: spawn ao update detached
            Dashboard-->>Banner: 202 ok true
            Banner->>Banner: phase started hide banner
        end
    end
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/web/src/app/api/update/route.ts:79-84
Per the cross-platform guide (`docs/CROSS_PLATFORM.md`): "Use `detached: !isWindows()` rather than always-`true` or always-`false`." On Windows, `detached: true` creates a new console process group, which behaves differently from POSIX detachment. `child.unref()` already prevents the Node.js event loop from waiting for the child on both platforms, so setting `detached: !isWindows()` achieves the same "fire and forget" result while respecting the Windows process-group convention.

```suggestion
    const child = spawn("ao", ["update"], {
      detached: !isWindows(),
      stdio: "ignore",
      shell: isWindows(),
      windowsHide: true,
    });
```

Reviews (7): Last reviewed commit: "chore(release-train): cosmetic — workflo..." | Re-trigger Greptile

Comment thread packages/cli/src/lib/update-channel-onboarding.ts Outdated
Comment thread packages/cli/src/commands/config.ts Outdated
Comment thread packages/cli/src/lib/update-channel-onboarding.ts Outdated
Comment thread packages/web/src/app/api/version/route.ts Outdated
Comment thread packages/web/src/app/api/update/route.ts
Comment thread packages/web/src/components/UpdateBanner.tsx
suraj-markup and others added 3 commits May 10, 2026 22:27
… banner, cron

Implements the full release pipeline described in release-process.html
(supersedes #1525, which only had the workflow scaffolding).

A. Release infrastructure — .github/workflows/canary.yml triggers on a cron
   ('30 17 * * 5,6,0,1,2', i.e. 23:00 IST Fri–Tue) plus workflow_dispatch,
   without the stale-SHA guard or the merged-PR-comment step from #1525
   (cron has no merged-PR context). release.yml uses changesets/action.
   .changeset/config.json adds the snapshot template and moves the private
   @aoagents/ao-web to ignore[].

B. Channel awareness (packages/cli/src/lib/update-check.ts) — new
   updateChannel field in the global-config Zod schema (stable | nightly
   | manual; defaults to manual so existing users see no surprise installs).
   fetchLatestVersion now reads dist-tags[channel] from the registry;
   isVersionOutdated compares prerelease segments numerically + lexically
   so SHA-suffixed nightlies sort correctly. maybeShowUpdateNotice and
   scheduleBackgroundRefresh skip entirely on manual.

C. Active-session guard (packages/cli/src/commands/update.ts) — before
   any handle*Update proceeds, sm.list() filters for working/idle/
   needs_input/stuck and refuses with `N session(s) active. Run
   `ao stop` first.` instead of auto-stopping (per the design doc:
   surprise-killing user work is worse than refusing).

D. Soft auto-install + onboarding — handleNpmUpdate skips the confirm
   prompt on stable/nightly. New packages/cli/src/lib/update-channel-
   onboarding.ts prompts once on the first `ao start` after this lands;
   ask-once gate keyed on the absence of updateChannel in the global
   config; dismissal persists `manual`. New `ao config set updateChannel
   <value>` command (also handles installMethod).

E. Dashboard banner — packages/web/src/app/api/version/route.ts reads
   the same cache file the CLI writes (~/.cache/ao/update-check.json,
   XDG-aware) and rejects cache entries from a different channel.
   packages/web/src/app/api/update/route.ts duplicates the active-session
   guard so the dashboard can return a structured 409. New UpdateBanner
   component wired into Dashboard.tsx — Tailwind only, var(--color-*)
   tokens, dismissible per-version via localStorage, deferred fetch so
   it doesn't shift the call order in existing dashboard tests.

F. Bun + Homebrew detection (update-check.ts) — new classifiers for
   ~/.bun/install/global/ (auto-installs `bun add -g @aoagents/ao@<channel>`)
   and /Cellar/ao/ (notice-only — `brew upgrade ao`, never auto-install
   because brew owns the symlinks). New installMethod override field in
   the global config to pin detection when path heuristics fail.

Tests: +155 (B/C/F unit, onboarding ask-once gate, /api/version + /api/update,
UpdateBanner visibility/dismiss/click). pnpm test, pnpm typecheck, pnpm lint
all green for the changes; the same 10 pre-existing test failures observed
on main are still present (all environment-dependent: ~/.cache/ao state, codex
binary install, /private path canonicalization on macOS).

Closes #1525

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI fixes:
- Web /api/update spawn ENOENT — attach `child.on("error", ...)` so the
  asynchronous spawn-error event from a missing `ao` binary doesn't bubble
  up as an unhandled error and crash vitest. The route already returns 202
  before the error fires; on real installs the user sees "no version change"
  if the install fails.
- start.test.ts pollution — runStartup calls `maybePromptForUpdateChannel`,
  which (with isHumanCaller mocked to true) writes to the real
  ~/.agent-orchestrator/config.yaml on the CI runner via persistUpdateChannel.
  Subsequent tests then load that newly-created (empty-projects) config and
  report "No projects configured" instead of the expected "project not found".
  Fix: stub `update-channel-onboarding.js` in start.test.ts so runStartup
  is a no-op for the channel prompt.

Review feedback:
- (P1) `runtime: "tmux"` hardcoded default in `persistUpdateChannel` and
  `loadOrInit` would lock Windows users into a non-functional tmux config
  when they dismiss the channel prompt. Both now use `getDefaultRuntime()`,
  matching `makeEmptyGlobalConfig` in core's global-config.ts.
- (P2) `hasChosenUpdateChannel` JSDoc inverted — the second "True when"
  bullet actually described the False case. Rewritten with separate
  True/False sections that match the implementation.
- (P2) `isVersionOutdated` was duplicated between the CLI and the dashboard
  /api/version route. Moved to a new shared module
  `packages/core/src/version-compare.ts`, exported from `@aoagents/ao-core`,
  consumed by both CLI (re-exports as `isVersionOutdated`) and the web route
  directly. Added 14 unit tests in core for the canonical implementation.

Defensive: `maybePromptForUpdateChannel` now validates the prompt result via
`UpdateChannelSchema.safeParse` before persisting — never writes `undefined`
or an unrecognized string to disk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…back

- (P1) `ao update` silently never ran on Windows because `spawn("ao", ...)`
  doesn't consult PATHEXT, so npm's `ao.cmd` shim wasn't found and the
  async ENOENT was swallowed by the error handler. Add `shell: isWindows()`
  + `windowsHide: true` per the cross-platform guide.
- (P1) Dismiss button was inert when the banner was in the `blocked` (409)
  or `error` phase — `setDismissedFor` set the localStorage flag but the
  hide condition required `phase === "idle"`, so the banner stayed pinned
  until reload. `handleDismiss` now resets phase to idle (and clears the
  error message) so the existing condition fires. Added a regression test
  covering dismiss from the 409 path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@suraj-markup suraj-markup force-pushed the feat/release-train-ao174 branch from 9f29131 to 8d0984c Compare May 10, 2026 17:00
…resolves npm.cmd

(P1) The dashboard /api/update spawn got `shell: isWindows()` + `windowsHide:
true` in 9f29131, but `runNpmInstall` in the CLI's `ao update` command was
still missing the same fix. On Windows, `spawn("npm", ...)` without a shell
wrapper doesn't consult PATHEXT, so npm/pnpm/bun's `*.cmd` shims never
resolve and the install silently ENOENTs.

Mirror the fix into runNpmInstall — it's the single spawn site behind every
non-git, non-homebrew install path (npm-global, pnpm-global, bun-global,
unknown), so this one change covers all four install methods.

Tests:
- Mock `isWindows` from @aoagents/ao-core so the spawn options can be
  inspected per-platform.
- Assert `shell: true, windowsHide: true, stdio: "inherit"` on Windows.
- Assert `shell: false` on macOS / Linux.
- Parametrize over pnpm-global / bun-global to confirm the same options flow
  through every npm-style install command.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@suraj-markup
Copy link
Copy Markdown
Collaborator Author

Pushed 3c2f706 — mirrored the shell: isWindows() + windowsHide: true fix from /api/update (9f29131) into runNpmInstall in packages/cli/src/commands/update.ts. Without it, ao update on Windows would silently ENOENT because spawn("npm", ...) without a shell wrapper doesn't consult PATHEXT, so the *.cmd shims never resolve.

Same single spawn site handles every npm-style install command (npm-global, pnpm-global, bun-global, unknown), so this one change covers all of them. Added 4 unit tests asserting the spawn options on Windows vs macOS/Linux, parametrized across npm/pnpm/bun.

Comment thread packages/web/src/app/api/version/route.ts
suraj-markup and others added 5 commits May 11, 2026 03:39
…alls

(P1) The dashboard banner never appeared for git-installed users because
`/api/version` ran `isVersionOutdated(current, "origin/main")`, and
`parseVersion("origin/main")` produces NaN parts that the early-exit guard
catches with `return false`. Git installs cache `latestVersion` as a git
ref (not a semver) and a precomputed `isOutdated` flag from `git fetch +
merge-base`; the CLI special-cases this in `update-check.ts`. Mirror the
same pattern here:

  cached.installMethod === "git"
    ? cached.isOutdated === true
    : isVersionOutdated(current, latest)

Also extend the local CacheData with `installMethod?: string` and
`isOutdated?: boolean` so the new branch type-checks. Kept as `string`
rather than importing the CLI's `InstallMethod` type — the literal "git"
compare is the only thing that matters here, and the web package shouldn't
take a dep on @aoagents/ao-cli.

Two new tests cover the git-install path: one asserts isOutdated=true is
trusted from the cache, the other asserts isOutdated=false (current with
origin) is trusted too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…flag guard

#3 — ensureNoActiveSessions now consults loadGlobalConfig() first as a quick
"any projects registered?" check, then routes through loadConfig(globalPath)
only when the registry actually has projects (loadConfig dispatches to
buildEffectiveConfigFromGlobalConfigPath when given the canonical global path
— see packages/core/src/config.ts). Defends against AO_GLOBAL_CONFIG override
to a non-canonical path. Three new tests cover: registered-projects path
fires the guard correctly; empty registry returns early without building a
SessionManager; missing global file returns early without even reading it.

#4 — Restored the rejection of git-only flags on non-git installs. Users
copy/pasting `ao update --skip-smoke` from older docs would silently no-op
on npm/pnpm/bun installs. Now exits non-zero with:
"--skip-smoke only applies to git installs (current install: npm-global)."
Test it.each across npm/pnpm/bun/homebrew/unknown plus a positive test that
git installs still accept the flag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a stable user runs `ao config set updateChannel nightly` and then
`ao update`, isVersionOutdated(0.5.0, 0.5.0-nightly-abc) returns false (per
semver, prerelease < stable on equal base). The old code printed "Already
on latest nightly" and exited without installing — confusing, because the
install command we'd run is genuinely a different dist-tag.

Fix: snapshot the previously-cached channel BEFORE forcing a refresh, then
detect a switch via `previousChannel !== activeChannel && !info.isOutdated`.
On switch:
  - Don't take the "already on latest" early-return.
  - Print a yellow "Channel switch detected: was X, now Y." notice.
  - Force a confirm prompt regardless of stable/nightly soft-install,
    defaulting to "no" (channel-switch should be explicit). Manual users
    still see their normal prompt.

Onboarding copy now includes one line about channel switches: "switching
later prompts before installing the other channel's build."

4 new tests: explicit switch fires the prompt + installs on yes; declines
on no; same-channel doesn't fire (back to "Already on latest"); first-ever
update with no previous cache doesn't fire either.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…are cache, dedup export

#5 — UpdateBanner no longer wraps its mount fetch in setTimeout(0). Production
code shouldn't bend to test mock ordering. Instead, the two brittle Dashboard
tests that relied on `mockImplementationOnce` queue ordering now route by URL
via `mockImplementation`, and the cadence test asserts "no other endpoints
were touched" instead of "no fetch was touched at all". Also added a
deliberate "no interval / re-fetch" comment per #6.

#7 — Promoted core's `makeEmptyGlobalConfig` to the public
`createDefaultGlobalConfig` (kept the internal alias for back-compat). Both
the CLI's `persistUpdateChannel` and `loadOrInit` (in `ao config`) now call
it instead of inlining the same defaults block. Single source of truth.

#8 — New `packages/core/src/update-cache.ts` exports
`getUpdateCheckCachePath`, `readUpdateCheckCacheRaw`, and
`getInstalledAoVersion`. The CLI's `update-check.ts` keeps its richer
install-method/channel/git-rev validation but now delegates path resolution
and version lookup to core. The dashboard's `/api/version` route drops its
duplicated `getCachePath`/`readCache`/`getCurrentVersion` and consumes from
core directly. Cache layout is one file, not two.

#9 — Removed the duplicate `export { isManualOnlyInstall }` from
`update.ts` (also dropped the unused import). The canonical export lives in
`update-check.ts`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… changeset trim

#1  release.yml: added a comment above `workflows: [CI]` warning that GitHub
    matches by name (not filename) and silently no-ops on mismatch — so a
    rename of ci.yml's `name:` field would mean releases stop triggering.

#10 UpdateBanner: replaced text-[13px] / text-[12px] with text-sm / text-xs
    to match the dashboard's chrome scale.

#6  Banner refresh: noted in the existing useEffect comment that we don't
    re-fetch — re-evaluate if "user kept tab open for days, missed an
    update" becomes a real complaint.

#11 .changeset/release-train.md: dropped @aoagents/ao-web from the version
    bump list. The package is `private: true` and in changeset's ignore[],
    so listing it was cosmetic and would just clutter the eventual release
    notes with a non-published artifact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@suraj-markup
Copy link
Copy Markdown
Collaborator Author

Pushed bd25890, 655b6db, dfc7cb1, 3786ba5 addressing all 10 findings.

MUST fix (commit bd25890)

  • feat: implement core services (metadata, event-bus, tmux, session-manager, lifecycle-manager) #3loadConfig(globalPath) semantics in ensureNoActiveSessions. Now consults loadGlobalConfig() first as a quick "any projects registered?" check, then routes through loadConfig(globalPath) only when the registry actually has projects. Defends against AO_GLOBAL_CONFIG override to a non-canonical path. 3 new tests cover: registered-projects path fires the guard correctly; empty registry returns early; missing global file returns early without even reading it.
  • feat: implement SCM and tracker plugins (github, linear) #4 — Restored --skip-smoke rejection on non-git installs. Now exits non-zero with --skip-smoke only applies to git installs (current install: npm-global).. Test it.each across npm/pnpm/bun/homebrew/unknown plus a positive test that git installs still accept it.

SHOULD fix (commit 655b6db)

  • feat: implement runtime and workspace plugins (tmux, process, worktree, clone) #2 — Channel-switch prompt. Snapshots the previously-cached channel BEFORE forcing a refresh, then detects previousChannel !== activeChannel && !info.isOutdated and forces an explicit confirm with default=no. Onboarding copy gained one line about "switching later prompts before installing the other channel's build." 4 new tests cover the switch path, the decline path, the same-channel no-op, and the first-ever-update no-op.

POLISH (commit dfc7cb1)

  • feat: agent plugins, OpenCode plugin, integration tests, CI #5setTimeout(0) deferral gone. UpdateBanner runs its mount fetch synchronously again. The two brittle Dashboard tests (projectOverview, renderCadence) now route by URL via mockImplementation instead of relying on mockImplementationOnce queue ordering. The cadence test asserts "no other endpoints touched" rather than "no fetch at all."
  • feat: notifier-composio plugin + integration tests for all plugins #7createDefaultGlobalConfig() extracted in core. Promoted internal makeEmptyGlobalConfig to a public export; CLI's persistUpdateChannel and loadOrInit (in ao config) both consume it. Single source of truth.
  • Integration: wire core services to CLI and web dashboard #8 — Cache layout unified. New packages/core/src/update-cache.ts exports getUpdateCheckCachePath, readUpdateCheckCacheRaw, getInstalledAoVersion. Both the CLI's update-check.ts and the dashboard's /api/version route consume from there. The CLI keeps its richer per-call validation (install-method / channel / git-rev) layered on top.
  • Fix cross-cutting code convention issues across all packages #9isManualOnlyInstall deduped. Removed the re-export from update.ts (also dropped the unused import); canonical export stays in update-check.ts.

COSMETIC (commit 3786ba5)

Test status

  • CLI tests: 690 passed / 4 pre-existing failures (env-dependent: ~/.cache/ao state, /private realpath on macOS).
  • Web tests: 900 passed / 2 pre-existing failures in projects-route.test.ts.
  • pnpm typecheck and pnpm lint both clean (0 errors).

Nothing deferred to follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant