abtars 0.2.1-alpha.4 → 0.2.1-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,21 +2,21 @@
2
2
  import { createRequire as __bundleCreateRequire } from 'node:module'; import { fileURLToPath as __bundleFileURLToPath } from 'node:url'; import { dirname as __bundleDirname } from 'node:path'; const require = __bundleCreateRequire(import.meta.url); const __chunk_filename = __bundleFileURLToPath(import.meta.url); const __chunk_dirname = __bundleDirname(__chunk_filename);
3
3
  import {
4
4
  install
5
- } from "./chunk-RJJWPMUZ.js";
5
+ } from "./chunk-4BUOO6WI.js";
6
6
  import {
7
- RETENTION,
8
7
  acquireLock,
9
- activate,
8
+ atomicSwap,
9
+ cleanStaleStaging,
10
+ configSnapshot,
10
11
  emptyManifest,
11
12
  hashFile,
12
- inspectLock,
13
+ healthProbe,
13
14
  packagePaths,
14
- pruneReleases,
15
- readCurrent,
16
15
  readManifest,
17
- releaseExists,
18
- writeManifest
19
- } from "./chunk-RVERPUHT.js";
16
+ readSentinel,
17
+ writeManifest,
18
+ writeSentinel
19
+ } from "./chunk-RSWUPUNA.js";
20
20
  import {
21
21
  restart
22
22
  } from "./chunk-URAQLQ2U.js";
@@ -488,7 +488,7 @@ async function runInteractive(existing) {
488
488
  const { intro, outro, text, select, confirm: confirm2, isCancel, cancel } = await import("./dist-J3T4XVKX.js");
489
489
  intro("abtars onboard \u2014 first-time setup");
490
490
  const noteEmpty = "press Enter to skip";
491
- const { packagePaths: pp, readManifest: rm3 } = await import("./deploy-lib-import-HCMZCKZD.js");
491
+ const { packagePaths: pp, readManifest: rm3 } = await import("./deploy-lib-import-SBKXDD3F.js");
492
492
  const mfPaths = pp("abtars");
493
493
  const mf = await rm3(mfPaths.manifest);
494
494
  const installMode = mf?.installMode ?? "supervised";
@@ -924,7 +924,7 @@ async function onboard(opts) {
924
924
  }
925
925
  if (secrets.length > 0) process.stdout.write(`\u2713 ${secrets.length} secrets \u2192 ${secretDirPath}
926
926
  `);
927
- const { writeManifest: writeManifest2 } = await import("./deploy-lib-import-HCMZCKZD.js");
927
+ const { writeManifest: writeManifest2 } = await import("./deploy-lib-import-SBKXDD3F.js");
928
928
  const mf = await readManifest(paths.manifest);
929
929
  if (mf) await writeManifest2(paths.manifest, { ...mf, installMode: answers.installMode });
930
930
  process.stdout.write(`\u2713 install mode: ${answers.installMode}
@@ -1055,8 +1055,8 @@ Next: 'abtars update' to build, then start the bridge.
1055
1055
  `);
1056
1056
  return 0;
1057
1057
  }
