diff --git a/.changeset/cutover-aware-ao-update.md b/.changeset/cutover-aware-ao-update.md new file mode 100644 index 000000000..7934a3f0d --- /dev/null +++ b/.changeset/cutover-aware-ao-update.md @@ -0,0 +1,11 @@ +--- +"@aoagents/ao-cli": minor +--- + +Teach `ao update` to perform the legacy-to-rewrite cutover (bridge 0.9.6). + +When a rewrite build is published under the npm `next` dist-tag (or `AO_CUTOVER_VERSION` is set) and the current install is still legacy (`major.minor < 0.10`), `ao update` now migrates the user's data via `ao migrate` (issue #2129) and installs the rewrite at the exact pinned version instead of running the normal channel update. + +The cutover flow: refuse if any active worker session is running (the orchestrator's own state never blocks), require a terminal confirmation (never auto-confirm a dashboard/api-invoked spawn), stop the daemon without restoring, run migration before the install replaces the legacy binary, install the rewrite, verify the new version, and finish without restarting the legacy daemon. `--check` now also reports `cutoverAvailable` and `cutoverTarget`. + +When no `next` build exists (the common case), `ao update` behaves exactly as before across all install methods. diff --git a/packages/cli/src/commands/update.ts b/packages/cli/src/commands/update.ts index ea70135aa..19c97eb6d 100644 --- a/packages/cli/src/commands/update.ts +++ b/packages/cli/src/commands/update.ts @@ -404,7 +404,9 @@ interface MigrationResult { * from stdout — the contract is documented on {@link MigrationSummary}. */ async function runMigration(): Promise { - const result = await runCommandCapture("ao", ["migrate", "--json"], { echo: true }); + // No echo: `--json` emits a machine-readable blob, not human progress. We + // surface a readable summary on success and the raw output on failure. + const result = await runCommandCapture("ao", ["migrate", "--json"]); return { exitCode: result.exitCode, summary: parseMigrationSummary(result.output), @@ -412,6 +414,13 @@ async function runMigration(): Promise { }; } +/** One-line human summary of a migration result, for the success path. */ +function formatMigrationSummary(summary: MigrationSummary | null): string { + if (!summary) return "Migration complete."; + const { projects, orchestrators } = summary; + return `Migration complete: ${projects.created} project(s), ${orchestrators.created} orchestrator(s) migrated.`; +} + function parseMigrationSummary(output: string): MigrationSummary | null { const match = output.match(/\{[\s\S]*\}/); if (!match) return null; @@ -513,13 +522,15 @@ async function handleCutover(method: InstallMethod, target: string): Promise