@slock-ai/computer 0.0.14 → 0.0.16-alpha.0

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.js CHANGED
@@ -16617,8 +16617,8 @@ var require_snapshot_recorder = __commonJS({
16617
16617
  "../../node_modules/.pnpm/undici@7.24.8/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
16618
16618
  "use strict";
16619
16619
  init_esm_shims();
16620
- var { writeFile: writeFile12, readFile: readFile16, mkdir: mkdir16 } = __require("fs/promises");
16621
- var { dirname: dirname13, resolve } = __require("path");
16620
+ var { writeFile: writeFile13, readFile: readFile17, mkdir: mkdir17 } = __require("fs/promises");
16621
+ var { dirname: dirname14, resolve } = __require("path");
16622
16622
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
16623
16623
  var { InvalidArgumentError: InvalidArgumentError2, UndiciError } = require_errors();
16624
16624
  var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
@@ -16819,7 +16819,7 @@ var require_snapshot_recorder = __commonJS({
16819
16819
  throw new InvalidArgumentError2("Snapshot path is required");
16820
16820
  }
16821
16821
  try {
16822
- const data = await readFile16(resolve(path3), "utf8");
16822
+ const data = await readFile17(resolve(path3), "utf8");
16823
16823
  const parsed = JSON.parse(data);
16824
16824
  if (Array.isArray(parsed)) {
16825
16825
  this.#snapshots.clear();
@@ -16849,12 +16849,12 @@ var require_snapshot_recorder = __commonJS({
16849
16849
  throw new InvalidArgumentError2("Snapshot path is required");
16850
16850
  }
16851
16851
  const resolvedPath = resolve(path3);
16852
- await mkdir16(dirname13(resolvedPath), { recursive: true });
16852
+ await mkdir17(dirname14(resolvedPath), { recursive: true });
16853
16853
  const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
16854
16854
  hash,
16855
16855
  snapshot
16856
16856
  }));
16857
- await writeFile12(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
16857
+ await writeFile13(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
16858
16858
  }
16859
16859
  /**
16860
16860
  * Clears all recorded snapshots
@@ -28469,8 +28469,8 @@ var require_graceful_fs = __commonJS({
28469
28469
  fs2.createReadStream = createReadStream;
28470
28470
  fs2.createWriteStream = createWriteStream;
28471
28471
  var fs$readFile = fs2.readFile;
28472
- fs2.readFile = readFile16;
28473
- function readFile16(path3, options, cb) {
28472
+ fs2.readFile = readFile17;
28473
+ function readFile17(path3, options, cb) {
28474
28474
  if (typeof options === "function")
28475
28475
  cb = options, options = null;
28476
28476
  return go$readFile(path3, options, cb);
@@ -28486,8 +28486,8 @@ var require_graceful_fs = __commonJS({
28486
28486
  }
28487
28487
  }
28488
28488
  var fs$writeFile = fs2.writeFile;
28489
- fs2.writeFile = writeFile12;
28490
- function writeFile12(path3, data, options, cb) {
28489
+ fs2.writeFile = writeFile13;
28490
+ function writeFile13(path3, data, options, cb) {
28491
28491
  if (typeof options === "function")
28492
28492
  cb = options, options = null;
28493
28493
  return go$writeFile(path3, data, options, cb);
@@ -28504,8 +28504,8 @@ var require_graceful_fs = __commonJS({
28504
28504
  }
28505
28505
  var fs$appendFile = fs2.appendFile;
28506
28506
  if (fs$appendFile)
28507
- fs2.appendFile = appendFile2;
28508
- function appendFile2(path3, data, options, cb) {
28507
+ fs2.appendFile = appendFile4;
28508
+ function appendFile4(path3, data, options, cb) {
28509
28509
  if (typeof options === "function")
28510
28510
  cb = options, options = null;
28511
28511
  return go$appendFile(path3, data, options, cb);
@@ -29737,7 +29737,7 @@ var ComputerAttachClient = class {
29737
29737
  * over the machine. On success the server marks the machine row migrated
29738
29738
  * and mints a fresh `sk_computer_*`. The raw legacy key MUST NOT be
29739
29739
  * persisted by the caller — only the freshly-minted sk_computer_* lands
29740
- * in attachment.json.
29740
+ * in runner.state.json.
29741
29741
  */
29742
29742
  async adoptLegacy(legacyApiKey, name) {
29743
29743
  const res = await (0, import_undici.fetch)(this.url("/api/computer/adopt-legacy"), {
@@ -29877,7 +29877,7 @@ function serverDir(slockHome, serverId) {
29877
29877
  return path2.join(serversDir(slockHome), assertValidServerId(serverId));
29878
29878
  }
29879
29879
  function serverAttachmentPath(slockHome, serverId) {
29880
- return path2.join(serverDir(slockHome, serverId), "attachment.json");
29880
+ return path2.join(serverDir(slockHome, serverId), "runner.state.json");
29881
29881
  }
29882
29882
  function serverDaemonPidPath(slockHome, serverId) {
29883
29883
  return path2.join(serverDir(slockHome, serverId), "daemon.pid");
@@ -29891,14 +29891,20 @@ function serverManagedFlagPath(slockHome, serverId) {
29891
29891
  function serverHealthPath(slockHome, serverId) {
29892
29892
  return path2.join(serverDir(slockHome, serverId), "health.json");
29893
29893
  }
29894
- function supervisorPidPath(slockHome) {
29894
+ function serviceStatePath(slockHome) {
29895
+ return path2.join(computerDir(slockHome), "service.state.json");
29896
+ }
29897
+ function servicePidPath(slockHome) {
29898
+ return path2.join(computerDir(slockHome), "service.pid");
29899
+ }
29900
+ function legacySupervisorPidPath(slockHome) {
29895
29901
  return path2.join(computerDir(slockHome), "supervisor.pid");
29896
29902
  }
29897
- function supervisorLogPath(slockHome) {
29898
- return path2.join(computerDir(slockHome), "supervisor.log");
29903
+ function serviceLogPath(slockHome) {
29904
+ return path2.join(computerDir(slockHome), "service.log");
29899
29905
  }
29900
- function supervisorVersionPath(slockHome) {
29901
- return path2.join(computerDir(slockHome), "supervisor-version.json");
29906
+ function serviceVersionPath(slockHome) {
29907
+ return path2.join(computerDir(slockHome), "service-version.json");
29902
29908
  }
29903
29909
  var HOSTNAME_SUFFIXES = [
29904
29910
  ".fritz.box",
@@ -30376,15 +30382,78 @@ async function runAttach(opts) {
30376
30382
  }
30377
30383
  }
30378
30384
 
30379
- // src/adopt.ts
30385
+ // src/setup.ts
30380
30386
  init_esm_shims();
30381
- import { readFile as readFile4 } from "fs/promises";
30387
+ var import_undici3 = __toESM(require_undici(), 1);
30388
+ import { chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename3, rm as rm2, writeFile as writeFile8 } from "fs/promises";
30389
+ import { dirname as dirname9 } from "path";
30390
+
30391
+ // src/lib/migration.ts
30392
+ init_esm_shims();
30393
+ import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
30394
+ import { join } from "path";
30395
+ var MACHINE_DIR_PREFIX = "machine-";
30396
+ async function readOwnerEvidence(installRoot, machineDirName) {
30397
+ const ownerFile = join(
30398
+ installRoot,
30399
+ "machines",
30400
+ machineDirName,
30401
+ "daemon.lock",
30402
+ "owner.json"
30403
+ );
30404
+ let raw;
30405
+ try {
30406
+ raw = await readFile3(ownerFile, "utf8");
30407
+ } catch {
30408
+ return {};
30409
+ }
30410
+ let parsed;
30411
+ try {
30412
+ parsed = JSON.parse(raw);
30413
+ } catch {
30414
+ return {};
30415
+ }
30416
+ const machineName = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : void 0;
30417
+ const serverUrl = typeof parsed.serverUrl === "string" && parsed.serverUrl.length > 0 ? parsed.serverUrl : void 0;
30418
+ return { machineName, serverUrl };
30419
+ }
30420
+ async function detectLegacyMigration(installRoot, loggedInUserId) {
30421
+ void loggedInUserId;
30422
+ const machinesDir = join(installRoot, "machines");
30423
+ let entries;
30424
+ try {
30425
+ entries = await readdir2(machinesDir);
30426
+ } catch {
30427
+ return { kind: "no-match" };
30428
+ }
30429
+ const candidates = [];
30430
+ for (const name of entries) {
30431
+ if (!name.startsWith(MACHINE_DIR_PREFIX)) continue;
30432
+ const evidence = await readOwnerEvidence(installRoot, name);
30433
+ candidates.push({
30434
+ machineId: name,
30435
+ machineName: evidence.machineName,
30436
+ serverUrl: evidence.serverUrl
30437
+ });
30438
+ }
30439
+ if (candidates.length === 0) return { kind: "no-match" };
30440
+ if (candidates.length === 1) {
30441
+ const only = candidates[0];
30442
+ return {
30443
+ kind: "match",
30444
+ machineId: only.machineId,
30445
+ machineName: only.machineName,
30446
+ serverUrl: only.serverUrl
30447
+ };
30448
+ }
30449
+ return { kind: "ambiguous", candidates };
30450
+ }
30382
30451
 
30383
30452
  // src/services/adoptLegacy.ts
30384
30453
  init_esm_shims();
30385
- import { chmod as chmod3, mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4, appendFile, stat } from "fs/promises";
30454
+ import { chmod as chmod3, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, appendFile, stat } from "fs/promises";
30386
30455
  import { createHash as createHash2 } from "crypto";
30387
- import { dirname as dirname4, join } from "path";
30456
+ import { dirname as dirname4, join as join2 } from "path";
30388
30457
  import { setTimeout as delay } from "timers/promises";
30389
30458
  function emit3(opts, event) {
30390
30459
  const cb = opts?.onEvent;
@@ -30398,7 +30467,7 @@ var LEGACY_STOP_WAIT_MS = 1e4;
30398
30467
  var LEGACY_STOP_POLL_MS = 200;
30399
30468
  function legacyLockOwnerPath(slockHome, rawKey) {
30400
30469
  const fingerprint = createHash2("sha256").update(rawKey).digest("hex").slice(0, 16);
30401
- return join(slockHome, "machines", `machine-${fingerprint}`, "daemon.lock", "owner.json");
30470
+ return join2(slockHome, "machines", `machine-${fingerprint}`, "daemon.lock", "owner.json");
30402
30471
  }
30403
30472
  function isProcessAlive(pid) {
30404
30473
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -30413,7 +30482,7 @@ function isProcessAlive(pid) {
30413
30482
  async function stopLegacyDaemonByOwnerFile(ownerFile) {
30414
30483
  let raw;
30415
30484
  try {
30416
- raw = await readFile3(ownerFile, "utf8");
30485
+ raw = await readFile4(ownerFile, "utf8");
30417
30486
  } catch {
30418
30487
  return { attempted: false, outcome: "absent" };
30419
30488
  }
@@ -30447,7 +30516,7 @@ async function stopLegacyDaemonByOwnerFile(ownerFile) {
30447
30516
  async function readLegacyOwnerEvidence(ownerFile) {
30448
30517
  let raw;
30449
30518
  try {
30450
- raw = await readFile3(ownerFile, "utf8");
30519
+ raw = await readFile4(ownerFile, "utf8");
30451
30520
  } catch {
30452
30521
  return { ownerFile, reason: "owner_file_absent" };
30453
30522
  }
@@ -30487,7 +30556,7 @@ async function adoptLegacy(input, options = {}) {
30487
30556
  const sessionFile = userSessionPath(slockHome);
30488
30557
  let session;
30489
30558
  try {
30490
- session = JSON.parse(await readFile3(sessionFile, "utf8"));
30559
+ session = JSON.parse(await readFile4(sessionFile, "utf8"));
30491
30560
  } catch (err) {
30492
30561
  throw new ComputerServiceError(
30493
30562
  "NO_USER_SESSION",
@@ -30733,134 +30802,18 @@ async function appendAdoptionLog(slockHome, line) {
30733
30802
  }
30734
30803
  }
30735
30804
 
30736
- // src/adopt.ts
30737
- async function resolveLegacyKey(inputs, env) {
30738
- const sources = [];
30739
- if (typeof inputs.legacyApiKey === "string" && inputs.legacyApiKey.length > 0) {
30740
- sources.push({
30741
- mode: "legacy_key_argv",
30742
- load: async () => inputs.legacyApiKey.trim()
30743
- });
30744
- }
30745
- if (typeof inputs.legacyApiKeyFile === "string" && inputs.legacyApiKeyFile.length > 0) {
30746
- sources.push({
30747
- mode: "legacy_key_file",
30748
- load: async () => {
30749
- const contents = await readFile4(inputs.legacyApiKeyFile, "utf8");
30750
- return contents.trim();
30751
- }
30752
- });
30753
- }
30754
- if (inputs.legacyApiKeyStdin) {
30755
- sources.push({
30756
- mode: "legacy_key_stdin",
30757
- load: async () => readAllStdin()
30758
- });
30759
- }
30760
- const envKey = env.SLOCK_LEGACY_API_KEY;
30761
- if (typeof envKey === "string" && envKey.trim().length > 0) {
30762
- sources.push({
30763
- mode: "legacy_key_env",
30764
- load: async () => envKey.trim()
30765
- });
30766
- }
30767
- if (sources.length === 0) {
30768
- fail(
30769
- "LEGACY_KEY_REQUIRED",
30770
- "No legacy api key provided. Pass exactly one of: --legacy-api-key <key>, --legacy-api-key-file <path>, --legacy-api-key-stdin, or SLOCK_LEGACY_API_KEY."
30771
- );
30772
- }
30773
- if (sources.length > 1) {
30774
- const modes = sources.map((s) => s.mode).join(", ");
30775
- fail(
30776
- "LEGACY_KEY_MULTIPLE_SOURCES",
30777
- `Multiple legacy api key sources provided (${modes}). Choose exactly one.`
30778
- );
30779
- }
30780
- const chosen = sources[0];
30781
- const rawKey = await chosen.load();
30782
- if (chosen.mode === "legacy_key_env") {
30783
- delete env.SLOCK_LEGACY_API_KEY;
30784
- }
30785
- if (!rawKey.startsWith("sk_machine_") && !rawKey.startsWith("sk_daemon_")) {
30786
- fail(
30787
- "LEGACY_KEY_INVALID",
30788
- "Provided key does not look like a legacy machine key (expected `sk_machine_*` or `sk_daemon_*`)."
30789
- );
30790
- }
30791
- return {
30792
- rawKey,
30793
- mode: chosen.mode,
30794
- redactedPrefix: rawKey.slice(0, 8)
30795
- };
30796
- }
30797
- async function readAllStdin() {
30798
- return new Promise((resolve, reject) => {
30799
- const chunks = [];
30800
- process.stdin.on("data", (chunk) => chunks.push(chunk));
30801
- process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8").trim()));
30802
- process.stdin.on("error", reject);
30803
- });
30804
- }
30805
- async function runAdoptLegacy(inputs) {
30806
- const legacy = await resolveLegacyKey(inputs, process.env);
30807
- try {
30808
- await adoptLegacy(
30809
- {
30810
- serverSlug: inputs.serverSlug,
30811
- serverUrl: inputs.serverUrl,
30812
- name: inputs.name,
30813
- rawKey: legacy.rawKey,
30814
- mode: legacy.mode,
30815
- redactedPrefix: legacy.redactedPrefix
30816
- },
30817
- {
30818
- onEvent: (event) => {
30819
- if (event.type === "adopting") {
30820
- info(
30821
- `Adopting legacy daemon for ${formatServerSlugDisplay(event.serverSlug)} via ${event.mode}\u2026`
30822
- );
30823
- } else if (event.type === "preflight") {
30824
- info(event.resumed ? "Adopted (resumed prior attachment); running preflight\u2026" : "Adopted; running preflight\u2026");
30825
- } else if (event.type === "adopted") {
30826
- info(`Adopted. Computer state written to ${event.attachmentPath}`);
30827
- info(` server: ${formatServerSlugDisplay(event.serverSlug)}`);
30828
- info(` serverMachine: ${event.serverMachineId}`);
30829
- info(` legacyMachine: ${event.legacyMachineId}`);
30830
- switch (event.legacyStop.outcome) {
30831
- case "absent":
30832
- info(" legacy daemon: not detected on this Computer (no local lock file)");
30833
- break;
30834
- case "already_dead":
30835
- info(` legacy daemon: already stopped (pid ${event.legacyStop.pid} not running)`);
30836
- break;
30837
- case "stopped":
30838
- info(` legacy daemon: stopped (pid ${event.legacyStop.pid}, SIGTERM)`);
30839
- break;
30840
- }
30841
- }
30842
- }
30843
- }
30844
- );
30845
- } catch (err) {
30846
- if (err instanceof CliExit) throw err;
30847
- if (err instanceof ComputerServiceError) {
30848
- fail(err.code, err.message);
30849
- }
30850
- throw err;
30851
- } finally {
30852
- legacy.rawKey = void 0;
30853
- }
30854
- if (!inputs.orchestrated) {
30855
- info(`Next: run \`slock-computer start\` to bring this server online under the Computer supervisor.`);
30856
- }
30857
- }
30805
+ // src/service.ts
30806
+ init_esm_shims();
30807
+ import { spawn as spawn2 } from "child_process";
30808
+ import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile7, open, rename as rename2 } from "fs/promises";
30809
+ import { dirname as dirname8, join as joinPath } from "path";
30810
+ import { fileURLToPath as fileURLToPath2 } from "url";
30858
30811
 
30859
- // src/setup.ts
30812
+ // src/cleanup.ts
30860
30813
  init_esm_shims();
30861
- var import_undici3 = __toESM(require_undici(), 1);
30862
- import { chmod as chmod4, mkdir as mkdir9, readdir as readdir3, readFile as readFile8, rename as rename3, rm as rm2, writeFile as writeFile8 } from "fs/promises";
30863
- import { dirname as dirname9, join as join3 } from "path";
30814
+ import { readdir as readdir3, stat as stat2, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
30815
+ import { spawn } from "child_process";
30816
+ import { join as join3 } from "path";
30864
30817
 
30865
30818
  // src/internal/process-primitives.ts
30866
30819
  init_esm_shims();
@@ -30895,18 +30848,7 @@ async function clearPidfileAt(pidfilePath) {
30895
30848
  }
30896
30849
  }
30897
30850
 
30898
- // src/supervisor.ts
30899
- init_esm_shims();
30900
- import { spawn as spawn2 } from "child_process";
30901
- import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile7, open, rename as rename2 } from "fs/promises";
30902
- import { dirname as dirname8, join as joinPath } from "path";
30903
- import { fileURLToPath as fileURLToPath2 } from "url";
30904
-
30905
30851
  // src/cleanup.ts
30906
- init_esm_shims();
30907
- import { readdir as readdir2, stat as stat2, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
30908
- import { spawn } from "child_process";
30909
- import { join as join2 } from "path";
30910
30852
  function emptyCleanupReport() {
30911
30853
  return {
30912
30854
  stalePidfiles: [],
@@ -30930,8 +30872,8 @@ async function cleanupStalePidfile(pidfilePath) {
30930
30872
  }
30931
30873
  async function cleanupAllStalePidfiles(slockHome) {
30932
30874
  const cleaned = [];
30933
- if (await cleanupStalePidfile(supervisorPidPath(slockHome))) {
30934
- cleaned.push(supervisorPidPath(slockHome));
30875
+ if (await cleanupStalePidfile(servicePidPath(slockHome))) {
30876
+ cleaned.push(servicePidPath(slockHome));
30935
30877
  }
30936
30878
  const attached = await listAttachedServerIds(slockHome);
30937
30879
  for (const sid of attached) {
@@ -30980,7 +30922,7 @@ async function cleanupOrphanProcesses(slockHome, psSpawn) {
30980
30922
  const managed = new Set(await listManagedServerIds(slockHome));
30981
30923
  const signaled = [];
30982
30924
  const knownPids = /* @__PURE__ */ new Set();
30983
- const supPid = await readPidfileAt(supervisorPidPath(slockHome));
30925
+ const supPid = await readPidfileAt(servicePidPath(slockHome));
30984
30926
  if (supPid !== null) knownPids.add(supPid);
30985
30927
  for (const sid of managed) {
30986
30928
  const pid = await readPidfileAt(serverDaemonPidPath(slockHome, sid));
@@ -31001,12 +30943,12 @@ async function cleanupOrphanProcesses(slockHome, psSpawn) {
31001
30943
  return signaled;
31002
30944
  }
31003
30945
  function quarantineDir(slockHome) {
31004
- return join2(computerDir(slockHome), ".quarantine");
30946
+ return join3(computerDir(slockHome), ".quarantine");
31005
30947
  }
31006
30948
  async function quarantineServerSubtree(slockHome, serverId) {
31007
- const src = join2(serversDir(slockHome), serverId);
30949
+ const src = join3(serversDir(slockHome), serverId);
31008
30950
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
31009
- const dest = join2(quarantineDir(slockHome), `${stamp}-${serverId}`);
30951
+ const dest = join3(quarantineDir(slockHome), `${stamp}-${serverId}`);
31010
30952
  await mkdir6(dirname6(dest), { recursive: true });
31011
30953
  await rename(src, dest);
31012
30954
  return dest;
@@ -31018,7 +30960,7 @@ function dirname6(p) {
31018
30960
  async function cleanupPowerLossPartialState(slockHome) {
31019
30961
  let entries;
31020
30962
  try {
31021
- entries = await readdir2(serversDir(slockHome));
30963
+ entries = await readdir3(serversDir(slockHome));
31022
30964
  } catch {
31023
30965
  return [];
31024
30966
  }
@@ -31045,11 +30987,11 @@ var TMP_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
31045
30987
  async function cleanupTmpFiles(slockHome) {
31046
30988
  const removed = [];
31047
30989
  const cdir = computerDir(slockHome);
31048
- const stagingDir = join2(cdir, "upgrade-staging");
30990
+ const stagingDir = join3(cdir, "upgrade-staging");
31049
30991
  try {
31050
- const versions = await readdir2(stagingDir);
30992
+ const versions = await readdir3(stagingDir);
31051
30993
  for (const v of versions) {
31052
- const vdir = join2(stagingDir, v);
30994
+ const vdir = join3(stagingDir, v);
31053
30995
  try {
31054
30996
  const s = await stat2(vdir);
31055
30997
  if (Date.now() - s.mtimeMs > TMP_MAX_AGE_MS) {
@@ -31060,13 +31002,13 @@ async function cleanupTmpFiles(slockHome) {
31060
31002
  }
31061
31003
  }
31062
31004
  try {
31063
- const remaining = await readdir2(stagingDir);
31005
+ const remaining = await readdir3(stagingDir);
31064
31006
  if (remaining.length === 0) await rmdir(stagingDir);
31065
31007
  } catch {
31066
31008
  }
31067
31009
  } catch {
31068
31010
  }
31069
- const snap = join2(cdir, "upgrade-snapshot.json");
31011
+ const snap = join3(cdir, "upgrade-snapshot.json");
31070
31012
  try {
31071
31013
  const s = await stat2(snap);
31072
31014
  if (Date.now() - s.mtimeMs > TMP_MAX_AGE_MS) {
@@ -31078,7 +31020,7 @@ async function cleanupTmpFiles(slockHome) {
31078
31020
  return removed;
31079
31021
  }
31080
31022
  async function cleanupStaleLock(slockHome) {
31081
- const lockDir = join2(computerDir(slockHome), ".lock");
31023
+ const lockDir = join3(computerDir(slockHome), ".lock");
31082
31024
  try {
31083
31025
  const s = await stat2(lockDir);
31084
31026
  if (Date.now() - s.mtimeMs > 60 * 1e3) {
@@ -31090,7 +31032,7 @@ async function cleanupStaleLock(slockHome) {
31090
31032
  return [];
31091
31033
  }
31092
31034
  async function forceReleaseLock(slockHome) {
31093
- const lockDir = join2(computerDir(slockHome), ".lock");
31035
+ const lockDir = join3(computerDir(slockHome), ".lock");
31094
31036
  try {
31095
31037
  await stat2(lockDir);
31096
31038
  await rm(lockDir, { recursive: true, force: true });
@@ -31114,7 +31056,7 @@ async function runFullCleanup(slockHome, options = {}) {
31114
31056
 
31115
31057
  // src/health.ts
31116
31058
  init_esm_shims();
31117
- import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7 } from "fs/promises";
31059
+ import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7, appendFile as appendFile2 } from "fs/promises";
31118
31060
  import { dirname as dirname7 } from "path";
31119
31061
  var CRASH_WINDOW_MS = 6e4;
31120
31062
  var DEGRADED_THRESHOLD = 3;
@@ -31186,6 +31128,43 @@ async function resetHealth(slockHome, serverId) {
31186
31128
  } catch {
31187
31129
  }
31188
31130
  }
31131
+ async function resetRunnerHealth(slockHome, serverId, nowMs = Date.now()) {
31132
+ if (!isValidServerId(serverId)) {
31133
+ return { status: "not-found" };
31134
+ }
31135
+ const wasDegraded = await isDegraded(slockHome, serverId, nowMs);
31136
+ const recent = await readCrashHistory(slockHome, serverId, nowMs);
31137
+ const previousState = wasDegraded ? "degraded" : "running";
31138
+ await resetHealth(slockHome, serverId);
31139
+ await emitRunnerStateTransition(
31140
+ slockHome,
31141
+ serverId,
31142
+ previousState,
31143
+ "running",
31144
+ "reset-runner"
31145
+ );
31146
+ return {
31147
+ status: "ok",
31148
+ previousState,
31149
+ clearedCrashCount: recent.length
31150
+ };
31151
+ }
31152
+ async function emitRunnerStateTransition(slockHome, serverId, fromState, toState, trigger) {
31153
+ const entry = {
31154
+ at: (/* @__PURE__ */ new Date()).toISOString(),
31155
+ kind: "runner-state-changed",
31156
+ serverId,
31157
+ fromState,
31158
+ toState,
31159
+ trigger
31160
+ };
31161
+ try {
31162
+ const path3 = serviceLogPath(slockHome);
31163
+ await mkdir7(dirname7(path3), { recursive: true });
31164
+ await appendFile2(path3, JSON.stringify(entry) + "\n");
31165
+ } catch {
31166
+ }
31167
+ }
31189
31168
 
31190
31169
  // src/services/start.ts
31191
31170
  init_esm_shims();
@@ -31223,7 +31202,7 @@ async function waitForManagedDaemonPids(slockHome, serverIds, opts) {
31223
31202
  function buildTimeoutMessage(slockHome, serverIds, ready, input) {
31224
31203
  const missing = serverIds.filter((id) => !ready.has(id));
31225
31204
  const target = input.serverId && missing.length === 1 ? `${input.serverLabel ?? input.serverId}` : `${missing.length} daemon(s): ${missing.join(", ")}`;
31226
- return `Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${supervisorLogPath(slockHome)} plus per-server daemon logs under ~/.slock/computer/servers/<serverId>/daemon.log.`;
31205
+ return `Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${serviceLogPath(slockHome)} plus per-server daemon logs under ~/.slock/computer/servers/<serverId>/daemon.log.`;
31227
31206
  }
31228
31207
  async function start(input, options = {}) {
31229
31208
  options.signal?.throwIfAborted?.();
@@ -31251,11 +31230,11 @@ async function start(input, options = {}) {
31251
31230
  for (const id of managedTargets) {
31252
31231
  await setServerManaged(slockHome, id);
31253
31232
  }
31254
- const existing = await readPidfileAt(supervisorPidPath(slockHome));
31233
+ const existing = await readPidfileAt(servicePidPath(slockHome));
31255
31234
  if (existing && isProcessAlive2(existing)) {
31256
31235
  emit4(options, {
31257
31236
  type: "already_running",
31258
- supervisorPid: existing,
31237
+ servicePid: existing,
31259
31238
  managedTargets,
31260
31239
  attachedCount: attached.length
31261
31240
  });
@@ -31272,8 +31251,8 @@ async function start(input, options = {}) {
31272
31251
  managedTargets,
31273
31252
  attachedCount: attached.length,
31274
31253
  ready: ready2,
31275
- supervisorPid: existing,
31276
- supervisorLogPath: supervisorLogPath(slockHome)
31254
+ servicePid: existing,
31255
+ serviceLogPath: serviceLogPath(slockHome)
31277
31256
  };
31278
31257
  }
31279
31258
  if (input.foreground) {
@@ -31282,41 +31261,48 @@ async function start(input, options = {}) {
31282
31261
  managedTargets,
31283
31262
  attachedCount: attached.length
31284
31263
  });
31285
- const supervise = options.runSupervise ?? runSupervise;
31286
- await supervise();
31264
+ const service = options.runService ?? runService;
31265
+ const previousParentLockMarker = process.env[PARENT_LOCK_HELD_ENV_VAR];
31266
+ process.env[PARENT_LOCK_HELD_ENV_VAR] = "1";
31267
+ try {
31268
+ await service();
31269
+ } finally {
31270
+ if (previousParentLockMarker === void 0) delete process.env[PARENT_LOCK_HELD_ENV_VAR];
31271
+ else process.env[PARENT_LOCK_HELD_ENV_VAR] = previousParentLockMarker;
31272
+ }
31287
31273
  return {
31288
31274
  status: "running",
31289
31275
  managedTargets,
31290
31276
  attachedCount: attached.length,
31291
31277
  ready: /* @__PURE__ */ new Map(),
31292
- supervisorPid: null,
31293
- supervisorLogPath: supervisorLogPath(slockHome)
31278
+ servicePid: null,
31279
+ serviceLogPath: serviceLogPath(slockHome)
31294
31280
  };
31295
31281
  }
31296
31282
  options.signal?.throwIfAborted?.();
31297
31283
  let pid;
31298
31284
  try {
31299
- pid = await (options.spawnDetachedSupervisor ?? spawnDetachedSupervisor)(slockHome);
31285
+ pid = await (options.spawnDetachedService ?? spawnDetachedService)(slockHome);
31300
31286
  } catch (err) {
31301
31287
  const msg = err instanceof Error ? err.message : String(err);
31302
31288
  throw new ComputerServiceError("SUPERVISOR_SPAWN_FAILED", msg, err);
31303
31289
  }
31304
31290
  emit4(options, {
31305
31291
  type: "spawned",
31306
- supervisorPid: pid,
31292
+ servicePid: pid,
31307
31293
  managedTargets,
31308
31294
  attachedCount: attached.length
31309
31295
  });
31310
31296
  if (options.signal?.aborted) {
31311
31297
  const ready2 = await pollReadyOnce(slockHome, managedTargets, options);
31312
- emit4(options, { type: "aborted", supervisorPid: pid, managedTargets, ready: ready2 });
31298
+ emit4(options, { type: "aborted", servicePid: pid, managedTargets, ready: ready2 });
31313
31299
  return {
31314
31300
  status: "aborted",
31315
31301
  managedTargets,
31316
31302
  attachedCount: attached.length,
31317
31303
  ready: ready2,
31318
- supervisorPid: pid,
31319
- supervisorLogPath: supervisorLogPath(slockHome)
31304
+ servicePid: pid,
31305
+ serviceLogPath: serviceLogPath(slockHome)
31320
31306
  };
31321
31307
  }
31322
31308
  const ready = await waitForManagedDaemonPids(slockHome, managedTargets, options);
@@ -31332,8 +31318,8 @@ async function start(input, options = {}) {
31332
31318
  managedTargets,
31333
31319
  attachedCount: attached.length,
31334
31320
  ready,
31335
- supervisorPid: pid,
31336
- supervisorLogPath: supervisorLogPath(slockHome)
31321
+ servicePid: pid,
31322
+ serviceLogPath: serviceLogPath(slockHome)
31337
31323
  };
31338
31324
  }
31339
31325
  async function pollReadyOnce(slockHome, serverIds, opts) {
@@ -31365,14 +31351,22 @@ async function stop(input = {}, options = {}) {
31365
31351
  const slockHome = resolveSlockHome();
31366
31352
  const readPidfile = options.readPidfile ?? readPidfileAt;
31367
31353
  const isAlive = options.isProcessAlive ?? isProcessAlive2;
31368
- const killer = options.killSupervisor ?? ((pid2) => {
31354
+ const killer = options.killService ?? ((pid2) => {
31369
31355
  process.kill(pid2, "SIGTERM");
31370
31356
  });
31371
31357
  const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
31372
31358
  const pollIntervalMs = options.pollIntervalMs ?? STOP_POLL_INTERVAL_MS;
31373
31359
  const timeoutMs = options.timeoutMs ?? STOP_TIMEOUT_MS;
31374
- const pidfilePath = supervisorPidPath(slockHome);
31375
- const pid = await readPidfile(pidfilePath);
31360
+ let pidfilePath = servicePidPath(slockHome);
31361
+ let pid = await readPidfile(pidfilePath);
31362
+ if (pid === null) {
31363
+ const legacyPidfilePath = legacySupervisorPidPath(slockHome);
31364
+ const legacyPid = await readPidfile(legacyPidfilePath);
31365
+ if (legacyPid !== null) {
31366
+ pidfilePath = legacyPidfilePath;
31367
+ pid = legacyPid;
31368
+ }
31369
+ }
31376
31370
  emit5(options, { type: "stopping", pid });
31377
31371
  if (pid === null) {
31378
31372
  emit5(options, { type: "not_running" });
@@ -31390,7 +31384,7 @@ async function stop(input = {}, options = {}) {
31390
31384
  const cause = err instanceof Error ? err : new Error(String(err));
31391
31385
  throw new ComputerServiceError(
31392
31386
  "STOP_SIGNAL_FAILED",
31393
- `Failed to send SIGTERM to supervisor (pid ${pid}): ${cause.message}. Check process permissions or run: kill ${pid}`,
31387
+ `Failed to send SIGTERM to service (pid ${pid}): ${cause.message}. Check process permissions or run: kill ${pid}`,
31394
31388
  err
31395
31389
  );
31396
31390
  }
@@ -31409,7 +31403,7 @@ async function stop(input = {}, options = {}) {
31409
31403
  }
31410
31404
  throw new ComputerServiceError(
31411
31405
  "STOP_TIMEOUT",
31412
- `Supervisor (pid ${pid}) did not exit within ${timeoutMs}ms after SIGTERM. Force-kill with: kill -9 ${pid}`
31406
+ `Service (pid ${pid}) did not exit within ${timeoutMs}ms after SIGTERM. Force-kill with: kill -9 ${pid}`
31413
31407
  );
31414
31408
  }
31415
31409
 
@@ -31525,16 +31519,16 @@ async function detach(input, options = {}) {
31525
31519
  };
31526
31520
  }
31527
31521
 
31528
- // src/supervisor.ts
31522
+ // src/service.ts
31529
31523
  function buildResidentSpawn(mode, serverId, selfEntry = process.argv[1] ?? "", execArgv = process.execArgv) {
31530
31524
  const tail = serverId ? [mode, serverId] : [mode];
31531
31525
  return { command: process.execPath, args: [...execArgv, selfEntry, ...tail] };
31532
31526
  }
31533
31527
  var PARENT_LOCK_HELD_ENV_VAR = "SLOCK_COMPUTER_PARENT_MUTATION_LOCK_HELD";
31534
- async function spawnDetachedSupervisor(slockHome) {
31528
+ async function spawnDetachedService(slockHome) {
31535
31529
  await mkdir8(computerDir(slockHome), { recursive: true });
31536
- const supLogFd = await open(supervisorLogPath(slockHome), "a");
31537
- const { command, args } = buildResidentSpawn("__supervise", null);
31530
+ const supLogFd = await open(serviceLogPath(slockHome), "a");
31531
+ const { command, args } = buildResidentSpawn("__service", null);
31538
31532
  const child = spawn2(command, args, {
31539
31533
  detached: true,
31540
31534
  stdio: ["ignore", supLogFd.fd, supLogFd.fd],
@@ -31551,9 +31545,9 @@ async function spawnDetachedSupervisor(slockHome) {
31551
31545
  child.unref();
31552
31546
  await supLogFd.close();
31553
31547
  if (!pid) {
31554
- throw new Error("SUPERVISOR_SPAWN_FAILED: could not spawn the supervisor process");
31548
+ throw new Error("SUPERVISOR_SPAWN_FAILED: could not spawn the service process");
31555
31549
  }
31556
- await writePidfileAt(supervisorPidPath(slockHome), pid);
31550
+ await writePidfileAt(servicePidPath(slockHome), pid);
31557
31551
  return pid;
31558
31552
  }
31559
31553
  var EX_CONFIG_EXIT_CODE = 78;
@@ -31579,7 +31573,7 @@ var defaultCoreFactory = async (creds) => {
31579
31573
  }
31580
31574
  return new coreMod.DaemonCore({ serverUrl: creds.serverUrl, apiKey: creds.apiKey, localTrace: true });
31581
31575
  };
31582
- function classifyChildExit(code, signal) {
31576
+ function classifyRunnerExit(code, signal) {
31583
31577
  if (code === EX_CONFIG_EXIT_CODE) return "config-error";
31584
31578
  if (signal === "SIGTERM" || signal === "SIGINT") return "graceful";
31585
31579
  if (code === 0) return "graceful";
@@ -31624,18 +31618,18 @@ function formatReadySummary(ready, serverIds, opts) {
31624
31618
  }
31625
31619
  return `Daemons for ${serverIds.length} managed server(s) are running.`;
31626
31620
  }
31627
- async function runSupervisorStartupRecovery(slockHome) {
31621
+ async function runServiceStartupRecovery(slockHome) {
31628
31622
  const parentHoldsLock = process.env[PARENT_LOCK_HELD_ENV_VAR] === "1";
31629
31623
  try {
31630
31624
  if (parentHoldsLock) {
31631
31625
  process.stderr.write(
31632
- "Supervisor startup: parent CLI holds mutation lock \u2014 skipping lock cleanup.\n"
31626
+ "Service startup: parent CLI holds mutation lock \u2014 skipping lock cleanup.\n"
31633
31627
  );
31634
31628
  } else {
31635
31629
  const releasedLocks = await forceReleaseLock(slockHome);
31636
31630
  if (releasedLocks.length > 0) {
31637
31631
  process.stderr.write(
31638
- `Supervisor startup: force-released ${releasedLocks.length} stale lock(s).
31632
+ `Service startup: force-released ${releasedLocks.length} stale lock(s).
31639
31633
  `
31640
31634
  );
31641
31635
  }
@@ -31643,19 +31637,19 @@ async function runSupervisorStartupRecovery(slockHome) {
31643
31637
  const report = await runFullCleanup(slockHome, { skipLockCleanup: parentHoldsLock });
31644
31638
  if (report.anyAction) {
31645
31639
  process.stderr.write(
31646
- `Supervisor startup recovery: cleaned ${report.stalePidfiles.length} pidfile(s), ${report.powerLossRecovered.length} quarantined, ${report.tmpFilesCleared.length} tmp file(s), ${report.staleLocks.length} stale lock(s).
31640
+ `Service startup recovery: cleaned ${report.stalePidfiles.length} pidfile(s), ${report.powerLossRecovered.length} quarantined, ${report.tmpFilesCleared.length} tmp file(s), ${report.staleLocks.length} stale lock(s).
31647
31641
  `
31648
31642
  );
31649
31643
  }
31650
31644
  } catch (err) {
31651
31645
  const msg = err instanceof Error ? err.message : String(err);
31652
31646
  process.stderr.write(
31653
- `Supervisor startup recovery pass failed: ${msg}. Continuing; cleanup will retry on next supervisor restart.
31647
+ `Service startup recovery pass failed: ${msg}. Continuing; cleanup will retry on next service restart.
31654
31648
  `
31655
31649
  );
31656
31650
  }
31657
31651
  }
31658
- async function resolveSupervisorIdentity() {
31652
+ async function resolveServiceIdentity() {
31659
31653
  const here = fileURLToPath2(import.meta.url);
31660
31654
  const installRoot = dirname8(dirname8(here));
31661
31655
  let version = null;
@@ -31669,30 +31663,30 @@ async function resolveSupervisorIdentity() {
31669
31663
  }
31670
31664
  return { installRoot, version };
31671
31665
  }
31672
- async function writeSupervisorVersionEvidence(slockHome) {
31666
+ async function writeServiceVersionEvidence(slockHome) {
31673
31667
  try {
31674
- const ident = await resolveSupervisorIdentity();
31668
+ const ident = await resolveServiceIdentity();
31675
31669
  const payload = {
31676
31670
  version: ident.version,
31677
31671
  installRoot: ident.installRoot,
31678
31672
  pid: process.pid,
31679
31673
  writtenAt: (/* @__PURE__ */ new Date()).toISOString()
31680
31674
  };
31681
- const dest = supervisorVersionPath(slockHome);
31675
+ const dest = serviceVersionPath(slockHome);
31682
31676
  const tmp = `${dest}.tmp`;
31683
31677
  await writeFile7(tmp, JSON.stringify(payload) + "\n", { mode: 384 });
31684
31678
  await rename2(tmp, dest);
31685
31679
  } catch (err) {
31686
31680
  const msg = err instanceof Error ? err.message : String(err);
31687
31681
  process.stderr.write(
31688
- `Supervisor: failed to write version evidence: ${msg}. Continuing.
31682
+ `Service: failed to write version evidence: ${msg}. Continuing.
31689
31683
  `
31690
31684
  );
31691
31685
  }
31692
31686
  }
31693
- async function readSupervisorVersionEvidence(slockHome) {
31687
+ async function readServiceVersionEvidence(slockHome) {
31694
31688
  try {
31695
- const raw = await readFile7(supervisorVersionPath(slockHome), "utf8");
31689
+ const raw = await readFile7(serviceVersionPath(slockHome), "utf8");
31696
31690
  const parsed = JSON.parse(raw);
31697
31691
  if (typeof parsed.installRoot !== "string" || typeof parsed.pid !== "number" || typeof parsed.writtenAt !== "string" || parsed.version !== null && typeof parsed.version !== "string") {
31698
31692
  return null;
@@ -31707,12 +31701,12 @@ async function readSupervisorVersionEvidence(slockHome) {
31707
31701
  return null;
31708
31702
  }
31709
31703
  }
31710
- async function runSupervise() {
31704
+ async function runService() {
31711
31705
  const slockHome = resolveSlockHome();
31712
31706
  await mkdir8(computerDir(slockHome), { recursive: true });
31713
- await runSupervisorStartupRecovery(slockHome);
31714
- await writePidfileAt(supervisorPidPath(slockHome), process.pid);
31715
- await writeSupervisorVersionEvidence(slockHome);
31707
+ await runServiceStartupRecovery(slockHome);
31708
+ await writePidfileAt(servicePidPath(slockHome), process.pid);
31709
+ await writeServiceVersionEvidence(slockHome);
31716
31710
  const children = /* @__PURE__ */ new Map();
31717
31711
  const { [PARENT_LOCK_HELD_ENV_VAR]: _parentLockMarker, ...childEnv } = process.env;
31718
31712
  const spawnChild = async (serverId) => {
@@ -31735,14 +31729,14 @@ async function runSupervise() {
31735
31729
  children.delete(serverId);
31736
31730
  await clearPidfileAt(serverDaemonPidPath(slockHome, serverId));
31737
31731
  if (handle.stopping) return;
31738
- const classification = classifyChildExit(code, signal);
31732
+ const classification = classifyRunnerExit(code, signal);
31739
31733
  if (classification === "config-error") {
31740
31734
  try {
31741
31735
  await markFatalConfig(slockHome, serverId, code, signal);
31742
31736
  } catch {
31743
31737
  }
31744
31738
  process.stderr.write(
31745
- `Supervisor: server ${serverId} child exited with EX_CONFIG (${EX_CONFIG_EXIT_CODE}); marked degraded, NOT auto-restarting. See ${serverDaemonLogPath(slockHome, serverId)} for the actionable error.
31739
+ `Service: server ${serverId} child exited with EX_CONFIG (${EX_CONFIG_EXIT_CODE}); marked degraded, NOT auto-restarting. See ${serverDaemonLogPath(slockHome, serverId)} for the actionable error.
31746
31740
  `
31747
31741
  );
31748
31742
  return;
@@ -31760,7 +31754,7 @@ async function runSupervise() {
31760
31754
  }
31761
31755
  if (await isDegraded(slockHome, serverId)) {
31762
31756
  process.stderr.write(
31763
- `Supervisor: server ${serverId} marked degraded (>=3 crashes in 60s); skipping auto-restart. Run \`slock-computer doctor ${serverId} --reset-health\` after fixing the underlying issue.
31757
+ `Service: server ${serverId} marked degraded (>=3 crashes in 60s); skipping auto-restart. Run \`slock-computer doctor ${serverId} --reset-health\` after fixing the underlying issue.
31764
31758
  `
31765
31759
  );
31766
31760
  return;
@@ -31793,7 +31787,7 @@ async function runSupervise() {
31793
31787
  if (shuttingDown) return;
31794
31788
  shuttingDown = true;
31795
31789
  for (const handle of children.values()) killChild(handle);
31796
- void clearPidfileAt(supervisorPidPath(slockHome)).then(() => process.exit(0));
31790
+ void clearPidfileAt(servicePidPath(slockHome)).then(() => process.exit(0));
31797
31791
  };
31798
31792
  process.on("SIGTERM", shutdown);
31799
31793
  process.on("SIGINT", shutdown);
@@ -31815,7 +31809,7 @@ async function runStart(opts = {}, deps = {}) {
31815
31809
  serverLabel: opts.serverLabel ?? null
31816
31810
  },
31817
31811
  {
31818
- spawnDetachedSupervisor: deps.spawnDetachedSupervisor,
31812
+ spawnDetachedService: deps.spawnDetachedService,
31819
31813
  readPidfile: deps.readPidfile,
31820
31814
  isProcessAlive: deps.isProcessAlive,
31821
31815
  sleep: deps.sleep,
@@ -31823,17 +31817,17 @@ async function runStart(opts = {}, deps = {}) {
31823
31817
  ensurePollIntervalMs: deps.ensurePollIntervalMs,
31824
31818
  onEvent: (event) => {
31825
31819
  if (event.type === "already_running") {
31826
- info(`Supervisor already running (pid ${event.supervisorPid}).`);
31820
+ info(`Service already running (pid ${event.servicePid}).`);
31827
31821
  } else if (event.type === "running") {
31828
31822
  info(
31829
- `Running supervisor in the foreground (managing ${event.managedTargets.length} of ${event.attachedCount} attached server(s)). Ctrl-C to stop.`
31823
+ `Running service in the foreground (managing ${event.managedTargets.length} of ${event.attachedCount} attached server(s)). Ctrl-C to stop.`
31830
31824
  );
31831
31825
  } else if (event.type === "spawned") {
31832
- info(`Supervisor started (pid ${event.supervisorPid}); keeps running after this terminal closes.`);
31826
+ info(`Service started (pid ${event.servicePid}); keeps running after this terminal closes.`);
31833
31827
  spawnedBackground = {
31834
31828
  managedCount: event.managedTargets.length,
31835
31829
  attachedCount: event.attachedCount,
31836
- logPath: supervisorLogPath(resolveSlockHome())
31830
+ logPath: serviceLogPath(resolveSlockHome())
31837
31831
  };
31838
31832
  } else if (event.type === "ready") {
31839
31833
  info(formatReadySummary(event.ready, event.managedTargets, opts));
@@ -31864,17 +31858,17 @@ async function runStop(deps = {}) {
31864
31858
  {
31865
31859
  readPidfile: deps.readPidfile,
31866
31860
  isProcessAlive: deps.isProcessAlive,
31867
- killSupervisor: deps.killSupervisor,
31861
+ killService: deps.killService,
31868
31862
  sleep: deps.sleep,
31869
31863
  pollIntervalMs: deps.pollIntervalMs,
31870
31864
  timeoutMs: deps.timeoutMs,
31871
31865
  onEvent: (event) => {
31872
31866
  if (event.type === "not_running") {
31873
- info("Supervisor not running.");
31867
+ info("Service not running.");
31874
31868
  } else if (event.type === "stale_pidfile_cleared") {
31875
- info(`Supervisor not running (cleared stale pidfile for pid ${event.pid}).`);
31869
+ info(`Service not running (cleared stale pidfile for pid ${event.pid}).`);
31876
31870
  } else if (event.type === "stopped") {
31877
- info(`Stopped supervisor (pid ${event.pid}).`);
31871
+ info(`Stopped service (pid ${event.pid}).`);
31878
31872
  }
31879
31873
  }
31880
31874
  }
@@ -31899,7 +31893,7 @@ async function runDetach(serverId, serverLabel = serverId) {
31899
31893
  } else if (event.type === "detached") {
31900
31894
  info(`Detached from server ${event.serverLabel}.`);
31901
31895
  info(
31902
- `The supervisor (if running) will stop that server's daemon on its next reconcile tick.`
31896
+ `The service (if running) will stop that server's daemon on its next reconcile tick.`
31903
31897
  );
31904
31898
  }
31905
31899
  }
@@ -31916,6 +31910,14 @@ async function runDetach(serverId, serverLabel = serverId) {
31916
31910
 
31917
31911
  // src/setup.ts
31918
31912
  var USER_SESSION_EXPIRY_LEEWAY_MS = 3e4;
31913
+ async function readUserSessionUserId(slockHome) {
31914
+ try {
31915
+ const parsed = JSON.parse(await readFile8(userSessionPath(slockHome), "utf8"));
31916
+ return typeof parsed.userId === "string" ? parsed.userId : "";
31917
+ } catch {
31918
+ return "";
31919
+ }
31920
+ }
31919
31921
  async function hasValidUserSession(slockHome) {
31920
31922
  try {
31921
31923
  const parsed = JSON.parse(await readFile8(userSessionPath(slockHome), "utf8"));
@@ -31983,49 +31985,74 @@ async function refreshUserSession(slockHome, serverUrl) {
31983
31985
  return false;
31984
31986
  }
31985
31987
  }
31986
- async function hasLiveLegacyDaemon(slockHome) {
31987
- let machineDirs;
31988
- try {
31989
- machineDirs = await readdir3(join3(slockHome, "machines"));
31990
- } catch {
31991
- return false;
31992
- }
31993
- for (const name of machineDirs) {
31994
- if (!name.startsWith("machine-")) continue;
31995
- try {
31996
- const raw = await readFile8(join3(slockHome, "machines", name, "daemon.lock", "owner.json"), "utf8");
31997
- const owner = JSON.parse(raw);
31998
- if (typeof owner.pid === "number" && isProcessAlive2(owner.pid)) return true;
31999
- } catch {
32000
- }
32001
- }
32002
- return false;
31988
+ async function defaultConfirmMigrate(prompt) {
31989
+ process.stdout.write(prompt);
31990
+ const line = await readLine(false);
31991
+ const norm = line.trim().toLowerCase();
31992
+ return norm === "y" || norm === "yes";
32003
31993
  }
32004
- function hasExplicitLegacyKeyInput(opts) {
32005
- return typeof opts.legacyApiKey === "string" && opts.legacyApiKey.length > 0 || typeof opts.legacyApiKeyFile === "string" && opts.legacyApiKeyFile.length > 0 || opts.legacyApiKeyStdin === true;
31994
+ async function defaultReadMigrateSecret(prompt) {
31995
+ process.stdout.write(prompt);
31996
+ const line = await readLine(true);
31997
+ process.stdout.write("\n");
31998
+ return line.trim();
32006
31999
  }
32007
- function hasAmbientLegacyKeyInput() {
32008
- return typeof process.env.SLOCK_LEGACY_API_KEY === "string" && process.env.SLOCK_LEGACY_API_KEY.trim().length > 0;
32000
+ async function readLine(masked) {
32001
+ const stdin = process.stdin;
32002
+ const wasRaw = stdin.isTTY === true && stdin.isRaw === true;
32003
+ const enterRaw = masked && stdin.isTTY === true && typeof stdin.setRawMode === "function";
32004
+ if (enterRaw) stdin.setRawMode(true);
32005
+ stdin.resume();
32006
+ return await new Promise((resolve) => {
32007
+ let buf = "";
32008
+ const cleanup = () => {
32009
+ stdin.off("data", onData);
32010
+ stdin.off("end", onEnd);
32011
+ stdin.pause();
32012
+ if (enterRaw) stdin.setRawMode(wasRaw);
32013
+ };
32014
+ const onData = (chunk) => {
32015
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
32016
+ for (const ch of text) {
32017
+ if (ch === "\n" || ch === "\r") {
32018
+ cleanup();
32019
+ resolve(buf);
32020
+ return;
32021
+ }
32022
+ if (ch === "") {
32023
+ cleanup();
32024
+ resolve("");
32025
+ return;
32026
+ }
32027
+ if (ch === "\x7F" || ch === "\b") {
32028
+ buf = buf.slice(0, -1);
32029
+ continue;
32030
+ }
32031
+ buf += ch;
32032
+ }
32033
+ };
32034
+ const onEnd = () => {
32035
+ cleanup();
32036
+ resolve(buf);
32037
+ };
32038
+ stdin.on("data", onData);
32039
+ stdin.on("end", onEnd);
32040
+ });
32009
32041
  }
32010
32042
  async function runSetup(opts, deps = {}) {
32011
32043
  const slockHome = resolveSlockHome();
32012
32044
  const isTty = deps.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
32013
32045
  const login2 = deps.runLogin ?? runLogin;
32014
32046
  const attach2 = deps.runAttach ?? runAttach;
32015
- const adopt = deps.runAdoptLegacy ?? runAdoptLegacy;
32016
32047
  const start2 = deps.runStart ?? runStart;
32017
32048
  const refreshSession = deps.refreshUserSession ?? refreshUserSession;
32018
- const legacyDaemonCheck = deps.hasLiveLegacyDaemon ?? hasLiveLegacyDaemon;
32049
+ const detectMigration = deps.detectLegacyMigration ?? detectLegacyMigration;
32050
+ const confirmMigrate = deps.confirmMigrate ?? defaultConfirmMigrate;
32051
+ const readMigrateSecret = deps.readMigrateSecret ?? defaultReadMigrateSecret;
32019
32052
  if (!isTty && !opts.yes) {
32020
32053
  fail(
32021
32054
  "NON_INTERACTIVE_SETUP_REQUIRES_FLAGS",
32022
- "Non-interactive setup requires --yes after you have confirmed the login/attach/adopt/start actions. Run `slock-computer login`, `slock-computer attach`, and `slock-computer start` separately for fully explicit automation."
32023
- );
32024
- }
32025
- if (!opts.adoptLegacy && hasExplicitLegacyKeyInput(opts)) {
32026
- fail(
32027
- "LEGACY_KEY_OUTSIDE_ADOPT",
32028
- "`--legacy-api-key*` options are only accepted with `slock-computer setup --adopt-legacy` or `slock-computer adopt-legacy`."
32055
+ "Non-interactive setup requires --yes after you have confirmed the login/attach/start actions. Run `slock-computer login`, `slock-computer attach`, and `slock-computer start` separately for fully explicit automation."
32029
32056
  );
32030
32057
  }
32031
32058
  const label = formatServerSlugDisplay(opts.serverSlug);
@@ -32050,34 +32077,82 @@ async function runSetup(opts, deps = {}) {
32050
32077
  if (attachment) {
32051
32078
  info(`Attachment: already attached to ${label}.`);
32052
32079
  } else {
32053
- const liveLegacy = await legacyDaemonCheck(slockHome);
32054
- const explicitLegacyKey = hasExplicitLegacyKeyInput(opts);
32055
- const hasLegacyKey = explicitLegacyKey || opts.adoptLegacy === true && hasAmbientLegacyKeyInput();
32056
- if (!opts.adoptLegacy && liveLegacy) {
32057
- fail(
32058
- "LEGACY_DAEMON_RUNNING",
32059
- `A legacy daemon appears to be running in ${slockHome}, so Computer setup stopped before creating a second attachment. To proceed: try the Computer beta in an isolated home with \`SLOCK_HOME=$HOME/.slock-computer-beta slock-computer setup ${label}\`; or stop the legacy daemon with \`pkill -f slock-daemon && rm -f ~/.slock/daemon.pid\`; or migrate the existing daemon with \`--adopt-legacy --legacy-api-key <key>\`.`
32060
- );
32061
- }
32062
- if (opts.adoptLegacy && hasLegacyKey) {
32063
- await adopt({
32064
- serverSlug: opts.serverSlug,
32065
- serverUrl: opts.serverUrl,
32066
- name: opts.name,
32067
- legacyApiKey: opts.legacyApiKey,
32068
- legacyApiKeyFile: opts.legacyApiKeyFile,
32069
- legacyApiKeyStdin: opts.legacyApiKeyStdin,
32070
- orchestrated: true
32071
- });
32072
- } else if (opts.adoptLegacy && liveLegacy) {
32073
- fail(
32074
- "LEGACY_DAEMON_RUNNING",
32075
- `A legacy daemon is running in ${slockHome}, but no legacy key was provided. To proceed: provide --legacy-api-key-file/--legacy-api-key/--legacy-api-key-stdin to adopt it; or stop the legacy daemon with \`pkill -f slock-daemon && rm -f ~/.slock/daemon.pid\`; or use an isolated home with \`SLOCK_HOME=$HOME/.slock-computer-beta slock-computer setup ${label}\` for a clean beta trial.`
32076
- );
32077
- } else {
32078
- if (opts.adoptLegacy) {
32079
- info("Legacy key: not provided; falling back to fresh device-login attach.");
32080
+ let migrated = false;
32081
+ if (isTty) {
32082
+ const detection = await detectMigration(slockHome, await readUserSessionUserId(slockHome));
32083
+ if (detection.kind === "match") {
32084
+ const where = detection.serverUrl ? ` attached to ${detection.serverUrl}` : "";
32085
+ const who = detection.machineName ? ` "${detection.machineName}"` : "";
32086
+ const accepted = await confirmMigrate(
32087
+ `Migration: detected legacy daemon machine${who}${where}. Migrate it under Computer instead of creating a fresh attachment? [y/N] `
32088
+ );
32089
+ if (accepted) {
32090
+ const rawKey = await readMigrateSecret(
32091
+ "Paste legacy api key (sk_machine_* or sk_daemon_*); input is hidden: "
32092
+ );
32093
+ if (rawKey.length === 0) {
32094
+ info("Migration: no key provided; falling back to fresh attach.");
32095
+ } else if (!rawKey.startsWith("sk_machine_") && !rawKey.startsWith("sk_daemon_")) {
32096
+ fail(
32097
+ "LEGACY_KEY_INVALID",
32098
+ "Provided key does not look like a legacy machine key (expected `sk_machine_*` or `sk_daemon_*`)."
32099
+ );
32100
+ } else {
32101
+ try {
32102
+ await adoptLegacy(
32103
+ {
32104
+ serverSlug: opts.serverSlug,
32105
+ serverUrl: opts.serverUrl,
32106
+ name: opts.name,
32107
+ rawKey,
32108
+ mode: "legacy_key_stdin",
32109
+ redactedPrefix: rawKey.slice(0, 8)
32110
+ },
32111
+ {
32112
+ onEvent: (event) => {
32113
+ if (event.type === "adopting") {
32114
+ info(
32115
+ `Adopting legacy daemon for ${formatServerSlugDisplay(event.serverSlug)} via ${event.mode}\u2026`
32116
+ );
32117
+ } else if (event.type === "preflight") {
32118
+ info(
32119
+ event.resumed ? "Adopted (resumed prior attachment); running preflight\u2026" : "Adopted; running preflight\u2026"
32120
+ );
32121
+ } else if (event.type === "adopted") {
32122
+ info(`Adopted. Computer state written to ${event.attachmentPath}`);
32123
+ info(` server: ${formatServerSlugDisplay(event.serverSlug)}`);
32124
+ info(` serverMachine: ${event.serverMachineId}`);
32125
+ info(` legacyMachine: ${event.legacyMachineId}`);
32126
+ switch (event.legacyStop.outcome) {
32127
+ case "absent":
32128
+ info(" legacy daemon: not detected on this Computer (no local lock file)");
32129
+ break;
32130
+ case "already_dead":
32131
+ info(` legacy daemon: already stopped (pid ${event.legacyStop.pid} not running)`);
32132
+ break;
32133
+ case "stopped":
32134
+ info(` legacy daemon: stopped (pid ${event.legacyStop.pid}, SIGTERM)`);
32135
+ break;
32136
+ }
32137
+ }
32138
+ }
32139
+ }
32140
+ );
32141
+ migrated = true;
32142
+ } catch (err) {
32143
+ if (err instanceof CliExit) throw err;
32144
+ if (err instanceof ComputerServiceError) {
32145
+ fail(err.code, err.message);
32146
+ }
32147
+ throw err;
32148
+ }
32149
+ }
32150
+ } else {
32151
+ info("Migration: declined; falling back to fresh attach.");
32152
+ }
32080
32153
  }
32154
+ }
32155
+ if (!migrated) {
32081
32156
  await attach2({
32082
32157
  serverSlug: opts.serverSlug,
32083
32158
  serverUrl: opts.serverUrl,
@@ -32090,7 +32165,7 @@ async function runSetup(opts, deps = {}) {
32090
32165
  if (!attachment) {
32091
32166
  fail(
32092
32167
  "SETUP_ATTACHMENT_MISSING",
32093
- `Setup did not produce a local attachment for ${label}. Re-run \`slock-computer attach ${label}\` or \`slock-computer adopt-legacy ${label}\`.`
32168
+ `Setup did not produce a local attachment for ${label}. Re-run \`slock-computer attach ${label}\`.`
32094
32169
  );
32095
32170
  }
32096
32171
  }
@@ -32138,37 +32213,36 @@ async function deriveHealth(slockHome, serverId, daemon) {
32138
32213
  if (await isDegraded(slockHome, serverId)) return "degraded";
32139
32214
  return "ok";
32140
32215
  }
32141
- async function buildStatusReport() {
32142
- const slockHome = resolveSlockHome();
32143
- const sessionRead = await readUserSession(userSessionPath(slockHome));
32216
+ async function buildStatusReport(installRoot) {
32217
+ const sessionRead = await readUserSession(userSessionPath(installRoot));
32144
32218
  const session = sessionRead.session;
32145
- const attachments = await listServerAttachments(slockHome);
32146
- const supervisor = {
32147
- ...await pidStatus(supervisorPidPath(slockHome)),
32148
- logPath: supervisorLogPath(slockHome)
32219
+ const attachments = await listServerAttachments(installRoot);
32220
+ const service = {
32221
+ ...await pidStatus(servicePidPath(installRoot)),
32222
+ logPath: serviceLogPath(installRoot)
32149
32223
  };
32150
32224
  const servers = [];
32151
32225
  for (const a of attachments) {
32152
- const daemon = await pidStatus(serverDaemonPidPath(slockHome, a.serverId));
32226
+ const daemon = await pidStatus(serverDaemonPidPath(installRoot, a.serverId));
32153
32227
  servers.push({
32154
32228
  serverId: a.serverId,
32155
32229
  serverSlug: a.serverSlug ?? null,
32156
32230
  serverMachineId: a.serverMachineId,
32157
32231
  serverUrl: a.serverUrl,
32158
32232
  attachedAt: a.attachedAt ?? null,
32159
- daemonLogPath: serverDaemonLogPath(slockHome, a.serverId),
32233
+ daemonLogPath: serverDaemonLogPath(installRoot, a.serverId),
32160
32234
  daemon,
32161
- health: await deriveHealth(slockHome, a.serverId, daemon)
32235
+ health: await deriveHealth(installRoot, a.serverId, daemon)
32162
32236
  });
32163
32237
  }
32164
32238
  const loggedIn = session?.kind === "user-session" && typeof session.accessToken === "string" && session.accessToken.length > 0;
32165
32239
  return {
32166
- slockHome,
32240
+ slockHome: installRoot,
32167
32241
  loggedIn,
32168
32242
  userId: session ? str(session.userId) : null,
32169
32243
  loginServerUrl: session ? str(session.serverUrl) : null,
32170
32244
  userSessionError: sessionRead.state === "invalid" ? sessionRead.error : null,
32171
- supervisor,
32245
+ service,
32172
32246
  servers
32173
32247
  };
32174
32248
  }
@@ -32176,7 +32250,7 @@ function pad(s, n) {
32176
32250
  return s.length >= n ? s : s + " ".repeat(n - s.length);
32177
32251
  }
32178
32252
  async function runStatus(opts) {
32179
- const report = await buildStatusReport();
32253
+ const report = await buildStatusReport(resolveSlockHome());
32180
32254
  if (opts.json) {
32181
32255
  info(JSON.stringify(report, null, 2));
32182
32256
  return;
@@ -32189,9 +32263,9 @@ async function runStatus(opts) {
32189
32263
  );
32190
32264
  if (report.loginServerUrl) info(`Login server: ${report.loginServerUrl}`);
32191
32265
  info(
32192
- `Supervisor: ${report.supervisor.running ? `running (pid ${report.supervisor.pid})` : "stopped \u2014 run `slock-computer start`"}`
32266
+ `Service: ${report.service.running ? `running (pid ${report.service.pid})` : "stopped \u2014 run `slock-computer start`"}`
32193
32267
  );
32194
- info(`Supervisor log: ${report.supervisor.logPath}`);
32268
+ info(`Service log: ${report.service.logPath}`);
32195
32269
  info("");
32196
32270
  if (report.servers.length === 0) {
32197
32271
  info("Attachments: none \u2014 run `slock-computer attach /<serverSlug>` (e.g. `/myserver`).");
@@ -32281,42 +32355,92 @@ async function resolveTargetAttachment(opts) {
32281
32355
  return a;
32282
32356
  }
32283
32357
 
32358
+ // src/lib/readers.ts
32359
+ init_esm_shims();
32360
+
32361
+ // src/lib/types.ts
32362
+ init_esm_shims();
32363
+ var StateReaderError = class extends Error {
32364
+ code;
32365
+ constructor(code, message) {
32366
+ super(message);
32367
+ this.name = "StateReaderError";
32368
+ this.code = code;
32369
+ }
32370
+ };
32371
+
32372
+ // src/lib/readers.ts
32373
+ async function listRunners(installRoot, opts = {}) {
32374
+ const all = await listServerAttachments(installRoot);
32375
+ let subset = all;
32376
+ if (opts.serverId !== void 0) {
32377
+ const found = all.find((a) => a.serverId === opts.serverId);
32378
+ if (!found) {
32379
+ throw new StateReaderError(
32380
+ "NOT_ATTACHED",
32381
+ `Server ${opts.serverId} is not attached to this Computer.`
32382
+ );
32383
+ }
32384
+ subset = [found];
32385
+ }
32386
+ const servers = [];
32387
+ for (const a of subset) {
32388
+ const client = new RunnersClient(a.serverUrl, a.apiKey);
32389
+ const result = await client.list();
32390
+ const idCols = { serverId: a.serverId, serverSlug: a.serverSlug ?? null };
32391
+ if (result.status === "success") {
32392
+ servers.push({
32393
+ ...idCols,
32394
+ status: "ok",
32395
+ whitelist: result.whitelist,
32396
+ runners: result.runners
32397
+ });
32398
+ } else if (result.status === "unauthorized") {
32399
+ servers.push({ ...idCols, status: "unauthorized" });
32400
+ } else {
32401
+ servers.push({ ...idCols, status: "error", code: result.code });
32402
+ }
32403
+ }
32404
+ return { servers };
32405
+ }
32406
+
32284
32407
  // src/runners.ts
32285
32408
  async function runRunnersList(opts) {
32286
- const a = await resolveTargetAttachment({ server: opts.server });
32287
- const client = new RunnersClient(a.serverUrl, a.apiKey);
32288
- const result = await client.list();
32289
- const label = a.serverSlug ? formatServerSlugDisplay(a.serverSlug) : a.serverId;
32290
- if (result.status === "unauthorized") {
32409
+ const serverId = await resolveTargetServerId({ server: opts.server });
32410
+ const installRoot = resolveSlockHome();
32411
+ const { servers } = await listRunners(installRoot, { serverId });
32412
+ const block = servers[0];
32413
+ const label = block.serverSlug ? formatServerSlugDisplay(block.serverSlug) : block.serverId;
32414
+ if (block.status === "unauthorized") {
32291
32415
  fail(
32292
32416
  "RUNNERS_UNAUTHORIZED",
32293
32417
  `The Computer credential for ${label} was rejected. Re-run \`slock-computer attach ${label}\`.`
32294
32418
  );
32295
32419
  }
32296
- if (result.status === "error") {
32420
+ if (block.status === "error") {
32297
32421
  fail(
32298
32422
  "RUNNERS_LIST_FAILED",
32299
- `Could not list runners on ${label} (${result.code}). Check --server-url / server version.`
32423
+ `Could not list runners on ${label} (${block.code}). Check --server-url / server version.`
32300
32424
  );
32301
32425
  }
32302
32426
  if (opts.json) {
32303
32427
  info(
32304
32428
  JSON.stringify(
32305
- { server: label, serverId: a.serverId, whitelist: result.whitelist, runners: result.runners },
32429
+ { server: label, serverId: block.serverId, whitelist: block.whitelist, runners: block.runners },
32306
32430
  null,
32307
32431
  2
32308
32432
  )
32309
32433
  );
32310
32434
  return;
32311
32435
  }
32312
- if (result.runners.length === 0) {
32436
+ if (block.runners.length === 0) {
32313
32437
  info(`No runners on server ${label}.`);
32314
32438
  return;
32315
32439
  }
32316
32440
  info("");
32317
32441
  info(`Server ${label}:`);
32318
32442
  info(" AGENT STATUS RUNTIME MODEL NAME");
32319
- for (const r of result.runners) {
32443
+ for (const r of block.runners) {
32320
32444
  info(
32321
32445
  ` ${r.agentId.padEnd(38)}${(r.status ?? "").padEnd(11)}${(r.runtime ?? "").padEnd(10)}${(r.model ?? "").padEnd(15)}${r.name ?? ""}`
32322
32446
  );
@@ -32365,20 +32489,20 @@ function redactSecrets(line) {
32365
32489
  return out;
32366
32490
  }
32367
32491
  async function runDoctorChecks() {
32368
- const report = await buildStatusReport();
32492
+ const slockHome = resolveSlockHome();
32493
+ const report = await buildStatusReport(slockHome);
32369
32494
  const checks = [];
32370
32495
  checks.push({ name: "SLOCK_HOME", ok: true, detail: report.slockHome });
32371
32496
  checks.push(
32372
32497
  report.userSessionError ? { name: "user session", ok: false, detail: "invalid user session file \u2014 re-run `slock-computer login`" } : report.loggedIn ? { name: "user session", ok: true, detail: `logged in (user ${report.userId ?? "?"})` } : { name: "user session", ok: false, detail: "not logged in \u2014 run `slock-computer login`" }
32373
32498
  );
32374
32499
  checks.push(
32375
- report.supervisor.running ? { name: "supervisor", ok: true, detail: `running (pid ${report.supervisor.pid})` } : {
32376
- name: "supervisor",
32500
+ report.service.running ? { name: "service", ok: true, detail: `running (pid ${report.service.pid})` } : {
32501
+ name: "service",
32377
32502
  ok: true,
32378
32503
  detail: "stopped (run `slock-computer start` when you want background)"
32379
32504
  }
32380
32505
  );
32381
- const slockHome = resolveSlockHome();
32382
32506
  const attachments = await listServerAttachments(slockHome);
32383
32507
  if (attachments.length === 0) {
32384
32508
  checks.push({
@@ -32420,7 +32544,7 @@ async function runDoctor(opts) {
32420
32544
  if (opts.resetHealth && opts.serverId) {
32421
32545
  await resetHealth(slockHome, opts.serverId);
32422
32546
  info(`Reset health state for server ${opts.serverLabel ?? opts.serverId}.`);
32423
- info(`Supervisor will resume auto-restart on next daemon exit.`);
32547
+ info(`Service will resume auto-restart on next daemon exit.`);
32424
32548
  }
32425
32549
  const checks = await runDoctorChecks();
32426
32550
  const allOk = checks.every((c) => c.ok);
@@ -32494,14 +32618,14 @@ import { readFile as readFile10 } from "fs/promises";
32494
32618
  var DEFAULT_LINES = 200;
32495
32619
  async function runLogs(opts) {
32496
32620
  const home = resolveSlockHome();
32497
- const file = opts.supervisor ? supervisorLogPath(home) : serverDaemonLogPath(home, await resolveTargetServerId({ server: opts.server }));
32621
+ const file = opts.service ? serviceLogPath(home) : serverDaemonLogPath(home, await resolveTargetServerId({ server: opts.server }));
32498
32622
  let content;
32499
32623
  try {
32500
32624
  content = await readFile10(file, "utf8");
32501
32625
  } catch {
32502
32626
  fail(
32503
32627
  "NO_DAEMON_LOG",
32504
- opts.supervisor ? `No supervisor log at ${file}. Start the supervisor first (\`slock-computer start\`).` : `No daemon log at ${file}. Start its daemon first (\`slock-computer start\`).`
32628
+ opts.service ? `No service log at ${file}. Start the service first (\`slock-computer start\`).` : `No daemon log at ${file}. Start its daemon first (\`slock-computer start\`).`
32505
32629
  );
32506
32630
  }
32507
32631
  const n = Number.isInteger(opts.lines) && opts.lines > 0 ? opts.lines : DEFAULT_LINES;
@@ -32511,16 +32635,166 @@ async function runLogs(opts) {
32511
32635
  for (const line of tail) info(redactSecrets(line));
32512
32636
  }
32513
32637
 
32638
+ // src/reset.ts
32639
+ init_esm_shims();
32640
+
32641
+ // src/serviceState.ts
32642
+ init_esm_shims();
32643
+ import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9, appendFile as appendFile3 } from "fs/promises";
32644
+ import { dirname as dirname10 } from "path";
32645
+
32646
+ // src/lib/state.ts
32647
+ init_esm_shims();
32648
+ var SERVICE_STATE_VALUES = [
32649
+ "starting",
32650
+ "running",
32651
+ "degraded",
32652
+ "stopping",
32653
+ "stopped"
32654
+ ];
32655
+ function isServiceState(value) {
32656
+ return typeof value === "string" && SERVICE_STATE_VALUES.includes(value);
32657
+ }
32658
+
32659
+ // src/serviceState.ts
32660
+ var DEFAULT_STATE = {
32661
+ state: "running",
32662
+ crashHistory: []
32663
+ };
32664
+ async function readServiceState(slockHome) {
32665
+ try {
32666
+ const raw = await readFile11(serviceStatePath(slockHome), "utf8");
32667
+ const parsed = JSON.parse(raw);
32668
+ if (!parsed || typeof parsed !== "object") return { ...DEFAULT_STATE };
32669
+ const obj = parsed;
32670
+ const state = isServiceState(obj.state) ? obj.state : "running";
32671
+ const crashHistory = Array.isArray(obj.crashHistory) ? obj.crashHistory.filter(isCrashEntry) : [];
32672
+ return { state, crashHistory };
32673
+ } catch {
32674
+ return { ...DEFAULT_STATE };
32675
+ }
32676
+ }
32677
+ async function writeServiceState(slockHome, file) {
32678
+ const path3 = serviceStatePath(slockHome);
32679
+ await mkdir10(dirname10(path3), { recursive: true });
32680
+ await writeFile9(path3, JSON.stringify(file), { mode: 384 });
32681
+ }
32682
+ function isCrashEntry(value) {
32683
+ if (!value || typeof value !== "object") return false;
32684
+ const obj = value;
32685
+ return typeof obj.at === "string";
32686
+ }
32687
+ async function emitServiceStateTransition(slockHome, fromState, toState, trigger) {
32688
+ const entry = {
32689
+ at: (/* @__PURE__ */ new Date()).toISOString(),
32690
+ kind: "service-state-changed",
32691
+ fromState,
32692
+ toState,
32693
+ trigger
32694
+ };
32695
+ try {
32696
+ const path3 = serviceLogPath(slockHome);
32697
+ await mkdir10(dirname10(path3), { recursive: true });
32698
+ await appendFile3(path3, JSON.stringify(entry) + "\n");
32699
+ } catch {
32700
+ }
32701
+ }
32702
+ async function clearServiceCrashHistory(slockHome) {
32703
+ const current = await readServiceState(slockHome);
32704
+ const previousState = current.state;
32705
+ const clearedCrashCount = current.crashHistory.length;
32706
+ const next = { state: "running", crashHistory: [] };
32707
+ await writeServiceState(slockHome, next);
32708
+ await emitServiceStateTransition(
32709
+ slockHome,
32710
+ previousState,
32711
+ "running",
32712
+ "reset-service"
32713
+ );
32714
+ return { previousState, clearedCrashCount };
32715
+ }
32716
+
32717
+ // src/reset.ts
32718
+ async function resetService(installRoot) {
32719
+ const { previousState, clearedCrashCount } = await clearServiceCrashHistory(installRoot);
32720
+ return {
32721
+ status: "ok",
32722
+ previousState,
32723
+ clearedCrashCount
32724
+ };
32725
+ }
32726
+ async function resetRunner(installRoot, serverId) {
32727
+ const attachment = await readServerAttachment(installRoot, serverId);
32728
+ if (!attachment) {
32729
+ return { status: "not-found", serverId };
32730
+ }
32731
+ const outcome = await resetRunnerHealth(installRoot, serverId);
32732
+ if (outcome.status === "not-found") {
32733
+ return { status: "not-found", serverId };
32734
+ }
32735
+ return {
32736
+ status: "ok",
32737
+ serverId,
32738
+ previousState: outcome.previousState ?? "running",
32739
+ clearedCrashCount: outcome.clearedCrashCount ?? 0
32740
+ };
32741
+ }
32742
+ async function runReset(opts) {
32743
+ const wantsService = opts.service === true;
32744
+ const wantsRunner = opts.runner === true;
32745
+ if (wantsService && wantsRunner) {
32746
+ fail(
32747
+ "RESET_SCOPE_AMBIGUOUS",
32748
+ "`--service` and `--runner` are mutually exclusive. Pass exactly one."
32749
+ );
32750
+ }
32751
+ if (!wantsService && !wantsRunner) {
32752
+ fail(
32753
+ "RESET_SCOPE_MISSING",
32754
+ "Pass `--service` to clear the service-level crash history, or `--runner` to clear a per-runner crash history."
32755
+ );
32756
+ }
32757
+ const slockHome = resolveSlockHome();
32758
+ if (wantsService) {
32759
+ const result2 = await resetService(slockHome);
32760
+ if (opts.json) {
32761
+ info(JSON.stringify(result2));
32762
+ return;
32763
+ }
32764
+ info(
32765
+ `Reset service crash history (previous state: ${result2.previousState}; cleared ${result2.clearedCrashCount} entr${result2.clearedCrashCount === 1 ? "y" : "ies"}).`
32766
+ );
32767
+ info("Service state transitioned to `running`. Runners were not touched.");
32768
+ return;
32769
+ }
32770
+ const serverId = await resolveTargetServerId({ server: opts.server });
32771
+ const result = await resetRunner(slockHome, serverId);
32772
+ if (result.status === "not-found") {
32773
+ fail(
32774
+ "RESET_RUNNER_NOT_FOUND",
32775
+ `Runner for server ${serverId} was not found.`
32776
+ );
32777
+ }
32778
+ if (opts.json) {
32779
+ info(JSON.stringify(result));
32780
+ return;
32781
+ }
32782
+ info(
32783
+ `Reset runner crash history for server ${result.serverId} (previous state: ${result.previousState}; cleared ${result.clearedCrashCount} entr${result.clearedCrashCount === 1 ? "y" : "ies"}).`
32784
+ );
32785
+ info("Runner state transitioned to `running`. Process was not respawned.");
32786
+ }
32787
+
32514
32788
  // src/concurrency.ts
32515
32789
  init_esm_shims();
32516
32790
  var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
32517
- import { mkdir as mkdir10 } from "fs/promises";
32791
+ import { mkdir as mkdir11 } from "fs/promises";
32518
32792
  import { join as join4 } from "path";
32519
32793
  var STALE_LOCK_THRESHOLD_MS = 6e4;
32520
32794
  async function withMutationLock(fn) {
32521
32795
  const slockHome = resolveSlockHome();
32522
32796
  const lockTarget = computerDir(slockHome);
32523
- await mkdir10(lockTarget, { recursive: true });
32797
+ await mkdir11(lockTarget, { recursive: true });
32524
32798
  const lockfilePath = join4(lockTarget, ".lock");
32525
32799
  let release = null;
32526
32800
  try {
@@ -32559,8 +32833,8 @@ async function withMutationLock(fn) {
32559
32833
 
32560
32834
  // src/channel.ts
32561
32835
  init_esm_shims();
32562
- import { readFile as readFile11, writeFile as writeFile9, mkdir as mkdir11 } from "fs/promises";
32563
- import { dirname as dirname10 } from "path";
32836
+ import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir12 } from "fs/promises";
32837
+ import { dirname as dirname11 } from "path";
32564
32838
  var DEFAULT_CHANNEL = "latest";
32565
32839
  var SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
32566
32840
  function parseChannel(raw) {
@@ -32575,7 +32849,7 @@ function parseChannel(raw) {
32575
32849
  }
32576
32850
  async function readChannel(slockHome) {
32577
32851
  try {
32578
- const raw = await readFile11(channelPath(slockHome), "utf8");
32852
+ const raw = await readFile12(channelPath(slockHome), "utf8");
32579
32853
  const parsed = parseChannel(raw);
32580
32854
  if (parsed !== null) return parsed;
32581
32855
  } catch {
@@ -32584,8 +32858,8 @@ async function readChannel(slockHome) {
32584
32858
  }
32585
32859
  async function writeChannel(slockHome, channel2) {
32586
32860
  const p = channelPath(slockHome);
32587
- await mkdir11(dirname10(p), { recursive: true });
32588
- await writeFile9(p, `${channel2}
32861
+ await mkdir12(dirname11(p), { recursive: true });
32862
+ await writeFile10(p, `${channel2}
32589
32863
  `, { mode: 384 });
32590
32864
  }
32591
32865
  async function runChannelShow(slockHome) {
@@ -32603,25 +32877,25 @@ async function runChannelSet(slockHome, raw) {
32603
32877
  }
32604
32878
  await writeChannel(slockHome, parsed);
32605
32879
  info(`Channel set to ${parsed}.`);
32606
- info(`Note: supervisor reads channel at startup; restart \`slock-computer start\` to apply.`);
32880
+ info(`Note: service reads channel at startup; restart \`slock-computer start\` to apply.`);
32607
32881
  }
32608
32882
 
32609
32883
  // src/upgradeCli.ts
32610
32884
  init_esm_shims();
32611
- import { readFile as readFile14 } from "fs/promises";
32885
+ import { readFile as readFile15 } from "fs/promises";
32612
32886
  import { fileURLToPath as fileURLToPath3 } from "url";
32613
- import { dirname as dirname11, join as join7 } from "path";
32887
+ import { dirname as dirname12, join as join7 } from "path";
32614
32888
 
32615
32889
  // src/upgrade.ts
32616
32890
  init_esm_shims();
32617
32891
  import { spawn as spawn4 } from "child_process";
32618
- import { mkdir as mkdir12, readFile as readFile13, writeFile as writeFile10, rm as rm3, rename as rename4 } from "fs/promises";
32892
+ import { mkdir as mkdir13, readFile as readFile14, writeFile as writeFile11, rm as rm3, rename as rename4 } from "fs/promises";
32619
32893
  import { join as join6 } from "path";
32620
32894
  import { createHash as createHash3 } from "crypto";
32621
32895
 
32622
32896
  // src/preflightDepDrift.ts
32623
32897
  init_esm_shims();
32624
- import { readFile as readFile12 } from "fs/promises";
32898
+ import { readFile as readFile13 } from "fs/promises";
32625
32899
  import { spawn as spawn3 } from "child_process";
32626
32900
  import { createRequire } from "module";
32627
32901
  import { join as join5 } from "path";
@@ -32630,12 +32904,10 @@ var DAEMON_PACKAGE_NAME = "@slock-ai/daemon";
32630
32904
  async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps = {}) {
32631
32905
  const readTarball = deps.readTarballPackageJson ?? defaultReadTarballPackageJson;
32632
32906
  const readCurrent = deps.readCurrentPackageJson ?? defaultReadCurrentPackageJson;
32633
- const readInstalled = deps.readInstalledDaemonVersion ?? defaultReadInstalledDaemonVersion;
32634
32907
  const satisfies = deps.semverSatisfies ?? defaultSemverSatisfies;
32635
32908
  const allowUnsafe = deps.allowUnsafeSpec === true;
32636
32909
  let targetPkg;
32637
32910
  let currentPkg;
32638
- let installedVersion;
32639
32911
  try {
32640
32912
  targetPkg = await readTarball(stagedTarballPath);
32641
32913
  } catch (e) {
@@ -32654,15 +32926,6 @@ async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps
32654
32926
  detail: `cannot read current binary package.json: ${errMsg(e)}`
32655
32927
  };
32656
32928
  }
32657
- try {
32658
- installedVersion = await readInstalled(currentBinaryDir);
32659
- } catch (e) {
32660
- return {
32661
- ok: false,
32662
- reason: "read_failed",
32663
- detail: `cannot read installed ${DAEMON_PACKAGE_NAME} version: ${errMsg(e)}`
32664
- };
32665
- }
32666
32929
  const targetSpec = targetPkg.dependencies?.[DAEMON_PACKAGE_NAME];
32667
32930
  const currentSpec = currentPkg.dependencies?.[DAEMON_PACKAGE_NAME];
32668
32931
  if (typeof targetSpec !== "string" || targetSpec.length === 0) {
@@ -32673,60 +32936,64 @@ async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps
32673
32936
  currentSpec
32674
32937
  };
32675
32938
  }
32676
- if (typeof currentSpec !== "string" || currentSpec.length === 0) {
32939
+ const unsafeTarget = isUnsafeSpec(targetSpec) || isUnparseableRange(targetSpec);
32940
+ if (!allowUnsafe && unsafeTarget) {
32677
32941
  return {
32678
32942
  ok: false,
32679
- reason: "read_failed",
32680
- detail: `current binary package.json has no ${DAEMON_PACKAGE_NAME} dependency entry`,
32943
+ reason: "unsafe_spec",
32944
+ detail: `${DAEMON_PACKAGE_NAME} target spec is not a safe semver range (target="${targetSpec}"). Auto-upgrade only supports target semver ranges that can be hydrated and verified before swap.`,
32945
+ currentSpec,
32681
32946
  targetSpec
32682
32947
  };
32683
32948
  }
32684
- if (installedVersion === null || installedVersion.length === 0) {
32949
+ void satisfies;
32950
+ return { ok: true, currentSpec, targetSpec };
32951
+ }
32952
+ async function verifyHydratedDaemonDependency(extractedPackageDir, targetSpec, deps = {}) {
32953
+ const readInstalled = deps.readInstalledDaemonVersion ?? defaultReadInstalledDaemonVersion;
32954
+ const satisfies = deps.semverSatisfies ?? defaultSemverSatisfies;
32955
+ const allowUnsafe = deps.allowUnsafeSpec === true;
32956
+ const unsafeTarget = isUnsafeSpec(targetSpec) || isUnparseableRange(targetSpec);
32957
+ if (!allowUnsafe && unsafeTarget) {
32685
32958
  return {
32686
32959
  ok: false,
32687
- reason: "read_failed",
32688
- detail: `${DAEMON_PACKAGE_NAME} is not installed at the expected location next to the current binary`,
32689
- currentSpec,
32960
+ reason: "unsafe_spec",
32961
+ detail: `${DAEMON_PACKAGE_NAME} target spec is not a safe semver range (target="${targetSpec}").`,
32690
32962
  targetSpec
32691
32963
  };
32692
32964
  }
32693
- const unsafeCurrent = isUnsafeSpec(currentSpec) || isUnparseableRange(currentSpec);
32694
- const unsafeTarget = isUnsafeSpec(targetSpec) || isUnparseableRange(targetSpec);
32695
- const anyUnsafe = unsafeCurrent || unsafeTarget;
32696
- if (!allowUnsafe && anyUnsafe) {
32965
+ let installedVersion;
32966
+ try {
32967
+ installedVersion = await readInstalled(extractedPackageDir);
32968
+ } catch (e) {
32697
32969
  return {
32698
32970
  ok: false,
32699
- reason: "unsafe_spec",
32700
- detail: `${DAEMON_PACKAGE_NAME} spec is not a safe semver range (current="${currentSpec}", target="${targetSpec}"). v1 auto-upgrade only supports semver ranges; re-install manually with \`npm install -g @slock-ai/computer@<version>\`.`,
32701
- currentSpec,
32702
- targetSpec,
32703
- installedVersion
32971
+ reason: "read_failed",
32972
+ detail: `cannot read hydrated ${DAEMON_PACKAGE_NAME} version: ${errMsg(e)}`,
32973
+ targetSpec
32704
32974
  };
32705
32975
  }
32706
- if (currentSpec !== targetSpec) {
32976
+ if (installedVersion === null || installedVersion.length === 0) {
32707
32977
  return {
32708
32978
  ok: false,
32709
- reason: "spec_changed",
32710
- detail: `${DAEMON_PACKAGE_NAME} dependency spec changed from "${currentSpec}" to "${targetSpec}". Auto-upgrade only swaps the Computer package root; the runtime dependency tree is not refreshed. Run \`npm install -g @slock-ai/computer@<version>\` manually.`,
32711
- currentSpec,
32712
- targetSpec,
32713
- installedVersion
32979
+ reason: "read_failed",
32980
+ detail: `${DAEMON_PACKAGE_NAME} is not installed in the hydrated staged package`,
32981
+ targetSpec
32714
32982
  };
32715
32983
  }
32716
- if (allowUnsafe && anyUnsafe) {
32717
- return { ok: true, currentSpec, targetSpec, installedVersion };
32984
+ if (allowUnsafe && unsafeTarget) {
32985
+ return { ok: true, targetSpec, installedVersion };
32718
32986
  }
32719
32987
  if (!satisfies(installedVersion, targetSpec)) {
32720
32988
  return {
32721
32989
  ok: false,
32722
32990
  reason: "installed_unsatisfied",
32723
- detail: `installed ${DAEMON_PACKAGE_NAME}@${installedVersion} does not satisfy target spec "${targetSpec}". Re-install with \`npm install -g @slock-ai/computer@<version>\`.`,
32724
- currentSpec,
32991
+ detail: `hydrated ${DAEMON_PACKAGE_NAME}@${installedVersion} does not satisfy target spec "${targetSpec}".`,
32725
32992
  targetSpec,
32726
32993
  installedVersion
32727
32994
  };
32728
32995
  }
32729
- return { ok: true, currentSpec, targetSpec, installedVersion };
32996
+ return { ok: true, targetSpec, installedVersion };
32730
32997
  }
32731
32998
  function isUnparseableRange(spec) {
32732
32999
  const s = spec.trim();
@@ -32792,7 +33059,7 @@ async function defaultReadTarballPackageJson(tarballPath) {
32792
33059
  return JSON.parse(raw);
32793
33060
  }
32794
33061
  async function defaultReadCurrentPackageJson(currentBinaryDir) {
32795
- const raw = await readFile12(join5(currentBinaryDir, "package.json"), "utf8");
33062
+ const raw = await readFile13(join5(currentBinaryDir, "package.json"), "utf8");
32796
33063
  return JSON.parse(raw);
32797
33064
  }
32798
33065
  async function defaultReadInstalledDaemonVersion(currentBinaryDir) {
@@ -32809,7 +33076,7 @@ async function defaultReadInstalledDaemonVersion(currentBinaryDir) {
32809
33076
  for (const base of searchPaths) {
32810
33077
  const candidate = join5(base, ...subPath, "package.json");
32811
33078
  try {
32812
- const raw = await readFile12(candidate, "utf8");
33079
+ const raw = await readFile13(candidate, "utf8");
32813
33080
  const parsed = JSON.parse(raw);
32814
33081
  if (parsed.name !== DAEMON_PACKAGE_NAME) continue;
32815
33082
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
@@ -32889,9 +33156,9 @@ function satisfiesTilde(ver, base) {
32889
33156
  // src/upgrade.ts
32890
33157
  async function stagePhase(slockHome, version, deps = {}) {
32891
33158
  const npmPack = deps.npmPack ?? defaultNpmPack;
32892
- const fsReadFile = deps.fsReadFile ?? readFile13;
33159
+ const fsReadFile = deps.fsReadFile ?? readFile14;
32893
33160
  const stagedPath = upgradeStagingDir(slockHome, version);
32894
- await mkdir12(stagedPath, { recursive: true });
33161
+ await mkdir13(stagedPath, { recursive: true });
32895
33162
  const packageRef = `@slock-ai/computer@${version}`;
32896
33163
  const result = await npmPack(stagedPath, packageRef);
32897
33164
  if (result.exitCode !== 0) {
@@ -32999,8 +33266,8 @@ async function cleanupStaged(slockHome, version) {
32999
33266
  async function snapshotPhase(slockHome, snap) {
33000
33267
  const path3 = upgradeSnapshotPath(slockHome);
33001
33268
  const tmp = `${path3}.tmp`;
33002
- await mkdir12(computerDir(slockHome), { recursive: true });
33003
- await writeFile10(tmp, JSON.stringify(snap, null, 2), { mode: 384 });
33269
+ await mkdir13(computerDir(slockHome), { recursive: true });
33270
+ await writeFile11(tmp, JSON.stringify(snap, null, 2), { mode: 384 });
33004
33271
  await rename4(tmp, path3);
33005
33272
  }
33006
33273
  async function clearUpgradeSnapshot(slockHome) {
@@ -33012,7 +33279,7 @@ async function clearUpgradeSnapshot(slockHome) {
33012
33279
  }
33013
33280
  async function extractTarball(tarballPath, destDir, deps = {}) {
33014
33281
  const tarSpawn = deps.tarSpawn ?? defaultTarSpawn;
33015
- await mkdir12(destDir, { recursive: true });
33282
+ await mkdir13(destDir, { recursive: true });
33016
33283
  const result = await tarSpawn(tarballPath, destDir);
33017
33284
  if (result.exitCode !== 0) {
33018
33285
  const err = new Error(
@@ -33116,49 +33383,49 @@ async function rollbackSwap(currentBinaryDir, deps = {}) {
33116
33383
  }
33117
33384
  }
33118
33385
  async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
33119
- const readSupervisorPid = deps.readSupervisorPid ?? (() => defaultReadSupervisorPid(slockHome));
33386
+ const readServicePid = deps.readServicePid ?? (() => defaultReadServicePid(slockHome));
33120
33387
  const isProcessAlive4 = deps.isProcessAlive ?? defaultIsProcessAlive;
33121
- const killSupervisor = deps.killSupervisor ?? defaultKillSupervisor;
33122
- const forceKillSupervisor = deps.forceKillSupervisor ?? defaultForceKillSupervisor;
33388
+ const killService = deps.killService ?? defaultKillService;
33389
+ const forceKillService = deps.forceKillService ?? defaultForceKillService;
33123
33390
  const waitForExit = deps.waitForExit ?? defaultWaitForExit;
33124
- const spawnFreshSupervisor = deps.spawnFreshSupervisor;
33391
+ const spawnFreshService = deps.spawnFreshService;
33125
33392
  const healthCheck = deps.healthCheck ?? (() => defaultHealthCheck(slockHome));
33126
33393
  const healthTimeoutMs = deps.healthTimeoutMs ?? 3e4;
33127
33394
  const healthPollIntervalMs = deps.healthPollIntervalMs ?? 500;
33128
- let supervisorStopped = false;
33395
+ let serviceStopped = false;
33129
33396
  try {
33130
- const pid = await readSupervisorPid();
33397
+ const pid = await readServicePid();
33131
33398
  if (pid === null || !isProcessAlive4(pid)) {
33132
- supervisorStopped = true;
33399
+ serviceStopped = true;
33133
33400
  } else {
33134
- await killSupervisor(pid);
33401
+ await killService(pid);
33135
33402
  const exited = await waitForExit(pid, 1e4);
33136
33403
  if (!exited) {
33137
- await forceKillSupervisor(pid);
33404
+ await forceKillService(pid);
33138
33405
  const escalatedExit = await waitForExit(pid, 5e3);
33139
- supervisorStopped = escalatedExit;
33406
+ serviceStopped = escalatedExit;
33140
33407
  } else {
33141
- supervisorStopped = true;
33408
+ serviceStopped = true;
33142
33409
  }
33143
33410
  }
33144
33411
  } catch (e) {
33145
33412
  return {
33146
33413
  binaryRestored: false,
33147
- supervisorStopped: false,
33148
- supervisorRespawned: false,
33149
- supervisorHealthy: false,
33414
+ serviceStopped: false,
33415
+ serviceRespawned: false,
33416
+ serviceHealthy: false,
33150
33417
  failedStep: "stop",
33151
33418
  reason: e instanceof Error ? e.message : String(e)
33152
33419
  };
33153
33420
  }
33154
- if (!supervisorStopped) {
33421
+ if (!serviceStopped) {
33155
33422
  return {
33156
33423
  binaryRestored: false,
33157
- supervisorStopped: false,
33158
- supervisorRespawned: false,
33159
- supervisorHealthy: false,
33424
+ serviceStopped: false,
33425
+ serviceRespawned: false,
33426
+ serviceHealthy: false,
33160
33427
  failedStep: "stop",
33161
- reason: "new-version supervisor did not exit (graceful + SIGKILL both timed out)"
33428
+ reason: "new-version service did not exit (graceful + SIGKILL both timed out)"
33162
33429
  };
33163
33430
  }
33164
33431
  const swapRb = await rollbackSwap(currentBinaryDir, {
@@ -33168,31 +33435,31 @@ async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
33168
33435
  if (!swapRb.rolledBack) {
33169
33436
  return {
33170
33437
  binaryRestored: false,
33171
- supervisorStopped: true,
33172
- supervisorRespawned: false,
33173
- supervisorHealthy: false,
33438
+ serviceStopped: true,
33439
+ serviceRespawned: false,
33440
+ serviceHealthy: false,
33174
33441
  failedStep: "binary",
33175
33442
  reason: "could not restore .prev \u2192 currentBinaryDir"
33176
33443
  };
33177
33444
  }
33178
- if (!spawnFreshSupervisor) {
33445
+ if (!spawnFreshService) {
33179
33446
  return {
33180
33447
  binaryRestored: true,
33181
- supervisorStopped: true,
33182
- supervisorRespawned: false,
33183
- supervisorHealthy: false,
33448
+ serviceStopped: true,
33449
+ serviceRespawned: false,
33450
+ serviceHealthy: false,
33184
33451
  failedStep: "respawn",
33185
- reason: "no spawnFreshSupervisor callback wired; old supervisor not respawned"
33452
+ reason: "no spawnFreshService callback wired; old service not respawned"
33186
33453
  };
33187
33454
  }
33188
33455
  try {
33189
- await spawnFreshSupervisor();
33456
+ await spawnFreshService();
33190
33457
  } catch (e) {
33191
33458
  return {
33192
33459
  binaryRestored: true,
33193
- supervisorStopped: true,
33194
- supervisorRespawned: false,
33195
- supervisorHealthy: false,
33460
+ serviceStopped: true,
33461
+ serviceRespawned: false,
33462
+ serviceHealthy: false,
33196
33463
  failedStep: "respawn",
33197
33464
  reason: e instanceof Error ? e.message : String(e)
33198
33465
  };
@@ -33209,53 +33476,53 @@ async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
33209
33476
  if (!healthy) {
33210
33477
  return {
33211
33478
  binaryRestored: true,
33212
- supervisorStopped: true,
33213
- supervisorRespawned: true,
33214
- supervisorHealthy: false,
33479
+ serviceStopped: true,
33480
+ serviceRespawned: true,
33481
+ serviceHealthy: false,
33215
33482
  failedStep: "health",
33216
- reason: "old supervisor failed health check after rollback respawn"
33483
+ reason: "old service failed health check after rollback respawn"
33217
33484
  };
33218
33485
  }
33219
33486
  return {
33220
33487
  binaryRestored: true,
33221
- supervisorStopped: true,
33222
- supervisorRespawned: true,
33223
- supervisorHealthy: true
33488
+ serviceStopped: true,
33489
+ serviceRespawned: true,
33490
+ serviceHealthy: true
33224
33491
  };
33225
33492
  }
33226
33493
  async function restartPhase(slockHome, deps = {}) {
33227
- const readSupervisorPid = deps.readSupervisorPid ?? (() => defaultReadSupervisorPid(slockHome));
33228
- const killSupervisor = deps.killSupervisor ?? defaultKillSupervisor;
33229
- const forceKillSupervisor = deps.forceKillSupervisor ?? defaultForceKillSupervisor;
33494
+ const readServicePid = deps.readServicePid ?? (() => defaultReadServicePid(slockHome));
33495
+ const killService = deps.killService ?? defaultKillService;
33496
+ const forceKillService = deps.forceKillService ?? defaultForceKillService;
33230
33497
  const waitForExit = deps.waitForExit ?? defaultWaitForExit;
33231
- const spawnFreshSupervisor = deps.spawnFreshSupervisor;
33498
+ const spawnFreshService = deps.spawnFreshService;
33232
33499
  const healthCheck = deps.healthCheck ?? (() => defaultHealthCheck(slockHome));
33233
33500
  const healthTimeoutMs = deps.healthTimeoutMs ?? 3e4;
33234
33501
  const healthPollIntervalMs = deps.healthPollIntervalMs ?? 500;
33235
33502
  const forceKill = deps.forceKill === true;
33236
- const oldPid = await readSupervisorPid();
33503
+ const oldPid = await readServicePid();
33237
33504
  if (oldPid !== null) {
33238
33505
  try {
33239
33506
  if (forceKill) {
33240
- await forceKillSupervisor(oldPid);
33507
+ await forceKillService(oldPid);
33241
33508
  } else {
33242
- await killSupervisor(oldPid);
33509
+ await killService(oldPid);
33243
33510
  const exited = await waitForExit(oldPid, 1e4);
33244
33511
  if (!exited) {
33245
- await forceKillSupervisor(oldPid);
33512
+ await forceKillService(oldPid);
33246
33513
  const escalatedExit = await waitForExit(oldPid, 5e3);
33247
33514
  if (!escalatedExit) {
33248
- return { ok: false, reason: "supervisor_kill_failed" };
33515
+ return { ok: false, reason: "service_kill_failed" };
33249
33516
  }
33250
33517
  }
33251
33518
  }
33252
33519
  } catch {
33253
- return { ok: false, reason: "supervisor_kill_failed" };
33520
+ return { ok: false, reason: "service_kill_failed" };
33254
33521
  }
33255
33522
  }
33256
- if (spawnFreshSupervisor) {
33523
+ if (spawnFreshService) {
33257
33524
  try {
33258
- await spawnFreshSupervisor();
33525
+ await spawnFreshService();
33259
33526
  } catch {
33260
33527
  return { ok: false, reason: "spawn_failed" };
33261
33528
  }
@@ -33269,16 +33536,16 @@ async function restartPhase(slockHome, deps = {}) {
33269
33536
  }
33270
33537
  return { ok: false, reason: "health_check_timeout" };
33271
33538
  }
33272
- async function defaultReadSupervisorPid(slockHome) {
33539
+ async function defaultReadServicePid(slockHome) {
33273
33540
  try {
33274
- const raw = (await readFile13(supervisorPidPath(slockHome), "utf8")).trim();
33541
+ const raw = (await readFile14(servicePidPath(slockHome), "utf8")).trim();
33275
33542
  const pid = Number.parseInt(raw, 10);
33276
33543
  return Number.isInteger(pid) && pid > 0 ? pid : null;
33277
33544
  } catch {
33278
33545
  return null;
33279
33546
  }
33280
33547
  }
33281
- async function defaultKillSupervisor(pid) {
33548
+ async function defaultKillService(pid) {
33282
33549
  try {
33283
33550
  process.kill(pid, "SIGTERM");
33284
33551
  } catch (err) {
@@ -33286,7 +33553,7 @@ async function defaultKillSupervisor(pid) {
33286
33553
  throw err;
33287
33554
  }
33288
33555
  }
33289
- async function defaultForceKillSupervisor(pid) {
33556
+ async function defaultForceKillService(pid) {
33290
33557
  try {
33291
33558
  process.kill(pid, "SIGKILL");
33292
33559
  } catch (err) {
@@ -33310,7 +33577,7 @@ async function defaultWaitForExit(pid, timeoutMs) {
33310
33577
  return false;
33311
33578
  }
33312
33579
  async function defaultHealthCheck(slockHome) {
33313
- const pid = await defaultReadSupervisorPid(slockHome);
33580
+ const pid = await defaultReadServicePid(slockHome);
33314
33581
  if (pid === null) return false;
33315
33582
  try {
33316
33583
  process.kill(pid, 0);
@@ -33390,6 +33657,18 @@ async function runUpgrade(slockHome, opts) {
33390
33657
  preflight
33391
33658
  };
33392
33659
  }
33660
+ const targetDaemonSpec = preflight.targetSpec;
33661
+ if (typeof targetDaemonSpec !== "string" || targetDaemonSpec.length === 0) {
33662
+ await cleanupStaged(slockHome, opts.targetVersion);
33663
+ return {
33664
+ ok: false,
33665
+ phase: "preflight",
33666
+ reason: `${preflight.detail ?? preflight.reason ?? "dep_drift"}: missing target ${DAEMON_PACKAGE_NAME} spec`,
33667
+ staged,
33668
+ verify,
33669
+ preflight
33670
+ };
33671
+ }
33393
33672
  const snap = {
33394
33673
  at: (/* @__PURE__ */ new Date()).toISOString(),
33395
33674
  fromVersion: opts.fromVersion,
@@ -33440,6 +33719,24 @@ async function runUpgrade(slockHome, opts) {
33440
33719
  verify
33441
33720
  };
33442
33721
  }
33722
+ const hydratedDaemon = await verifyHydratedDaemonDependency(
33723
+ extractedPackageDir,
33724
+ targetDaemonSpec,
33725
+ deps.preflight ?? {}
33726
+ );
33727
+ if (!hydratedDaemon.ok) {
33728
+ await cleanupStaged(slockHome, opts.targetVersion);
33729
+ await clearUpgradeSnapshot(slockHome);
33730
+ return {
33731
+ ok: false,
33732
+ phase: "hydrate",
33733
+ reason: `staged dependency tree verification failed: ${hydratedDaemon.detail ?? hydratedDaemon.reason ?? "hydrated dependency verification failed"}`,
33734
+ staged,
33735
+ verify,
33736
+ preflight,
33737
+ hydratedDaemon
33738
+ };
33739
+ }
33443
33740
  let swap;
33444
33741
  try {
33445
33742
  swap = await swapPhase(opts.currentBinaryDir, extractedPackageDir, {
@@ -33456,11 +33753,11 @@ async function runUpgrade(slockHome, opts) {
33456
33753
  };
33457
33754
  }
33458
33755
  const restart = await restartPhase(slockHome, {
33459
- readSupervisorPid: deps.readSupervisorPid,
33460
- killSupervisor: deps.killSupervisor,
33461
- forceKillSupervisor: deps.forceKillSupervisor,
33756
+ readServicePid: deps.readServicePid,
33757
+ killService: deps.killService,
33758
+ forceKillService: deps.forceKillService,
33462
33759
  waitForExit: deps.waitForExit,
33463
- spawnFreshSupervisor: deps.spawnFreshSupervisor,
33760
+ spawnFreshService: deps.spawnFreshService,
33464
33761
  healthCheck: deps.healthCheck,
33465
33762
  healthTimeoutMs: deps.healthTimeoutMs,
33466
33763
  healthPollIntervalMs: deps.healthPollIntervalMs,
@@ -33468,12 +33765,12 @@ async function runUpgrade(slockHome, opts) {
33468
33765
  });
33469
33766
  if (!restart.ok) {
33470
33767
  const rb = await rollbackRestart(slockHome, opts.currentBinaryDir, {
33471
- readSupervisorPid: deps.readSupervisorPid,
33768
+ readServicePid: deps.readServicePid,
33472
33769
  isProcessAlive: deps.isProcessAlive,
33473
- killSupervisor: deps.killSupervisor,
33474
- forceKillSupervisor: deps.forceKillSupervisor,
33770
+ killService: deps.killService,
33771
+ forceKillService: deps.forceKillService,
33475
33772
  waitForExit: deps.waitForExit,
33476
- spawnFreshSupervisor: deps.spawnFreshSupervisor,
33773
+ spawnFreshService: deps.spawnFreshService,
33477
33774
  healthCheck: deps.healthCheck,
33478
33775
  healthTimeoutMs: deps.healthTimeoutMs,
33479
33776
  healthPollIntervalMs: deps.healthPollIntervalMs,
@@ -33488,7 +33785,7 @@ async function runUpgrade(slockHome, opts) {
33488
33785
  verify,
33489
33786
  swap,
33490
33787
  restart,
33491
- rolledBack: rb.binaryRestored && rb.supervisorHealthy,
33788
+ rolledBack: rb.binaryRestored && rb.serviceHealthy,
33492
33789
  rollback: rb
33493
33790
  };
33494
33791
  }
@@ -33501,12 +33798,12 @@ async function runUpgrade(slockHome, opts) {
33501
33798
  });
33502
33799
  if (!rolling.ok) {
33503
33800
  const rb = await rollbackRestart(slockHome, opts.currentBinaryDir, {
33504
- readSupervisorPid: deps.readSupervisorPid,
33801
+ readServicePid: deps.readServicePid,
33505
33802
  isProcessAlive: deps.isProcessAlive,
33506
- killSupervisor: deps.killSupervisor,
33507
- forceKillSupervisor: deps.forceKillSupervisor,
33803
+ killService: deps.killService,
33804
+ forceKillService: deps.forceKillService,
33508
33805
  waitForExit: deps.waitForExit,
33509
- spawnFreshSupervisor: deps.spawnFreshSupervisor,
33806
+ spawnFreshService: deps.spawnFreshService,
33510
33807
  healthCheck: deps.healthCheck,
33511
33808
  healthTimeoutMs: deps.healthTimeoutMs,
33512
33809
  healthPollIntervalMs: deps.healthPollIntervalMs,
@@ -33523,12 +33820,12 @@ async function runUpgrade(slockHome, opts) {
33523
33820
  swap,
33524
33821
  restart,
33525
33822
  rolling,
33526
- rolledBack: rb.binaryRestored && rb.supervisorHealthy,
33823
+ rolledBack: rb.binaryRestored && rb.serviceHealthy,
33527
33824
  rollback: rb
33528
33825
  };
33529
33826
  }
33530
33827
  await cleanupSuccessPhase(slockHome, opts.targetVersion, swap.prevBinaryDir);
33531
- return { ok: true, phase: "cleanup", staged, verify, swap, restart, rolling };
33828
+ return { ok: true, phase: "cleanup", staged, verify, preflight, hydratedDaemon, swap, restart, rolling };
33532
33829
  }
33533
33830
  async function rollingDaemonHealthCheck(slockHome, deps = {}) {
33534
33831
  const list = deps.listManagedServerIds ?? listManagedServerIds;
@@ -33564,7 +33861,7 @@ async function rollingDaemonHealthCheck(slockHome, deps = {}) {
33564
33861
  }
33565
33862
  async function defaultReadDaemonPid(slockHome, serverId) {
33566
33863
  try {
33567
- const raw = (await readFile13(serverDaemonPidPath(slockHome, serverId), "utf8")).trim();
33864
+ const raw = (await readFile14(serverDaemonPidPath(slockHome, serverId), "utf8")).trim();
33568
33865
  const pid = Number.parseInt(raw, 10);
33569
33866
  return Number.isInteger(pid) && pid > 0 ? pid : null;
33570
33867
  } catch {
@@ -33592,7 +33889,7 @@ async function locateStagedTarball(stagedPath) {
33592
33889
 
33593
33890
  // src/upgradeLog.ts
33594
33891
  init_esm_shims();
33595
- import { chmod as chmod5, mkdir as mkdir13, open as open2 } from "fs/promises";
33892
+ import { chmod as chmod5, mkdir as mkdir14, open as open2 } from "fs/promises";
33596
33893
  var FILE_MODE = 384;
33597
33894
  var UPGRADE_ERROR_CODES = [
33598
33895
  "UPGRADE_DEPS_CHANGED",
@@ -33637,7 +33934,7 @@ function assertUpgradeLogEntry(entry) {
33637
33934
  }
33638
33935
  async function appendUpgradeLogEntry(slockHome, entry) {
33639
33936
  assertUpgradeLogEntry(entry);
33640
- await mkdir13(computerDir(slockHome), { recursive: true });
33937
+ await mkdir14(computerDir(slockHome), { recursive: true });
33641
33938
  const path3 = upgradeLogPath(slockHome);
33642
33939
  const at = entry.at ?? formatUpgradeLogTimestamp();
33643
33940
  const fullEntry = { ...entry, at };
@@ -33660,7 +33957,7 @@ function isEphemeralNpxContext(binaryDir) {
33660
33957
  async function readBundledDaemonVersion(binaryDir) {
33661
33958
  try {
33662
33959
  const pkgPath = join7(binaryDir, "package.json");
33663
- const raw = await readFile14(pkgPath, "utf8");
33960
+ const raw = await readFile15(pkgPath, "utf8");
33664
33961
  const parsed = JSON.parse(raw);
33665
33962
  const pinned = parsed.dependencies?.["@slock-ai/daemon"];
33666
33963
  if (typeof pinned !== "string" || pinned.length === 0) return null;
@@ -33670,8 +33967,8 @@ async function readBundledDaemonVersion(binaryDir) {
33670
33967
  return null;
33671
33968
  }
33672
33969
  }
33673
- async function defaultSpawnFreshSupervisor(slockHome) {
33674
- await spawnDetachedSupervisor(slockHome);
33970
+ async function defaultSpawnFreshService(slockHome) {
33971
+ await spawnDetachedService(slockHome);
33675
33972
  }
33676
33973
  async function runUpgradeCli(slockHome, opts, deps = {}) {
33677
33974
  let channel2;
@@ -33771,7 +34068,7 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
33771
34068
  info(`Drain mode: ${drainMode}${drainMode === "force" ? " (in-flight turns will be dropped)" : ""}.`);
33772
34069
  }
33773
34070
  const runUpgradeFn = deps.runUpgradeFn ?? runUpgrade;
33774
- const spawnFreshSupervisor = deps.spawnFreshSupervisor ?? defaultSpawnFreshSupervisor;
34071
+ const spawnFreshService = deps.spawnFreshService ?? defaultSpawnFreshService;
33775
34072
  const outcome = await runUpgradeFn(slockHome, {
33776
34073
  targetVersion,
33777
34074
  fromVersion,
@@ -33779,12 +34076,12 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
33779
34076
  currentBinaryDir,
33780
34077
  drainMode,
33781
34078
  deps: {
33782
- // Wire the production supervisor-spawn into BOTH phase 5 (happy)
34079
+ // Wire the production service-spawn into BOTH phase 5 (happy)
33783
34080
  // and the operational-rollback respawn path inside `runUpgrade`.
33784
34081
  // Without this, phase 5 falls through to a no-spawn health-poll
33785
34082
  // and rollback later trips `failedStep="respawn"`. See Dayu
33786
34083
  // blocker (#wg-slock-computer:b43b36fb msg=911eb84e).
33787
- spawnFreshSupervisor: () => spawnFreshSupervisor(slockHome)
34084
+ spawnFreshService: () => spawnFreshService(slockHome)
33788
34085
  }
33789
34086
  });
33790
34087
  const readBundledFn = deps.readBundledDaemonVersion ?? readBundledDaemonVersion;
@@ -33814,7 +34111,7 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
33814
34111
  }
33815
34112
  if (outcome.phase === "cleanup") {
33816
34113
  info(
33817
- `Upgrade ${fromVersion} \u2192 ${targetVersion} succeeded; cleanup phase reported a non-fatal issue: ${outcome.reason ?? "unknown"}. Active layout + supervisor are healthy.`
34114
+ `Upgrade ${fromVersion} \u2192 ${targetVersion} succeeded; cleanup phase reported a non-fatal issue: ${outcome.reason ?? "unknown"}. Active layout + service are healthy.`
33818
34115
  );
33819
34116
  await appendUpgradeLogEntry(slockHome, {
33820
34117
  fromBundle: bundle(fromVersion),
@@ -33854,6 +34151,9 @@ function mapFailurePhaseToCode(outcome) {
33854
34151
  case "extract":
33855
34152
  return "UPGRADE_INTEGRITY_FAILED";
33856
34153
  case "hydrate":
34154
+ if (outcome.hydratedDaemon?.ok === false) {
34155
+ return "UPGRADE_INTEGRITY_FAILED";
34156
+ }
33857
34157
  return "UPGRADE_NETWORK_FAILED";
33858
34158
  case "swap":
33859
34159
  return "UPGRADE_SWAP_FAILED";
@@ -33888,12 +34188,12 @@ async function defaultFetchDistTags() {
33888
34188
  }
33889
34189
  function defaultCurrentBinaryDir() {
33890
34190
  const here = fileURLToPath3(import.meta.url);
33891
- return dirname11(dirname11(here));
34191
+ return dirname12(dirname12(here));
33892
34192
  }
33893
34193
  async function defaultCurrentVersion() {
33894
34194
  const pkgPath = join7(defaultCurrentBinaryDir(), "package.json");
33895
34195
  try {
33896
- const raw = await readFile14(pkgPath, "utf8");
34196
+ const raw = await readFile15(pkgPath, "utf8");
33897
34197
  const parsed = JSON.parse(raw);
33898
34198
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
33899
34199
  return parsed.version;
@@ -33905,7 +34205,7 @@ async function defaultCurrentVersion() {
33905
34205
 
33906
34206
  // src/upgradeTestHarness.ts
33907
34207
  init_esm_shims();
33908
- import { mkdir as mkdir14, readdir as readdir4, stat as stat3, writeFile as writeFile11 } from "fs/promises";
34208
+ import { mkdir as mkdir15, readdir as readdir4, stat as stat3, writeFile as writeFile12 } from "fs/promises";
33909
34209
  import { join as join8 } from "path";
33910
34210
  import { createHash as createHash4 } from "crypto";
33911
34211
  var PHASES = /* @__PURE__ */ new Set([
@@ -33964,7 +34264,7 @@ function buildSimulatedDeps(slockHome, opts) {
33964
34264
  return { tarballPath: "", exitCode: 1, stderr: "simulated stage failure" };
33965
34265
  }
33966
34266
  const filename = `slock-ai-computer-${targetVersion}.tgz`;
33967
- await writeFile11(join8(cwd, filename), tarballBytes);
34267
+ await writeFile12(join8(cwd, filename), tarballBytes);
33968
34268
  return { tarballPath: join8(cwd, filename), exitCode: 0, stderr: "" };
33969
34269
  },
33970
34270
  fetchAdvertisedHash: async () => {
@@ -33976,16 +34276,16 @@ function buildSimulatedDeps(slockHome, opts) {
33976
34276
  if (opts.simulateFail === "extract") {
33977
34277
  return { exitCode: 1, stderr: "simulated extract failure" };
33978
34278
  }
33979
- await mkdir14(join8(destDir, "package"), { recursive: true });
33980
- await writeFile11(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
34279
+ await mkdir15(join8(destDir, "package"), { recursive: true });
34280
+ await writeFile12(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
33981
34281
  return { exitCode: 0, stderr: "" };
33982
34282
  },
33983
34283
  npmInstall: async () => ({ exitCode: 0, stderr: "" }),
33984
34284
  fsRename: opts.simulateFail === "swap" ? async () => {
33985
34285
  throw new Error("simulated swap failure: fsRename");
33986
34286
  } : void 0,
33987
- readSupervisorPid: async () => null,
33988
- spawnFreshSupervisor: async () => {
34287
+ readServicePid: async () => null,
34288
+ spawnFreshService: async () => {
33989
34289
  await maybeSleep();
33990
34290
  spawnCalls += 1;
33991
34291
  if (opts.simulateFail === "restart" && spawnCalls === 1) {
@@ -34038,7 +34338,7 @@ function buildSimulatedDeps(slockHome, opts) {
34038
34338
  }
34039
34339
  async function arrangeSnapshotFailure(slockHome) {
34040
34340
  const snapshotPath = join8(slockHome, "computer", "upgrade-snapshot.json");
34041
- await mkdir14(snapshotPath, { recursive: true });
34341
+ await mkdir15(snapshotPath, { recursive: true });
34042
34342
  }
34043
34343
  async function pathInfo(path3) {
34044
34344
  try {
@@ -34077,7 +34377,7 @@ async function captureStateSnapshot(slockHome, version, currentBinaryDir) {
34077
34377
  upgradeSnapshot: await pathInfo(upgradeSnapshotPath(slockHome)),
34078
34378
  stagingDir: stagingExists,
34079
34379
  stagingEntries,
34080
- supervisorPid: await pathInfo(supervisorPidPath(slockHome)),
34380
+ servicePid: await pathInfo(servicePidPath(slockHome)),
34081
34381
  channelFile: await pathInfo(join8(computerDir(slockHome), "channel")),
34082
34382
  computerDir: await pathInfo(computerDir(slockHome)),
34083
34383
  servers,
@@ -34093,12 +34393,12 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
34093
34393
  process.exitCode = 1;
34094
34394
  return;
34095
34395
  }
34096
- await mkdir14(slockHome, { recursive: true });
34396
+ await mkdir15(slockHome, { recursive: true });
34097
34397
  if (opts.simulateFail === "snapshot") {
34098
34398
  await arrangeSnapshotFailure(slockHome);
34099
34399
  }
34100
34400
  const { opts: upgradeOpts } = buildSimulatedDeps(slockHome, opts);
34101
- await mkdir14(upgradeOpts.currentBinaryDir, { recursive: true });
34401
+ await mkdir15(upgradeOpts.currentBinaryDir, { recursive: true });
34102
34402
  let outcome;
34103
34403
  try {
34104
34404
  outcome = await runUpgrade(slockHome, upgradeOpts);
@@ -34136,9 +34436,9 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
34136
34436
 
34137
34437
  // src/upgradeInstallSmoke.ts
34138
34438
  init_esm_shims();
34139
- import { copyFile, mkdir as mkdir15, readFile as readFile15 } from "fs/promises";
34439
+ import { copyFile, mkdir as mkdir16, readFile as readFile16 } from "fs/promises";
34140
34440
  import { createHash as createHash5 } from "crypto";
34141
- import { dirname as dirname12, isAbsolute, join as join9, resolve as pathResolve } from "path";
34441
+ import { dirname as dirname13, isAbsolute, join as join9, resolve as pathResolve } from "path";
34142
34442
  import { fileURLToPath as fileURLToPath4 } from "url";
34143
34443
  async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
34144
34444
  if (typeof opts.packageTarball !== "string" || opts.packageTarball.trim().length === 0) {
@@ -34147,7 +34447,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
34147
34447
  const tarballPath = isAbsolute(opts.packageTarball) ? opts.packageTarball : pathResolve(opts.packageTarball);
34148
34448
  let tarballBytes;
34149
34449
  try {
34150
- tarballBytes = await readFile15(tarballPath);
34450
+ tarballBytes = await readFile16(tarballPath);
34151
34451
  } catch (e) {
34152
34452
  const msg = e instanceof Error ? e.message : String(e);
34153
34453
  throw new Error(`__upgrade-install-smoke: cannot read --package-tarball ${tarballPath}: ${msg}`);
@@ -34156,10 +34456,10 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
34156
34456
  const currentBinaryDir = opts.currentBinaryDir ?? (deps.currentBinaryDir ?? defaultCurrentBinaryDirLocal)();
34157
34457
  const fromVersion = opts.fromVersion ?? await (deps.currentVersion ?? defaultCurrentVersionLocal)();
34158
34458
  const channel2 = opts.channel ?? "latest";
34159
- const spawnFreshSupervisor = deps.spawnFreshSupervisor ?? (async (h) => {
34160
- await spawnDetachedSupervisor(h);
34459
+ const spawnFreshService = deps.spawnFreshService ?? (async (h) => {
34460
+ await spawnDetachedService(h);
34161
34461
  });
34162
- await mkdir15(slockHome, { recursive: true });
34462
+ await mkdir16(slockHome, { recursive: true });
34163
34463
  const outcome = await runUpgrade(slockHome, {
34164
34464
  targetVersion: opts.targetVersion,
34165
34465
  fromVersion,
@@ -34185,7 +34485,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
34185
34485
  // dependencies from the registry; this smoke isolates the swap /
34186
34486
  // restart mechanics and keeps dependency hydration out of scope.
34187
34487
  npmInstall: async () => ({ exitCode: 0, stderr: "" }),
34188
- spawnFreshSupervisor: () => spawnFreshSupervisor(slockHome),
34488
+ spawnFreshService: () => spawnFreshService(slockHome),
34189
34489
  // PR-E 18/n: the workspace-built tarball still carries
34190
34490
  // `workspace:*` (or `file:` after pack-rewrite in some workflows)
34191
34491
  // for `@slock-ai/daemon`, which production preflight rejects as
@@ -34196,7 +34496,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
34196
34496
  preflight: { allowUnsafeSpec: true }
34197
34497
  }
34198
34498
  });
34199
- const activeVersion = await readSupervisorVersionEvidence(slockHome);
34499
+ const activeVersion = await readServiceVersionEvidence(slockHome);
34200
34500
  return { outcome, activeVersion };
34201
34501
  }
34202
34502
  async function runUpgradeInstallSmokeCli(slockHome, opts, writer = (s) => process.stdout.write(s)) {
@@ -34217,12 +34517,12 @@ async function runUpgradeInstallSmokeCli(slockHome, opts, writer = (s) => proces
34217
34517
  }
34218
34518
  function defaultCurrentBinaryDirLocal() {
34219
34519
  const here = fileURLToPath4(import.meta.url);
34220
- return dirname12(dirname12(here));
34520
+ return dirname13(dirname13(here));
34221
34521
  }
34222
34522
  async function defaultCurrentVersionLocal() {
34223
34523
  const pkgPath = join9(defaultCurrentBinaryDirLocal(), "package.json");
34224
34524
  try {
34225
- const raw = await readFile15(pkgPath, "utf8");
34525
+ const raw = await readFile16(pkgPath, "utf8");
34226
34526
  const parsed = JSON.parse(raw);
34227
34527
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
34228
34528
  return parsed.version;
@@ -34267,22 +34567,18 @@ program2.name("slock-computer").description("Slock Computer \u2014 local-machine
34267
34567
  program2.command("login").description("Log in via device-code (one user identity per Computer / SLOCK_HOME).").option("--server-url <url>", `Slock API base URL; defaults to SLOCK_SERVER_URL or ${DEFAULT_SLOCK_SERVER_URL}`).action(withCliExit(async (opts) => {
34268
34568
  await runLogin({ serverUrl: opts.serverUrl });
34269
34569
  }));
34270
- program2.command("attach").argument("<serverSlug>", "target Slock server slug (canonical form `/myserver`; bare `myserver` accepted)").description("Attach this Computer to one Slock server (add-not-replace; multi-server OK).").option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "Computer display name; defaults to a sanitized hostname").option("--no-run", "only authorize + write local state; do not start the supervisor").option("--foreground", "run the supervisor in this terminal instead of the background").action(withCliExit(async (serverSlug, opts) => {
34570
+ program2.command("attach").argument("<serverSlug>", "target Slock server slug (canonical form `/myserver`; bare `myserver` accepted)").description("Attach this Computer to one Slock server (add-not-replace; multi-server OK).").option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "Computer display name; defaults to a sanitized hostname").option("--no-run", "only authorize + write local state; do not start the service").option("--foreground", "run the service in this terminal instead of the background").action(withCliExit(async (serverSlug, opts) => {
34271
34571
  await withMutationLock(
34272
34572
  () => runAttach({ serverSlug, serverUrl: opts.serverUrl, name: opts.name, run: opts.run, foreground: opts.foreground })
34273
34573
  );
34274
34574
  }));
34275
- program2.command("setup").argument("<serverSlug>", "target Slock server slug (canonical form `/myserver`; bare `myserver` accepted)").description("Set up this Computer for one server: login if needed, attach/adopt if needed, then start.").option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "Computer display name for a new attachment/adoption; defaults to a sanitized hostname").option("--adopt-legacy", "attempt one-time migration from a legacy daemon before fresh attach fallback").option("--legacy-api-key <key>", "legacy key for --adopt-legacy (migration-only; prefer file/stdin)").option("--legacy-api-key-file <path>", "path to a file containing the legacy key for --adopt-legacy").option("--legacy-api-key-stdin", "read the legacy key from stdin for --adopt-legacy").option("--no-start", "stop after login + attach/adopt; do not start the supervisor").option("--foreground", "run the supervisor in this terminal instead of the background").option("-y, --yes", "allow non-interactive setup after confirming the planned actions").action(
34575
+ program2.command("setup").argument("<serverSlug>", "target Slock server slug (canonical form `/myserver`; bare `myserver` accepted)").description("Set up this Computer for one server: login if needed, attach (or \xA7X.1 migrate-prompt) if needed, then start.").option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "Computer display name for a new attachment; defaults to a sanitized hostname").option("--no-start", "stop after login + attach; do not start the service").option("--foreground", "run the service in this terminal instead of the background").option("-y, --yes", "allow non-interactive setup after confirming the planned actions").action(
34276
34576
  withCliExit(async (serverSlug, opts) => {
34277
34577
  await withMutationLock(
34278
34578
  () => runSetup({
34279
34579
  serverSlug,
34280
34580
  serverUrl: opts.serverUrl,
34281
34581
  name: opts.name,
34282
- adoptLegacy: opts.adoptLegacy,
34283
- legacyApiKey: opts.legacyApiKey,
34284
- legacyApiKeyFile: opts.legacyApiKeyFile,
34285
- legacyApiKeyStdin: opts.legacyApiKeyStdin,
34286
34582
  start: opts.start,
34287
34583
  foreground: opts.foreground,
34288
34584
  yes: opts.yes
@@ -34290,29 +34586,13 @@ program2.command("setup").argument("<serverSlug>", "target Slock server slug (ca
34290
34586
  );
34291
34587
  })
34292
34588
  );
34293
- program2.command("adopt-legacy").argument("<serverSlug>", "target Slock server slug (the legacy machine's server)").description(
34294
- "One-time migration: trade a legacy sk_machine_* / sk_daemon_* key for a fresh Computer attachment (sk_computer_*)."
34295
- ).option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "name to record on the new Computer attachment (default: existing machine name)").option("--legacy-api-key <key>", "raw legacy key on argv (insecure on shared shells; prefer --legacy-api-key-file or stdin)").option("--legacy-api-key-file <path>", "path to a 0600 file containing the legacy key (one line)").option("--legacy-api-key-stdin", "read the legacy key from stdin").action(
34296
- withCliExit(async (serverSlug, opts) => {
34297
- await withMutationLock(
34298
- () => runAdoptLegacy({
34299
- serverSlug,
34300
- serverUrl: opts.serverUrl,
34301
- name: opts.name,
34302
- legacyApiKey: opts.legacyApiKey,
34303
- legacyApiKeyFile: opts.legacyApiKeyFile,
34304
- legacyApiKeyStdin: opts.legacyApiKeyStdin
34305
- })
34306
- );
34307
- })
34308
- );
34309
34589
  program2.command("detach").argument("<serverSlug>", "server slug to detach from this Computer (canonical form `/myserver`)").description("Remove ONE server's local attachment; never touches user-session or other servers.").action(withCliExit(async (serverSlug) => {
34310
34590
  await withMutationLock(async () => runDetach(await resolveTargetServerId({ server: serverSlug }), serverSlug));
34311
34591
  }));
34312
- program2.command("status").description("Show this Computer's aggregate state (login + supervisor + per-server daemons).").option("--json", "emit the machine-readable report").action(withCliExit(async (opts) => {
34592
+ program2.command("status").description("Show this Computer's aggregate state (login + service + per-server daemons).").option("--json", "emit the machine-readable report").action(withCliExit(async (opts) => {
34313
34593
  await runStatus({ json: opts.json });
34314
34594
  }));
34315
- program2.command("start").argument("[serverSlug]", "optional: verify this server is attached and ensure its daemon is reconciled (default: ensure all attached)").description("Start/ensure the Computer supervisor (manages all per-server daemons).").option("--foreground", "stay in this terminal instead of detaching").action(withCliExit(async (serverSlug, opts) => {
34595
+ program2.command("start").argument("[serverSlug]", "optional: verify this server is attached and ensure its daemon is reconciled (default: ensure all attached)").description("Start/ensure the Computer service (manages all per-server daemons).").option("--foreground", "stay in this terminal instead of detaching").action(withCliExit(async (serverSlug, opts) => {
34316
34596
  await withMutationLock(
34317
34597
  async () => runStart({
34318
34598
  foreground: opts.foreground,
@@ -34321,10 +34601,10 @@ program2.command("start").argument("[serverSlug]", "optional: verify this server
34321
34601
  })
34322
34602
  );
34323
34603
  }));
34324
- program2.command("stop").description("Stop the Computer supervisor (and all managed per-server daemons).").action(withCliExit(async () => {
34604
+ program2.command("stop").description("Stop the Computer service (and all managed per-server daemons).").action(withCliExit(async () => {
34325
34605
  await withMutationLock(() => runStop());
34326
34606
  }));
34327
- program2.command("doctor").argument("[serverSlug]", "optional: scope detail (recent crashes) to one server").description("Diagnose login + per-server attachments + per-server preflight (no secrets).").option("--json", "emit the machine-readable report").option("--cleanup", "after diagnosis, run the local residue cleanup pass").option("--fix", "alias for --cleanup (same behavior)").option("--reset-health", "clear <serverSlug>'s crash history so supervisor resumes auto-restart").action(
34607
+ program2.command("doctor").argument("[serverSlug]", "optional: scope detail (recent crashes) to one server").description("Diagnose login + per-server attachments + per-server preflight (no secrets).").option("--json", "emit the machine-readable report").option("--cleanup", "after diagnosis, run the local residue cleanup pass").option("--fix", "alias for --cleanup (same behavior)").option("--reset-health", "clear <serverSlug>'s crash history so service resumes auto-restart").action(
34328
34608
  withCliExit(
34329
34609
  async (serverSlug, opts) => {
34330
34610
  const serverId = serverSlug ? await resolveTargetServerId({ server: serverSlug }) : void 0;
@@ -34339,8 +34619,20 @@ program2.command("doctor").argument("[serverSlug]", "optional: scope detail (rec
34339
34619
  }
34340
34620
  )
34341
34621
  );
34342
- program2.command("logs").description("Tail one server's daemon log (or the supervisor log); secrets redacted.").option("--lines <n>", "trailing lines to show (default 200)", (v) => Number.parseInt(v, 10)).option("--server <slug>", "select target server slug (required when \u22652 attached)").option("--supervisor", "tail the global supervisor log instead of a per-server daemon log").action(withCliExit(async (opts) => {
34343
- await runLogs({ lines: opts.lines, server: opts.server ?? null, supervisor: !!opts.supervisor });
34622
+ program2.command("reset").description("Clear a degraded state and resume the service's auto-restart loop.").option("--service", "clear the service-level crash history (cascade record)").option("--runner", "clear a runner's crash history (selected by `--server`)").option("--server <slug>", "with `--runner`, select target server slug (required when \u22652 attached)").option("--json", "emit the machine-readable result").action(
34623
+ withCliExit(async (opts) => {
34624
+ await withMutationLock(
34625
+ () => runReset({
34626
+ service: opts.service,
34627
+ runner: opts.runner,
34628
+ server: opts.server,
34629
+ json: opts.json
34630
+ })
34631
+ );
34632
+ })
34633
+ );
34634
+ program2.command("logs").description("Tail one server's daemon log (or the service log); secrets redacted.").option("--lines <n>", "trailing lines to show (default 200)", (v) => Number.parseInt(v, 10)).option("--server <slug>", "select target server slug (required when \u22652 attached)").option("--service", "tail the global service log instead of a per-server daemon log").action(withCliExit(async (opts) => {
34635
+ await runLogs({ lines: opts.lines, server: opts.server ?? null, service: !!opts.service });
34344
34636
  }));
34345
34637
  var runners = program2.command("runners").description("Computer runner control plane (per-server scoped; \xA712 whitelist server-side).");
34346
34638
  runners.command("list").description("List runners on one attached server.").option("--json", "emit the machine-readable list").option("--server <slug>", "select target server slug (required when \u22652 attached)").action(withCliExit(async (opts) => {
@@ -34364,9 +34656,8 @@ program2.command("upgrade").description(
34364
34656
  [
34365
34657
  "Auto-upgrade this Computer to the latest version in its channel (or --target-version <semver>).",
34366
34658
  "",
34367
- "v1 swaps the @slock-ai/computer package root only. Releases that change runtime",
34368
- "dependency requirements (e.g. @slock-ai/daemon version range) fail closed with",
34369
- "UPGRADE_DEPS_CHANGED and require manual `npm install -g @slock-ai/computer@<version>`."
34659
+ "Stages the target package, hydrates production dependencies, verifies the",
34660
+ "hydrated @slock-ai/daemon version, then swaps the Computer package root."
34370
34661
  ].join("\n")
34371
34662
  ).option("--dry-run", "stage + verify only; do not swap or restart").option("--channel <name>", "override channel for this invocation (latest | alpha | pinned:<semver>)").option("--target-version <semver>", "override target version explicitly (bypasses channel resolution)").option("--drain <mode>", "\xA72.7 drain mode: drain (default) | defer (abort if daemon busy) | force (SIGKILL, drop in-flight)").option("--force", "alias for --drain force (stuck-daemon recovery; drops in-flight turns)").action(
34372
34663
  withCliExit(
@@ -34421,8 +34712,8 @@ program2.command("upgrade").description(
34421
34712
  }
34422
34713
  )
34423
34714
  );
34424
- program2.command("__supervise", { hidden: true }).action(withCliExit(async () => {
34425
- await runSupervise();
34715
+ program2.command("__service", { hidden: true }).action(withCliExit(async () => {
34716
+ await runService();
34426
34717
  }));
34427
34718
  program2.command("__run", { hidden: true }).argument("<serverId>", "server id this daemon child is bound to").action(withCliExit(async (serverId) => {
34428
34719
  await runResident(serverId);
@@ -34459,21 +34750,6 @@ program2.command("__upgrade-install-smoke", { hidden: true }).requiredOption("--
34459
34750
  );
34460
34751
  })
34461
34752
  );
34462
- {
34463
- const argv = process.argv.slice(2);
34464
- const isAdopt = argv[0] === "adopt-legacy";
34465
- const isSetup = argv[0] === "setup";
34466
- const strayLegacyFlag = argv.find(
34467
- (a) => a === "--legacy-api-key" || a.startsWith("--legacy-api-key=") || a === "--legacy-api-key-file" || a.startsWith("--legacy-api-key-file=") || a === "--legacy-api-key-stdin"
34468
- );
34469
- if (strayLegacyFlag && !isAdopt && !isSetup) {
34470
- process.stderr.write(
34471
- `slock-computer: LEGACY_KEY_OUTSIDE_ADOPT: ${strayLegacyFlag} is only accepted by \`slock-computer adopt-legacy\` or \`slock-computer setup --adopt-legacy\`. Remove it or run an adopt flow.
34472
- `
34473
- );
34474
- process.exit(2);
34475
- }
34476
- }
34477
34753
  program2.parseAsync(process.argv).catch((err) => {
34478
34754
  process.stderr.write(`slock-computer: ${err instanceof Error ? err.message : String(err)}
34479
34755
  `);