@roamcode.ai/server 1.0.3 → 1.0.4

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.
package/dist/index.d.ts CHANGED
@@ -996,6 +996,13 @@ declare class Updater {
996
996
  private persistReleaseCache;
997
997
  private refresh;
998
998
  getVersion(force?: boolean): Promise<VersionInfo>;
999
+ private readRawStatus;
1000
+ /**
1001
+ * Reconcile the durable status with runtime truth on every read. This prevents a helper that failed
1002
+ * before its first write (bad executable, missing file, launch failure) from leaving every client on
1003
+ * “Starting…” forever, and lets a restarted server confirm success even if the helper's final write
1004
+ * raced the service restart.
1005
+ */
999
1006
  readStatus(): UpdateStatus;
1000
1007
  private writeStatus;
1001
1008
  readLastGoodVersion(): string | undefined;
package/dist/index.js CHANGED
@@ -2191,13 +2191,14 @@ async function installManagedRelease(opts) {
2191
2191
  }
2192
2192
 
2193
2193
  // src/updater.ts
2194
- var RUNNING_VERSION = "1.0.3" ? "1.0.3".replace(/^v/, "") : packageVersion();
2194
+ var RUNNING_VERSION = "1.0.4" ? "1.0.4".replace(/^v/, "") : packageVersion();
2195
2195
  var RUNNING_BUILD = RUNNING_VERSION;
2196
2196
  var RELEASES_API = "https://api.github.com/repos/burakgon/roamcode/releases?per_page=100";
2197
2197
  var RELEASE_MANIFEST_ASSET = "roamcode-release.json";
2198
2198
  var CHECK_CACHE_MS = 15 * 6e4;
2199
2199
  var FAILED_CHECK_TTL_MS = 3e4;
2200
2200
  var FETCH_TIMEOUT_MS = 2e4;
2201
+ var UPDATE_STARTUP_TIMEOUT_MS = 2e4;
2201
2202
  var UPDATE_STALE_MS = 30 * 6e4;
2202
2203
  var defaultUpdaterFs = {
2203
2204
  existsSync: nodeExistsSync,
@@ -2435,7 +2436,7 @@ var Updater = class {
2435
2436
  buildDrift: installDrift
2436
2437
  };
2437
2438
  }
2438
- readStatus() {
2439
+ readRawStatus() {
2439
2440
  try {
2440
2441
  const parsed = JSON.parse(this.fs.readFileSync(join8(this.dataDir, "update-status.json")));
2441
2442
  return parsed && typeof parsed.state === "string" ? parsed : { state: "idle" };
@@ -2443,6 +2444,49 @@ var Updater = class {
2443
2444
  return { state: "idle" };
2444
2445
  }
2445
2446
  }
2447
+ /**
2448
+ * Reconcile the durable status with runtime truth on every read. This prevents a helper that failed
2449
+ * before its first write (bad executable, missing file, launch failure) from leaving every client on
2450
+ * “Starting…” forever, and lets a restarted server confirm success even if the helper's final write
2451
+ * raced the service restart.
2452
+ */
2453
+ readStatus() {
2454
+ const status = this.readRawStatus();
2455
+ if (status.state === "idle" || status.state === "done" || status.state === "failed") return status;
2456
+ if (status.target && status.operationId) {
2457
+ const active = readActiveVersion(this.installRoot());
2458
+ if (active === status.target && this.runningVersion === status.target) {
2459
+ const done = {
2460
+ operationId: status.operationId,
2461
+ state: "done",
2462
+ phase: "done",
2463
+ target: status.target,
2464
+ ...status.fromVersion ? { fromVersion: status.fromVersion } : {},
2465
+ updatedAt: this.now()
2466
+ };
2467
+ this.writeStatus(done);
2468
+ return done;
2469
+ }
2470
+ }
2471
+ const age = this.now() - (status.updatedAt ?? 0);
2472
+ const helperHasNotReported = status.state === "starting" && (status.phase === "starting" || status.phase === "preparing migration" || status.phase === "preparing rollback");
2473
+ if (helperHasNotReported && age >= UPDATE_STARTUP_TIMEOUT_MS || age >= UPDATE_STALE_MS) {
2474
+ const failed = {
2475
+ operationId: status.operationId ?? "unknown",
2476
+ state: "failed",
2477
+ phase: "failed",
2478
+ ...status.target ? { target: status.target } : {},
2479
+ ...status.fromVersion ? { fromVersion: status.fromVersion } : {},
2480
+ error: helperHasNotReported ? "The update process could not start. The current version is still running; retry the update." : "The update stopped reporting progress. The current version is still available; retry the update.",
2481
+ ...status.log ? { log: status.log } : {},
2482
+ updatedAt: this.now()
2483
+ };
2484
+ this.writeStatus(failed);
2485
+ this.inFlight = false;
2486
+ return failed;
2487
+ }
2488
+ return status;
2489
+ }
2446
2490
  writeStatus(status) {
2447
2491
  this.fs.mkdirSync(this.dataDir);
2448
2492
  const path = join8(this.dataDir, "update-status.json");
@@ -2469,7 +2513,7 @@ var Updater = class {
2469
2513
  return this.now() - (status.updatedAt ?? 0) < UPDATE_STALE_MS;
2470
2514
  }
2471
2515
  finalizeRestartIfHealthy() {
2472
- const status = this.readStatus();
2516
+ const status = this.readRawStatus();
2473
2517
  if (status.state !== "restarting" || !status.target || !status.operationId) return;
2474
2518
  const active = readActiveVersion(this.installRoot());
2475
2519
  if (active !== status.target || this.runningVersion !== status.target) return;
@@ -2557,6 +2601,20 @@ var Updater = class {
2557
2601
  updatedAt: this.now()
2558
2602
  });
2559
2603
  });
2604
+ child.on("exit", (code, signal) => {
2605
+ if (code === 0) return;
2606
+ const current = this.readRawStatus();
2607
+ if (current.operationId !== operationId || current.state === "done" || current.state === "failed" || current.state === "idle")
2608
+ return;
2609
+ this.inFlight = false;
2610
+ this.writeStatus({
2611
+ ...status,
2612
+ state: "failed",
2613
+ phase: "failed",
2614
+ error: `The update process exited before installation began (${signal ?? `code ${code ?? "unknown"}`}). The current version is still running.`,
2615
+ updatedAt: this.now()
2616
+ });
2617
+ });
2560
2618
  child.unref();
2561
2619
  return { started: true, operationId, target };
2562
2620
  } catch (error) {
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- #!/usr/bin/env node
3
2
 
4
3
  // src/managed-update-helper.ts
5
4
  import { readFileSync as readFileSync3, unlinkSync as unlinkSync2 } from "fs";
package/dist/start.js CHANGED
@@ -765,12 +765,13 @@ function readPreviousVersion(root) {
765
765
  }
766
766
 
767
767
  // src/updater.ts
768
- var RUNNING_VERSION = "1.0.3" ? "1.0.3".replace(/^v/, "") : packageVersion();
768
+ var RUNNING_VERSION = "1.0.4" ? "1.0.4".replace(/^v/, "") : packageVersion();
769
769
  var RELEASES_API = "https://api.github.com/repos/burakgon/roamcode/releases?per_page=100";
770
770
  var RELEASE_MANIFEST_ASSET = "roamcode-release.json";
771
771
  var CHECK_CACHE_MS = 15 * 6e4;
772
772
  var FAILED_CHECK_TTL_MS = 3e4;
773
773
  var FETCH_TIMEOUT_MS = 2e4;
774
+ var UPDATE_STARTUP_TIMEOUT_MS = 2e4;
774
775
  var UPDATE_STALE_MS = 30 * 6e4;
775
776
  var defaultUpdaterFs = {
776
777
  existsSync: nodeExistsSync,
@@ -1007,7 +1008,7 @@ var Updater = class {
1007
1008
  buildDrift: installDrift
1008
1009
  };
1009
1010
  }
1010
- readStatus() {
1011
+ readRawStatus() {
1011
1012
  try {
1012
1013
  const parsed = JSON.parse(this.fs.readFileSync(join6(this.dataDir, "update-status.json")));
1013
1014
  return parsed && typeof parsed.state === "string" ? parsed : { state: "idle" };
@@ -1015,6 +1016,49 @@ var Updater = class {
1015
1016
  return { state: "idle" };
1016
1017
  }
1017
1018
  }
1019
+ /**
1020
+ * Reconcile the durable status with runtime truth on every read. This prevents a helper that failed
1021
+ * before its first write (bad executable, missing file, launch failure) from leaving every client on
1022
+ * “Starting…” forever, and lets a restarted server confirm success even if the helper's final write
1023
+ * raced the service restart.
1024
+ */
1025
+ readStatus() {
1026
+ const status = this.readRawStatus();
1027
+ if (status.state === "idle" || status.state === "done" || status.state === "failed") return status;
1028
+ if (status.target && status.operationId) {
1029
+ const active = readActiveVersion(this.installRoot());
1030
+ if (active === status.target && this.runningVersion === status.target) {
1031
+ const done = {
1032
+ operationId: status.operationId,
1033
+ state: "done",
1034
+ phase: "done",
1035
+ target: status.target,
1036
+ ...status.fromVersion ? { fromVersion: status.fromVersion } : {},
1037
+ updatedAt: this.now()
1038
+ };
1039
+ this.writeStatus(done);
1040
+ return done;
1041
+ }
1042
+ }
1043
+ const age = this.now() - (status.updatedAt ?? 0);
1044
+ const helperHasNotReported = status.state === "starting" && (status.phase === "starting" || status.phase === "preparing migration" || status.phase === "preparing rollback");
1045
+ if (helperHasNotReported && age >= UPDATE_STARTUP_TIMEOUT_MS || age >= UPDATE_STALE_MS) {
1046
+ const failed = {
1047
+ operationId: status.operationId ?? "unknown",
1048
+ state: "failed",
1049
+ phase: "failed",
1050
+ ...status.target ? { target: status.target } : {},
1051
+ ...status.fromVersion ? { fromVersion: status.fromVersion } : {},
1052
+ error: helperHasNotReported ? "The update process could not start. The current version is still running; retry the update." : "The update stopped reporting progress. The current version is still available; retry the update.",
1053
+ ...status.log ? { log: status.log } : {},
1054
+ updatedAt: this.now()
1055
+ };
1056
+ this.writeStatus(failed);
1057
+ this.inFlight = false;
1058
+ return failed;
1059
+ }
1060
+ return status;
1061
+ }
1018
1062
  writeStatus(status) {
1019
1063
  this.fs.mkdirSync(this.dataDir);
1020
1064
  const path = join6(this.dataDir, "update-status.json");
@@ -1041,7 +1085,7 @@ var Updater = class {
1041
1085
  return this.now() - (status.updatedAt ?? 0) < UPDATE_STALE_MS;
1042
1086
  }
1043
1087
  finalizeRestartIfHealthy() {
1044
- const status = this.readStatus();
1088
+ const status = this.readRawStatus();
1045
1089
  if (status.state !== "restarting" || !status.target || !status.operationId) return;
1046
1090
  const active = readActiveVersion(this.installRoot());
1047
1091
  if (active !== status.target || this.runningVersion !== status.target) return;
@@ -1129,6 +1173,20 @@ var Updater = class {
1129
1173
  updatedAt: this.now()
1130
1174
  });
1131
1175
  });
1176
+ child.on("exit", (code, signal) => {
1177
+ if (code === 0) return;
1178
+ const current = this.readRawStatus();
1179
+ if (current.operationId !== operationId || current.state === "done" || current.state === "failed" || current.state === "idle")
1180
+ return;
1181
+ this.inFlight = false;
1182
+ this.writeStatus({
1183
+ ...status,
1184
+ state: "failed",
1185
+ phase: "failed",
1186
+ error: `The update process exited before installation began (${signal ?? `code ${code ?? "unknown"}`}). The current version is still running.`,
1187
+ updatedAt: this.now()
1188
+ });
1189
+ });
1132
1190
  child.unref();
1133
1191
  return { started: true, operationId, target };
1134
1192
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roamcode.ai/server",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Host-native server for RoamCode",
5
5
  "homepage": "https://roamcode.ai",
6
6
  "bugs": "https://github.com/burakgon/roamcode/issues",
@@ -40,7 +40,7 @@
40
40
  "node-pty": "1.1.0",
41
41
  "web-push": "^3.6.7",
42
42
  "zod": "^4.4.3",
43
- "@roamcode.ai/web": "1.0.3"
43
+ "@roamcode.ai/web": "1.0.4"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/better-sqlite3": "^7.6.13",