@slock-ai/computer 0.0.14 → 0.0.15

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);
@@ -29891,6 +29891,9 @@ function serverManagedFlagPath(slockHome, serverId) {
29891
29891
  function serverHealthPath(slockHome, serverId) {
29892
29892
  return path2.join(serverDir(slockHome, serverId), "health.json");
29893
29893
  }
29894
+ function serviceStatePath(slockHome) {
29895
+ return path2.join(computerDir(slockHome), "service.state.json");
29896
+ }
29894
29897
  function supervisorPidPath(slockHome) {
29895
29898
  return path2.join(computerDir(slockHome), "supervisor.pid");
29896
29899
  }
@@ -30376,15 +30379,78 @@ async function runAttach(opts) {
30376
30379
  }
30377
30380
  }
30378
30381
 
30379
- // src/adopt.ts
30382
+ // src/setup.ts
30380
30383
  init_esm_shims();
30381
- import { readFile as readFile4 } from "fs/promises";
30384
+ var import_undici3 = __toESM(require_undici(), 1);
30385
+ import { chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename3, rm as rm2, writeFile as writeFile8 } from "fs/promises";
30386
+ import { dirname as dirname9 } from "path";
30387
+
30388
+ // src/lib/migration.ts
30389
+ init_esm_shims();
30390
+ import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
30391
+ import { join } from "path";
30392
+ var MACHINE_DIR_PREFIX = "machine-";
30393
+ async function readOwnerEvidence(installRoot, machineDirName) {
30394
+ const ownerFile = join(
30395
+ installRoot,
30396
+ "machines",
30397
+ machineDirName,
30398
+ "daemon.lock",
30399
+ "owner.json"
30400
+ );
30401
+ let raw;
30402
+ try {
30403
+ raw = await readFile3(ownerFile, "utf8");
30404
+ } catch {
30405
+ return {};
30406
+ }
30407
+ let parsed;
30408
+ try {
30409
+ parsed = JSON.parse(raw);
30410
+ } catch {
30411
+ return {};
30412
+ }
30413
+ const machineName = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : void 0;
30414
+ const serverUrl = typeof parsed.serverUrl === "string" && parsed.serverUrl.length > 0 ? parsed.serverUrl : void 0;
30415
+ return { machineName, serverUrl };
30416
+ }
30417
+ async function detectLegacyMigration(installRoot, loggedInUserId) {
30418
+ void loggedInUserId;
30419
+ const machinesDir = join(installRoot, "machines");
30420
+ let entries;
30421
+ try {
30422
+ entries = await readdir2(machinesDir);
30423
+ } catch {
30424
+ return { kind: "no-match" };
30425
+ }
30426
+ const candidates = [];
30427
+ for (const name of entries) {
30428
+ if (!name.startsWith(MACHINE_DIR_PREFIX)) continue;
30429
+ const evidence = await readOwnerEvidence(installRoot, name);
30430
+ candidates.push({
30431
+ machineId: name,
30432
+ machineName: evidence.machineName,
30433
+ serverUrl: evidence.serverUrl
30434
+ });
30435
+ }
30436
+ if (candidates.length === 0) return { kind: "no-match" };
30437
+ if (candidates.length === 1) {
30438
+ const only = candidates[0];
30439
+ return {
30440
+ kind: "match",
30441
+ machineId: only.machineId,
30442
+ machineName: only.machineName,
30443
+ serverUrl: only.serverUrl
30444
+ };
30445
+ }
30446
+ return { kind: "ambiguous", candidates };
30447
+ }
30382
30448
 
30383
30449
  // src/services/adoptLegacy.ts
30384
30450
  init_esm_shims();
30385
- import { chmod as chmod3, mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4, appendFile, stat } from "fs/promises";
30451
+ import { chmod as chmod3, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, appendFile, stat } from "fs/promises";
30386
30452
  import { createHash as createHash2 } from "crypto";
30387
- import { dirname as dirname4, join } from "path";
30453
+ import { dirname as dirname4, join as join2 } from "path";
30388
30454
  import { setTimeout as delay } from "timers/promises";