1058
- const { existsSync: existsSync8 } = await import("node:fs");
1059
- const hasRelease = existsSync8(join6(paths.home, "current"));
1058
+ const { existsSync: existsSync10 } = await import("node:fs");
1059
+ const hasRelease = existsSync10(join6(paths.home, "current"));
1060
1060
  if (!hasRelease) {
1061
1061
  process.stdout.write(`
1062
1062
  Next: run 'abtars update' to stage the release, then start the bridge.
@@ -1076,9 +1076,9 @@ Next: start the bridge
1076
1076
  return 0;
1077
1077
  }
1078
1078
  async function seedDefaultTasks(chatId, abtarsHome4) {
1079
- const { existsSync: existsSync8 } = await import("node:fs");
1079
+ const { existsSync: existsSync10 } = await import("node:fs");
1080
1080
  const tasksJson = join6(abtarsHome4, "tasks", "tasks.json");
1081
- if (existsSync8(tasksJson)) {
1081
+ if (existsSync10(tasksJson)) {
1082
1082
  process.stdout.write(`\u2022 tasks.json exists \u2014 skipping default-task seed
1083
1083
  `);
1084
1084
  return;
@@ -1091,7 +1091,7 @@ async function seedDefaultTasks(chatId, abtarsHome4) {
1091
1091
  ];
1092
1092
  let templatePath = null;
1093
1093
  for (const p of candidates) {
1094
- if (existsSync8(p)) {
1094
+ if (existsSync10(p)) {
1095
1095
  templatePath = p;
1096
1096
  break;
1097
1097
  }
@@ -1148,80 +1148,59 @@ async function seedDefaultTasks(chatId, abtarsHome4) {
1148
1148
  }
1149
1149
 
1150
1150
  // src/cli/commands/rollback.ts
1151
- import { hostname } from "node:os";
1152
- async function rollback(opts) {
1151
+ import { existsSync as existsSync5, renameSync as renameSync3, rmSync as rmSync2 } from "node:fs";
1152
+ import { join as join7 } from "node:path";
1153
+ async function rollback() {
1153
1154
  const paths = packagePaths("abtars");
1154
1155
  const manifest = await readManifest(paths.manifest);
1155
- if (!manifest || !manifest.version) {
1156
- process.stderr.write(`No active release; nothing to roll back.
1156
+ if (!existsSync5(paths.appPrev)) {
1157
+ process.stderr.write(`Nothing to roll back to (no app.prev/ found).
1157
1158
  `);
1158
1159
  return 2;
1159
1160
  }
1160
- if (manifest.priorReleases.length === 0 && opts.to === void 0) {
1161
- process.stderr.write(`No prior releases recorded; nothing to roll back to.
1161
+ if (!manifest?.version) {
1162
+ process.stderr.write(`No active release in manifest; nothing to roll back.
1162
1163
  `);
1163
1164
  return 2;
1164
1165
  }
1165
- const target = opts.to ?? manifest.priorReleases[0].version;
1166
- if (target === manifest.version) {
1167
- process.stdout.write(`Already at ${target}; no-op.
1168
- `);
1169
- return 0;
1170
- }
1171
- if (!await releaseExists(paths.releases, target)) {
1172
- const available = [manifest.version, ...manifest.priorReleases.map((r) => r.version)];
1173
- process.stderr.write(
1174
- `Target release '${target}' does not exist in ${paths.releases}.
1175
- Available: ${available.join(", ")}
1176
- If pruned, rebuild from the target's git SHA with 'abtars update' instead.
1177
- `
1178
- );
1179
- return 2;
1180
- }
1181
- const targetRecord = target === manifest.version ? {
1182
- version: manifest.version,
1183
- commit: manifest.commit,
1184
- activatedAt: manifest.activatedAt,
1185
- packageLockHash: manifest.packageLockHash
1186
- } : manifest.priorReleases.find((r) => r.version === target) ?? null;
1187
- if (manifest.packageLockHash && targetRecord?.packageLockHash && manifest.packageLockHash !== targetRecord.packageLockHash) {
1188
- process.stderr.write(
1189
- `v${manifest.version} pinned different deps than v${target} (package-lock hashes differ).
1190
- Rollback via symlink is unsafe. Instead:
1191
- git checkout ${targetRecord.commit ?? "<commit-from-manifest>"}
1192
- abtars update --from-local
1193
- This rebuilds node_modules/ to match the target release.
1194
- `
1195
- );
1196
- return 3;
1197
- }
1198
- const release = await acquireLock(paths.lock, `rollback --to ${target}`);
1166
+ const release = await acquireLock(paths.lock, "rollback");
1199
1167
  try {
1200
- await activate(paths.current, target);
1201
- process.stdout.write(`\u2713 current -> releases/${target}
1202
- `);
1203
- const newPrior = {
1204
- version: manifest.version,
1205
- commit: manifest.commit,
1206
- activatedAt: manifest.activatedAt,
1207
- packageLockHash: manifest.packageLockHash
1208
- };
1209
- const remainingPriors = manifest.priorReleases.filter((r) => r.version !== target);
1210
- await writeManifest(paths.manifest, {
1211
- ...manifest,
1212
- version: target,
1213
- commit: targetRecord?.commit ?? null,
1214
- packageLockHash: targetRecord?.packageLockHash ?? null,
1215
- activatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1216
- priorReleases: [newPrior, ...remainingPriors]
1168
+ const brokenDir = join7(paths.home, "app.broken");
1169
+ rmSync2(brokenDir, { recursive: true, force: true });
1170
+ renameSync3(paths.app, brokenDir);
1171
+ renameSync3(paths.appPrev, paths.app);
1172
+ rmSync2(brokenDir, { recursive: true, force: true });
1173
+ process.stdout.write(`\u2713 rolled back: app.prev/ \u2192 app/
1174
+ `);
1175
+ if (manifest.previousVersion) {
1176
+ await writeManifest(paths.manifest, {
1177
+ ...manifest,
1178
+ version: manifest.previousVersion,
1179
+ commit: manifest.previousCommit,
1180
+ activatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1181
+ previousVersion: manifest.version,
1182
+ previousCommit: manifest.commit
1183
+ });
1184
+ process.stdout.write(`\u2713 manifest: ${manifest.version} \u2192 ${manifest.previousVersion}
1185
+ `);
1186
+ }
1187
+ const restartTs = Date.now();
1188
+ const { restart: restart2 } = await import("./abtars-restart.js");
1189
+ process.stdout.write(`\u267B\uFE0F Restarting bridge...
1190
+ `);
1191
+ await restart2({ cold: true }).catch(() => {
1217
1192
  });
1218
- process.stdout.write(`\u2713 manifest updated
1193
+ const health = await healthProbe(paths.home, restartTs, 6e4);
1194
+ if (health.healthy) {
1195
+ process.stdout.write(`\u2713 Bridge healthy (PID ${health.pid})
1219
1196
  `);
1197
+ } else {
1198
+ process.stderr.write(`\u26A0\uFE0F Bridge may not have started. Check logs.
1199
+ `);
1200
+ }
1220
1201
  process.stdout.write(`
1221
- Rollback complete: ${target}
1202
+ Rollback complete.
1222
1203
  `);
1223
- void emptyManifest;
1224
- void hostname;
1225
1204
  return 0;
1226
1205
  } finally {
1227
1206
  await release();
@@ -1229,13 +1208,12 @@ Rollback complete: ${target}
1229
1208
  }
1230
1209
 
1231
1210
  // src/cli/commands/status.ts
1211
+ import { existsSync as existsSync6 } from "node:fs";
1232
1212
  import { readFileSync as readFileSync3 } from "node:fs";
1233
- import { join as join7 } from "node:path";
1213
+ import { join as join8 } from "node:path";
1234
1214
  async function status() {
1235
1215
  const paths = packagePaths("abtars");
1236
1216
  const manifest = await readManifest(paths.manifest);
1237
- const current = await readCurrent(paths.current);
1238
- const lock = await inspectLock(paths.lock);
1239
1217
  if (!manifest) {
1240
1218
  process.stdout.write(
1241
1219
  `abtars: not installed (no manifest at ${paths.manifest})
@@ -1244,6 +1222,8 @@ Run 'abtars install' to set up.
1244
1222
  );
1245
1223
  return 1;
1246
1224
  }
1225
+ const appExists = existsSync6(paths.app);
1226
+ const appPrevExists = existsSync6(paths.appPrev);
1247
1227
  const lines = [
1248
1228
  `abtars status`,
1249
1229
  ` home: ${paths.home}`,
@@ -1253,68 +1233,62 @@ Run 'abtars install' to set up.
1253
1233
  ` source: ${manifest.source}`,
1254
1234
  ` mode: ${manifest.installMode ?? "supervised"}`,
1255
1235
  ` activated: ${manifest.activatedAt}`,
1256
- ` current ->: ${current ?? "(missing)"}`,
1257
- ` host: ${manifest.host}`,
1258
- ` prior: ${manifest.priorReleases.length > 0 ? manifest.priorReleases.map((r) => r.version).join(", ") : "(none)"}`
1236
+ ` app/: ${appExists ? "\u2713 present" : "\u2717 missing"}`,
1237
+ ` app.prev/: ${appPrevExists ? "\u2713 present" : "\u25CB none"}`,
1238
+ ` previous: ${manifest.previousVersion ?? "(none)"}`,
1239
+ ` host: ${manifest.host}`
1259
1240
  ];
1260
- if (lock.held) {
1261
- const alive = (() => {
1262
- try {
1263
- process.kill(lock.content.pid, 0);
1264
- return true;
1265
- } catch {
1266
- return false;
1267
- }
1268
- })();
1269
- lines.push(
1270
- ` bridge: ${alive ? "\u25CF running" : "\u2717 dead"} (pid ${lock.content.pid})${lock.stale ? " \u2014 STALE" : ""}`
1271
- );
1272
- } else {
1273
- try {
1274
- const bridgeLock = JSON.parse(readFileSync3(join7(paths.home, "bridge.lock"), "utf-8"));
1275
- if (bridgeLock.pid) {
1276
- const alive = (() => {
1277
- try {
1278
- process.kill(bridgeLock.pid, 0);
1279
- return true;
1280
- } catch {
1281
- return false;
1282
- }
1283
- })();
1284
- lines.push(` bridge: ${alive ? "\u25CF running" : "\u2717 dead"} (pid ${bridgeLock.pid})`);
1285
- } else {
1286
- lines.push(` bridge: \u25CB stopped`);
1287
- }
1288
- } catch {
1241
+ try {
1242
+ const bridgeLock = JSON.parse(readFileSync3(join8(paths.home, "bridge.lock"), "utf-8"));
1243
+ if (bridgeLock.pid) {
1244
+ const alive = (() => {
1245
+ try {
1246
+ process.kill(bridgeLock.pid, 0);
1247
+ return true;
1248
+ } catch {
1249
+ return false;
1250
+ }
1251
+ })();
1252
+ lines.push(` bridge: ${alive ? "\u25CF running" : "\u2717 dead"} (pid ${bridgeLock.pid})`);
1253
+ } else {
1289
1254
  lines.push(` bridge: \u25CB stopped`);
1290
1255
  }
1256
+ } catch {
1257
+ lines.push(` bridge: \u25CB stopped`);
1291
1258
  }
1292
1259
  process.stdout.write(`${lines.join("\n")}
1293
1260
  `);
1294
- if (current !== null && manifest.version !== "" && current !== manifest.version) {
1295
- process.stderr.write(
1296
- `
1297
- Warning: current symlink points at '${current}' but manifest says '${manifest.version}'.
1298
- Re-run 'abtars update' or 'abtars rollback' to reconcile.
1299
- `
1300
- );
1261
+ const sentinel = readSentinel(paths.home);
1262
+ if (sentinel?.status === "pending") {
1263
+ const age = Date.now() - new Date(sentinel.startedAt).getTime();
1264
+ if (age > 5 * 6e4) {
1265
+ process.stderr.write(`
1266
+ \u26A0\uFE0F Last update (${sentinel.version}) may have failed \u2014 bridge never confirmed boot.
1267
+ `);
1268
+ return 1;
1269
+ }
1270
+ }
1271
+ if (!appExists) {
1272
+ process.stderr.write(`
1273
+ \u26A0\uFE0F app/ directory missing. Run 'abtars update' to deploy.
1274
+ `);
1301
1275
  return 1;
1302
1276
  }
1303
1277
  return 0;
1304
1278
  }
1305
1279
 
1306
1280
  // src/cli/commands/update.ts
1307
- init_log_and_swallow();
1308
- import { hostname as hostname2 } from "node:os";
1309
- import { join as join10, dirname as dirname3 } from "node:path";
1310
- import { readFileSync as readFileSync5, existsSync as existsSync7 } from "node:fs";
1311
- import { copyFile as copyFile2, mkdir as mkdir4, chmod, readdir, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
1281
+ import { hostname } from "node:os";
1282
+ import { join as join11 } from "node:path";
1283
+ import { readFileSync as readFileSync5, existsSync as existsSync9 } from "node:fs";
1284
+ import { copyFile as copyFile2, mkdir as mkdir4, chmod, readdir } from "node:fs/promises";
1285
+ import { rmSync as rmSync3, cpSync, readdirSync as readdirSync3, mkdirSync as mkdirSync2 } from "node:fs";
1312
1286
 
1313
1287
  // src/cli/update-sources/local.ts
1314
1288
  import { spawnSync as spawnSync2 } from "node:child_process";
1315
- import { existsSync as existsSync5 } from "node:fs";
1289
+ import { existsSync as existsSync7 } from "node:fs";
1316
1290
  import { copyFile, cp, mkdir as mkdir2, readFile as readFile2, rm, writeFile as writeFile2 } from "node:fs/promises";
1317
- import { join as join8 } from "node:path";
1291
+ import { join as join9 } from "node:path";
1318
1292
  var LocalBuildError = class extends Error {
1319
1293
  constructor(message, hint) {
1320
1294
  super(hint ? `${message}
@@ -1376,7 +1350,7 @@ ${upstream} is ahead by ${behind} commit${behind === 1 ? "" : "s"}.`,
1376
1350
  return { commit, branch };
1377
1351
  }
1378
1352
  async function readPackageVersion(repoRoot) {
1379
- const pkg = JSON.parse(await readFile2(join8(repoRoot, "package.json"), "utf-8"));
1353
+ const pkg = JSON.parse(await readFile2(join9(repoRoot, "package.json"), "utf-8"));
1380
1354
  if (typeof pkg.version !== "string") {
1381
1355
  throw new LocalBuildError("package.json has no version field.");
1382
1356
  }
@@ -1384,30 +1358,30 @@ async function readPackageVersion(repoRoot) {
1384
1358
  }
1385
1359
  function makeLocalBuildSource(opts = {}) {
1386
1360
  const repoRoot = opts.repoRoot ?? process.cwd();
1387
- const isNpmPackage = !existsSync5(join8(repoRoot, ".git"));
1361
+ const isNpmPackage = !existsSync7(join9(repoRoot, ".git"));
1388
1362
  return {
1389
1363
  name: "local",
1390
1364
  async prepare(ctx) {
1391
1365
  if (isNpmPackage) {
1392
- const bundleDir = join8(repoRoot, "bundle");
1393
- if (!existsSync5(bundleDir)) {
1366
+ const bundleDir = join9(repoRoot, "bundle");
1367
+ if (!existsSync7(bundleDir)) {
1394
1368
  throw new Error(`No bundle/ found at ${repoRoot}. Run from the abtars npm package or a git checkout.`);
1395
1369
  }
1396
1370
  const pkgVersion2 = await readPackageVersion(repoRoot);
1397
1371
  const version2 = pkgVersion2;
1398
- const stagedPath2 = join8(ctx.releasesDir, version2);
1372
+ const stagedPath2 = ctx.stagingDir;
1399
1373
  await rm(stagedPath2, { recursive: true, force: true });
1400
1374
  await mkdir2(stagedPath2, { recursive: true });
1401
- await cp(bundleDir, join8(stagedPath2, "bundle"), { recursive: true });
1402
- const coreDir = join8(repoRoot, "core");
1403
- if (existsSync5(coreDir)) await cp(coreDir, join8(stagedPath2, "core"), { recursive: true });
1404
- const scriptsSrc = join8(repoRoot, "scripts");
1405
- if (existsSync5(scriptsSrc)) await cp(scriptsSrc, join8(stagedPath2, "scripts"), { recursive: true });
1406
- const configSrc = join8(repoRoot, "config");
1407
- if (existsSync5(configSrc)) await cp(configSrc, join8(stagedPath2, "config"), { recursive: true });
1408
- const manifestSrc = join8(repoRoot, "install-manifest.json");
1409
- if (existsSync5(manifestSrc)) await copyFile(manifestSrc, join8(stagedPath2, "install-manifest.json"));
1410
- await writeFile2(join8(stagedPath2, "package.json"), JSON.stringify({ type: "module", name: "abtars", version: version2 }, null, 2) + "\n");
1375
+ await cp(bundleDir, join9(stagedPath2, "bundle"), { recursive: true });
1376
+ const coreDir = join9(repoRoot, "core");
1377
+ if (existsSync7(coreDir)) await cp(coreDir, join9(stagedPath2, "core"), { recursive: true });
1378
+ const scriptsSrc = join9(repoRoot, "scripts");
1379
+ if (existsSync7(scriptsSrc)) await cp(scriptsSrc, join9(stagedPath2, "scripts"), { recursive: true });
1380
+ const configSrc = join9(repoRoot, "config");
1381
+ if (existsSync7(configSrc)) await cp(configSrc, join9(stagedPath2, "config"), { recursive: true });
1382
+ const manifestSrc = join9(repoRoot, "install-manifest.json");
1383
+ if (existsSync7(manifestSrc)) await copyFile(manifestSrc, join9(stagedPath2, "install-manifest.json"));
1384
+ await writeFile2(join9(stagedPath2, "package.json"), JSON.stringify({ type: "module", name: "abtars", version: version2 }, null, 2) + "\n");
1411
1385
  process.stdout.write(`\u2713 staged ${version2} (from npm package)
1412
1386
  `);
1413
1387
  return { version: version2, stagedPath: stagedPath2, commit: null, branch: null, packageLockHash: null, source: "local" };
@@ -1418,42 +1392,18 @@ function makeLocalBuildSource(opts = {}) {
1418
1392
  if (opts.skipInstall !== true) {
1419
1393
  runCmd("npm", ["install", "--no-audit", "--no-fund"], repoRoot);
1420
1394
  }
1421
- const useBundle = process.env["AGENTBRIDGE_BUILD_MODE"] !== "tsc";
1422
- if (useBundle) {
1423
- runCmd("npm", ["run", "bundle"], repoRoot);
1424
- const stagedPath2 = join8(ctx.releasesDir, version);
1425
- await rm(stagedPath2, { recursive: true, force: true });
1426
- await mkdir2(stagedPath2, { recursive: true });
1427
- await cp(join8(repoRoot, "bundle"), join8(stagedPath2, "bundle"), { recursive: true });
1428
- const coreSkillsSrc = join8(repoRoot, "core", "skills");
1429
- if (existsSync5(coreSkillsSrc)) {
1430
- await cp(coreSkillsSrc, join8(stagedPath2, "core", "skills"), { recursive: true });
1431
- }
1432
- await writeFile2(join8(stagedPath2, "package.json"), JSON.stringify({ type: "module", name: "abtars", version }, null, 2) + "\n");
1433
- await copyFile(join8(repoRoot, "install-manifest.json"), join8(stagedPath2, "install-manifest.json"));
1434
- const packageLockHash2 = await hashFile(join8(repoRoot, "package-lock.json"));
1435
- return { version, stagedPath: stagedPath2, commit, branch, packageLockHash: packageLockHash2, source: "local" };
1436
- }
1437
- runCmd("npm", ["run", "build"], repoRoot);
1438
- const stagedPath = join8(ctx.releasesDir, version);
1395
+ runCmd("npm", ["run", "bundle"], repoRoot);
1396
+ const stagedPath = ctx.stagingDir;
1439
1397
  await rm(stagedPath, { recursive: true, force: true });
1440
1398
  await mkdir2(stagedPath, { recursive: true });
1441
- await cp(join8(repoRoot, "dist"), join8(stagedPath, "dist"), { recursive: true });
1442
- await copyFile(join8(repoRoot, "install-manifest.json"), join8(stagedPath, "install-manifest.json"));
1443
- await rm(ctx.nodeModulesDir, { recursive: true, force: true });
1444
- await mkdir2(ctx.nodeModulesDir, { recursive: true });
1445
- const rsyncResult = spawnSync2(
1446
- "rsync",
1447
- ["-aL", "--quiet", `${join8(repoRoot, "node_modules")}/`, `${ctx.nodeModulesDir}/`],
1448
- { stdio: "inherit" }
1449
- );
1450
- if (rsyncResult.status !== 0) {
1451
- throw new LocalBuildError(
1452
- `rsync of node_modules failed (status ${rsyncResult.status ?? -1})`,
1453
- `Ensure rsync is installed. Falling back to node cp would re-create symlinks.`
1454
- );
1399
+ await cp(join9(repoRoot, "bundle"), join9(stagedPath, "bundle"), { recursive: true });
1400
+ const coreSkillsSrc = join9(repoRoot, "core", "skills");
1401
+ if (existsSync7(coreSkillsSrc)) {
1402
+ await cp(coreSkillsSrc, join9(stagedPath, "core", "skills"), { recursive: true });
1455
1403
  }
1456
- const packageLockHash = await hashFile(join8(repoRoot, "package-lock.json"));
1404
+ await writeFile2(join9(stagedPath, "package.json"), JSON.stringify({ type: "module", name: "abtars", version }, null, 2) + "\n");
1405
+ await copyFile(join9(repoRoot, "install-manifest.json"), join9(stagedPath, "install-manifest.json"));
1406
+ const packageLockHash = await hashFile(join9(repoRoot, "package-lock.json"));
1457
1407
  return { version, stagedPath, commit, branch, packageLockHash, source: "local" };
1458
1408
  }
1459
1409
  };
@@ -1461,9 +1411,9 @@ function makeLocalBuildSource(opts = {}) {
1461
1411
 
1462
1412
  // src/cli/update-sources/npm.ts
1463
1413
  import { spawnSync as spawnSync3 } from "node:child_process";
1464
- import { existsSync as existsSync6, readFileSync as readFileSync4, unlinkSync as unlinkSync4 } from "node:fs";
1414
+ import { existsSync as existsSync8, readFileSync as readFileSync4, unlinkSync as unlinkSync4 } from "node:fs";
1465
1415
  import { mkdir as mkdir3, rm as rm2 } from "node:fs/promises";
1466
- import { join as join9 } from "node:path";
1416
+ import { join as join10 } from "node:path";
1467
1417
  var TIMEOUT_MS = 6e4;
1468
1418
  function run(cmd, args, cwd) {
1469
1419
  const r = spawnSync3(cmd, args, { cwd, encoding: "utf-8", timeout: TIMEOUT_MS });
@@ -1473,7 +1423,7 @@ function run(cmd, args, cwd) {
1473
1423
  }
1474
1424
  function readLocalVersion(home) {
1475
1425
  try {
1476
- const pkg = JSON.parse(readFileSync4(join9(home, "current", "package.json"), "utf-8"));
1426
+ const pkg = JSON.parse(readFileSync4(join10(home, "app", "package.json"), "utf-8"));
1477
1427
  return pkg.version ?? null;
1478
1428
  } catch {
1479
1429
  return null;
@@ -1488,14 +1438,14 @@ function makeNpmSource(packageName) {
1488
1438
  if (latest === current) {
1489
1439
  throw new Error(`Already at latest version (${latest}). Nothing to update.`);
1490
1440
  }
1491
- const stagedPath = join9(ctx.releasesDir, latest);
1441
+ const stagedPath = ctx.stagingDir;
1492
1442
  await rm2(stagedPath, { recursive: true, force: true });
1493
1443
  await mkdir3(stagedPath, { recursive: true });
1494
1444
  run("npm", ["pack", `${packageName}@${latest}`, "--pack-destination", stagedPath], stagedPath);
1495
1445
  const tgzName = `${packageName}-${latest}.tgz`.replace("@", "").replace("/", "-");
1496
- const tgzPath = join9(stagedPath, tgzName);
1446
+ const tgzPath = join10(stagedPath, tgzName);
1497
1447
  run("tar", ["-xzf", tgzPath, "--strip-components=1"], stagedPath);
1498
- if (existsSync6(tgzPath)) unlinkSync4(tgzPath);
1448
+ if (existsSync8(tgzPath)) unlinkSync4(tgzPath);
1499
1449
  run("npm", ["install", "--omit=dev", "--no-audit", "--no-fund"], stagedPath);
1500
1450
  return { version: latest, stagedPath, commit: null, branch: null, packageLockHash: null, source: "npm" };
1501
1451
  }
@@ -1518,37 +1468,65 @@ Use --source local (default) or --source npm.
1518
1468
  return 2;
1519
1469
  }
1520
1470
  const paths = packagePaths("abtars");
1471
+ const sentinel = readSentinel(paths.home);
1472
+ if (sentinel?.status === "pending") {
1473
+ const age = Date.now() - new Date(sentinel.startedAt).getTime();
1474
+ if (age > 5 * 6e4) {
1475
+ process.stderr.write(`\u26A0\uFE0F Previous update (${sentinel.version}) never completed successfully. Proceeding...
1476
+ `);
1477
+ }
1478
+ }
1479
+ if (opts.check) {
1480
+ return checkForUpdates(paths.home, opts);
1481
+ }
1521
1482
  const release = await acquireLock(paths.lock, `update --source ${opts.source}`);
1522
1483
  try {
1484
+ cleanStaleStaging(paths.appStaging);
1485
+ const cleanupHandler = () => {
1486
+ if (existsSync9(paths.appStaging) && existsSync9(paths.app)) {
1487
+ rmSync3(paths.appStaging, { recursive: true, force: true });
1488
+ }
1489
+ };
1490
+ process.on("SIGHUP", () => {
1491
+ cleanupHandler();
1492
+ process.exit(130);
1493
+ });
1494
+ process.on("SIGTERM", () => {
1495
+ cleanupHandler();
1496
+ process.exit(143);
1497
+ });
1523
1498
  let repoRoot = opts.repoRoot ?? process.cwd();
1524
- if (!opts.repoRoot && !existsSync7(join10(repoRoot, ".git"))) {
1499
+ if (!opts.repoRoot && !existsSync9(join11(repoRoot, ".git"))) {
1525
1500
  const { realpathSync } = await import("node:fs");
1526
1501
  const scriptPath = realpathSync(process.argv[1] ?? "");
1527
- const candidate = join10(dirname3(scriptPath), "..");
1528
- if (existsSync7(join10(candidate, "bundle"))) repoRoot = candidate;
1502
+ const { dirname: dirname3 } = await import("node:path");
1503
+ const candidate = join11(dirname3(scriptPath), "..");
1504
+ if (existsSync9(join11(candidate, "bundle"))) repoRoot = candidate;
1529
1505
  }
1530
1506
  const source = opts.source === "npm" ? makeNpmSource("abtars") : makeLocalBuildSource({ repoRoot, allowStale: opts.fromLocal });
1531
1507
  if (opts.fromLocal) {
1532
1508
  showHintOnce("update-from-local", "Building from working copy (--from-local). To sync with remote first: git pull && abtars update");
1533
1509
  }
1534
- process.stdout.write(`Building from local checkout (${process.cwd()})...
1510
+ if (opts.dryRun) {
1511
+ return printDryRun(paths, repoRoot, opts);
1512
+ }
1513
+ process.stdout.write(`Building from local checkout (${repoRoot})...
1535
1514
  `);
1536
1515
  const staged = await source.prepare({
1537
- releasesDir: paths.releases,
1538
- nodeModulesDir: paths.nodeModules,
1516
+ stagingDir: paths.appStaging,
1539
1517
  home: paths.home,
1540
1518
  allowStale: opts.fromLocal
1541
1519
  });
1542
- process.stdout.write(`\u2713 staged ${staged.version} at ${staged.stagedPath}
1520
+ process.stdout.write(`\u2713 staged ${staged.version}
1543
1521
  `);
1544
1522
  {
1545
- const pkgPath = join10(staged.stagedPath, "package.json");
1546
- const { readFileSync: readFileSync6, writeFileSync: writeFileSync2 } = await import("node:fs");
1523
+ const pkgPath = join11(staged.stagedPath, "package.json");
1547
1524
  try {
1548
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
1525
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
1549
1526
  const externals = { patchright: "^1.59.4", "rettiwt-api": "^4.1.3" };
1550
1527
  pkg.dependencies = { ...pkg.dependencies, ...externals };
1551
1528
  if (pkg.dependencies?.abmind?.startsWith("file:")) delete pkg.dependencies.abmind;
1529
+ const { writeFileSync: writeFileSync2 } = await import("node:fs");
1552
1530
  writeFileSync2(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1553
1531
  const { execSync } = await import("node:child_process");
1554
1532
  execSync("npm install --omit=dev --ignore-scripts 2>/dev/null", { cwd: staged.stagedPath, timeout: 6e4 });
@@ -1559,283 +1537,291 @@ Use --source local (default) or --source npm.
1559
1537
  `);
1560
1538
  }
1561
1539
  }
1562
- {
1563
- const { existsSync: existsSync8, unlinkSync: unlinkSync5, symlinkSync } = await import("node:fs");
1564
- const mainLink = join10(staged.stagedPath, "main.js");
1565
- try {
1566
- unlinkSync5(mainLink);
1567
- } catch (err) {
1568
- logAndSwallow("update", "op", err);
1569
- }
1570
- const entry = existsSync8(join10(staged.stagedPath, "bundle", "abtars.js")) ? "bundle/abtars.js" : "dist/main.js";
1571
- symlinkSync(entry, mainLink);
1572
- }
1573
- const { existsSync: ex2, symlinkSync: sl2, mkdirSync: mk2, unlinkSync: ul2 } = await import("node:fs");
1574
- const { dirname: dn2 } = await import("node:path");
1575
- await activate(paths.current, staged.version);
1576
- process.stdout.write(`\u2713 current -> releases/${staged.version}
1577
- `);
1578
- const globalModules = join10(dn2(process.execPath), "..", "lib", "node_modules");
1579
- const abmindHome = process.env["ABMIND_HOME"] ?? join10(process.env["HOME"] ?? "", ".abmind");
1580
- const links = [];
1581
- const globalAbmind = join10(globalModules, "abmind");
1582
- const homeAbmind = join10(process.env["HOME"] ?? "", "abmind");
1583
- const localAbmind = join10(paths.home, "current", "node_modules", "abmind");
1584
- if (ex2(globalAbmind)) links.push({ name: "abmind", target: globalAbmind });
1585
- else if (ex2(homeAbmind)) links.push({ name: "abmind", target: homeAbmind });
1586
- else if (ex2(localAbmind)) links.push({ name: "abmind", target: localAbmind });
1587
- const bsq3 = join10(abmindHome, "lib", "node_modules", "better-sqlite3");
1588
- if (ex2(bsq3)) links.push({ name: "better-sqlite3", target: bsq3 });
1589
- if (links.length > 0) {
1590
- const bundleNm = join10(paths.home, "current", "bundle", "node_modules");
1591
- const rootNm = join10(paths.home, "current", "node_modules");
1592
- for (const dir of [bundleNm, rootNm]) {
1593
- mk2(dir, { recursive: true });
1594
- for (const { name, target } of links) {
1595
- const dest = join10(dir, name);
1596
- try {
1597
- if (ex2(dest)) ul2(dest);
1598
- } catch {
1599
- }
1600
- try {
1601
- sl2(target, dest);
1602
- } catch {
1603
- }
1604
- }
1605
- }
1540
+ await copyAbmind(staged.stagedPath, repoRoot);
1541
+ const entryPoint = join11(staged.stagedPath, "bundle", "abtars.js");
1542
+ if (!existsSync9(entryPoint)) {
1543
+ process.stderr.write(`\u274C Entry point not found: ${entryPoint}
1544
+ `);
1545
+ return 1;
1606
1546
  }
1547
+ configSnapshot(paths.config);
1548
+ process.stdout.write(`\u2713 config snapshot (3-slot rotation)
1549
+ `);
1550
+ atomicSwap(paths.app, paths.appPrev, paths.appStaging);
1551
+ process.stdout.write(`\u2713 atomic swap: app.staging/ \u2192 app/
1552
+ `);
1553
+ await postSwapHousekeeping(paths, repoRoot, staged);
1607
1554
  const prior = await readManifest(paths.manifest);
1608
- const now = (/* @__PURE__ */ new Date()).toISOString();
1609
- const newPriorReleases = prior?.version ? [
1610
- {
1611
- version: prior.version,
1612
- commit: prior.commit,
1613
- activatedAt: prior.activatedAt,
1614
- packageLockHash: prior.packageLockHash
1615
- },
1616
- ...prior.priorReleases ?? []
1617
- ].slice(0, RETENTION - 1) : prior?.priorReleases ?? [];
1555
+ const sentinelData = {
1556
+ version: staged.version,
1557
+ previousVersion: prior?.version ?? null,
1558
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1559
+ status: "pending"
1560
+ };
1561
+ writeSentinel(paths.home, sentinelData);
1618
1562
  await writeManifest(paths.manifest, {
1619
- ...prior ?? emptyManifest("abtars", hostname2()),
1563
+ ...prior ?? emptyManifest("abtars", hostname()),
1620
1564
  version: staged.version,
1621
1565
  commit: staged.commit,
1622
1566
  branch: staged.branch,
1623
1567
  packageLockHash: staged.packageLockHash,
1624
- activatedAt: now,
1568
+ activatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1625
1569
  source: "local",
1626
- priorReleases: newPriorReleases
1570
+ previousVersion: prior?.version ?? null,
1571
+ previousCommit: prior?.commit ?? null
1627
1572
  });
1628
1573
  process.stdout.write(`\u2713 manifest updated
1629
1574
  `);
1630
- const pruned = await pruneReleases(
1631
- paths.releases,
1632
- [staged.version, ...newPriorReleases.map((r) => r.version)],
1633
- staged.version,
1634
- RETENTION
1635
- );
1636
- if (pruned.length > 0) {
1637
- process.stdout.write(`\u2713 pruned ${pruned.length} old release${pruned.length === 1 ? "" : "s"}: ${pruned.join(", ")}
1575
+ const restartTimestamp = Date.now();
1576
+ const restarted = await restartBridge(paths);
1577
+ if (!restarted) {
1578
+ process.stdout.write(`\u26A0\uFE0F Could not restart bridge. Start manually.
1638
1579
  `);
1580
+ return 0;
1639
1581
  }
1640
- process.stdout.write(`
1641
- Update complete: ${staged.version}
1582
+ process.stdout.write(`Waiting for bridge health...
1642
1583
  `);
1643
- const transportJson = join10(paths.home, "config", "transport.json");
1644
- if (existsSync7(transportJson)) {
1645
- try {
1646
- const tc = JSON.parse(readFileSync5(transportJson, "utf-8"));
1647
- let cleared = false;
1648
- for (const agent of Object.values(tc.agents ?? {})) {
1649
- if (agent.demoted) {
1650
- delete agent.demoted;
1651
- delete agent.demotedReason;
1652
- delete agent.demotedModel;
1653
- cleared = true;
1654
- }
1655
- for (const fb of agent.fallbacks ?? []) {
1656
- if (fb.demoted) {
1657
- delete fb.demoted;
1658
- delete fb.demotedReason;
1659
- delete fb.demotedModel;
1660
- cleared = true;
1661
- }
1662
- }
1663
- }
1664
- if (cleared) {
1665
- const { writeFileSync: wfs } = await import("node:fs");
1666
- wfs(transportJson, JSON.stringify(tc, null, 2) + "\n");
1667
- }
1668
- } catch {
1669
- }
1670
- }
1671
- const installManifestPath = join10(repoRoot, "install-manifest.json");
1672
- const installManifest = JSON.parse(await readFile3(installManifestPath, "utf-8"));
1673
- const repoScripts = join10(repoRoot, "scripts");
1674
- const destScripts = join10(paths.home, "scripts");
1675
- await mkdir4(destScripts, { recursive: true });
1676
- const allScriptFiles = await readdir(repoScripts).catch(() => []);
1677
- const matchesInclude = (name) => installManifest.scripts.include.some((pattern) => {
1678
- const ext = pattern.replace("*", "");
1679
- return name.endsWith(ext);
1680
- });
1681
- const scriptFiles = allScriptFiles.filter(matchesInclude);
1682
- const home = process.env["HOME"] ?? "";
1683
- let serviceChanged = false;
1684
- const installMode = (await readManifest(paths.manifest))?.installMode ?? "supervised";
1685
- const isExecutable = (name) => {
1686
- const ext = installManifest.scripts.executable.replace("*", "");
1687
- return name.endsWith(ext);
1688
- };
1689
- for (const name of scriptFiles) {
1690
- await copyFile2(join10(repoScripts, name), join10(destScripts, name));
1691
- if (isExecutable(name)) await chmod(join10(destScripts, name), 493);
1692
- const macService = installManifest.services.supervised.macos;
1693
- if (macService && name === macService.plist && process.platform === "darwin" && home && installMode === "supervised") {
1694
- const launchAgentsDir = join10(home, "Library", "LaunchAgents");
1695
- await mkdir4(launchAgentsDir, { recursive: true });
1696
- const dst = join10(launchAgentsDir, name);
1697
- const oldContent = await readFile3(dst, "utf-8").catch(() => "");
1698
- let templated = await readFile3(join10(repoScripts, name), "utf-8");
1699
- for (const ph of macService.placeholders) templated = templated.replaceAll(ph, home);
1700
- await writeFile3(dst, templated);
1701
- if (oldContent !== templated) serviceChanged = true;
1702
- }
1703
- const linuxService = installManifest.services.supervised.linux;
1704
- if (linuxService?.units.includes(name) && process.platform === "linux" && home && installMode === "supervised") {
1705
- const systemdDir = join10(home, ".config", "systemd", "user");
1706
- await mkdir4(systemdDir, { recursive: true });
1707
- const dst = join10(systemdDir, name);
1708
- const oldContent = await readFile3(dst, "utf-8").catch(() => "");
1709
- await copyFile2(join10(repoScripts, name), dst);
1710
- const newContent = await readFile3(dst, "utf-8").catch(() => "");
1711
- if (oldContent !== newContent) serviceChanged = true;
1712
- }
1584
+ const health = await healthProbe(paths.home, restartTimestamp, 6e4);
1585
+ if (health.healthy) {
1586
+ writeSentinel(paths.home, { ...sentinelData, status: "success" });
1587
+ process.stdout.write(`\u2713 Bridge healthy (PID ${health.pid}, tick at ${new Date(health.heartbeat).toISOString()})
1588
+ `);
1589
+ return 0;
1713
1590
  }
1714
- process.stdout.write(`\u2713 scripts refreshed (${scriptFiles.length} files)
1591
+ process.stderr.write(`\u274C Bridge unhealthy after 60s. Auto-rolling back...
1592
+ `);
1593
+ if (!existsSync9(paths.appPrev)) {
1594
+ process.stderr.write(`\u274C No app.prev/ to roll back to. Manual intervention required.
1595
+ `);
1596
+ process.stderr.write(` Check: ~/.abtars/logs/bridge.log
1715
1597
  `);
1716
- const { writeWrapper } = await import("./install-GEXWJYJC.js");
1717
- await mkdir4(paths.bin, { recursive: true });
1718
- for (const name of installManifest.cliWrappers) {
1719
- await writeWrapper(paths.bin, name, paths.current, false);
1598
+ return 2;
1720
1599
  }
1721
- process.stdout.write(`\u2713 wrappers refreshed (${installManifest.cliWrappers.length} files)
1722
- `);
1723
- const { rmSync: rmSync2, cpSync, readdirSync: readdirSync3 } = await import("node:fs");
1724
- const skillsCoreSrc = join10(staged.stagedPath, "core", "skills");
1725
- const skillsCoreDst = join10(paths.home, "skills", "core");
1726
- if (existsSync7(skillsCoreSrc)) {
1727
- rmSync2(skillsCoreDst, { recursive: true, force: true });
1728
- cpSync(skillsCoreSrc, skillsCoreDst, { recursive: true });
1729
- const files = readdirSync3(skillsCoreDst, { recursive: true });
1730
- const count = files.filter((f) => f.endsWith("SKILL.md")).length;
1731
- process.stdout.write(`\u2713 skills/core synced (${count} skills)
1600
+ const brokenDir = join11(paths.home, "app.broken");
1601
+ rmSync3(brokenDir, { recursive: true, force: true });
1602
+ const { renameSync: renameSync4 } = await import("node:fs");
1603
+ renameSync4(paths.app, brokenDir);
1604
+ renameSync4(paths.appPrev, paths.app);
1605
+ if (prior) {
1606
+ await writeManifest(paths.manifest, prior);
1607
+ }
1608
+ const rollbackTs = Date.now();
1609
+ await restartBridge(paths);
1610
+ const rollbackHealth = await healthProbe(paths.home, rollbackTs, 3e4);
1611
+ if (rollbackHealth.healthy) {
1612
+ process.stderr.write(`\u26A0\uFE0F Rolled back to previous version. Investigate ${brokenDir} for the failure.
1732
1613
  `);
1614
+ rmSync3(brokenDir, { recursive: true, force: true });
1615
+ return 1;
1733
1616
  }
1734
- for (const d of ["custom", "downloaded", "self"]) {
1735
- await mkdir4(join10(paths.home, "skills", d), { recursive: true });
1617
+ process.stderr.write(`\u274C Rollback also failed. Manual intervention required.
1618
+ `);
1619
+ process.stderr.write(` Check: ~/.abtars/logs/bridge.log
1620
+ `);
1621
+ process.stderr.write(` Broken version preserved at: ${brokenDir}
1622
+ `);
1623
+ return 2;
1624
+ } finally {
1625
+ await release();
1626
+ }
1627
+ }
1628
+ async function copyAbmind(stagedPath, repoRoot) {
1629
+ const candidates = [
1630
+ process.env["ABMIND_REPO"],
1631
+ join11(repoRoot, "..", "abmind"),
1632
+ join11(process.env["HOME"] ?? "", "abmind")
1633
+ ].filter(Boolean);
1634
+ for (const src of candidates) {
1635
+ const distDir = join11(src, "dist");
1636
+ if (existsSync9(distDir)) {
1637
+ const dest = join11(stagedPath, "node_modules", "abmind");
1638
+ mkdirSync2(dest, { recursive: true });
1639
+ cpSync(distDir, join11(dest, "dist"), { recursive: true });
1640
+ if (existsSync9(join11(src, "package.json"))) cpSync(join11(src, "package.json"), join11(dest, "package.json"));
1641
+ if (existsSync9(join11(src, "prompts"))) cpSync(join11(src, "prompts"), join11(dest, "prompts"), { recursive: true });
1642
+ process.stdout.write(`\u2713 abmind copied from ${src}
1643
+ `);
1644
+ return;
1736
1645
  }
1737
- for (const stale of ["memory", "ops", "tools", "coding"]) {
1738
- const p = join10(paths.home, "skills", stale);
1739
- if (existsSync7(p)) {
1740
- rmSync2(p, { recursive: true, force: true });
1646
+ }
1647
+ process.stdout.write(`\u26A0 abmind source not found (checked: ${candidates.join(", ")}). Bridge may fail to start.
1648
+ `);
1649
+ }
1650
+ async function postSwapHousekeeping(paths, repoRoot, _staged) {
1651
+ const { loadManifest } = await import("./install-manifest-QRWID3KZ.js");
1652
+ const installManifest = loadManifest(paths.app);
1653
+ const repoScripts = join11(repoRoot, "scripts");
1654
+ const destScripts = join11(paths.home, "scripts");
1655
+ await mkdir4(destScripts, { recursive: true });
1656
+ const allScriptFiles = await readdir(repoScripts).catch(() => []);
1657
+ const matchesInclude = (name) => installManifest.scripts.include.some((pattern) => name.endsWith(pattern.replace("*", "")));
1658
+ const scriptFiles = allScriptFiles.filter(matchesInclude);
1659
+ const isExecutable = (name) => name.endsWith(installManifest.scripts.executable.replace("*", ""));
1660
+ for (const name of scriptFiles) {
1661
+ await copyFile2(join11(repoScripts, name), join11(destScripts, name));
1662
+ if (isExecutable(name)) await chmod(join11(destScripts, name), 493);
1663
+ }
1664
+ process.stdout.write(`\u2713 scripts refreshed (${scriptFiles.length} files)
1665
+ `);
1666
+ const { writeWrapper } = await import("./install-SH4UVUXQ.js");
1667
+ await mkdir4(paths.bin, { recursive: true });
1668
+ for (const name of installManifest.cliWrappers) {
1669
+ await writeWrapper(paths.bin, name, paths.app, false);
1670
+ }
1671
+ process.stdout.write(`\u2713 wrappers refreshed (${installManifest.cliWrappers.length} files)
1672
+ `);
1673
+ const skillsCoreSrc = join11(paths.app, "core", "skills");
1674
+ const skillsCoreDst = join11(paths.home, "skills", "core");
1675
+ if (existsSync9(skillsCoreSrc)) {
1676
+ rmSync3(skillsCoreDst, { recursive: true, force: true });
1677
+ cpSync(skillsCoreSrc, skillsCoreDst, { recursive: true });
1678
+ const files = readdirSync3(skillsCoreDst, { recursive: true });
1679
+ const count = files.filter((f) => f.endsWith("SKILL.md")).length;
1680
+ process.stdout.write(`\u2713 skills/core synced (${count} skills)
1681
+ `);
1682
+ }
1683
+ for (const d of ["custom", "downloaded", "self"]) {
1684
+ await mkdir4(join11(paths.home, "skills", d), { recursive: true });
1685
+ }
1686
+ const releaseConfig = join11(paths.app, "config");
1687
+ const destConfig = join11(paths.home, "config");
1688
+ if (existsSync9(releaseConfig)) {
1689
+ for (const f of readdirSync3(releaseConfig)) {
1690
+ const src = join11(releaseConfig, f);
1691
+ if (f.endsWith(".example")) {
1692
+ cpSync(src, join11(destConfig, f));
1693
+ const target = join11(destConfig, f.replace(".example", ""));
1694
+ if (!existsSync9(target)) cpSync(src, target);
1741
1695
  }
1742
1696
  }
1743
- const releaseConfig = join10(staged.stagedPath, "config");
1744
- const destConfig = join10(paths.home, "config");
1745
- if (existsSync7(releaseConfig)) {
1746
- for (const f of readdirSync3(releaseConfig)) {
1747
- const src = join10(releaseConfig, f);
1748
- if (f.endsWith(".example")) {
1749
- cpSync(src, join10(destConfig, f));
1750
- const target = join10(destConfig, f.replace(".example", ""));
1751
- if (!existsSync7(target)) cpSync(src, target);
1697
+ const defaultTransport = join11(releaseConfig, "transport.default.json");
1698
+ if (existsSync9(defaultTransport)) cpSync(defaultTransport, join11(destConfig, "transport.default.json"));
1699
+ }
1700
+ const transportJson = join11(paths.home, "config", "transport.json");
1701
+ if (existsSync9(transportJson)) {
1702
+ try {
1703
+ const tc = JSON.parse(readFileSync5(transportJson, "utf-8"));
1704
+ let cleared = false;
1705
+ for (const agent of Object.values(tc.agents ?? {})) {
1706
+ if (agent.demoted) {
1707
+ delete agent.demoted;
1708
+ delete agent.demotedReason;
1709
+ delete agent.demotedModel;
1710
+ cleared = true;
1711
+ }
1712
+ for (const fb of agent.fallbacks ?? []) {
1713
+ if (fb.demoted) {
1714
+ delete fb.demoted;
1715
+ delete fb.demotedReason;
1716
+ delete fb.demotedModel;
1717
+ cleared = true;
1718
+ }
1752
1719
  }
1753
1720
  }
1754
- const defaultTransport = join10(releaseConfig, "transport.default.json");
1755
- if (existsSync7(defaultTransport)) cpSync(defaultTransport, join10(destConfig, "transport.default.json"));
1756
- }
1757
- const coreSrc = join10(staged.stagedPath, "core");
1758
- const coreDst = join10(paths.home, "core");
1759
- if (existsSync7(coreSrc)) {
1760
- await mkdir4(coreDst, { recursive: true });
1761
- cpSync(coreSrc, coreDst, { recursive: true });
1762
- }
1763
- if (serviceChanged) {
1764
- if (process.platform === "darwin") {
1765
- process.stdout.write(`\u26A0\uFE0F LaunchAgent plist updated \u2014 reload with: launchctl bootout gui/$(id -u)/com.abtars.watchdog && launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.abtars.watchdog.plist
1766
- `);
1767
- } else {
1768
- process.stdout.write(`\u26A0\uFE0F systemd service updated \u2014 reload with: systemctl --user daemon-reload && systemctl --user restart abtars-watchdog
1769
- `);
1721
+ if (cleared) {
1722
+ const { writeFileSync: writeFileSync2 } = await import("node:fs");
1723
+ writeFileSync2(transportJson, JSON.stringify(tc, null, 2) + "\n");
1770
1724
  }
1725
+ } catch {
1771
1726
  }
1772
- void hashFile;
1773
- const { ensureInstallInvariants } = await import("./ensure-invariants-BJIEOSJ2.js");
1774
- const invariantResults = await ensureInstallInvariants(process.cwd(), paths.home);
1775
- if (invariantResults.length > 0) {
1776
- process.stdout.write(`\u2713 invariants: ${invariantResults.join(", ")}
1727
+ }
1728
+ const doctorPath = join11(paths.home, "scripts", "doctor.sh");
1729
+ if (existsSync9(doctorPath)) {
1730
+ process.stdout.write("\n\u{1FA7A} Health check...\n");
1731
+ try {
1732
+ const { execSync } = await import("node:child_process");
1733
+ execSync(`bash "${doctorPath}" --fix`, { stdio: "inherit", timeout: 3e4 });
1734
+ } catch (err) {
1735
+ process.stderr.write(`\u26A0\uFE0F doctor --fix failed: ${err instanceof Error ? err.message : String(err)}
1777
1736
  `);
1778
1737
  }
1779
- const doctorPath = join10(paths.home, "scripts", "doctor.sh");
1780
- if (existsSync7(doctorPath)) {
1781
- process.stdout.write("\n\u{1FA7A} Health check...\n");
1738
+ }
1739
+ const { ensureInstallInvariants } = await import("./ensure-invariants-BJIEOSJ2.js");
1740
+ const invariantResults = await ensureInstallInvariants(process.cwd(), paths.home);
1741
+ if (invariantResults.length > 0) {
1742
+ process.stdout.write(`\u2713 invariants: ${invariantResults.join(", ")}
1743
+ `);
1744
+ }
1745
+ void hashFile;
1746
+ }
1747
+ async function restartBridge(paths) {
1748
+ const manifest = await readManifest(paths.manifest);
1749
+ const mode = manifest?.installMode;
1750
+ if (!mode) {
1751
+ process.stderr.write("\u274C installMode not set in manifest.json. Run 'abtars install' first.\n");
1752
+ return false;
1753
+ }
1754
+ if (mode === "supervised-daemon" || mode === "supervised") {
1755
+ process.stdout.write("\n\u267B\uFE0F Restarting bridge via watchdog...\n");
1756
+ const wdLock = join11(paths.home, "watchdog.lock");
1757
+ const wdPid = readJsonField2(wdLock, "pid");
1758
+ if (wdPid && wdPid > 0) {
1782
1759
  try {
1783
- const { execSync } = await import("node:child_process");
1784
- execSync(`bash "${doctorPath}" --fix`, { stdio: "inherit", timeout: 3e4 });
1785
- } catch (err) {
1786
- process.stderr.write(`\u26A0\uFE0F doctor --fix failed: ${err instanceof Error ? err.message : String(err)}
1760
+ process.kill(wdPid, "SIGUSR1");
1761
+ process.stdout.write(` USR1 sent to watchdog (PID ${wdPid})
1762
+ `);
1763
+ return true;
1764
+ } catch {
1765
+ process.stdout.write(`\u26A0\uFE0F Could not signal watchdog (PID ${wdPid}).
1787
1766
  `);
1788
1767
  }
1789
1768
  }
1790
- const manifestForRestart = await readManifest(paths.manifest);
1791
- const restartMode = manifestForRestart?.installMode;
1792
- if (!restartMode) {
1793
- process.stderr.write("\u274C installMode not set in manifest.json. Run 'abtars install' first.\n");
1794
- return 1;
1795
- }
1796
- if (restartMode === "supervised-daemon" || restartMode === "supervised") {
1797
- process.stdout.write("\nRestarting bridge via watchdog...\n");
1798
- const wdLock = join10(paths.home, "watchdog.lock");
1799
- const wdPid = readJsonField2(wdLock, "pid");
1800
- if (wdPid && wdPid > 0) {
1801
- try {
1802
- process.kill(wdPid, "SIGUSR1");
1803
- process.stdout.write(`\u267B\uFE0F USR1 sent to watchdog (PID ${wdPid}) \u2014 bridge will restart
1769
+ const { restart: restart3 } = await import("./abtars-restart.js");
1770
+ await restart3({ cold: true }).catch((err) => {
1771
+ process.stderr.write(`\u26A0\uFE0F Restart failed: ${err instanceof Error ? err.message : String(err)}
1804
1772
  `);
1805
- } catch {
1806
- process.stdout.write(`\u26A0\uFE0F Could not signal watchdog (PID ${wdPid}). Restart manually:
1807
- `);
1808
- if (process.platform === "darwin") {
1809
- process.stdout.write(` launchctl kickstart -k gui/$(id -u)/com.abtars.watchdog
1810
- `);
1811
- } else {
1812
- process.stdout.write(` systemctl --user restart abtars-watchdog
1773
+ });
1774
+ return true;
1775
+ }
1776
+ process.stdout.write("\n\u267B\uFE0F Restarting bridge...\n");
1777
+ const { restart: restart2 } = await import("./abtars-restart.js");
1778
+ await restart2({ cold: true }).catch((err) => {
1779
+ process.stderr.write(`\u26A0\uFE0F Restart failed: ${err instanceof Error ? err.message : String(err)}
1813
1780
  `);
1814
- }
1815
- }
1816
- } else {
1817
- process.stdout.write(`\u26A0\uFE0F Watchdog not running. Cold restart...
1781
+ });
1782
+ return true;
1783
+ }
1784
+ function printDryRun(paths, repoRoot, opts) {
1785
+ const { spawnSync: spawnSync4 } = __require("node:child_process");
1786
+ const commit = spawnSync4("git", ["rev-parse", "--short", "HEAD"], { cwd: repoRoot, encoding: "utf-8" }).stdout?.trim() ?? "?";
1787
+ const branch = spawnSync4("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: repoRoot, encoding: "utf-8" }).stdout?.trim() ?? "?";
1788
+ const wdLock = join11(paths.home, "watchdog.lock");
1789
+ const wdPid = readJsonField2(wdLock, "pid");
1790
+ process.stdout.write(`
1791
+ Dry run \u2014 no changes will be made.
1792
+ Source: ${opts.source} (commit ${commit} on ${branch})
1793
+ Staging to: ${paths.appStaging}
1794
+ Swap: app/ \u2192 app.prev/, app.staging/ \u2192 app/
1795
+ Config snap: config/ \u2192 config/.pre-update/ (3-slot rotation)
1796
+ Restart: ${wdPid ? `USR1 to watchdog (PID ${wdPid})` : "cold restart"}
1797
+ Health: poll bridge.lock for 60s
1798
+ On failure: auto-rollback (swap app/ \u2194 app.prev/)
1818
1799
  `);
1819
- const { restart: restart2 } = await import("./abtars-restart.js");
1820
- await restart2({ cold: true }).catch((err) => {
1821
- process.stderr.write(`\u26A0\uFE0F Restart failed: ${err instanceof Error ? err.message : String(err)}
1800
+ return 0;
1801
+ }
1802
+ async function checkForUpdates(home, opts) {
1803
+ const repoRoot = opts.repoRoot ?? process.cwd();
1804
+ if (!existsSync9(join11(repoRoot, ".git"))) {
1805
+ process.stderr.write("Not a git repository. --check requires a git checkout.\n");
1806
+ return 2;
1807
+ }
1808
+ const { spawnSync: spawnSync4 } = await import("node:child_process");
1809
+ spawnSync4("git", ["fetch", "--quiet"], { cwd: repoRoot });
1810
+ const result = spawnSync4("git", ["rev-list", "--count", "HEAD..origin/dev"], { cwd: repoRoot, encoding: "utf-8" });
1811
+ const ahead = parseInt(result.stdout?.trim() ?? "0", 10);
1812
+ const manifest = await readManifest(join11(home, "manifest.json"));
1813
+ process.stdout.write(`Current: ${manifest?.version ?? "unknown"} (deployed ${manifest?.activatedAt ?? "never"})
1822
1814
  `);
1823
- });
1824
- }
1825
- } else {
1826
- process.stdout.write("\nRestarting bridge...\n");
1827
- const { restart: restart2 } = await import("./abtars-restart.js");
1828
- await restart2({ cold: true }).catch((err) => {
1829
- process.stderr.write(`\u26A0\uFE0F Restart failed: ${err instanceof Error ? err.message : String(err)}
1815
+ if (ahead === 0) {
1816
+ process.stdout.write(`Remote: up to date
1830
1817
  `);
1831
- });
1832
- }
1833
- const { printHealthSummary } = await import("./health-check-RJ2SUJYL.js");
1834
- printHealthSummary(paths.home);
1835
1818
  return 0;
1836
- } finally {
1837
- await release();
1838
1819
  }
1820
+ process.stdout.write(`Remote: dev is ${ahead} commit${ahead === 1 ? "" : "s"} ahead
1821
+ `);
1822
+ process.stdout.write(`Action: run 'abtars update' to apply
1823
+ `);
1824
+ return 2;
1839
1825
  }
1840
1826
 
1841
1827
  // src/cli/abtars.ts
@@ -1905,9 +1891,7 @@ async function main(argv) {
1905
1891
  allowAbmindMismatch: flags.get("allow-abmind-mismatch") === true
1906
1892
  });
1907
1893
  case "rollback":
1908
- return await rollback({
1909
- to: typeof flags.get("to") === "string" ? flags.get("to") : void 0
1910
- });
1894
+ return await rollback();
1911
1895
  case "backup":
1912
1896
  return await backup(typeof flags.get("output") === "string" ? flags.get("output") : void 0);
1913
1897
  case "restore": {