@botiverse/raft-computer 0.0.58 → 0.0.59

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
@@ -5874,7 +5874,7 @@ var require_connect = __commonJS({
5874
5874
  const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions);
5875
5875
  timeout = timeout == null ? 1e4 : timeout;
5876
5876
  allowH2 = allowH2 != null ? allowH2 : false;
5877
- return function connect2({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) {
5877
+ return function connect3({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) {
5878
5878
  let socket;
5879
5879
  if (protocol === "https:") {
5880
5880
  if (!tls) {
@@ -11552,7 +11552,7 @@ var require_client = __commonJS({
11552
11552
  tls,
11553
11553
  strictContentLength,
11554
11554
  maxCachedSessions,
11555
- connect: connect3,
11555
+ connect: connect4,
11556
11556
  maxRequestsPerClient,
11557
11557
  localAddress,
11558
11558
  maxResponseSize,
@@ -11609,7 +11609,7 @@ var require_client = __commonJS({
11609
11609
  if (bodyTimeout != null && (!Number.isInteger(bodyTimeout) || bodyTimeout < 0)) {
11610
11610
  throw new InvalidArgumentError2("bodyTimeout must be a positive integer or zero");
11611
11611
  }
11612
- if (connect3 != null && typeof connect3 !== "function" && typeof connect3 !== "object") {
11612
+ if (connect4 != null && typeof connect4 !== "function" && typeof connect4 !== "object") {
11613
11613
  throw new InvalidArgumentError2("connect must be a function or an object");
11614
11614
  }
11615
11615
  if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) {
@@ -11643,8 +11643,8 @@ var require_client = __commonJS({
11643
11643
  throw new InvalidArgumentError2("pingInterval must be a positive integer, greater or equal to 0");
11644
11644
  }
11645
11645
  super();
11646
- if (typeof connect3 !== "function") {
11647
- connect3 = buildConnector({
11646
+ if (typeof connect4 !== "function") {
11647
+ connect4 = buildConnector({
11648
11648
  ...tls,
11649
11649
  maxCachedSessions,
11650
11650
  allowH2,
@@ -11652,14 +11652,14 @@ var require_client = __commonJS({
11652
11652
  socketPath,
11653
11653
  timeout: connectTimeout,
11654
11654
  ...typeof autoSelectFamily === "boolean" ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : void 0,
11655
- ...connect3
11655
+ ...connect4
11656
11656
  });
11657
11657
  } else if (socketPath != null) {
11658
- const customConnect = connect3;
11659
- connect3 = (opts, callback) => customConnect({ ...opts, socketPath }, callback);
11658
+ const customConnect = connect4;
11659
+ connect4 = (opts, callback) => customConnect({ ...opts, socketPath }, callback);
11660
11660
  }
11661
11661
  this[kUrl] = util.parseOrigin(url);
11662
- this[kConnector] = connect3;
11662
+ this[kConnector] = connect4;
11663
11663
  this[kPipelining] = pipelining != null ? pipelining : 1;
11664
11664
  this[kMaxHeadersSize] = maxHeaderSize;
11665
11665
  this[kKeepAliveDefaultTimeout] = keepAliveTimeout == null ? 4e3 : keepAliveTimeout;
@@ -11717,7 +11717,7 @@ var require_client = __commonJS({
11717
11717
  );
11718
11718
  }
11719
11719
  [kConnect](cb) {
11720
- connect2(this);
11720
+ connect3(this);
11721
11721
  this.once("connect", cb);
11722
11722
  }
11723
11723
  [kDispatch](opts, handler) {
@@ -11779,7 +11779,7 @@ var require_client = __commonJS({
11779
11779
  assert(client[kSize] === 0);
11780
11780
  }
11781
11781
  }
11782
- function connect2(client) {
11782
+ function connect3(client) {
11783
11783
  assert(!client[kConnecting]);
11784
11784
  assert(!client[kHTTPContext]);
11785
11785
  let { host, hostname, protocol, port } = client[kUrl];
@@ -11958,7 +11958,7 @@ var require_client = __commonJS({
11958
11958
  return;
11959
11959
  }
11960
11960
  if (!client[kHTTPContext]) {
11961
- connect2(client);
11961
+ connect3(client);
11962
11962
  return;
11963
11963
  }
11964
11964
  if (client[kHTTPContext].destroyed) {
@@ -12257,7 +12257,7 @@ var require_pool = __commonJS({
12257
12257
  constructor(origin, {
12258
12258
  connections,
12259
12259
  factory = defaultFactory,
12260
- connect: connect2,
12260
+ connect: connect3,
12261
12261
  connectTimeout,
12262
12262
  tls,
12263
12263
  maxCachedSessions,
@@ -12274,24 +12274,24 @@ var require_pool = __commonJS({
12274
12274
  if (typeof factory !== "function") {
12275
12275
  throw new InvalidArgumentError2("factory must be a function.");
12276
12276
  }
12277
- if (connect2 != null && typeof connect2 !== "function" && typeof connect2 !== "object") {
12277
+ if (connect3 != null && typeof connect3 !== "function" && typeof connect3 !== "object") {
12278
12278
  throw new InvalidArgumentError2("connect must be a function or an object");
12279
12279
  }
12280
- if (typeof connect2 !== "function") {
12281
- connect2 = buildConnector({
12280
+ if (typeof connect3 !== "function") {
12281
+ connect3 = buildConnector({
12282
12282
  ...tls,
12283
12283
  maxCachedSessions,
12284
12284
  allowH2,
12285
12285
  socketPath,
12286
12286
  timeout: connectTimeout,
12287
12287
  ...typeof autoSelectFamily === "boolean" ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : void 0,
12288
- ...connect2
12288
+ ...connect3
12289
12289
  });
12290
12290
  }
12291
12291
  super();
12292
12292
  this[kConnections] = connections || null;
12293
12293
  this[kUrl] = util.parseOrigin(origin);
12294
- this[kOptions] = { ...util.deepClone(options), connect: connect2, allowH2, clientTtl, socketPath };
12294
+ this[kOptions] = { ...util.deepClone(options), connect: connect3, allowH2, clientTtl, socketPath };
12295
12295
  this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : void 0;
12296
12296
  this[kFactory] = factory;
12297
12297
  this.on("connect", (origin2, targets) => {
@@ -12510,7 +12510,7 @@ var require_round_robin_pool = __commonJS({
12510
12510
  constructor(origin, {
12511
12511
  connections,
12512
12512
  factory = defaultFactory,
12513
- connect: connect2,
12513
+ connect: connect3,
12514
12514
  connectTimeout,
12515
12515
  tls,
12516
12516
  maxCachedSessions,
@@ -12527,24 +12527,24 @@ var require_round_robin_pool = __commonJS({
12527
12527
  if (typeof factory !== "function") {
12528
12528
  throw new InvalidArgumentError2("factory must be a function.");
12529
12529
  }
12530
- if (connect2 != null && typeof connect2 !== "function" && typeof connect2 !== "object") {
12530
+ if (connect3 != null && typeof connect3 !== "function" && typeof connect3 !== "object") {
12531
12531
  throw new InvalidArgumentError2("connect must be a function or an object");
12532
12532
  }
12533
- if (typeof connect2 !== "function") {
12534
- connect2 = buildConnector({
12533
+ if (typeof connect3 !== "function") {
12534
+ connect3 = buildConnector({
12535
12535
  ...tls,
12536
12536
  maxCachedSessions,
12537
12537
  allowH2,
12538
12538
  socketPath,
12539
12539
  timeout: connectTimeout,
12540
12540
  ...typeof autoSelectFamily === "boolean" ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : void 0,
12541
- ...connect2
12541
+ ...connect3
12542
12542
  });
12543
12543
  }
12544
12544
  super();
12545
12545
  this[kConnections] = connections || null;
12546
12546
  this[kUrl] = util.parseOrigin(origin);
12547
- this[kOptions] = { ...util.deepClone(options), connect: connect2, allowH2, clientTtl, socketPath };
12547
+ this[kOptions] = { ...util.deepClone(options), connect: connect3, allowH2, clientTtl, socketPath };
12548
12548
  this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : void 0;
12549
12549
  this[kFactory] = factory;
12550
12550
  this[kIndex] = -1;
@@ -12619,21 +12619,21 @@ var require_agent = __commonJS({
12619
12619
  return opts && opts.connections === 1 ? new Client(origin, opts) : new Pool(origin, opts);
12620
12620
  }
12621
12621
  var Agent = class extends DispatcherBase {
12622
- constructor({ factory = defaultFactory, maxOrigins = Infinity, connect: connect2, ...options } = {}) {
12622
+ constructor({ factory = defaultFactory, maxOrigins = Infinity, connect: connect3, ...options } = {}) {
12623
12623
  if (typeof factory !== "function") {
12624
12624
  throw new InvalidArgumentError2("factory must be a function.");
12625
12625
  }
12626
- if (connect2 != null && typeof connect2 !== "function" && typeof connect2 !== "object") {
12626
+ if (connect3 != null && typeof connect3 !== "function" && typeof connect3 !== "object") {
12627
12627
  throw new InvalidArgumentError2("connect must be a function or an object");
12628
12628
  }
12629
12629
  if (typeof maxOrigins !== "number" || Number.isNaN(maxOrigins) || maxOrigins <= 0) {
12630
12630
  throw new InvalidArgumentError2("maxOrigins must be a number greater than 0");
12631
12631
  }
12632
12632
  super();
12633
- if (connect2 && typeof connect2 !== "function") {
12634
- connect2 = { ...connect2 };
12633
+ if (connect3 && typeof connect3 !== "function") {
12634
+ connect3 = { ...connect3 };
12635
12635
  }
12636
- this[kOptions] = { ...util.deepClone(options), maxOrigins, connect: connect2 };
12636
+ this[kOptions] = { ...util.deepClone(options), maxOrigins, connect: connect3 };
12637
12637
  this[kFactory] = factory;
12638
12638
  this[kClients] = /* @__PURE__ */ new Map();
12639
12639
  this[kOrigins] = /* @__PURE__ */ new Set();
@@ -13432,16 +13432,16 @@ var require_proxy_agent = __commonJS({
13432
13432
  }
13433
13433
  var Http1ProxyWrapper = class extends DispatcherBase {
13434
13434
  #client;
13435
- constructor(proxyUrl, { headers = {}, connect: connect2, factory }) {
13435
+ constructor(proxyUrl, { headers = {}, connect: connect3, factory }) {
13436
13436
  if (!proxyUrl) {
13437
13437
  throw new InvalidArgumentError2("Proxy URL is mandatory");
13438
13438
  }
13439
13439
  super();
13440
13440
  this[kProxyHeaders] = headers;
13441
13441
  if (factory) {
13442
- this.#client = factory(proxyUrl, { connect: connect2 });
13442
+ this.#client = factory(proxyUrl, { connect: connect3 });
13443
13443
  } else {
13444
- this.#client = new Client(proxyUrl, { connect: connect2 });
13444
+ this.#client = new Client(proxyUrl, { connect: connect3 });
13445
13445
  }
13446
13446
  }
13447
13447
  [kDispatch](opts, handler) {
@@ -13502,7 +13502,7 @@ var require_proxy_agent = __commonJS({
13502
13502
  } else if (username && password) {
13503
13503
  this[kProxyHeaders]["proxy-authorization"] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString("base64")}`;
13504
13504
  }
13505
- const connect2 = buildConnector({ ...opts.proxyTls });
13505
+ const connect3 = buildConnector({ ...opts.proxyTls });
13506
13506
  this[kConnectEndpoint] = buildConnector({ ...opts.requestTls });
13507
13507
  const agentFactory = opts.factory || defaultAgentFactory;
13508
13508
  const factory = (origin2, options) => {
@@ -13510,7 +13510,7 @@ var require_proxy_agent = __commonJS({
13510
13510
  if (this[kProxy].protocol === "socks5:" || this[kProxy].protocol === "socks:") {
13511
13511
  return new Socks5ProxyAgent(this[kProxy].uri, {
13512
13512
  headers: this[kProxyHeaders],
13513
- connect: connect2,
13513
+ connect: connect3,
13514
13514
  factory: agentFactory,
13515
13515
  username: opts.username || username,
13516
13516
  password: opts.password || password,
@@ -13520,7 +13520,7 @@ var require_proxy_agent = __commonJS({
13520
13520
  if (!this[kTunnelProxy] && protocol2 === "http:" && this[kProxy].protocol === "http:") {
13521
13521
  return new Http1ProxyWrapper(this[kProxy].uri, {
13522
13522
  headers: this[kProxyHeaders],
13523
- connect: connect2,
13523
+ connect: connect3,
13524
13524
  factory: agentFactory
13525
13525
  });
13526
13526
  }
@@ -13529,7 +13529,7 @@ var require_proxy_agent = __commonJS({
13529
13529
  if (protocol === "socks5:" || protocol === "socks:") {
13530
13530
  this[kClient] = null;
13531
13531
  } else {
13532
- this[kClient] = clientFactory(url, { connect: connect2 });
13532
+ this[kClient] = clientFactory(url, { connect: connect3 });
13533
13533
  }
13534
13534
  this[kAgent] = new Agent({
13535
13535
  ...opts,
@@ -14139,7 +14139,7 @@ var require_h2c_client = __commonJS({
14139
14139
  "h2c-client: Only h2c protocol is supported"
14140
14140
  );
14141
14141
  }
14142
- const { connect: connect2, maxConcurrentStreams, pipelining, ...opts } = clientOpts ?? {};
14142
+ const { connect: connect3, maxConcurrentStreams, pipelining, ...opts } = clientOpts ?? {};
14143
14143
  let defaultMaxConcurrentStreams = 100;
14144
14144
  let defaultPipelining = 100;
14145
14145
  if (maxConcurrentStreams != null && Number.isInteger(maxConcurrentStreams) && maxConcurrentStreams > 0) {
@@ -15328,10 +15328,10 @@ var require_api_connect = __commonJS({
15328
15328
  }
15329
15329
  }
15330
15330
  };
15331
- function connect2(opts, callback) {
15331
+ function connect3(opts, callback) {
15332
15332
  if (callback === void 0) {
15333
15333
  return new Promise((resolve2, reject) => {
15334
- connect2.call(this, opts, (err, data) => {
15334
+ connect3.call(this, opts, (err, data) => {
15335
15335
  return err ? reject(err) : resolve2(data);
15336
15336
  });
15337
15337
  });
@@ -15348,7 +15348,7 @@ var require_api_connect = __commonJS({
15348
15348
  queueMicrotask(() => callback(err, { opaque }));
15349
15349
  }
15350
15350
  }
15351
- module.exports = connect2;
15351
+ module.exports = connect3;
15352
15352
  }
15353
15353
  });
15354
15354
 
@@ -29971,9 +29971,6 @@ function serverDir(slockHome, serverId) {
29971
29971
  function serverAttachmentPath(slockHome, serverId) {
29972
29972
  return path2.join(serverDir(slockHome, serverId), "runner.state.json");
29973
29973
  }
29974
- function legacyServerAttachmentPath(slockHome, serverId) {
29975
- return path2.join(serverDir(slockHome, serverId), "attachment.json");
29976
- }
29977
29974
  function serverRunnerPidPath(slockHome, serverId) {
29978
29975
  return path2.join(serverDir(slockHome, serverId), "server-runner.pid");
29979
29976
  }
@@ -29995,18 +29992,8 @@ function serviceStatePath(slockHome) {
29995
29992
  function servicePidPath(slockHome) {
29996
29993
  return path2.join(serviceRunDir(slockHome), "service.pid");
29997
29994
  }
29998
- function legacyServicePidPath(slockHome) {
29999
- return path2.join(computerDir(slockHome), "service.pid");
30000
- }
30001
- function legacySupervisorPidPath(slockHome) {
30002
- return path2.join(computerDir(slockHome), "supervisor.pid");
30003
- }
30004
29995
  function servicePidReadFallback(slockHome) {
30005
- return [
30006
- servicePidPath(slockHome),
30007
- legacyServicePidPath(slockHome),
30008
- legacySupervisorPidPath(slockHome)
30009
- ];
29996
+ return [servicePidPath(slockHome)];
30010
29997
  }
30011
29998
  function serviceLogPath(slockHome) {
30012
29999
  return path2.join(serviceRunDir(slockHome), "service.log");
@@ -30244,14 +30231,7 @@ async function readAttachmentAt(path3) {
30244
30231
  }
30245
30232
  async function readServerAttachment(slockHome, serverId) {
30246
30233
  if (!isValidServerId(serverId)) return null;
30247
- const current = await readAttachmentAt(serverAttachmentPath(slockHome, serverId));
30248
- const legacy = await readAttachmentAt(legacyServerAttachmentPath(slockHome, serverId));
30249
- if (!current) return legacy;
30250
- if (!legacy) return current;
30251
- if (legacy.adoptedFromLegacy === true && current.adoptedFromLegacy !== true && legacy.serverMachineId !== current.serverMachineId) {
30252
- return legacy;
30253
- }
30254
- return current;
30234
+ return readAttachmentAt(serverAttachmentPath(slockHome, serverId));
30255
30235
  }
30256
30236
  async function writeServerAttachment(slockHome, attachment) {
30257
30237
  if (!isValidServerId(attachment.serverId)) return;
@@ -30512,8 +30492,8 @@ async function runAttach(opts) {
30512
30492
  // src/setup.ts
30513
30493
  init_esm_shims();
30514
30494
  var import_undici3 = __toESM(require_undici(), 1);
30515
- import { chmod as chmod6, mkdir as mkdir12, readFile as readFile11, rename as rename4, rm as rm3, writeFile as writeFile10 } from "fs/promises";
30516
- import { dirname as dirname11 } from "path";
30495
+ import { chmod as chmod6, mkdir as mkdir13, readFile as readFile12, rename as rename4, rm as rm3, writeFile as writeFile11 } from "fs/promises";
30496
+ import { dirname as dirname12 } from "path";
30517
30497
 
30518
30498
  // src/lib/migration.ts
30519
30499
  init_esm_shims();
@@ -31039,8 +31019,8 @@ function readComputerVersion(moduleUrl = import.meta.url) {
31039
31019
  var COMPUTER_VERSION = readComputerVersion();
31040
31020
 
31041
31021
  // src/service.ts
31042
- import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile9, open, rename as rename3 } from "fs/promises";
31043
- import { dirname as dirname10, join as joinPath } from "path";
31022
+ import { mkdir as mkdir12, readFile as readFile11, writeFile as writeFile10, open, rename as rename3 } from "fs/promises";
31023
+ import { dirname as dirname11, join as joinPath } from "path";
31044
31024
  import { fileURLToPath as fileURLToPath2 } from "url";
31045
31025
 
31046
31026
  // src/cleanup.ts
@@ -31400,6 +31380,53 @@ async function emitRunnerStateTransition(slockHome, serverId, fromState, toState
31400
31380
  }
31401
31381
  }
31402
31382
 
31383
+ // src/lib/runnerStateMachine.ts
31384
+ init_esm_shims();
31385
+ var RUNNER_TRIGGER = {
31386
+ spawn: "spawn",
31387
+ spawnFailed: "spawn-failed",
31388
+ ready: "ready",
31389
+ exitGraceful: "exit-graceful",
31390
+ exitCrash: "exit-crash",
31391
+ exitCrashDegraded: "exit-crash-degraded",
31392
+ exitConfigError: "exit-config-error",
31393
+ detachStop: "detach-stop",
31394
+ shutdownStop: "shutdown-stop",
31395
+ reset: "reset"
31396
+ };
31397
+ function nextRunnerStateOnExit(exitClass, budgetBreached) {
31398
+ if (exitClass === "config-error") return "degraded";
31399
+ if (exitClass === "crash") return budgetBreached ? "degraded" : "crashed";
31400
+ return "stopped";
31401
+ }
31402
+ function exitTrigger(exitClass, budgetBreached) {
31403
+ if (exitClass === "config-error") return RUNNER_TRIGGER.exitConfigError;
31404
+ if (exitClass === "crash") {
31405
+ return budgetBreached ? RUNNER_TRIGGER.exitCrashDegraded : RUNNER_TRIGGER.exitCrash;
31406
+ }
31407
+ return RUNNER_TRIGGER.exitGraceful;
31408
+ }
31409
+ function canSpawn(rec, wanted, now) {
31410
+ if (!wanted) return false;
31411
+ if (!rec) return true;
31412
+ if (rec.child) return false;
31413
+ if (rec.lifecycle !== "crashed" && rec.lifecycle !== "stopped") return false;
31414
+ return now >= (rec.backoffUntil ?? 0);
31415
+ }
31416
+ function rehydrateRunnerRecord(serverId, degraded) {
31417
+ return {
31418
+ serverId,
31419
+ lifecycle: degraded ? "degraded" : "stopped",
31420
+ stopping: false
31421
+ };
31422
+ }
31423
+ function applyRunnerReset(rec) {
31424
+ if (rec.lifecycle !== "degraded") return false;
31425
+ rec.lifecycle = "stopped";
31426
+ rec.backoffUntil = void 0;
31427
+ return true;
31428
+ }
31429
+
31403
31430
  // src/services/start.ts
31404
31431
  init_esm_shims();
31405
31432
 
@@ -32575,120 +32602,569 @@ async function listRunners(installRoot, opts = {}) {
32575
32602
  return { servers };
32576
32603
  }
32577
32604
 
32578
- // src/service.ts
32579
- var seaRequire = createRequire2(import.meta.url);
32580
- var cachedIsSea;
32581
- function isSeaBinary() {
32582
- if (cachedIsSea !== void 0) return cachedIsSea;
32605
+ // src/reset.ts
32606
+ init_esm_shims();
32607
+
32608
+ // src/serviceState.ts
32609
+ init_esm_shims();
32610
+ import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile9, appendFile as appendFile3 } from "fs/promises";
32611
+ import { dirname as dirname10 } from "path";
32612
+
32613
+ // src/lib/state.ts
32614
+ init_esm_shims();
32615
+ var SERVICE_STATE_VALUES = [
32616
+ "starting",
32617
+ "running",
32618
+ "degraded",
32619
+ "stopping",
32620
+ "stopped"
32621
+ ];
32622
+ function isServiceState(value) {
32623
+ return typeof value === "string" && SERVICE_STATE_VALUES.includes(value);
32624
+ }
32625
+
32626
+ // src/serviceState.ts
32627
+ var DEFAULT_STATE = {
32628
+ state: "running",
32629
+ crashHistory: []
32630
+ };
32631
+ async function readServiceState(slockHome) {
32583
32632
  try {
32584
- cachedIsSea = seaRequire("node:sea").isSea();
32633
+ const raw = await readFile10(serviceStatePath(slockHome), "utf8");
32634
+ const parsed = JSON.parse(raw);
32635
+ if (!parsed || typeof parsed !== "object") return { ...DEFAULT_STATE };
32636
+ const obj = parsed;
32637
+ const state = isServiceState(obj.state) ? obj.state : "running";
32638
+ const crashHistory = Array.isArray(obj.crashHistory) ? obj.crashHistory.filter(isCrashEntry) : [];
32639
+ return { state, crashHistory };
32585
32640
  } catch {
32586
- cachedIsSea = false;
32641
+ return { ...DEFAULT_STATE };
32587
32642
  }
32588
- return cachedIsSea;
32589
32643
  }
32590
- function buildResidentSpawn(mode, serverId, selfEntry = process.argv[1] ?? "", execArgv = process.execArgv, isSea = isSeaBinary()) {
32591
- const tail = serverId ? [mode, serverId] : [mode];
32592
- if (isSea) {
32593
- return { command: process.execPath, args: [...execArgv, ...tail] };
32594
- }
32595
- return { command: process.execPath, args: [...execArgv, selfEntry, ...tail] };
32644
+ async function writeServiceState(slockHome, file) {
32645
+ const path3 = serviceStatePath(slockHome);
32646
+ await mkdir11(dirname10(path3), { recursive: true });
32647
+ await writeFile9(path3, JSON.stringify(file), { mode: 384 });
32596
32648
  }
32597
- var PARENT_LOCK_HELD_ENV_VAR = "SLOCK_COMPUTER_PARENT_MUTATION_LOCK_HELD";
32598
- async function spawnDetachedService(slockHome) {
32599
- await mkdir11(serviceRunDir(slockHome), { recursive: true });
32600
- const supLogFd = await open(serviceLogPath(slockHome), "a");
32601
- const { command, args } = buildResidentSpawn("__service", null);
32602
- const child = spawn2(command, args, {
32603
- detached: true,
32604
- stdio: ["ignore", supLogFd.fd, supLogFd.fd],
32605
- windowsHide: true,
32606
- env: { ...process.env, [PARENT_LOCK_HELD_ENV_VAR]: "1" }
32607
- });
32608
- child.on("error", (err) => {
32609
- process.stderr.write(
32610
- `${JSON.stringify({ ok: false, code: "SUPERVISOR_SPAWN_FAILED", message: err.message })}
32611
- `
32612
- );
32613
- });
32614
- const pid = child.pid;
32615
- child.unref();
32616
- await supLogFd.close();
32617
- if (!pid) {
32618
- throw new Error("SUPERVISOR_SPAWN_FAILED: could not spawn the service process");
32619
- }
32620
- await writePidfileAt(servicePidPath(slockHome), pid);
32621
- return pid;
32649
+ function isCrashEntry(value) {
32650
+ if (!value || typeof value !== "object") return false;
32651
+ const obj = value;
32652
+ return typeof obj.at === "string";
32622
32653
  }
32623
- var EX_CONFIG_EXIT_CODE = 78;
32624
- async function ensureSeaRuntimePackageDir() {
32625
- if (!isSeaBinary() || process.env.PI_PACKAGE_DIR) return;
32654
+ async function emitServiceStateTransition(slockHome, fromState, toState, trigger) {
32655
+ const entry = {
32656
+ at: (/* @__PURE__ */ new Date()).toISOString(),
32657
+ kind: "service-state-changed",
32658
+ fromState,
32659
+ toState,
32660
+ trigger
32661
+ };
32626
32662
  try {
32627
- const dir = joinPath(resolveSlockHome(), "runtime-pkg");
32628
- await mkdir11(dir, { recursive: true });
32629
- await writeFile9(
32630
- joinPath(dir, "package.json"),
32631
- `${JSON.stringify({ name: "slock-computer-sea-runtime", version: COMPUTER_VERSION })}
32632
- `
32633
- );
32634
- process.env.PI_PACKAGE_DIR = dir;
32663
+ const path3 = serviceLogPath(slockHome);
32664
+ await mkdir11(dirname10(path3), { recursive: true });
32665
+ await appendFile3(path3, JSON.stringify(entry) + "\n");
32635
32666
  } catch {
32636
32667
  }
32637
32668
  }
32638
- var defaultCoreFactory = async (creds) => {
32639
- await ensureSeaRuntimePackageDir();
32640
- let coreMod;
32669
+ async function clearServiceCrashHistory(slockHome) {
32670
+ const current = await readServiceState(slockHome);
32671
+ const previousState = current.state;
32672
+ const clearedCrashCount = current.crashHistory.length;
32673
+ const next = { state: "running", crashHistory: [] };
32674
+ await writeServiceState(slockHome, next);
32675
+ await emitServiceStateTransition(
32676
+ slockHome,
32677
+ previousState,
32678
+ "running",
32679
+ "reset-service"
32680
+ );
32681
+ return { previousState, clearedCrashCount };
32682
+ }
32683
+
32684
+ // src/targetServer.ts
32685
+ init_esm_shims();
32686
+ function attachmentLabel(a) {
32687
+ return formatServerSlugDisplay(a.serverSlug);
32688
+ }
32689
+ async function refreshAttachmentSlug(home, attachment) {
32641
32690
  try {
32642
- coreMod = await import("@botiverse/raft-daemon/core");
32643
- } catch (err) {
32644
- const code = err?.code;
32645
- const isModuleMissing = code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || err instanceof Error && /Cannot find (module|package)/i.test(err.message);
32646
- if (isModuleMissing) {
32647
- process.stderr.write(
32648
- `slock-computer: per-server daemon failed to load \`@botiverse/raft-daemon/core\`.
32649
- Reason: ${err instanceof Error ? err.message : String(err)}
32650
- This usually means a source-run workspace without a built daemon dist.
32651
- Fix: run \`pnpm --filter @botiverse/raft-daemon build\` once, then \`slock-computer doctor <server> --reset-health && slock-computer start\`.
32652
- (Packaged/npx installs ship the dist already \u2014 this only affects local source-run setups.)
32653
- `
32691
+ const client = new ComputerAttachClient(attachment.serverUrl, "");
32692
+ const result = await client.preflight(attachment.apiKey);
32693
+ if (!result.ok || !result.serverSlug || result.serverSlug === attachment.serverSlug) {
32694
+ return attachment;
32695
+ }
32696
+ const updated = { ...attachment, serverSlug: result.serverSlug };
32697
+ await writeServerAttachment(home, updated);
32698
+ return updated;
32699
+ } catch {
32700
+ return attachment;
32701
+ }
32702
+ }
32703
+ async function listAttachmentsWithFreshSlugs(home) {
32704
+ const attachments = await listServerAttachments(home);
32705
+ return await Promise.all(attachments.map((a) => refreshAttachmentSlug(home, a)));
32706
+ }
32707
+ async function resolveTargetServerId(opts) {
32708
+ const home = resolveSlockHome();
32709
+ let attachments = await listServerAttachments(home);
32710
+ if (attachments.length === 0) {
32711
+ fail("NO_ATTACHMENT", "No server attachments yet. Run `slock-computer attach /<serverSlug>` (e.g. `/myserver`) first.");
32712
+ }
32713
+ const requested = normalizeServerSlug(opts.server ?? "");
32714
+ if (requested) {
32715
+ let found = attachments.find((a) => a.serverSlug === requested);
32716
+ if (!found) {
32717
+ attachments = await listAttachmentsWithFreshSlugs(home);
32718
+ found = attachments.find((a) => a.serverSlug === requested);
32719
+ }
32720
+ if (!found) {
32721
+ fail(
32722
+ "NOT_ATTACHED",
32723
+ `Server slug ${formatServerSlugDisplay(requested)} is not attached. Attached server slugs: ${attachments.map(attachmentLabel).join(", ")}.`
32654
32724
  );
32655
- process.exit(EX_CONFIG_EXIT_CODE);
32656
32725
  }
32657
- throw err;
32726
+ return found.serverId;
32658
32727
  }
32659
- process.env.SLOCK_COMPUTER_VERSION = COMPUTER_VERSION;
32660
- return new coreMod.DaemonCore({
32661
- serverUrl: creds.serverUrl,
32662
- apiKey: creds.apiKey,
32663
- localTrace: true,
32664
- // In a SEA single-binary there is no sidecar `slock` CLI script for the
32665
- // daemon to resolve, so inject the `__cli` sentinel: the agent CLI wrapper
32666
- // then execs `<exe> __cli "$@"`, re-execing this binary in CLI mode
32667
- // (runBundledSlockCli). Normal installs leave this unset → the daemon
32668
- // resolves the bundled CLI dist path as before.
32669
- slockCliPath: isSeaBinary() ? "__cli" : void 0,
32670
- // Managed-Computer remote control (server WS runner). This runner
32671
- // is the service's in-process `__run` child, so:
32672
- // restart → SIGTERM ourselves → runResident's shutdown does
32673
- // core.stop() + exit 0 → classifyRunnerExit = graceful → the
32674
- // service supervisor respawns this runner (effective restart).
32675
- // upgrade (SEA + requestId) → run the SEA upgrade IN-PROCESS so we can
32676
- // stream `computer:upgrade:progress` over this live WS, then SIGTERM
32677
- // ourselves; the supervisor respawns the swapped binary and the new
32678
- // process reports `done` via onComputerUpgradeReconcile on reconnect.
32679
- // upgrade (non-SEA dev run, or no requestId) → SEA-only no-op. The
32680
- // npm-install-root upgrade path is retired: a managed Computer is a
32681
- // SEA single binary; a source/dev run has no binary to self-swap.
32682
- onComputerControl: (action, ctx) => {
32683
- if (action === "restart") {
32684
- process.kill(process.pid, "SIGTERM");
32685
- return;
32728
+ if (attachments.length === 1) return attachments[0].serverId;
32729
+ fail(
32730
+ "AMBIGUOUS_SERVER",
32731
+ `Multiple servers attached (${attachments.map(attachmentLabel).join(", ")}). Pass \`--server /<serverSlug>\` (e.g. \`--server ${attachmentLabel(attachments[0])}\`) to choose one.`
32732
+ );
32733
+ }
32734
+ async function resolveTargetAttachment(opts) {
32735
+ const serverId = await resolveTargetServerId(opts);
32736
+ const a = await readServerAttachment(resolveSlockHome(), serverId);
32737
+ if (!a) {
32738
+ const label = opts.server ?? serverId;
32739
+ fail("INVALID_ATTACHMENT", `Attachment for ${label} is missing/invalid. Re-run \`slock-computer attach ${label}\`.`);
32740
+ }
32741
+ return a;
32742
+ }
32743
+
32744
+ // src/lib/ipc-client.ts
32745
+ init_esm_shims();
32746
+ import { connect as connect2 } from "net";
32747
+ import { randomUUID } from "crypto";
32748
+ var DEFAULT_PROTOCOL_VERSION = 1;
32749
+ var CLIENT_KIND = "lib";
32750
+ var CLIENT_VERSION = "0.0.0";
32751
+ function resolveTransportPath2(installRoot) {
32752
+ return process.platform === "win32" ? serviceWindowsPipeName(installRoot) : serviceSocketPath(installRoot);
32753
+ }
32754
+ async function connectService(installRoot, options = {}) {
32755
+ const protocolVersion = options.protocolVersion ?? DEFAULT_PROTOCOL_VERSION;
32756
+ const transportPath = resolveTransportPath2(installRoot);
32757
+ const socket = await new Promise((resolve2, reject) => {
32758
+ const s = connect2({ path: transportPath });
32759
+ s.once("connect", () => resolve2(s));
32760
+ s.once("error", (err) => reject(
32761
+ new ServiceClientError("IPC_PROTOCOL_HANDSHAKE_FAILED", `unable to connect to ${transportPath}: ${err.message}`, err)
32762
+ ));
32763
+ });
32764
+ const decoder = new FrameDecoder();
32765
+ const pendingRequests = /* @__PURE__ */ new Map();
32766
+ const eventQueue = [];
32767
+ const eventWaiters = [];
32768
+ let closed = false;
32769
+ let protocolError = null;
32770
+ function terminate(error) {
32771
+ if (closed) return;
32772
+ closed = true;
32773
+ if (error) protocolError = error;
32774
+ socket.destroy();
32775
+ for (const pending of pendingRequests.values()) {
32776
+ pending.reject(error ?? new ServiceClientError("IPC_CLIENT_CLOSED", "service client closed before response"));
32777
+ }
32778
+ pendingRequests.clear();
32779
+ for (const waiter of eventWaiters) waiter.resolve(null);
32780
+ eventWaiters.length = 0;
32781
+ }
32782
+ function dispatchFrame(frame) {
32783
+ if (!isRecord2(frame) || typeof frame.type !== "string") {
32784
+ throw new ServiceClientError("IPC_MALFORMED_FRAME", "frame is missing required `type` field");
32785
+ }
32786
+ if (frame.type === "event") {
32787
+ const event = { kind: String(frame.kind), payload: frame.payload };
32788
+ if (eventWaiters.length > 0) {
32789
+ const waiter = eventWaiters.shift();
32790
+ waiter.resolve(event);
32791
+ } else {
32792
+ eventQueue.push(event);
32686
32793
  }
32687
- if (isSeaBinary() && ctx.requestId) {
32688
- return runManagedSeaUpgrade(ctx.requestId, ctx);
32794
+ return;
32795
+ }
32796
+ if (frame.type === "response") {
32797
+ const id = String(frame.id ?? "");
32798
+ const pending = pendingRequests.get(id);
32799
+ if (!pending) return;
32800
+ pendingRequests.delete(id);
32801
+ if ("error" in frame && isRecord2(frame.error)) {
32802
+ const code = String(frame.error.code ?? "IPC_MALFORMED_FRAME");
32803
+ const message = String(frame.error.message ?? "service request failed");
32804
+ pending.reject(new ServiceClientError(code, message));
32805
+ } else {
32806
+ pending.resolve(frame.result);
32689
32807
  }
32690
- console.warn(
32691
- `[Computer] Ignoring upgrade request: in-process SEA upgrade unavailable (isSea=${isSeaBinary()}, requestId=${ctx.requestId ? "present" : "absent"}). Managed upgrade requires a SEA binary invoked with a requestId.`
32808
+ return;
32809
+ }
32810
+ if (frame.type === "ping") {
32811
+ socket.write(encodeFrame({ type: "pong" }));
32812
+ return;
32813
+ }
32814
+ if (frame.type === "pong") {
32815
+ return;
32816
+ }
32817
+ }
32818
+ socket.on("data", (chunk) => {
32819
+ try {
32820
+ decoder.push(chunk);
32821
+ const frames = decoder.drain();
32822
+ for (const frame of frames) {
32823
+ dispatchFrame(frame);
32824
+ }
32825
+ } catch (error) {
32826
+ terminate(error instanceof ServiceClientError ? error : new ServiceClientError("IPC_MALFORMED_FRAME", "frame decode failed", error));
32827
+ }
32828
+ });
32829
+ socket.on("close", () => terminate(null));
32830
+ socket.on("error", (err) => terminate(
32831
+ new ServiceClientError("IPC_MALFORMED_FRAME", `socket error: ${err.message}`, err)
32832
+ ));
32833
+ await new Promise((resolve2, reject) => {
32834
+ let settled = false;
32835
+ const settle = (action) => {
32836
+ if (settled) return;
32837
+ settled = true;
32838
+ socket.removeListener("close", onHandshakeClose);
32839
+ socket.removeListener("error", onHandshakeError);
32840
+ action();
32841
+ };
32842
+ const onHandshakeClose = () => settle(() => {
32843
+ reject(new ServiceClientError(
32844
+ "IPC_PROTOCOL_HANDSHAKE_FAILED",
32845
+ "service closed connection before sending hello-ack"
32846
+ ));
32847
+ });
32848
+ const onHandshakeError = (err) => settle(() => {
32849
+ reject(new ServiceClientError(
32850
+ "IPC_PROTOCOL_HANDSHAKE_FAILED",
32851
+ `handshake socket error: ${err.message}`,
32852
+ err
32853
+ ));
32854
+ });
32855
+ socket.once("close", onHandshakeClose);
32856
+ socket.once("error", onHandshakeError);
32857
+ const onFirstFrame = (chunk) => {
32858
+ socket.removeListener("data", onFirstFrame);
32859
+ try {
32860
+ decoder.push(chunk);
32861
+ const frames = decoder.drain();
32862
+ if (frames.length === 0) {
32863
+ socket.once("data", onFirstFrame);
32864
+ return;
32865
+ }
32866
+ const [firstFrame, ...remaining] = frames;
32867
+ if (!isRecord2(firstFrame) || typeof firstFrame.type !== "string") {
32868
+ settle(() => reject(new ServiceClientError("IPC_PROTOCOL_HANDSHAKE_FAILED", "handshake response missing `type`")));
32869
+ return;
32870
+ }
32871
+ if (firstFrame.type === "hello-reject") {
32872
+ const reason = String(firstFrame.reason ?? "IPC_PROTOCOL_VERSION_UNSUPPORTED");
32873
+ settle(() => reject(new ServiceClientError(
32874
+ reason === "IPC_PROTOCOL_VERSION_UNSUPPORTED" ? "IPC_PROTOCOL_VERSION_UNSUPPORTED" : "IPC_PROTOCOL_HANDSHAKE_FAILED",
32875
+ `service rejected handshake: ${String(firstFrame.reason ?? "unknown")}`
32876
+ )));
32877
+ return;
32878
+ }
32879
+ if (firstFrame.type !== "hello-ack") {
32880
+ settle(() => reject(new ServiceClientError("IPC_PROTOCOL_HANDSHAKE_FAILED", `expected hello-ack, got \`${firstFrame.type}\``)));
32881
+ return;
32882
+ }
32883
+ for (const frame of remaining) {
32884
+ dispatchFrame(frame);
32885
+ }
32886
+ settle(resolve2);
32887
+ } catch (error) {
32888
+ settle(() => reject(error instanceof ServiceClientError ? error : new ServiceClientError("IPC_PROTOCOL_HANDSHAKE_FAILED", "handshake decode failed", error)));
32889
+ }
32890
+ };
32891
+ socket.once("data", onFirstFrame);
32892
+ socket.write(encodeFrame({
32893
+ type: "hello",
32894
+ protocolVersion,
32895
+ clientKind: CLIENT_KIND,
32896
+ clientVersion: CLIENT_VERSION
32897
+ }));
32898
+ });
32899
+ const events = {
32900
+ [Symbol.asyncIterator]() {
32901
+ return {
32902
+ async next() {
32903
+ if (eventQueue.length > 0) {
32904
+ return { value: eventQueue.shift(), done: false };
32905
+ }
32906
+ if (closed) {
32907
+ if (protocolError) throw protocolError;
32908
+ return { value: void 0, done: true };
32909
+ }
32910
+ return new Promise((resolve2, reject) => {
32911
+ eventWaiters.push({
32912
+ resolve: (event) => {
32913
+ if (event === null) {
32914
+ if (protocolError) reject(protocolError);
32915
+ else resolve2({ value: void 0, done: true });
32916
+ } else {
32917
+ resolve2({ value: event, done: false });
32918
+ }
32919
+ }
32920
+ });
32921
+ });
32922
+ },
32923
+ async return() {
32924
+ terminate(null);
32925
+ return { value: void 0, done: true };
32926
+ }
32927
+ };
32928
+ }
32929
+ };
32930
+ const client = {
32931
+ events,
32932
+ async request(method, params, _options = {}) {
32933
+ if (closed) {
32934
+ throw protocolError ?? new ServiceClientError("IPC_CLIENT_CLOSED", "service client is closed");
32935
+ }
32936
+ const id = randomUUID();
32937
+ return new Promise((resolve2, reject) => {
32938
+ pendingRequests.set(id, {
32939
+ resolve: (value) => resolve2(value),
32940
+ reject
32941
+ });
32942
+ try {
32943
+ socket.write(encodeFrame({ type: "request", id, method, params }));
32944
+ } catch (error) {
32945
+ pendingRequests.delete(id);
32946
+ reject(error);
32947
+ }
32948
+ });
32949
+ },
32950
+ async close() {
32951
+ terminate(null);
32952
+ }
32953
+ };
32954
+ return client;
32955
+ }
32956
+ function isRecord2(value) {
32957
+ return typeof value === "object" && value !== null && !Array.isArray(value);
32958
+ }
32959
+
32960
+ // src/reset.ts
32961
+ async function resetViaServiceOrDisk(slockHome, viaService, direct) {
32962
+ let client = null;
32963
+ try {
32964
+ client = await connectService(slockHome);
32965
+ } catch {
32966
+ client = null;
32967
+ }
32968
+ if (!client) return direct();
32969
+ try {
32970
+ return await viaService(client);
32971
+ } finally {
32972
+ await client.close();
32973
+ }
32974
+ }
32975
+ async function resetService(installRoot) {
32976
+ const { previousState, clearedCrashCount } = await clearServiceCrashHistory(installRoot);
32977
+ return {
32978
+ status: "ok",
32979
+ previousState,
32980
+ clearedCrashCount
32981
+ };
32982
+ }
32983
+ async function resetRunner(installRoot, serverId) {
32984
+ const attachment = await readServerAttachment(installRoot, serverId);
32985
+ if (!attachment) {
32986
+ return { status: "not-found", serverId };
32987
+ }
32988
+ const outcome = await resetRunnerHealth(installRoot, serverId);
32989
+ if (outcome.status === "not-found") {
32990
+ return { status: "not-found", serverId };
32991
+ }
32992
+ return {
32993
+ status: "ok",
32994
+ serverId,
32995
+ previousState: outcome.previousState ?? "running",
32996
+ clearedCrashCount: outcome.clearedCrashCount ?? 0
32997
+ };
32998
+ }
32999
+ async function runReset(opts) {
33000
+ const wantsService = opts.service === true;
33001
+ const wantsRunner = opts.runner === true;
33002
+ if (wantsService && wantsRunner) {
33003
+ fail(
33004
+ "RESET_SCOPE_AMBIGUOUS",
33005
+ "`--service` and `--runner` are mutually exclusive. Pass exactly one."
33006
+ );
33007
+ }
33008
+ if (!wantsService && !wantsRunner) {
33009
+ fail(
33010
+ "RESET_SCOPE_MISSING",
33011
+ "Pass `--service` to clear the service-level crash history, or `--runner` to clear a per-runner crash history."
33012
+ );
33013
+ }
33014
+ const slockHome = resolveSlockHome();
33015
+ if (wantsService) {
33016
+ const result2 = await resetViaServiceOrDisk(
33017
+ slockHome,
33018
+ (client) => client.request("reset-service", void 0),
33019
+ () => resetService(slockHome)
33020
+ );
33021
+ if (opts.json) {
33022
+ info(JSON.stringify(result2));
33023
+ return;
33024
+ }
33025
+ info(
33026
+ `Reset service crash history (previous state: ${result2.previousState}; cleared ${result2.clearedCrashCount} entr${result2.clearedCrashCount === 1 ? "y" : "ies"}).`
33027
+ );
33028
+ info("Service state transitioned to `running`. Runners were not touched.");
33029
+ return;
33030
+ }
33031
+ const serverId = await resolveTargetServerId({ server: opts.server });
33032
+ const result = await resetViaServiceOrDisk(
33033
+ slockHome,
33034
+ (client) => client.request("reset-runner", { serverId }),
33035
+ () => resetRunner(slockHome, serverId)
33036
+ );
33037
+ if (result.status === "not-found") {
33038
+ fail(
33039
+ "RESET_RUNNER_NOT_FOUND",
33040
+ `Runner for server ${serverId} was not found.`
33041
+ );
33042
+ }
33043
+ if (opts.json) {
33044
+ info(JSON.stringify(result));
33045
+ return;
33046
+ }
33047
+ info(
33048
+ `Reset runner crash history for server ${result.serverId} (previous state: ${result.previousState}; cleared ${result.clearedCrashCount} entr${result.clearedCrashCount === 1 ? "y" : "ies"}).`
33049
+ );
33050
+ info("Runner state transitioned to `running`. Process was not respawned.");
33051
+ }
33052
+
33053
+ // src/service.ts
33054
+ import { randomUUID as randomUUID2 } from "crypto";
33055
+ var seaRequire = createRequire2(import.meta.url);
33056
+ var cachedIsSea;
33057
+ function isSeaBinary() {
33058
+ if (cachedIsSea !== void 0) return cachedIsSea;
33059
+ try {
33060
+ cachedIsSea = seaRequire("node:sea").isSea();
33061
+ } catch {
33062
+ cachedIsSea = false;
33063
+ }
33064
+ return cachedIsSea;
33065
+ }
33066
+ function buildResidentSpawn(mode, serverId, selfEntry = process.argv[1] ?? "", execArgv = process.execArgv, isSea = isSeaBinary()) {
33067
+ const tail = serverId ? [mode, serverId] : [mode];
33068
+ if (isSea) {
33069
+ return { command: process.execPath, args: [...execArgv, ...tail] };
33070
+ }
33071
+ return { command: process.execPath, args: [...execArgv, selfEntry, ...tail] };
33072
+ }
33073
+ var PARENT_LOCK_HELD_ENV_VAR = "SLOCK_COMPUTER_PARENT_MUTATION_LOCK_HELD";
33074
+ async function spawnDetachedService(slockHome) {
33075
+ await mkdir12(serviceRunDir(slockHome), { recursive: true });
33076
+ const supLogFd = await open(serviceLogPath(slockHome), "a");
33077
+ const { command, args } = buildResidentSpawn("__service", null);
33078
+ const child = spawn2(command, args, {
33079
+ detached: true,
33080
+ stdio: ["ignore", supLogFd.fd, supLogFd.fd],
33081
+ windowsHide: true,
33082
+ env: { ...process.env, [PARENT_LOCK_HELD_ENV_VAR]: "1" }
33083
+ });
33084
+ child.on("error", (err) => {
33085
+ process.stderr.write(
33086
+ `${JSON.stringify({ ok: false, code: "SUPERVISOR_SPAWN_FAILED", message: err.message })}
33087
+ `
33088
+ );
33089
+ });
33090
+ const pid = child.pid;
33091
+ child.unref();
33092
+ await supLogFd.close();
33093
+ if (!pid) {
33094
+ throw new Error("SUPERVISOR_SPAWN_FAILED: could not spawn the service process");
33095
+ }
33096
+ await writePidfileAt(servicePidPath(slockHome), pid);
33097
+ return pid;
33098
+ }
33099
+ var EX_CONFIG_EXIT_CODE = 78;
33100
+ async function ensureSeaRuntimePackageDir() {
33101
+ if (!isSeaBinary() || process.env.PI_PACKAGE_DIR) return;
33102
+ try {
33103
+ const dir = joinPath(resolveSlockHome(), "runtime-pkg");
33104
+ await mkdir12(dir, { recursive: true });
33105
+ await writeFile10(
33106
+ joinPath(dir, "package.json"),
33107
+ `${JSON.stringify({ name: "slock-computer-sea-runtime", version: COMPUTER_VERSION })}
33108
+ `
33109
+ );
33110
+ process.env.PI_PACKAGE_DIR = dir;
33111
+ } catch {
33112
+ }
33113
+ }
33114
+ var defaultCoreFactory = async (creds) => {
33115
+ await ensureSeaRuntimePackageDir();
33116
+ let coreMod;
33117
+ try {
33118
+ coreMod = await import("@botiverse/raft-daemon/core");
33119
+ } catch (err) {
33120
+ const code = err?.code;
33121
+ const isModuleMissing = code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || err instanceof Error && /Cannot find (module|package)/i.test(err.message);
33122
+ if (isModuleMissing) {
33123
+ process.stderr.write(
33124
+ `slock-computer: per-server daemon failed to load \`@botiverse/raft-daemon/core\`.
33125
+ Reason: ${err instanceof Error ? err.message : String(err)}
33126
+ This usually means a source-run workspace without a built daemon dist.
33127
+ Fix: run \`pnpm --filter @botiverse/raft-daemon build\` once, then \`slock-computer doctor <server> --reset-health && slock-computer start\`.
33128
+ (Packaged/npx installs ship the dist already \u2014 this only affects local source-run setups.)
33129
+ `
33130
+ );
33131
+ process.exit(EX_CONFIG_EXIT_CODE);
33132
+ }
33133
+ throw err;
33134
+ }
33135
+ process.env.SLOCK_COMPUTER_VERSION = COMPUTER_VERSION;
33136
+ return new coreMod.DaemonCore({
33137
+ serverUrl: creds.serverUrl,
33138
+ apiKey: creds.apiKey,
33139
+ localTrace: true,
33140
+ // In a SEA single-binary there is no sidecar `slock` CLI script for the
33141
+ // daemon to resolve, so inject the `__cli` sentinel: the agent CLI wrapper
33142
+ // then execs `<exe> __cli "$@"`, re-execing this binary in CLI mode
33143
+ // (runBundledSlockCli). Normal installs leave this unset → the daemon
33144
+ // resolves the bundled CLI dist path as before.
33145
+ slockCliPath: isSeaBinary() ? "__cli" : void 0,
33146
+ // Managed-Computer remote control (server → WS → runner). This runner
33147
+ // is the service's in-process `__run` child, so:
33148
+ // restart → SIGTERM ourselves → runResident's shutdown does
33149
+ // core.stop() + exit 0 → classifyRunnerExit = graceful → the
33150
+ // service supervisor respawns this runner (effective restart).
33151
+ // upgrade (SEA + requestId) → run the SEA upgrade IN-PROCESS so we can
33152
+ // stream `computer:upgrade:progress` over this live WS, then SIGTERM
33153
+ // ourselves; the supervisor respawns the swapped binary and the new
33154
+ // process reports `done` via onComputerUpgradeReconcile on reconnect.
33155
+ // upgrade (non-SEA dev run, or no requestId) → SEA-only no-op. The
33156
+ // npm-install-root upgrade path is retired: a managed Computer is a
33157
+ // SEA single binary; a source/dev run has no binary to self-swap.
33158
+ onComputerControl: (action, ctx) => {
33159
+ if (action === "restart") {
33160
+ process.kill(process.pid, "SIGTERM");
33161
+ return;
33162
+ }
33163
+ if (isSeaBinary() && ctx.requestId) {
33164
+ return runManagedSeaUpgrade(ctx.requestId, ctx);
33165
+ }
33166
+ console.warn(
33167
+ `[Computer] Ignoring upgrade request: in-process SEA upgrade unavailable (isSea=${isSeaBinary()}, requestId=${ctx.requestId ? "present" : "absent"}). Managed upgrade requires a SEA binary invoked with a requestId.`
32692
33168
  );
32693
33169
  },
32694
33170
  // Blip-stitch: on every (re)connect, if THIS process booted from a freshly
@@ -32778,9 +33254,6 @@ async function runResident(serverId, deps = {}) {
32778
33254
  }
32779
33255
  var RECONCILE_INTERVAL_MS = 5e3;
32780
33256
  var CHILD_RESTART_BACKOFF_MS = 2e3;
32781
- function canSupervisorSpawnChild(serverId, running, restarting) {
32782
- return !running.has(serverId) && !restarting.has(serverId);
32783
- }
32784
33257
  function formatReadySummary(ready, serverIds, opts) {
32785
33258
  if (opts.serverId && serverIds.length === 1) {
32786
33259
  const pid = ready.get(opts.serverId);
@@ -32821,10 +33294,10 @@ async function runServiceStartupRecovery(slockHome) {
32821
33294
  }
32822
33295
  async function resolveServiceIdentity() {
32823
33296
  const here = fileURLToPath2(import.meta.url);
32824
- const installRoot = dirname10(dirname10(here));
33297
+ const installRoot = dirname11(dirname11(here));
32825
33298
  let version = null;
32826
33299
  try {
32827
- const raw = await readFile10(joinPath(installRoot, "package.json"), "utf8");
33300
+ const raw = await readFile11(joinPath(installRoot, "package.json"), "utf8");
32828
33301
  const parsed = JSON.parse(raw);
32829
33302
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
32830
33303
  version = parsed.version;
@@ -32847,7 +33320,7 @@ async function writeServiceVersionEvidence(slockHome) {
32847
33320
  };
32848
33321
  const dest = serviceVersionPath(slockHome);
32849
33322
  const tmp = `${dest}.tmp`;
32850
- await writeFile9(tmp, JSON.stringify(payload) + "\n", { mode: 384 });
33323
+ await writeFile10(tmp, JSON.stringify(payload) + "\n", { mode: 384 });
32851
33324
  await rename3(tmp, dest);
32852
33325
  } catch (err) {
32853
33326
  const msg = err instanceof Error ? err.message : String(err);
@@ -32857,7 +33330,7 @@ async function writeServiceVersionEvidence(slockHome) {
32857
33330
  );
32858
33331
  }
32859
33332
  }
32860
- async function startServiceIpcSeam(slockHome) {
33333
+ async function startServiceIpcSeam(slockHome, mutations) {
32861
33334
  const handlers = {
32862
33335
  "service-status": async () => readServiceStatus(slockHome),
32863
33336
  "runner-status": async ({ serverId }) => {
@@ -32870,7 +33343,18 @@ async function startServiceIpcSeam(slockHome) {
32870
33343
  throw err;
32871
33344
  }
32872
33345
  },
32873
- "list-runners": async () => listRunners(slockHome)
33346
+ "list-runners": async () => listRunners(slockHome),
33347
+ "reset-service": async () => mutations ? mutations.resetService() : resetService(slockHome),
33348
+ "reset-runner": async ({ serverId }) => mutations ? mutations.resetRunner(serverId) : resetRunner(slockHome, serverId),
33349
+ "upgrade-start": async (params) => {
33350
+ if (!mutations) {
33351
+ throw new ServiceClientError(
33352
+ "IPC_MALFORMED_FRAME",
33353
+ "upgrade-start requires a live supervisor mutation surface"
33354
+ );
33355
+ }
33356
+ return mutations.upgradeStart(params);
33357
+ }
32874
33358
  };
32875
33359
  const ipc = createIpcServer({ installRoot: slockHome, handlers });
32876
33360
  try {
@@ -32888,17 +33372,33 @@ async function startServiceIpcSeam(slockHome) {
32888
33372
  }
32889
33373
  async function runService() {
32890
33374
  const slockHome = resolveSlockHome();
32891
- await mkdir11(serviceRunDir(slockHome), { recursive: true });
33375
+ await mkdir12(serviceRunDir(slockHome), { recursive: true });
32892
33376
  await runServiceStartupRecovery(slockHome);
32893
33377
  await writePidfileAt(servicePidPath(slockHome), process.pid);
32894
33378
  await writeServiceVersionEvidence(slockHome);
32895
- const children = /* @__PURE__ */ new Map();
32896
- const restarting = /* @__PURE__ */ new Set();
33379
+ const runners2 = /* @__PURE__ */ new Map();
33380
+ let shuttingDown = false;
33381
+ let inFlightUpgrade = null;
32897
33382
  const { [PARENT_LOCK_HELD_ENV_VAR]: _parentLockMarker, ...childEnv } = process.env;
33383
+ const emitTransition = (serverId, from, to, trigger) => {
33384
+ void emitRunnerStateTransition(slockHome, serverId, from, to, trigger);
33385
+ };
33386
+ let scheduleReconcile = () => {
33387
+ };
32898
33388
  const spawnChild = async (serverId) => {
32899
- if (children.has(serverId)) return;
33389
+ if (shuttingDown) return;
33390
+ const existing = runners2.get(serverId);
33391
+ if (existing?.child) return;
33392
+ const fromState = existing?.lifecycle ?? "stopped";
33393
+ const record = existing ?? { serverId, lifecycle: "stopped", stopping: false };
33394
+ record.lifecycle = "starting";
33395
+ record.stopping = false;
33396
+ record.backoffUntil = void 0;
33397
+ record.child = void 0;
33398
+ runners2.set(serverId, record);
33399
+ emitTransition(serverId, fromState, "starting", RUNNER_TRIGGER.spawn);
32900
33400
  const logPath = serverRunnerLogPath(slockHome, serverId);
32901
- await mkdir11(dirname10(logPath), { recursive: true });
33401
+ await mkdir12(dirname11(logPath), { recursive: true });
32902
33402
  const logFd = await open(logPath, "a");
32903
33403
  const { command, args } = buildResidentSpawn("__run", serverId);
32904
33404
  const child = spawn2(command, args, {
@@ -32907,89 +33407,159 @@ async function runService() {
32907
33407
  env: childEnv
32908
33408
  });
32909
33409
  await logFd.close();
32910
- if (!child.pid) return;
32911
- const handle = { serverId, child, stopping: false };
32912
- children.set(serverId, handle);
33410
+ if (!child.pid) {
33411
+ record.lifecycle = "crashed";
33412
+ record.backoffUntil = Date.now() + CHILD_RESTART_BACKOFF_MS;
33413
+ emitTransition(serverId, "starting", "crashed", RUNNER_TRIGGER.spawnFailed);
33414
+ scheduleReconcile(CHILD_RESTART_BACKOFF_MS);
33415
+ return;
33416
+ }
33417
+ record.child = child;
33418
+ record.lifecycle = "running";
33419
+ emitTransition(serverId, "starting", "running", RUNNER_TRIGGER.ready);
32913
33420
  await writePidfileAt(serverRunnerPidPath(slockHome, serverId), child.pid);
32914
33421
  child.on("exit", (code, signal) => {
32915
33422
  void (async () => {
32916
- children.delete(serverId);
33423
+ const rec = runners2.get(serverId);
33424
+ if (!rec) return;
33425
+ rec.child = void 0;
32917
33426
  await clearPidfileAt(serverRunnerPidPath(slockHome, serverId));
32918
- if (handle.stopping) return;
32919
- const classification = classifyRunnerExit(code, signal);
32920
- if (classification === "config-error") {
33427
+ if (rec.stopping) {
33428
+ const trigger = shuttingDown ? RUNNER_TRIGGER.shutdownStop : RUNNER_TRIGGER.detachStop;
33429
+ const prev2 = rec.lifecycle;
33430
+ rec.lifecycle = "stopped";
33431
+ emitTransition(serverId, prev2, "stopped", trigger);
33432
+ return;
33433
+ }
33434
+ const exitClass = classifyRunnerExit(code, signal);
33435
+ if (exitClass === "config-error") {
32921
33436
  try {
32922
33437
  await markFatalConfig(slockHome, serverId, code, signal);
32923
33438
  } catch {
32924
33439
  }
33440
+ const prev2 = rec.lifecycle;
33441
+ rec.lifecycle = "degraded";
33442
+ emitTransition(serverId, prev2, "degraded", RUNNER_TRIGGER.exitConfigError);
32925
33443
  process.stderr.write(
32926
33444
  `Service: server ${serverId} child exited with EX_CONFIG (${EX_CONFIG_EXIT_CODE}); marked degraded, NOT auto-restarting. See ${serverRunnerLogPath(slockHome, serverId)} for the actionable error.
32927
33445
  `
32928
33446
  );
32929
33447
  return;
32930
33448
  }
32931
- if (classification === "graceful") {
32932
- restarting.add(serverId);
33449
+ if (exitClass === "crash") {
32933
33450
  try {
32934
- await new Promise((r) => setTimeout(r, CHILD_RESTART_BACKOFF_MS));
32935
- if (await readServerAttachment(slockHome, serverId) && !shuttingDown) {
32936
- await spawnChild(serverId);
32937
- }
32938
- } finally {
32939
- restarting.delete(serverId);
33451
+ await recordCrash(slockHome, serverId, code, signal);
33452
+ } catch {
32940
33453
  }
32941
- return;
32942
- }
32943
- try {
32944
- await recordCrash(slockHome, serverId, code, signal);
32945
- } catch {
32946
33454
  }
32947
- if (await isDegraded(slockHome, serverId)) {
33455
+ const budgetBreached = exitClass === "crash" ? await isDegraded(slockHome, serverId) : false;
33456
+ const prev = rec.lifecycle;
33457
+ const to = nextRunnerStateOnExit(exitClass, budgetBreached);
33458
+ rec.lifecycle = to;
33459
+ emitTransition(serverId, prev, to, exitTrigger(exitClass, budgetBreached));
33460
+ if (to === "degraded") {
32948
33461
  process.stderr.write(
32949
33462
  `Service: server ${serverId} marked degraded (>=3 crashes in 60s); skipping auto-restart. Run \`slock-computer doctor ${serverId} --reset-health\` after fixing the underlying issue.
32950
33463
  `
32951
33464
  );
32952
33465
  return;
32953
33466
  }
32954
- restarting.add(serverId);
32955
- try {
32956
- await new Promise((r) => setTimeout(r, CHILD_RESTART_BACKOFF_MS));
32957
- if (await readServerAttachment(slockHome, serverId) && !shuttingDown) {
32958
- await spawnChild(serverId);
32959
- }
32960
- } finally {
32961
- restarting.delete(serverId);
32962
- }
33467
+ rec.backoffUntil = Date.now() + CHILD_RESTART_BACKOFF_MS;
33468
+ scheduleReconcile(CHILD_RESTART_BACKOFF_MS);
32963
33469
  })();
32964
33470
  });
32965
33471
  };
32966
- const killChild = (handle) => {
32967
- handle.stopping = true;
33472
+ const killChild = (rec) => {
33473
+ rec.stopping = true;
32968
33474
  try {
32969
- handle.child.kill("SIGTERM");
33475
+ rec.child?.kill("SIGTERM");
32970
33476
  } catch {
32971
33477
  }
32972
33478
  };
32973
33479
  const reconcile = async () => {
33480
+ if (shuttingDown) return;
32974
33481
  const wanted = new Set(await listManagedServerIds(slockHome));
33482
+ const now = Date.now();
32975
33483
  for (const id of wanted) {
32976
- if (canSupervisorSpawnChild(id, children, restarting)) await spawnChild(id);
33484
+ if (canSpawn(runners2.get(id), true, now)) await spawnChild(id);
32977
33485
  }
32978
- for (const [id, handle] of children) {
32979
- if (!wanted.has(id)) killChild(handle);
33486
+ for (const [id, rec] of runners2) {
33487
+ if (!wanted.has(id) && rec.child) killChild(rec);
32980
33488
  }
32981
33489
  };
32982
- const ipc = await startServiceIpcSeam(slockHome);
32983
- let shuttingDown = false;
33490
+ scheduleReconcile = (delayMs) => {
33491
+ setTimeout(() => void reconcile(), Math.max(0, delayMs));
33492
+ };
33493
+ const ipc = await startServiceIpcSeam(slockHome, {
33494
+ resetService: () => resetService(slockHome),
33495
+ resetRunner: async (serverId) => {
33496
+ const result = await resetRunner(slockHome, serverId);
33497
+ if (result.status === "ok") {
33498
+ const rec = runners2.get(serverId);
33499
+ if (rec && applyRunnerReset(rec)) {
33500
+ emitTransition(serverId, "degraded", "stopped", RUNNER_TRIGGER.reset);
33501
+ scheduleReconcile(0);
33502
+ }
33503
+ }
33504
+ return result;
33505
+ },
33506
+ // `upgrade-start` (phase ①b): the running service drives the SEA upgrade
33507
+ // in-process (reusing the tested resolveAndRunSeaUpgrade) instead of the
33508
+ // service spawning a `slock-computer upgrade` subprocess — single-writer
33509
+ // initiation. Async: resolve the target version (so the `started` response
33510
+ // carries it), then kick off download+verify+swap WITHOUT awaiting so the
33511
+ // IPC response flushes first; the swap + self-restart use the EXISTING
33512
+ // mechanism unchanged (path 1 — restart is orthogonal, evolving under XX).
33513
+ // A second concurrent upgrade-start is reported `already-running`.
33514
+ upgradeStart: async ({ targetVersion: explicit }) => {
33515
+ if (!isSeaBinary()) {
33516
+ throw new ServiceClientError(
33517
+ "IPC_MALFORMED_FRAME",
33518
+ "upgrade-start is SEA-only; this service is a non-SEA (dev/npm) run"
33519
+ );
33520
+ }
33521
+ if (inFlightUpgrade) {
33522
+ return { status: "already-running", ...inFlightUpgrade };
33523
+ }
33524
+ const channel2 = await readChannel(slockHome);
33525
+ const baseUrl = resolveUpgradeBaseUrl();
33526
+ const resolved = explicit ?? await resolveSeaTargetVersion(channel2, baseUrl) ?? COMPUTER_VERSION;
33527
+ const upgradeId = randomUUID2();
33528
+ inFlightUpgrade = { upgradeId, targetVersion: resolved };
33529
+ void (async () => {
33530
+ try {
33531
+ await resolveAndRunSeaUpgrade({
33532
+ slockHome,
33533
+ requestId: upgradeId,
33534
+ fromVersion: COMPUTER_VERSION,
33535
+ currentBinaryPath: process.execPath,
33536
+ nowIso: (/* @__PURE__ */ new Date()).toISOString(),
33537
+ deps: explicit ? { resolveTargetVersion: async () => explicit } : void 0
33538
+ });
33539
+ } catch (err) {
33540
+ process.stderr.write(
33541
+ `Service: upgrade ${upgradeId} failed: ${err instanceof Error ? err.message : String(err)}
33542
+ `
33543
+ );
33544
+ } finally {
33545
+ inFlightUpgrade = null;
33546
+ }
33547
+ })();
33548
+ return { status: "started", upgradeId, targetVersion: resolved };
33549
+ }
33550
+ });
32984
33551
  const shutdown = () => {
32985
33552
  if (shuttingDown) return;
32986
33553
  shuttingDown = true;
32987
- for (const handle of children.values()) killChild(handle);
33554
+ for (const rec of runners2.values()) if (rec.child) killChild(rec);
32988
33555
  void ipc.close();
32989
33556
  void clearPidfileAt(servicePidPath(slockHome)).then(() => process.exit(0));
32990
33557
  };
32991
33558
  process.on("SIGTERM", shutdown);
32992
33559
  process.on("SIGINT", shutdown);
33560
+ for (const id of await listManagedServerIds(slockHome)) {
33561
+ runners2.set(id, rehydrateRunnerRecord(id, await isDegraded(slockHome, id)));
33562
+ }
32993
33563
  await reconcile();
32994
33564
  const timer = setInterval(() => {
32995
33565
  void reconcile();
@@ -33111,7 +33681,7 @@ async function runDetach(serverId, serverLabel = serverId) {
33111
33681
  var USER_SESSION_EXPIRY_LEEWAY_MS = 3e4;
33112
33682
  async function readUserSessionAuth(slockHome) {
33113
33683
  try {
33114
- const parsed = JSON.parse(await readFile11(userSessionPath(slockHome), "utf8"));
33684
+ const parsed = JSON.parse(await readFile12(userSessionPath(slockHome), "utf8"));
33115
33685
  return {
33116
33686
  accessToken: typeof parsed.accessToken === "string" ? parsed.accessToken : "",
33117
33687
  ...typeof parsed.serverUrl === "string" ? { serverUrl: parsed.serverUrl } : {}
@@ -33122,7 +33692,7 @@ async function readUserSessionAuth(slockHome) {
33122
33692
  }
33123
33693
  async function hasValidUserSession(slockHome) {
33124
33694
  try {
33125
- const parsed = JSON.parse(await readFile11(userSessionPath(slockHome), "utf8"));
33695
+ const parsed = JSON.parse(await readFile12(userSessionPath(slockHome), "utf8"));
33126
33696
  return parsed.kind === "user-session" && typeof parsed.accessToken === "string" && parsed.accessToken.length > 0 && !isJwtExpired(parsed.accessToken);
33127
33697
  } catch {
33128
33698
  return false;
@@ -33142,7 +33712,7 @@ async function refreshUserSession(slockHome, serverUrl) {
33142
33712
  const file = userSessionPath(slockHome);
33143
33713
  let session;
33144
33714
  try {
33145
- session = JSON.parse(await readFile11(file, "utf8"));
33715
+ session = JSON.parse(await readFile12(file, "utf8"));
33146
33716
  } catch {
33147
33717
  return false;
33148
33718
  }
@@ -33161,8 +33731,8 @@ async function refreshUserSession(slockHome, serverUrl) {
33161
33731
  if (res.status !== 200 || typeof body?.accessToken !== "string" || typeof body.refreshToken !== "string") {
33162
33732
  return false;
33163
33733
  }
33164
- await mkdir12(dirname11(file), { recursive: true });
33165
- await writeFile10(
33734
+ await mkdir13(dirname12(file), { recursive: true });
33735
+ await writeFile11(
33166
33736
  tmpFile,
33167
33737
  JSON.stringify(
33168
33738
  {
@@ -33491,68 +34061,6 @@ async function runSetup(opts, deps = {}) {
33491
34061
 
33492
34062
  // src/runners.ts
33493
34063
  init_esm_shims();
33494
-
33495
- // src/targetServer.ts
33496
- init_esm_shims();
33497
- function attachmentLabel(a) {
33498
- return formatServerSlugDisplay(a.serverSlug);
33499
- }
33500
- async function refreshAttachmentSlug(home, attachment) {
33501
- try {
33502
- const client = new ComputerAttachClient(attachment.serverUrl, "");
33503
- const result = await client.preflight(attachment.apiKey);
33504
- if (!result.ok || !result.serverSlug || result.serverSlug === attachment.serverSlug) {
33505
- return attachment;
33506
- }
33507
- const updated = { ...attachment, serverSlug: result.serverSlug };
33508
- await writeServerAttachment(home, updated);
33509
- return updated;
33510
- } catch {
33511
- return attachment;
33512
- }
33513
- }
33514
- async function listAttachmentsWithFreshSlugs(home) {
33515
- const attachments = await listServerAttachments(home);
33516
- return await Promise.all(attachments.map((a) => refreshAttachmentSlug(home, a)));
33517
- }
33518
- async function resolveTargetServerId(opts) {
33519
- const home = resolveSlockHome();
33520
- let attachments = await listServerAttachments(home);
33521
- if (attachments.length === 0) {
33522
- fail("NO_ATTACHMENT", "No server attachments yet. Run `slock-computer attach /<serverSlug>` (e.g. `/myserver`) first.");
33523
- }
33524
- const requested = normalizeServerSlug(opts.server ?? "");
33525
- if (requested) {
33526
- let found = attachments.find((a) => a.serverSlug === requested);
33527
- if (!found) {
33528
- attachments = await listAttachmentsWithFreshSlugs(home);
33529
- found = attachments.find((a) => a.serverSlug === requested);
33530
- }
33531
- if (!found) {
33532
- fail(
33533
- "NOT_ATTACHED",
33534
- `Server slug ${formatServerSlugDisplay(requested)} is not attached. Attached server slugs: ${attachments.map(attachmentLabel).join(", ")}.`
33535
- );
33536
- }
33537
- return found.serverId;
33538
- }
33539
- if (attachments.length === 1) return attachments[0].serverId;
33540
- fail(
33541
- "AMBIGUOUS_SERVER",
33542
- `Multiple servers attached (${attachments.map(attachmentLabel).join(", ")}). Pass \`--server /<serverSlug>\` (e.g. \`--server ${attachmentLabel(attachments[0])}\`) to choose one.`
33543
- );
33544
- }
33545
- async function resolveTargetAttachment(opts) {
33546
- const serverId = await resolveTargetServerId(opts);
33547
- const a = await readServerAttachment(resolveSlockHome(), serverId);
33548
- if (!a) {
33549
- const label = opts.server ?? serverId;
33550
- fail("INVALID_ATTACHMENT", `Attachment for ${label} is missing/invalid. Re-run \`slock-computer attach ${label}\`.`);
33551
- }
33552
- return a;
33553
- }
33554
-
33555
- // src/runners.ts
33556
34064
  async function runRunnersList(opts) {
33557
34065
  const serverId = await resolveTargetServerId({ server: opts.server });
33558
34066
  const installRoot = resolveSlockHome();
@@ -33757,14 +34265,14 @@ async function runDoctor(opts) {
33757
34265
 
33758
34266
  // src/logs.ts
33759
34267
  init_esm_shims();
33760
- import { readFile as readFile12 } from "fs/promises";
34268
+ import { readFile as readFile13 } from "fs/promises";
33761
34269
  var DEFAULT_LINES = 200;
33762
34270
  async function runLogs(opts) {
33763
34271
  const home = resolveSlockHome();
33764
34272
  const file = opts.service ? serviceLogPath(home) : serverRunnerLogPath(home, await resolveTargetServerId({ server: opts.server }));
33765
34273
  let content;
33766
34274
  try {
33767
- content = await readFile12(file, "utf8");
34275
+ content = await readFile13(file, "utf8");
33768
34276
  } catch {
33769
34277
  fail(
33770
34278
  "NO_DAEMON_LOG",
@@ -33778,156 +34286,6 @@ async function runLogs(opts) {
33778
34286
  for (const line of tail) info(redactSecrets(line));
33779
34287
  }
33780
34288
 
33781
- // src/reset.ts
33782
- init_esm_shims();
33783
-
33784
- // src/serviceState.ts
33785
- init_esm_shims();
33786
- import { mkdir as mkdir13, readFile as readFile13, writeFile as writeFile11, appendFile as appendFile3 } from "fs/promises";
33787
- import { dirname as dirname12 } from "path";
33788
-
33789
- // src/lib/state.ts
33790
- init_esm_shims();
33791
- var SERVICE_STATE_VALUES = [
33792
- "starting",
33793
- "running",
33794
- "degraded",
33795
- "stopping",
33796
- "stopped"
33797
- ];
33798
- function isServiceState(value) {
33799
- return typeof value === "string" && SERVICE_STATE_VALUES.includes(value);
33800
- }
33801
-
33802
- // src/serviceState.ts
33803
- var DEFAULT_STATE = {
33804
- state: "running",
33805
- crashHistory: []
33806
- };
33807
- async function readServiceState(slockHome) {
33808
- try {
33809
- const raw = await readFile13(serviceStatePath(slockHome), "utf8");
33810
- const parsed = JSON.parse(raw);
33811
- if (!parsed || typeof parsed !== "object") return { ...DEFAULT_STATE };
33812
- const obj = parsed;
33813
- const state = isServiceState(obj.state) ? obj.state : "running";
33814
- const crashHistory = Array.isArray(obj.crashHistory) ? obj.crashHistory.filter(isCrashEntry) : [];
33815
- return { state, crashHistory };
33816
- } catch {
33817
- return { ...DEFAULT_STATE };
33818
- }
33819
- }
33820
- async function writeServiceState(slockHome, file) {
33821
- const path3 = serviceStatePath(slockHome);
33822
- await mkdir13(dirname12(path3), { recursive: true });
33823
- await writeFile11(path3, JSON.stringify(file), { mode: 384 });
33824
- }
33825
- function isCrashEntry(value) {
33826
- if (!value || typeof value !== "object") return false;
33827
- const obj = value;
33828
- return typeof obj.at === "string";
33829
- }
33830
- async function emitServiceStateTransition(slockHome, fromState, toState, trigger) {
33831
- const entry = {
33832
- at: (/* @__PURE__ */ new Date()).toISOString(),
33833
- kind: "service-state-changed",
33834
- fromState,
33835
- toState,
33836
- trigger
33837
- };
33838
- try {
33839
- const path3 = serviceLogPath(slockHome);
33840
- await mkdir13(dirname12(path3), { recursive: true });
33841
- await appendFile3(path3, JSON.stringify(entry) + "\n");
33842
- } catch {
33843
- }
33844
- }
33845
- async function clearServiceCrashHistory(slockHome) {
33846
- const current = await readServiceState(slockHome);
33847
- const previousState = current.state;
33848
- const clearedCrashCount = current.crashHistory.length;
33849
- const next = { state: "running", crashHistory: [] };
33850
- await writeServiceState(slockHome, next);
33851
- await emitServiceStateTransition(
33852
- slockHome,
33853
- previousState,
33854
- "running",
33855
- "reset-service"
33856
- );
33857
- return { previousState, clearedCrashCount };
33858
- }
33859
-
33860
- // src/reset.ts
33861
- async function resetService(installRoot) {
33862
- const { previousState, clearedCrashCount } = await clearServiceCrashHistory(installRoot);
33863
- return {
33864
- status: "ok",
33865
- previousState,
33866
- clearedCrashCount
33867
- };
33868
- }
33869
- async function resetRunner(installRoot, serverId) {
33870
- const attachment = await readServerAttachment(installRoot, serverId);
33871
- if (!attachment) {
33872
- return { status: "not-found", serverId };
33873
- }
33874
- const outcome = await resetRunnerHealth(installRoot, serverId);
33875
- if (outcome.status === "not-found") {
33876
- return { status: "not-found", serverId };
33877
- }
33878
- return {
33879
- status: "ok",
33880
- serverId,
33881
- previousState: outcome.previousState ?? "running",
33882
- clearedCrashCount: outcome.clearedCrashCount ?? 0
33883
- };
33884
- }
33885
- async function runReset(opts) {
33886
- const wantsService = opts.service === true;
33887
- const wantsRunner = opts.runner === true;
33888
- if (wantsService && wantsRunner) {
33889
- fail(
33890
- "RESET_SCOPE_AMBIGUOUS",
33891
- "`--service` and `--runner` are mutually exclusive. Pass exactly one."
33892
- );
33893
- }
33894
- if (!wantsService && !wantsRunner) {
33895
- fail(
33896
- "RESET_SCOPE_MISSING",
33897
- "Pass `--service` to clear the service-level crash history, or `--runner` to clear a per-runner crash history."
33898
- );
33899
- }
33900
- const slockHome = resolveSlockHome();
33901
- if (wantsService) {
33902
- const result2 = await resetService(slockHome);
33903
- if (opts.json) {
33904
- info(JSON.stringify(result2));
33905
- return;
33906
- }
33907
- info(
33908
- `Reset service crash history (previous state: ${result2.previousState}; cleared ${result2.clearedCrashCount} entr${result2.clearedCrashCount === 1 ? "y" : "ies"}).`
33909
- );
33910
- info("Service state transitioned to `running`. Runners were not touched.");
33911
- return;
33912
- }
33913
- const serverId = await resolveTargetServerId({ server: opts.server });
33914
- const result = await resetRunner(slockHome, serverId);
33915
- if (result.status === "not-found") {
33916
- fail(
33917
- "RESET_RUNNER_NOT_FOUND",
33918
- `Runner for server ${serverId} was not found.`
33919
- );
33920
- }
33921
- if (opts.json) {
33922
- info(JSON.stringify(result));
33923
- return;
33924
- }
33925
- info(
33926
- `Reset runner crash history for server ${result.serverId} (previous state: ${result.previousState}; cleared ${result.clearedCrashCount} entr${result.clearedCrashCount === 1 ? "y" : "ies"}).`
33927
- );
33928
- info("Runner state transitioned to `running`. Process was not respawned.");
33929
- }
33930
-
33931
34289
  // src/concurrency.ts
33932
34290
  init_esm_shims();
33933
34291
  var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
@@ -33988,11 +34346,9 @@ function resolveUpgradeTrigger(raw) {
33988
34346
  return raw === "web" || raw === "tray" ? raw : "cli";
33989
34347
  }
33990
34348
  var UPGRADE_ERROR_CODES = [
33991
- "UPGRADE_DEPS_CHANGED",
33992
34349
  "UPGRADE_NETWORK_FAILED",
33993
34350
  "UPGRADE_INTEGRITY_FAILED",
33994
34351
  "UPGRADE_SWAP_FAILED",
33995
- "UPGRADE_RESTART_FAILED",
33996
34352
  "UPGRADE_NO_TARGET",
33997
34353
  "UPGRADE_ALREADY_RUNNING"
33998
34354
  ];
@@ -34345,6 +34701,31 @@ program2.command("upgrade").description(
34345
34701
  async (opts) => {
34346
34702
  const slockHome = resolveSlockHome();
34347
34703
  const trigger = resolveUpgradeTrigger(process.env.SLOCK_UPGRADE_TRIGGER);
34704
+ if (!opts.dryRun && !opts.channel) {
34705
+ let client = null;
34706
+ try {
34707
+ client = await connectService(slockHome);
34708
+ } catch {
34709
+ client = null;
34710
+ }
34711
+ if (client) {
34712
+ try {
34713
+ const r = await client.request("upgrade-start", {
34714
+ targetVersion: opts.targetVersion
34715
+ });
34716
+ if (r.status === "already-running") {
34717
+ info(`An upgrade to ${r.targetVersion} is already running (id ${r.upgradeId}).`);
34718
+ } else {
34719
+ info(
34720
+ `Upgrade to ${r.targetVersion} started (id ${r.upgradeId}). The service downloads, verifies, swaps the binary, and restarts on it; run \`slock-computer status\` to confirm.`
34721
+ );
34722
+ }
34723
+ } finally {
34724
+ await client.close();
34725
+ }
34726
+ return;
34727
+ }
34728
+ }
34348
34729
  try {
34349
34730
  await withMutationLock(
34350
34731
  () => runUpgradeCli(slockHome, {