30389
30455
  function emit3(opts, event) {
30390
30456
  const cb = opts?.onEvent;
@@ -30398,7 +30464,7 @@ var LEGACY_STOP_WAIT_MS = 1e4;
30398
30464
  var LEGACY_STOP_POLL_MS = 200;
30399
30465
  function legacyLockOwnerPath(slockHome, rawKey) {
30400
30466
  const fingerprint = createHash2("sha256").update(rawKey).digest("hex").slice(0, 16);
30401
- return join(slockHome, "machines", `machine-${fingerprint}`, "daemon.lock", "owner.json");
30467
+ return join2(slockHome, "machines", `machine-${fingerprint}`, "daemon.lock", "owner.json");
30402
30468
  }
30403
30469
  function isProcessAlive(pid) {
30404
30470
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -30413,7 +30479,7 @@ function isProcessAlive(pid) {
30413
30479
  async function stopLegacyDaemonByOwnerFile(ownerFile) {
30414
30480
  let raw;
30415
30481
  try {
30416
- raw = await readFile3(ownerFile, "utf8");
30482
+ raw = await readFile4(ownerFile, "utf8");
30417
30483
  } catch {
30418
30484
  return { attempted: false, outcome: "absent" };
30419
30485
  }
@@ -30447,7 +30513,7 @@ async function stopLegacyDaemonByOwnerFile(ownerFile) {
30447
30513
  async function readLegacyOwnerEvidence(ownerFile) {
30448
30514
  let raw;
30449
30515
  try {
30450
- raw = await readFile3(ownerFile, "utf8");
30516
+ raw = await readFile4(ownerFile, "utf8");
30451
30517
  } catch {
30452
30518
  return { ownerFile, reason: "owner_file_absent" };
30453
30519
  }
@@ -30487,7 +30553,7 @@ async function adoptLegacy(input, options = {}) {
30487
30553
  const sessionFile = userSessionPath(slockHome);
30488
30554
  let session;
30489
30555
  try {
30490
- session = JSON.parse(await readFile3(sessionFile, "utf8"));
30556
+ session = JSON.parse(await readFile4(sessionFile, "utf8"));
30491
30557
  } catch (err) {
30492
30558
  throw new ComputerServiceError(
30493
30559
  "NO_USER_SESSION",
@@ -30733,134 +30799,18 @@ async function appendAdoptionLog(slockHome, line) {
30733
30799
  }
30734
30800
  }
30735
30801
 
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
- }
30802
+ // src/supervisor.ts
30803
+ init_esm_shims();
30804
+ import { spawn as spawn2 } from "child_process";
30805
+ import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile7, open, rename as rename2 } from "fs/promises";
30806
+ import { dirname as dirname8, join as joinPath } from "path";
30807
+ import { fileURLToPath as fileURLToPath2 } from "url";
30858
30808
 
30859
- // src/setup.ts
30809
+ // src/cleanup.ts
30860
30810
  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";
30811
+ import { readdir as readdir3, stat as stat2, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
30812
+ import { spawn } from "child_process";
30813
+ import { join as join3 } from "path";
30864
30814
 
30865
30815
  // src/internal/process-primitives.ts
30866
30816
  init_esm_shims();
@@ -30895,18 +30845,7 @@ async function clearPidfileAt(pidfilePath) {
30895
30845
  }
30896
30846
  }
30897
30847
 
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
30848
  // 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
30849
  function emptyCleanupReport() {
30911
30850
  return {
30912
30851
  stalePidfiles: [],
@@ -31001,12 +30940,12 @@ async function cleanupOrphanProcesses(slockHome, psSpawn) {
31001
30940
  return signaled;
31002
30941
  }
31003
30942
  function quarantineDir(slockHome) {
31004
- return join2(computerDir(slockHome), ".quarantine");
30943
+ return join3(computerDir(slockHome), ".quarantine");
31005
30944
  }
31006
30945
  async function quarantineServerSubtree(slockHome, serverId) {
31007
- const src = join2(serversDir(slockHome), serverId);
30946
+ const src = join3(serversDir(slockHome), serverId);
31008
30947
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
31009
- const dest = join2(quarantineDir(slockHome), `${stamp}-${serverId}`);
30948
+ const dest = join3(quarantineDir(slockHome), `${stamp}-${serverId}`);
31010
30949
  await mkdir6(dirname6(dest), { recursive: true });
31011
30950
  await rename(src, dest);
31012
30951
  return dest;
@@ -31018,7 +30957,7 @@ function dirname6(p) {
31018
30957
  async function cleanupPowerLossPartialState(slockHome) {
31019
30958
  let entries;
31020
30959
  try {
31021
- entries = await readdir2(serversDir(slockHome));
30960
+ entries = await readdir3(serversDir(slockHome));
31022
30961
  } catch {
31023
30962
  return [];
31024
30963
  }
@@ -31045,11 +30984,11 @@ var TMP_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
31045
30984
  async function cleanupTmpFiles(slockHome) {
31046
30985
  const removed = [];
31047
30986
  const cdir = computerDir(slockHome);
31048
- const stagingDir = join2(cdir, "upgrade-staging");
30987
+ const stagingDir = join3(cdir, "upgrade-staging");
31049
30988
  try {
31050
- const versions = await readdir2(stagingDir);
30989
+ const versions = await readdir3(stagingDir);
31051
30990
  for (const v of versions) {
31052
- const vdir = join2(stagingDir, v);
30991
+ const vdir = join3(stagingDir, v);
31053
30992
  try {
31054
30993
  const s = await stat2(vdir);
31055
30994
  if (Date.now() - s.mtimeMs > TMP_MAX_AGE_MS) {
@@ -31060,13 +30999,13 @@ async function cleanupTmpFiles(slockHome) {
31060
30999
  }
31061
31000
  }
31062
31001
  try {
31063
- const remaining = await readdir2(stagingDir);
31002
+ const remaining = await readdir3(stagingDir);
31064
31003
  if (remaining.length === 0) await rmdir(stagingDir);
31065
31004
  } catch {
31066
31005
  }
31067
31006
  } catch {
31068
31007
  }
31069
- const snap = join2(cdir, "upgrade-snapshot.json");
31008
+ const snap = join3(cdir, "upgrade-snapshot.json");
31070
31009
  try {
31071
31010
  const s = await stat2(snap);
31072
31011
  if (Date.now() - s.mtimeMs > TMP_MAX_AGE_MS) {
@@ -31078,7 +31017,7 @@ async function cleanupTmpFiles(slockHome) {
31078
31017
  return removed;
31079
31018
  }
31080
31019
  async function cleanupStaleLock(slockHome) {
31081
- const lockDir = join2(computerDir(slockHome), ".lock");
31020
+ const lockDir = join3(computerDir(slockHome), ".lock");
31082
31021
  try {
31083
31022
  const s = await stat2(lockDir);
31084
31023
  if (Date.now() - s.mtimeMs > 60 * 1e3) {
@@ -31090,7 +31029,7 @@ async function cleanupStaleLock(slockHome) {
31090
31029
  return [];
31091
31030
  }
31092
31031
  async function forceReleaseLock(slockHome) {
31093
- const lockDir = join2(computerDir(slockHome), ".lock");
31032
+ const lockDir = join3(computerDir(slockHome), ".lock");
31094
31033
  try {
31095
31034
  await stat2(lockDir);
31096
31035
  await rm(lockDir, { recursive: true, force: true });
@@ -31114,7 +31053,7 @@ async function runFullCleanup(slockHome, options = {}) {
31114
31053
 
31115
31054
  // src/health.ts
31116
31055
  init_esm_shims();
31117
- import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7 } from "fs/promises";
31056
+ import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7, appendFile as appendFile2 } from "fs/promises";
31118
31057
  import { dirname as dirname7 } from "path";
31119
31058
  var CRASH_WINDOW_MS = 6e4;
31120
31059
  var DEGRADED_THRESHOLD = 3;
@@ -31186,6 +31125,43 @@ async function resetHealth(slockHome, serverId) {
31186
31125
  } catch {
31187
31126
  }
31188
31127
  }
31128
+ async function resetRunnerHealth(slockHome, serverId, nowMs = Date.now()) {
31129
+ if (!isValidServerId(serverId)) {
31130
+ return { status: "not-found" };
31131
+ }
31132
+ const wasDegraded = await isDegraded(slockHome, serverId, nowMs);
31133
+ const recent = await readCrashHistory(slockHome, serverId, nowMs);
31134
+ const previousState = wasDegraded ? "degraded" : "running";
31135
+ await resetHealth(slockHome, serverId);
31136
+ await emitRunnerStateTransition(
31137
+ slockHome,
31138
+ serverId,
31139
+ previousState,
31140
+ "running",
31141
+ "reset-runner"
31142
+ );
31143
+ return {
31144
+ status: "ok",
31145
+ previousState,
31146
+ clearedCrashCount: recent.length
31147
+ };
31148
+ }
31149
+ async function emitRunnerStateTransition(slockHome, serverId, fromState, toState, trigger) {
31150
+ const entry = {
31151
+ at: (/* @__PURE__ */ new Date()).toISOString(),
31152
+ kind: "runner-state-changed",
31153
+ serverId,
31154
+ fromState,
31155
+ toState,
31156
+ trigger
31157
+ };
31158
+ try {
31159
+ const path3 = supervisorLogPath(slockHome);
31160
+ await mkdir7(dirname7(path3), { recursive: true });
31161
+ await appendFile2(path3, JSON.stringify(entry) + "\n");
31162
+ } catch {
31163
+ }
31164
+ }
31189
31165
 
31190
31166
  // src/services/start.ts
31191
31167
  init_esm_shims();
@@ -31283,7 +31259,14 @@ async function start(input, options = {}) {
31283
31259
  attachedCount: attached.length
31284
31260
  });
31285
31261
  const supervise = options.runSupervise ?? runSupervise;
31286
- await supervise();
31262
+ const previousParentLockMarker = process.env[PARENT_LOCK_HELD_ENV_VAR];
31263
+ process.env[PARENT_LOCK_HELD_ENV_VAR] = "1";
31264
+ try {
31265
+ await supervise();
31266
+ } finally {
31267
+ if (previousParentLockMarker === void 0) delete process.env[PARENT_LOCK_HELD_ENV_VAR];
31268
+ else process.env[PARENT_LOCK_HELD_ENV_VAR] = previousParentLockMarker;
31269
+ }
31287
31270
  return {
31288
31271
  status: "running",
31289
31272
  managedTargets,
@@ -31916,6 +31899,14 @@ async function runDetach(serverId, serverLabel = serverId) {
31916
31899
 
31917
31900
  // src/setup.ts
31918
31901
  var USER_SESSION_EXPIRY_LEEWAY_MS = 3e4;
31902
+ async function readUserSessionUserId(slockHome) {
31903
+ try {
31904
+ const parsed = JSON.parse(await readFile8(userSessionPath(slockHome), "utf8"));
31905
+ return typeof parsed.userId === "string" ? parsed.userId : "";
31906
+ } catch {
31907
+ return "";
31908
+ }
31909
+ }
31919
31910
  async function hasValidUserSession(slockHome) {
31920
31911
  try {
31921
31912
  const parsed = JSON.parse(await readFile8(userSessionPath(slockHome), "utf8"));
@@ -31983,49 +31974,74 @@ async function refreshUserSession(slockHome, serverUrl) {
31983
31974
  return false;
31984
31975
  }
31985
31976
  }
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;
31977
+ async function defaultConfirmMigrate(prompt) {
31978
+ process.stdout.write(prompt);
31979
+ const line = await readLine(false);
31980
+ const norm = line.trim().toLowerCase();
31981
+ return norm === "y" || norm === "yes";
32003
31982
  }
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;
31983
+ async function defaultReadMigrateSecret(prompt) {
31984
+ process.stdout.write(prompt);
31985
+ const line = await readLine(true);
31986
+ process.stdout.write("\n");
31987
+ return line.trim();
32006
31988
  }
32007
- function hasAmbientLegacyKeyInput() {
32008
- return typeof process.env.SLOCK_LEGACY_API_KEY === "string" && process.env.SLOCK_LEGACY_API_KEY.trim().length > 0;
31989
+ async function readLine(masked) {
31990
+ const stdin = process.stdin;
31991
+ const wasRaw = stdin.isTTY === true && stdin.isRaw === true;
31992
+ const enterRaw = masked && stdin.isTTY === true && typeof stdin.setRawMode === "function";
31993
+ if (enterRaw) stdin.setRawMode(true);
31994
+ stdin.resume();
31995
+ return await new Promise((resolve) => {
31996
+ let buf = "";
31997
+ const cleanup = () => {
31998
+ stdin.off("data", onData);
31999
+ stdin.off("end", onEnd);
32000
+ stdin.pause();
32001
+ if (enterRaw) stdin.setRawMode(wasRaw);
32002
+ };
32003
+ const onData = (chunk) => {
32004
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
32005
+ for (const ch of text) {
32006
+ if (ch === "\n" || ch === "\r") {
32007
+ cleanup();
32008
+ resolve(buf);
32009
+ return;
32010
+ }
32011
+ if (ch === "") {
32012
+ cleanup();
32013
+ resolve("");
32014
+ return;
32015
+ }
32016
+ if (ch === "\x7F" || ch === "\b") {
32017
+ buf = buf.slice(0, -1);
32018
+ continue;
32019
+ }
32020
+ buf += ch;
32021
+ }
32022
+ };
32023
+ const onEnd = () => {
32024
+ cleanup();
32025
+ resolve(buf);
32026
+ };
32027
+ stdin.on("data", onData);
32028
+ stdin.on("end", onEnd);
32029
+ });
32009
32030
  }
32010
32031
  async function runSetup(opts, deps = {}) {
32011
32032
  const slockHome = resolveSlockHome();
32012
32033
  const isTty = deps.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
32013
32034
  const login2 = deps.runLogin ?? runLogin;
32014
32035
  const attach2 = deps.runAttach ?? runAttach;
32015
- const adopt = deps.runAdoptLegacy ?? runAdoptLegacy;
32016
32036
  const start2 = deps.runStart ?? runStart;
32017
32037
  const refreshSession = deps.refreshUserSession ?? refreshUserSession;
32018
- const legacyDaemonCheck = deps.hasLiveLegacyDaemon ?? hasLiveLegacyDaemon;
32038
+ const detectMigration = deps.detectLegacyMigration ?? detectLegacyMigration;
32039
+ const confirmMigrate = deps.confirmMigrate ?? defaultConfirmMigrate;
32040
+ const readMigrateSecret = deps.readMigrateSecret ?? defaultReadMigrateSecret;
32019
32041
  if (!isTty && !opts.yes) {
32020
32042
  fail(
32021
32043
  "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`."
32044
+ "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
32045
  );
32030
32046
  }
32031
32047
  const label = formatServerSlugDisplay(opts.serverSlug);
@@ -32050,34 +32066,82 @@ async function runSetup(opts, deps = {}) {
32050
32066
  if (attachment) {
32051
32067
  info(`Attachment: already attached to ${label}.`);
32052
32068
  } 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.");
32069
+ let migrated = false;
32070
+ if (isTty) {
32071
+ const detection = await detectMigration(slockHome, await readUserSessionUserId(slockHome));
32072
+ if (detection.kind === "match") {
32073
+ const where = detection.serverUrl ? ` attached to ${detection.serverUrl}` : "";
32074
+ const who = detection.machineName ? ` "${detection.machineName}"` : "";
32075
+ const accepted = await confirmMigrate(
32076
+ `Migration: detected legacy daemon machine${who}${where}. Migrate it under Computer instead of creating a fresh attachment? [y/N] `
32077
+ );
32078
+ if (accepted) {
32079
+ const rawKey = await readMigrateSecret(
32080
+ "Paste legacy api key (sk_machine_* or sk_daemon_*); input is hidden: "
32081
+ );
32082
+ if (rawKey.length === 0) {
32083
+ info("Migration: no key provided; falling back to fresh attach.");
32084
+ } else if (!rawKey.startsWith("sk_machine_") && !rawKey.startsWith("sk_daemon_")) {
32085
+ fail(
32086
+ "LEGACY_KEY_INVALID",
32087
+ "Provided key does not look like a legacy machine key (expected `sk_machine_*` or `sk_daemon_*`)."
32088
+ );
32089
+ } else {
32090
+ try {
32091
+ await adoptLegacy(
32092
+ {
32093
+ serverSlug: opts.serverSlug,
32094
+ serverUrl: opts.serverUrl,
32095
+ name: opts.name,
32096
+ rawKey,
32097
+ mode: "legacy_key_stdin",
32098
+ redactedPrefix: rawKey.slice(0, 8)
32099
+ },
32100
+ {
32101
+ onEvent: (event) => {
32102
+ if (event.type === "adopting") {
32103
+ info(
32104
+ `Adopting legacy daemon for ${formatServerSlugDisplay(event.serverSlug)} via ${event.mode}\u2026`
32105
+ );
32106
+ } else if (event.type === "preflight") {
32107
+ info(
32108
+ event.resumed ? "Adopted (resumed prior attachment); running preflight\u2026" : "Adopted; running preflight\u2026"
32109
+ );
32110
+ } else if (event.type === "adopted") {
32111
+ info(`Adopted. Computer state written to ${event.attachmentPath}`);
32112
+ info(` server: ${formatServerSlugDisplay(event.serverSlug)}`);
32113
+ info(` serverMachine: ${event.serverMachineId}`);
32114
+ info(` legacyMachine: ${event.legacyMachineId}`);
32115
+ switch (event.legacyStop.outcome) {
32116
+ case "absent":
32117
+ info(" legacy daemon: not detected on this Computer (no local lock file)");
32118
+ break;
32119
+ case "already_dead":
32120
+ info(` legacy daemon: already stopped (pid ${event.legacyStop.pid} not running)`);
32121
+ break;
32122
+ case "stopped":
32123
+ info(` legacy daemon: stopped (pid ${event.legacyStop.pid}, SIGTERM)`);
32124
+ break;
32125
+ }
32126
+ }
32127
+ }
32128
+ }
32129
+ );
32130
+ migrated = true;
32131
+ } catch (err) {
32132
+ if (err instanceof CliExit) throw err;
32133
+ if (err instanceof ComputerServiceError) {
32134
+ fail(err.code, err.message);
32135
+ }
32136
+ throw err;
32137
+ }
32138
+ }
32139
+ } else {
32140
+ info("Migration: declined; falling back to fresh attach.");
32141
+ }
32080
32142
  }
32143
+ }
32144
+ if (!migrated) {
32081
32145
  await attach2({
32082
32146
  serverSlug: opts.serverSlug,
32083
32147
  serverUrl: opts.serverUrl,
@@ -32090,7 +32154,7 @@ async function runSetup(opts, deps = {}) {
32090
32154
  if (!attachment) {
32091
32155
  fail(
32092
32156
  "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}\`.`
32157
+ `Setup did not produce a local attachment for ${label}. Re-run \`slock-computer attach ${label}\`.`
32094
32158
  );
32095
32159
  }
32096
32160
  }
@@ -32138,32 +32202,31 @@ async function deriveHealth(slockHome, serverId, daemon) {
32138
32202
  if (await isDegraded(slockHome, serverId)) return "degraded";
32139
32203
  return "ok";
32140
32204
  }
32141
- async function buildStatusReport() {
32142
- const slockHome = resolveSlockHome();
32143
- const sessionRead = await readUserSession(userSessionPath(slockHome));
32205
+ async function buildStatusReport(installRoot) {
32206
+ const sessionRead = await readUserSession(userSessionPath(installRoot));
32144
32207
  const session = sessionRead.session;
32145
- const attachments = await listServerAttachments(slockHome);
32208
+ const attachments = await listServerAttachments(installRoot);
32146
32209
  const supervisor = {
32147
- ...await pidStatus(supervisorPidPath(slockHome)),
32148
- logPath: supervisorLogPath(slockHome)
32210
+ ...await pidStatus(supervisorPidPath(installRoot)),
32211
+ logPath: supervisorLogPath(installRoot)
32149
32212
  };
32150
32213
  const servers = [];
32151
32214
  for (const a of attachments) {
32152
- const daemon = await pidStatus(serverDaemonPidPath(slockHome, a.serverId));
32215
+ const daemon = await pidStatus(serverDaemonPidPath(installRoot, a.serverId));
32153
32216
  servers.push({
32154
32217
  serverId: a.serverId,
32155
32218
  serverSlug: a.serverSlug ?? null,
32156
32219
  serverMachineId: a.serverMachineId,
32157
32220
  serverUrl: a.serverUrl,
32158
32221
  attachedAt: a.attachedAt ?? null,
32159
- daemonLogPath: serverDaemonLogPath(slockHome, a.serverId),
32222
+ daemonLogPath: serverDaemonLogPath(installRoot, a.serverId),
32160
32223
  daemon,
32161
- health: await deriveHealth(slockHome, a.serverId, daemon)
32224
+ health: await deriveHealth(installRoot, a.serverId, daemon)
32162
32225
  });
32163
32226
  }
32164
32227
  const loggedIn = session?.kind === "user-session" && typeof session.accessToken === "string" && session.accessToken.length > 0;
32165
32228
  return {
32166
- slockHome,
32229
+ slockHome: installRoot,
32167
32230
  loggedIn,
32168
32231
  userId: session ? str(session.userId) : null,
32169
32232
  loginServerUrl: session ? str(session.serverUrl) : null,
@@ -32176,7 +32239,7 @@ function pad(s, n) {
32176
32239
  return s.length >= n ? s : s + " ".repeat(n - s.length);
32177
32240
  }
32178
32241
  async function runStatus(opts) {
32179
- const report = await buildStatusReport();
32242
+ const report = await buildStatusReport(resolveSlockHome());
32180
32243
  if (opts.json) {
32181
32244
  info(JSON.stringify(report, null, 2));
32182
32245
  return;
@@ -32281,42 +32344,92 @@ async function resolveTargetAttachment(opts) {
32281
32344
  return a;
32282
32345
  }
32283
32346
 
32347
+ // src/lib/readers.ts
32348
+ init_esm_shims();
32349
+
32350
+ // src/lib/types.ts
32351
+ init_esm_shims();
32352
+ var StateReaderError = class extends Error {
32353
+ code;
32354
+ constructor(code, message) {
32355
+ super(message);
32356
+ this.name = "StateReaderError";
32357
+ this.code = code;
32358
+ }
32359
+ };
32360
+
32361
+ // src/lib/readers.ts
32362
+ async function listRunners(installRoot, opts = {}) {
32363
+ const all = await listServerAttachments(installRoot);
32364
+ let subset = all;
32365
+ if (opts.serverId !== void 0) {
32366
+ const found = all.find((a) => a.serverId === opts.serverId);
32367
+ if (!found) {
32368
+ throw new StateReaderError(
32369
+ "NOT_ATTACHED",
32370
+ `Server ${opts.serverId} is not attached to this Computer.`
32371
+ );
32372
+ }
32373
+ subset = [found];
32374
+ }
32375
+ const servers = [];
32376
+ for (const a of subset) {
32377
+ const client = new RunnersClient(a.serverUrl, a.apiKey);
32378
+ const result = await client.list();
32379
+ const idCols = { serverId: a.serverId, serverSlug: a.serverSlug ?? null };
32380
+ if (result.status === "success") {
32381
+ servers.push({
32382
+ ...idCols,
32383
+ status: "ok",
32384
+ whitelist: result.whitelist,
32385
+ runners: result.runners
32386
+ });
32387
+ } else if (result.status === "unauthorized") {
32388
+ servers.push({ ...idCols, status: "unauthorized" });
32389
+ } else {
32390
+ servers.push({ ...idCols, status: "error", code: result.code });
32391
+ }
32392
+ }
32393
+ return { servers };
32394
+ }
32395
+
32284
32396
  // src/runners.ts
32285
32397
  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") {
32398
+ const serverId = await resolveTargetServerId({ server: opts.server });
32399
+ const installRoot = resolveSlockHome();
32400
+ const { servers } = await listRunners(installRoot, { serverId });
32401
+ const block = servers[0];
32402
+ const label = block.serverSlug ? formatServerSlugDisplay(block.serverSlug) : block.serverId;
32403
+ if (block.status === "unauthorized") {
32291
32404
  fail(
32292
32405
  "RUNNERS_UNAUTHORIZED",
32293
32406
  `The Computer credential for ${label} was rejected. Re-run \`slock-computer attach ${label}\`.`
32294
32407
  );
32295
32408
  }
32296
- if (result.status === "error") {
32409
+ if (block.status === "error") {
32297
32410
  fail(
32298
32411
  "RUNNERS_LIST_FAILED",
32299
- `Could not list runners on ${label} (${result.code}). Check --server-url / server version.`
32412
+ `Could not list runners on ${label} (${block.code}). Check --server-url / server version.`
32300
32413
  );
32301
32414
  }
32302
32415
  if (opts.json) {
32303
32416
  info(
32304
32417
  JSON.stringify(
32305
- { server: label, serverId: a.serverId, whitelist: result.whitelist, runners: result.runners },
32418
+ { server: label, serverId: block.serverId, whitelist: block.whitelist, runners: block.runners },
32306
32419
  null,
32307
32420
  2
32308
32421
  )
32309
32422
  );
32310
32423
  return;
32311
32424
  }
32312
- if (result.runners.length === 0) {
32425
+ if (block.runners.length === 0) {
32313
32426
  info(`No runners on server ${label}.`);
32314
32427
  return;
32315
32428
  }
32316
32429
  info("");
32317
32430
  info(`Server ${label}:`);
32318
32431
  info(" AGENT STATUS RUNTIME MODEL NAME");
32319
- for (const r of result.runners) {
32432
+ for (const r of block.runners) {
32320
32433
  info(
32321
32434
  ` ${r.agentId.padEnd(38)}${(r.status ?? "").padEnd(11)}${(r.runtime ?? "").padEnd(10)}${(r.model ?? "").padEnd(15)}${r.name ?? ""}`
32322
32435
  );
@@ -32365,7 +32478,8 @@ function redactSecrets(line) {
32365
32478
  return out;
32366
32479
  }
32367
32480
  async function runDoctorChecks() {
32368
- const report = await buildStatusReport();
32481
+ const slockHome = resolveSlockHome();
32482
+ const report = await buildStatusReport(slockHome);
32369
32483
  const checks = [];
32370
32484
  checks.push({ name: "SLOCK_HOME", ok: true, detail: report.slockHome });
32371
32485
  checks.push(
@@ -32378,7 +32492,6 @@ async function runDoctorChecks() {
32378
32492
  detail: "stopped (run `slock-computer start` when you want background)"
32379
32493
  }
32380
32494
  );
32381
- const slockHome = resolveSlockHome();
32382
32495
  const attachments = await listServerAttachments(slockHome);
32383
32496
  if (attachments.length === 0) {
32384
32497
  checks.push({
@@ -32511,16 +32624,166 @@ async function runLogs(opts) {
32511
32624
  for (const line of tail) info(redactSecrets(line));
32512
32625
  }
32513
32626
 
32627
+ // src/reset.ts
32628
+ init_esm_shims();
32629
+
32630
+ // src/serviceState.ts
32631
+ init_esm_shims();
32632
+ import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9, appendFile as appendFile3 } from "fs/promises";
32633
+ import { dirname as dirname10 } from "path";
32634
+
32635
+ // src/lib/state.ts
32636
+ init_esm_shims();
32637
+ var SERVICE_STATE_VALUES = [
32638
+ "starting",
32639
+ "running",
32640
+ "degraded",
32641
+ "stopping",
32642
+ "stopped"
32643
+ ];
32644
+ function isServiceState(value) {
32645
+ return typeof value === "string" && SERVICE_STATE_VALUES.includes(value);
32646
+ }
32647
+
32648
+ // src/serviceState.ts
32649
+ var DEFAULT_STATE = {
32650
+ state: "running",
32651
+ crashHistory: []
32652
+ };
32653
+ async function readServiceState(slockHome) {
32654
+ try {
32655
+ const raw = await readFile11(serviceStatePath(slockHome), "utf8");
32656
+ const parsed = JSON.parse(raw);
32657
+ if (!parsed || typeof parsed !== "object") return { ...DEFAULT_STATE };
32658
+ const obj = parsed;
32659
+ const state = isServiceState(obj.state) ? obj.state : "running";
32660
+ const crashHistory = Array.isArray(obj.crashHistory) ? obj.crashHistory.filter(isCrashEntry) : [];
32661
+ return { state, crashHistory };
32662
+ } catch {
32663
+ return { ...DEFAULT_STATE };
32664
+ }
32665
+ }
32666
+ async function writeServiceState(slockHome, file) {
32667
+ const path3 = serviceStatePath(slockHome);
32668
+ await mkdir10(dirname10(path3), { recursive: true });
32669
+ await writeFile9(path3, JSON.stringify(file), { mode: 384 });
32670
+ }
32671
+ function isCrashEntry(value) {
32672
+ if (!value || typeof value !== "object") return false;
32673
+ const obj = value;
32674
+ return typeof obj.at === "string";
32675
+ }
32676
+ async function emitServiceStateTransition(slockHome, fromState, toState, trigger) {
32677
+ const entry = {
32678
+ at: (/* @__PURE__ */ new Date()).toISOString(),
32679
+ kind: "service-state-changed",
32680
+ fromState,
32681
+ toState,
32682
+ trigger
32683
+ };
32684
+ try {
32685
+ const path3 = supervisorLogPath(slockHome);
32686
+ await mkdir10(dirname10(path3), { recursive: true });
32687
+ await appendFile3(path3, JSON.stringify(entry) + "\n");
32688
+ } catch {
32689
+ }
32690
+ }
32691
+ async function clearServiceCrashHistory(slockHome) {
32692
+ const current = await readServiceState(slockHome);
32693
+ const previousState = current.state;
32694
+ const clearedCrashCount = current.crashHistory.length;
32695
+ const next = { state: "running", crashHistory: [] };
32696
+ await writeServiceState(slockHome, next);
32697
+ await emitServiceStateTransition(
32698
+ slockHome,
32699
+ previousState,
32700
+ "running",
32701
+ "reset-service"
32702
+ );
32703
+ return { previousState, clearedCrashCount };
32704
+ }
32705
+
32706
+ // src/reset.ts
32707
+ async function resetService(installRoot) {
32708
+ const { previousState, clearedCrashCount } = await clearServiceCrashHistory(installRoot);
32709
+ return {
32710
+ status: "ok",
32711
+ previousState,
32712
+ clearedCrashCount
32713
+ };
32714
+ }
32715
+ async function resetRunner(installRoot, serverId) {
32716
+ const attachment = await readServerAttachment(installRoot, serverId);
32717
+ if (!attachment) {
32718
+ return { status: "not-found", serverId };
32719
+ }
32720
+ const outcome = await resetRunnerHealth(installRoot, serverId);
32721
+ if (outcome.status === "not-found") {
32722
+ return { status: "not-found", serverId };
32723
+ }
32724
+ return {
32725
+ status: "ok",
32726
+ serverId,
32727
+ previousState: outcome.previousState ?? "running",
32728
+ clearedCrashCount: outcome.clearedCrashCount ?? 0
32729
+ };
32730
+ }
32731
+ async function runReset(opts) {
32732
+ const wantsService = opts.service === true;
32733
+ const wantsRunner = opts.runner === true;
32734
+ if (wantsService && wantsRunner) {
32735
+ fail(
32736
+ "RESET_SCOPE_AMBIGUOUS",
32737
+ "`--service` and `--runner` are mutually exclusive. Pass exactly one."
32738
+ );
32739
+ }
32740
+ if (!wantsService && !wantsRunner) {
32741
+ fail(
32742
+ "RESET_SCOPE_MISSING",
32743
+ "Pass `--service` to clear the service-level crash history, or `--runner` to clear a per-runner crash history."
32744
+ );
32745
+ }
32746
+ const slockHome = resolveSlockHome();
32747
+ if (wantsService) {
32748
+ const result2 = await resetService(slockHome);
32749
+ if (opts.json) {
32750
+ info(JSON.stringify(result2));
32751
+ return;
32752
+ }
32753
+ info(
32754
+ `Reset service crash history (previous state: ${result2.previousState}; cleared ${result2.clearedCrashCount} entr${result2.clearedCrashCount === 1 ? "y" : "ies"}).`
32755
+ );
32756
+ info("Service state transitioned to `running`. Runners were not touched.");
32757
+ return;
32758
+ }
32759
+ const serverId = await resolveTargetServerId({ server: opts.server });
32760
+ const result = await resetRunner(slockHome, serverId);
32761
+ if (result.status === "not-found") {
32762
+ fail(
32763
+ "RESET_RUNNER_NOT_FOUND",
32764
+ `Runner for server ${serverId} was not found.`
32765
+ );
32766
+ }
32767
+ if (opts.json) {
32768
+ info(JSON.stringify(result));
32769
+ return;
32770
+ }
32771
+ info(
32772
+ `Reset runner crash history for server ${result.serverId} (previous state: ${result.previousState}; cleared ${result.clearedCrashCount} entr${result.clearedCrashCount === 1 ? "y" : "ies"}).`
32773
+ );
32774
+ info("Runner state transitioned to `running`. Process was not respawned.");
32775
+ }
32776
+
32514
32777
  // src/concurrency.ts
32515
32778
  init_esm_shims();
32516
32779
  var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
32517
- import { mkdir as mkdir10 } from "fs/promises";
32780
+ import { mkdir as mkdir11 } from "fs/promises";
32518
32781
  import { join as join4 } from "path";
32519
32782
  var STALE_LOCK_THRESHOLD_MS = 6e4;
32520
32783
  async function withMutationLock(fn) {
32521
32784
  const slockHome = resolveSlockHome();
32522
32785
  const lockTarget = computerDir(slockHome);
32523
- await mkdir10(lockTarget, { recursive: true });
32786
+ await mkdir11(lockTarget, { recursive: true });
32524
32787
  const lockfilePath = join4(lockTarget, ".lock");
32525
32788
  let release = null;
32526
32789
  try {
@@ -32559,8 +32822,8 @@ async function withMutationLock(fn) {
32559
32822
 
32560
32823
  // src/channel.ts
32561
32824
  init_esm_shims();
32562
- import { readFile as readFile11, writeFile as writeFile9, mkdir as mkdir11 } from "fs/promises";
32563
- import { dirname as dirname10 } from "path";
32825
+ import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir12 } from "fs/promises";
32826
+ import { dirname as dirname11 } from "path";
32564
32827
  var DEFAULT_CHANNEL = "latest";
32565
32828
  var SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
32566
32829
  function parseChannel(raw) {
@@ -32575,7 +32838,7 @@ function parseChannel(raw) {
32575
32838
  }
32576
32839
  async function readChannel(slockHome) {
32577
32840
  try {
32578
- const raw = await readFile11(channelPath(slockHome), "utf8");
32841
+ const raw = await readFile12(channelPath(slockHome), "utf8");
32579
32842
  const parsed = parseChannel(raw);
32580
32843
  if (parsed !== null) return parsed;
32581
32844
  } catch {
@@ -32584,8 +32847,8 @@ async function readChannel(slockHome) {
32584
32847
  }
32585
32848
  async function writeChannel(slockHome, channel2) {
32586
32849
  const p = channelPath(slockHome);
32587
- await mkdir11(dirname10(p), { recursive: true });
32588
- await writeFile9(p, `${channel2}
32850
+ await mkdir12(dirname11(p), { recursive: true });
32851
+ await writeFile10(p, `${channel2}
32589
32852
  `, { mode: 384 });
32590
32853
  }
32591
32854
  async function runChannelShow(slockHome) {
@@ -32608,20 +32871,20 @@ async function runChannelSet(slockHome, raw) {
32608
32871
 
32609
32872
  // src/upgradeCli.ts
32610
32873
  init_esm_shims();
32611
- import { readFile as readFile14 } from "fs/promises";
32874
+ import { readFile as readFile15 } from "fs/promises";
32612
32875
  import { fileURLToPath as fileURLToPath3 } from "url";
32613
- import { dirname as dirname11, join as join7 } from "path";
32876
+ import { dirname as dirname12, join as join7 } from "path";
32614
32877
 
32615
32878
  // src/upgrade.ts
32616
32879
  init_esm_shims();
32617
32880
  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";
32881
+ import { mkdir as mkdir13, readFile as readFile14, writeFile as writeFile11, rm as rm3, rename as rename4 } from "fs/promises";
32619
32882
  import { join as join6 } from "path";
32620
32883
  import { createHash as createHash3 } from "crypto";
32621
32884
 
32622
32885
  // src/preflightDepDrift.ts
32623
32886
  init_esm_shims();
32624
- import { readFile as readFile12 } from "fs/promises";
32887
+ import { readFile as readFile13 } from "fs/promises";
32625
32888
  import { spawn as spawn3 } from "child_process";
32626
32889
  import { createRequire } from "module";
32627
32890
  import { join as join5 } from "path";
@@ -32630,12 +32893,10 @@ var DAEMON_PACKAGE_NAME = "@slock-ai/daemon";
32630
32893
  async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps = {}) {
32631
32894
  const readTarball = deps.readTarballPackageJson ?? defaultReadTarballPackageJson;
32632
32895
  const readCurrent = deps.readCurrentPackageJson ?? defaultReadCurrentPackageJson;
32633
- const readInstalled = deps.readInstalledDaemonVersion ?? defaultReadInstalledDaemonVersion;
32634
32896
  const satisfies = deps.semverSatisfies ?? defaultSemverSatisfies;
32635
32897
  const allowUnsafe = deps.allowUnsafeSpec === true;
32636
32898
  let targetPkg;
32637
32899
  let currentPkg;
32638
- let installedVersion;
32639
32900
  try {
32640
32901
  targetPkg = await readTarball(stagedTarballPath);
32641
32902
  } catch (e) {
@@ -32654,15 +32915,6 @@ async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps
32654
32915
  detail: `cannot read current binary package.json: ${errMsg(e)}`
32655
32916
  };
32656
32917
  }
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
32918
  const targetSpec = targetPkg.dependencies?.[DAEMON_PACKAGE_NAME];
32667
32919
  const currentSpec = currentPkg.dependencies?.[DAEMON_PACKAGE_NAME];
32668
32920
  if (typeof targetSpec !== "string" || targetSpec.length === 0) {
@@ -32673,60 +32925,64 @@ async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps
32673
32925
  currentSpec
32674
32926
  };
32675
32927
  }
32676
- if (typeof currentSpec !== "string" || currentSpec.length === 0) {
32928
+ const unsafeTarget = isUnsafeSpec(targetSpec) || isUnparseableRange(targetSpec);
32929
+ if (!allowUnsafe && unsafeTarget) {
32677
32930
  return {
32678
32931
  ok: false,
32679
- reason: "read_failed",
32680
- detail: `current binary package.json has no ${DAEMON_PACKAGE_NAME} dependency entry`,
32932
+ reason: "unsafe_spec",
32933
+ 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.`,
32934
+ currentSpec,
32681
32935
  targetSpec
32682
32936
  };
32683
32937
  }
32684
- if (installedVersion === null || installedVersion.length === 0) {
32938
+ void satisfies;
32939
+ return { ok: true, currentSpec, targetSpec };
32940
+ }
32941
+ async function verifyHydratedDaemonDependency(extractedPackageDir, targetSpec, deps = {}) {
32942
+ const readInstalled = deps.readInstalledDaemonVersion ?? defaultReadInstalledDaemonVersion;
32943
+ const satisfies = deps.semverSatisfies ?? defaultSemverSatisfies;
32944
+ const allowUnsafe = deps.allowUnsafeSpec === true;
32945
+ const unsafeTarget = isUnsafeSpec(targetSpec) || isUnparseableRange(targetSpec);
32946
+ if (!allowUnsafe && unsafeTarget) {
32685
32947
  return {
32686
32948
  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,
32949
+ reason: "unsafe_spec",
32950
+ detail: `${DAEMON_PACKAGE_NAME} target spec is not a safe semver range (target="${targetSpec}").`,
32690
32951
  targetSpec
32691
32952
  };
32692
32953
  }
32693
- const unsafeCurrent = isUnsafeSpec(currentSpec) || isUnparseableRange(currentSpec);
32694
- const unsafeTarget = isUnsafeSpec(targetSpec) || isUnparseableRange(targetSpec);
32695
- const anyUnsafe = unsafeCurrent || unsafeTarget;
32696
- if (!allowUnsafe && anyUnsafe) {
32954
+ let installedVersion;
32955
+ try {
32956
+ installedVersion = await readInstalled(extractedPackageDir);
32957
+ } catch (e) {
32697
32958
  return {
32698
32959
  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
32960
+ reason: "read_failed",
32961
+ detail: `cannot read hydrated ${DAEMON_PACKAGE_NAME} version: ${errMsg(e)}`,
32962
+ targetSpec
32704
32963
  };
32705
32964
  }
32706
- if (currentSpec !== targetSpec) {
32965
+ if (installedVersion === null || installedVersion.length === 0) {
32707
32966
  return {
32708
32967
  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
32968
+ reason: "read_failed",
32969
+ detail: `${DAEMON_PACKAGE_NAME} is not installed in the hydrated staged package`,
32970
+ targetSpec
32714
32971
  };
32715
32972
  }
32716
- if (allowUnsafe && anyUnsafe) {
32717
- return { ok: true, currentSpec, targetSpec, installedVersion };
32973
+ if (allowUnsafe && unsafeTarget) {
32974
+ return { ok: true, targetSpec, installedVersion };
32718
32975
  }
32719
32976
  if (!satisfies(installedVersion, targetSpec)) {
32720
32977
  return {
32721
32978
  ok: false,
32722
32979
  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,
32980
+ detail: `hydrated ${DAEMON_PACKAGE_NAME}@${installedVersion} does not satisfy target spec "${targetSpec}".`,
32725
32981
  targetSpec,
32726
32982
  installedVersion
32727
32983
  };
32728
32984
  }
32729
- return { ok: true, currentSpec, targetSpec, installedVersion };
32985
+ return { ok: true, targetSpec, installedVersion };
32730
32986
  }
32731
32987
  function isUnparseableRange(spec) {
32732
32988
  const s = spec.trim();
@@ -32792,7 +33048,7 @@ async function defaultReadTarballPackageJson(tarballPath) {
32792
33048
  return JSON.parse(raw);
32793
33049
  }
32794
33050
  async function defaultReadCurrentPackageJson(currentBinaryDir) {
32795
- const raw = await readFile12(join5(currentBinaryDir, "package.json"), "utf8");
33051
+ const raw = await readFile13(join5(currentBinaryDir, "package.json"), "utf8");
32796
33052
  return JSON.parse(raw);
32797
33053
  }
32798
33054
  async function defaultReadInstalledDaemonVersion(currentBinaryDir) {
@@ -32809,7 +33065,7 @@ async function defaultReadInstalledDaemonVersion(currentBinaryDir) {
32809
33065
  for (const base of searchPaths) {
32810
33066
  const candidate = join5(base, ...subPath, "package.json");
32811
33067
  try {
32812
- const raw = await readFile12(candidate, "utf8");
33068
+ const raw = await readFile13(candidate, "utf8");
32813
33069
  const parsed = JSON.parse(raw);
32814
33070
  if (parsed.name !== DAEMON_PACKAGE_NAME) continue;
32815
33071
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
@@ -32889,9 +33145,9 @@ function satisfiesTilde(ver, base) {
32889
33145
  // src/upgrade.ts
32890
33146
  async function stagePhase(slockHome, version, deps = {}) {
32891
33147
  const npmPack = deps.npmPack ?? defaultNpmPack;
32892
- const fsReadFile = deps.fsReadFile ?? readFile13;
33148
+ const fsReadFile = deps.fsReadFile ?? readFile14;
32893
33149
  const stagedPath = upgradeStagingDir(slockHome, version);
32894
- await mkdir12(stagedPath, { recursive: true });
33150
+ await mkdir13(stagedPath, { recursive: true });
32895
33151
  const packageRef = `@slock-ai/computer@${version}`;
32896
33152
  const result = await npmPack(stagedPath, packageRef);
32897
33153
  if (result.exitCode !== 0) {
@@ -32999,8 +33255,8 @@ async function cleanupStaged(slockHome, version) {
32999
33255
  async function snapshotPhase(slockHome, snap) {
33000
33256
  const path3 = upgradeSnapshotPath(slockHome);
33001
33257
  const tmp = `${path3}.tmp`;
33002
- await mkdir12(computerDir(slockHome), { recursive: true });
33003
- await writeFile10(tmp, JSON.stringify(snap, null, 2), { mode: 384 });
33258
+ await mkdir13(computerDir(slockHome), { recursive: true });
33259
+ await writeFile11(tmp, JSON.stringify(snap, null, 2), { mode: 384 });
33004
33260
  await rename4(tmp, path3);
33005
33261
  }
33006
33262
  async function clearUpgradeSnapshot(slockHome) {
@@ -33012,7 +33268,7 @@ async function clearUpgradeSnapshot(slockHome) {
33012
33268
  }
33013
33269
  async function extractTarball(tarballPath, destDir, deps = {}) {
33014
33270
  const tarSpawn = deps.tarSpawn ?? defaultTarSpawn;
33015
- await mkdir12(destDir, { recursive: true });
33271
+ await mkdir13(destDir, { recursive: true });
33016
33272
  const result = await tarSpawn(tarballPath, destDir);
33017
33273
  if (result.exitCode !== 0) {
33018
33274
  const err = new Error(
@@ -33271,7 +33527,7 @@ async function restartPhase(slockHome, deps = {}) {
33271
33527
  }
33272
33528
  async function defaultReadSupervisorPid(slockHome) {
33273
33529
  try {
33274
- const raw = (await readFile13(supervisorPidPath(slockHome), "utf8")).trim();
33530
+ const raw = (await readFile14(supervisorPidPath(slockHome), "utf8")).trim();
33275
33531
  const pid = Number.parseInt(raw, 10);
33276
33532
  return Number.isInteger(pid) && pid > 0 ? pid : null;
33277
33533
  } catch {
@@ -33390,6 +33646,18 @@ async function runUpgrade(slockHome, opts) {
33390
33646
  preflight
33391
33647
  };
33392
33648
  }
33649
+ const targetDaemonSpec = preflight.targetSpec;
33650
+ if (typeof targetDaemonSpec !== "string" || targetDaemonSpec.length === 0) {
33651
+ await cleanupStaged(slockHome, opts.targetVersion);
33652
+ return {
33653
+ ok: false,
33654
+ phase: "preflight",
33655
+ reason: `${preflight.detail ?? preflight.reason ?? "dep_drift"}: missing target ${DAEMON_PACKAGE_NAME} spec`,
33656
+ staged,
33657
+ verify,
33658
+ preflight
33659
+ };
33660
+ }
33393
33661
  const snap = {
33394
33662
  at: (/* @__PURE__ */ new Date()).toISOString(),
33395
33663
  fromVersion: opts.fromVersion,
@@ -33440,6 +33708,24 @@ async function runUpgrade(slockHome, opts) {
33440
33708
  verify
33441
33709
  };
33442
33710
  }
33711
+ const hydratedDaemon = await verifyHydratedDaemonDependency(
33712
+ extractedPackageDir,
33713
+ targetDaemonSpec,
33714
+ deps.preflight ?? {}
33715
+ );
33716
+ if (!hydratedDaemon.ok) {
33717
+ await cleanupStaged(slockHome, opts.targetVersion);
33718
+ await clearUpgradeSnapshot(slockHome);
33719
+ return {
33720
+ ok: false,
33721
+ phase: "hydrate",
33722
+ reason: `staged dependency tree verification failed: ${hydratedDaemon.detail ?? hydratedDaemon.reason ?? "hydrated dependency verification failed"}`,
33723
+ staged,
33724
+ verify,
33725
+ preflight,
33726
+ hydratedDaemon
33727
+ };
33728
+ }
33443
33729
  let swap;
33444
33730
  try {
33445
33731
  swap = await swapPhase(opts.currentBinaryDir, extractedPackageDir, {
@@ -33528,7 +33814,7 @@ async function runUpgrade(slockHome, opts) {
33528
33814
  };
33529
33815
  }
33530
33816
  await cleanupSuccessPhase(slockHome, opts.targetVersion, swap.prevBinaryDir);
33531
- return { ok: true, phase: "cleanup", staged, verify, swap, restart, rolling };
33817
+ return { ok: true, phase: "cleanup", staged, verify, preflight, hydratedDaemon, swap, restart, rolling };
33532
33818
  }
33533
33819
  async function rollingDaemonHealthCheck(slockHome, deps = {}) {
33534
33820
  const list = deps.listManagedServerIds ?? listManagedServerIds;
@@ -33564,7 +33850,7 @@ async function rollingDaemonHealthCheck(slockHome, deps = {}) {
33564
33850
  }
33565
33851
  async function defaultReadDaemonPid(slockHome, serverId) {
33566
33852
  try {
33567
- const raw = (await readFile13(serverDaemonPidPath(slockHome, serverId), "utf8")).trim();
33853
+ const raw = (await readFile14(serverDaemonPidPath(slockHome, serverId), "utf8")).trim();
33568
33854
  const pid = Number.parseInt(raw, 10);
33569
33855
  return Number.isInteger(pid) && pid > 0 ? pid : null;
33570
33856
  } catch {
@@ -33592,7 +33878,7 @@ async function locateStagedTarball(stagedPath) {
33592
33878
 
33593
33879
  // src/upgradeLog.ts
33594
33880
  init_esm_shims();
33595
- import { chmod as chmod5, mkdir as mkdir13, open as open2 } from "fs/promises";
33881
+ import { chmod as chmod5, mkdir as mkdir14, open as open2 } from "fs/promises";
33596
33882
  var FILE_MODE = 384;
33597
33883
  var UPGRADE_ERROR_CODES = [
33598
33884
  "UPGRADE_DEPS_CHANGED",
@@ -33637,7 +33923,7 @@ function assertUpgradeLogEntry(entry) {
33637
33923
  }
33638
33924
  async function appendUpgradeLogEntry(slockHome, entry) {
33639
33925
  assertUpgradeLogEntry(entry);
33640
- await mkdir13(computerDir(slockHome), { recursive: true });
33926
+ await mkdir14(computerDir(slockHome), { recursive: true });
33641
33927
  const path3 = upgradeLogPath(slockHome);
33642
33928
  const at = entry.at ?? formatUpgradeLogTimestamp();
33643
33929
  const fullEntry = { ...entry, at };
@@ -33660,7 +33946,7 @@ function isEphemeralNpxContext(binaryDir) {
33660
33946
  async function readBundledDaemonVersion(binaryDir) {
33661
33947
  try {
33662
33948
  const pkgPath = join7(binaryDir, "package.json");
33663
- const raw = await readFile14(pkgPath, "utf8");
33949
+ const raw = await readFile15(pkgPath, "utf8");
33664
33950
  const parsed = JSON.parse(raw);
33665
33951
  const pinned = parsed.dependencies?.["@slock-ai/daemon"];
33666
33952
  if (typeof pinned !== "string" || pinned.length === 0) return null;
@@ -33854,6 +34140,9 @@ function mapFailurePhaseToCode(outcome) {
33854
34140
  case "extract":
33855
34141
  return "UPGRADE_INTEGRITY_FAILED";
33856
34142
  case "hydrate":
34143
+ if (outcome.hydratedDaemon?.ok === false) {
34144
+ return "UPGRADE_INTEGRITY_FAILED";
34145
+ }
33857
34146
  return "UPGRADE_NETWORK_FAILED";
33858
34147
  case "swap":
33859
34148
  return "UPGRADE_SWAP_FAILED";
@@ -33888,12 +34177,12 @@ async function defaultFetchDistTags() {
33888
34177
  }
33889
34178
  function defaultCurrentBinaryDir() {
33890
34179
  const here = fileURLToPath3(import.meta.url);
33891
- return dirname11(dirname11(here));
34180
+ return dirname12(dirname12(here));
33892
34181
  }
33893
34182
  async function defaultCurrentVersion() {
33894
34183
  const pkgPath = join7(defaultCurrentBinaryDir(), "package.json");
33895
34184
  try {
33896
- const raw = await readFile14(pkgPath, "utf8");
34185
+ const raw = await readFile15(pkgPath, "utf8");
33897
34186
  const parsed = JSON.parse(raw);
33898
34187
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
33899
34188
  return parsed.version;
@@ -33905,7 +34194,7 @@ async function defaultCurrentVersion() {
33905
34194
 
33906
34195
  // src/upgradeTestHarness.ts
33907
34196
  init_esm_shims();
33908
- import { mkdir as mkdir14, readdir as readdir4, stat as stat3, writeFile as writeFile11 } from "fs/promises";
34197
+ import { mkdir as mkdir15, readdir as readdir4, stat as stat3, writeFile as writeFile12 } from "fs/promises";
33909
34198
  import { join as join8 } from "path";
33910
34199
  import { createHash as createHash4 } from "crypto";
33911
34200
  var PHASES = /* @__PURE__ */ new Set([
@@ -33964,7 +34253,7 @@ function buildSimulatedDeps(slockHome, opts) {
33964
34253
  return { tarballPath: "", exitCode: 1, stderr: "simulated stage failure" };
33965
34254
  }
33966
34255
  const filename = `slock-ai-computer-${targetVersion}.tgz`;
33967
- await writeFile11(join8(cwd, filename), tarballBytes);
34256
+ await writeFile12(join8(cwd, filename), tarballBytes);
33968
34257
  return { tarballPath: join8(cwd, filename), exitCode: 0, stderr: "" };
33969
34258
  },
33970
34259
  fetchAdvertisedHash: async () => {
@@ -33976,8 +34265,8 @@ function buildSimulatedDeps(slockHome, opts) {
33976
34265
  if (opts.simulateFail === "extract") {
33977
34266
  return { exitCode: 1, stderr: "simulated extract failure" };
33978
34267
  }
33979
- await mkdir14(join8(destDir, "package"), { recursive: true });
33980
- await writeFile11(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
34268
+ await mkdir15(join8(destDir, "package"), { recursive: true });
34269
+ await writeFile12(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
33981
34270
  return { exitCode: 0, stderr: "" };
33982
34271
  },
33983
34272
  npmInstall: async () => ({ exitCode: 0, stderr: "" }),
@@ -34038,7 +34327,7 @@ function buildSimulatedDeps(slockHome, opts) {
34038
34327
  }
34039
34328
  async function arrangeSnapshotFailure(slockHome) {
34040
34329
  const snapshotPath = join8(slockHome, "computer", "upgrade-snapshot.json");
34041
- await mkdir14(snapshotPath, { recursive: true });
34330
+ await mkdir15(snapshotPath, { recursive: true });
34042
34331
  }
34043
34332
  async function pathInfo(path3) {
34044
34333
  try {
@@ -34093,12 +34382,12 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
34093
34382
  process.exitCode = 1;
34094
34383
  return;
34095
34384
  }
34096
- await mkdir14(slockHome, { recursive: true });
34385
+ await mkdir15(slockHome, { recursive: true });
34097
34386
  if (opts.simulateFail === "snapshot") {
34098
34387
  await arrangeSnapshotFailure(slockHome);
34099
34388
  }
34100
34389
  const { opts: upgradeOpts } = buildSimulatedDeps(slockHome, opts);
34101
- await mkdir14(upgradeOpts.currentBinaryDir, { recursive: true });
34390
+ await mkdir15(upgradeOpts.currentBinaryDir, { recursive: true });
34102
34391
  let outcome;
34103
34392
  try {
34104
34393
  outcome = await runUpgrade(slockHome, upgradeOpts);
@@ -34136,9 +34425,9 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
34136
34425
 
34137
34426
  // src/upgradeInstallSmoke.ts
34138
34427
  init_esm_shims();
34139
- import { copyFile, mkdir as mkdir15, readFile as readFile15 } from "fs/promises";
34428
+ import { copyFile, mkdir as mkdir16, readFile as readFile16 } from "fs/promises";
34140
34429
  import { createHash as createHash5 } from "crypto";
34141
- import { dirname as dirname12, isAbsolute, join as join9, resolve as pathResolve } from "path";
34430
+ import { dirname as dirname13, isAbsolute, join as join9, resolve as pathResolve } from "path";
34142
34431
  import { fileURLToPath as fileURLToPath4 } from "url";
34143
34432
  async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
34144
34433
  if (typeof opts.packageTarball !== "string" || opts.packageTarball.trim().length === 0) {
@@ -34147,7 +34436,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
34147
34436
  const tarballPath = isAbsolute(opts.packageTarball) ? opts.packageTarball : pathResolve(opts.packageTarball);
34148
34437
  let tarballBytes;
34149
34438
  try {
34150
- tarballBytes = await readFile15(tarballPath);
34439
+ tarballBytes = await readFile16(tarballPath);
34151
34440
  } catch (e) {
34152
34441
  const msg = e instanceof Error ? e.message : String(e);
34153
34442
  throw new Error(`__upgrade-install-smoke: cannot read --package-tarball ${tarballPath}: ${msg}`);
@@ -34159,7 +34448,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
34159
34448
  const spawnFreshSupervisor = deps.spawnFreshSupervisor ?? (async (h) => {
34160
34449
  await spawnDetachedSupervisor(h);
34161
34450
  });
34162
- await mkdir15(slockHome, { recursive: true });
34451
+ await mkdir16(slockHome, { recursive: true });
34163
34452
  const outcome = await runUpgrade(slockHome, {
34164
34453
  targetVersion: opts.targetVersion,
34165
34454
  fromVersion,
@@ -34217,12 +34506,12 @@ async function runUpgradeInstallSmokeCli(slockHome, opts, writer = (s) => proces
34217
34506
  }
34218
34507
  function defaultCurrentBinaryDirLocal() {
34219
34508
  const here = fileURLToPath4(import.meta.url);
34220
- return dirname12(dirname12(here));
34509
+ return dirname13(dirname13(here));
34221
34510
  }
34222
34511
  async function defaultCurrentVersionLocal() {
34223
34512
  const pkgPath = join9(defaultCurrentBinaryDirLocal(), "package.json");
34224
34513
  try {
34225
- const raw = await readFile15(pkgPath, "utf8");
34514
+ const raw = await readFile16(pkgPath, "utf8");
34226
34515
  const parsed = JSON.parse(raw);
34227
34516
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
34228
34517
  return parsed.version;
@@ -34272,17 +34561,13 @@ program2.command("attach").argument("<serverSlug>", "target Slock server slug (c
34272
34561
  () => runAttach({ serverSlug, serverUrl: opts.serverUrl, name: opts.name, run: opts.run, foreground: opts.foreground })
34273
34562
  );
34274
34563
  }));
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(
34564
+ 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 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(
34276
34565
  withCliExit(async (serverSlug, opts) => {
34277
34566
  await withMutationLock(
34278
34567
  () => runSetup({
34279
34568
  serverSlug,
34280
34569
  serverUrl: opts.serverUrl,
34281
34570
  name: opts.name,
34282
- adoptLegacy: opts.adoptLegacy,
34283
- legacyApiKey: opts.legacyApiKey,
34284
- legacyApiKeyFile: opts.legacyApiKeyFile,
34285
- legacyApiKeyStdin: opts.legacyApiKeyStdin,
34286
34571
  start: opts.start,
34287
34572
  foreground: opts.foreground,
34288
34573
  yes: opts.yes
@@ -34290,22 +34575,6 @@ program2.command("setup").argument("<serverSlug>", "target Slock server slug (ca
34290
34575
  );
34291
34576
  })
34292
34577
  );
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
34578
  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
34579
  await withMutationLock(async () => runDetach(await resolveTargetServerId({ server: serverSlug }), serverSlug));
34311
34580
  }));
@@ -34339,6 +34608,18 @@ program2.command("doctor").argument("[serverSlug]", "optional: scope detail (rec
34339
34608
  }
34340
34609
  )
34341
34610
  );
34611
+ program2.command("reset").description("Clear a degraded state and resume the supervisor'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(
34612
+ withCliExit(async (opts) => {
34613
+ await withMutationLock(
34614
+ () => runReset({
34615
+ service: opts.service,
34616
+ runner: opts.runner,
34617
+ server: opts.server,
34618
+ json: opts.json
34619
+ })
34620
+ );
34621
+ })
34622
+ );
34342
34623
  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
34624
  await runLogs({ lines: opts.lines, server: opts.server ?? null, supervisor: !!opts.supervisor });
34344
34625
  }));
@@ -34364,9 +34645,8 @@ program2.command("upgrade").description(
34364
34645
  [
34365
34646
  "Auto-upgrade this Computer to the latest version in its channel (or --target-version <semver>).",
34366
34647
  "",
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>`."
34648
+ "Stages the target package, hydrates production dependencies, verifies the",
34649
+ "hydrated @slock-ai/daemon version, then swaps the Computer package root."
34370
34650
  ].join("\n")
34371
34651
  ).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
34652
  withCliExit(
@@ -34459,21 +34739,6 @@ program2.command("__upgrade-install-smoke", { hidden: true }).requiredOption("--
34459
34739
  );
34460
34740
  })
34461
34741
  );
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
34742
  program2.parseAsync(process.argv).catch((err) => {
34478
34743
  process.stderr.write(`slock-computer: ${err instanceof Error ? err.message : String(err)}
34479
34744
  `);