@botiverse/raft-computer 0.0.57 → 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 +1486 -745
- package/dist/lib/index.d.ts +9 -5
- package/dist/lib/index.js +73 -78
- package/package.json +2 -2
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
|
|
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:
|
|
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 (
|
|
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
|
|
11647
|
-
|
|
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
|
-
...
|
|
11655
|
+
...connect4
|
|
11656
11656
|
});
|
|
11657
11657
|
} else if (socketPath != null) {
|
|
11658
|
-
const customConnect =
|
|
11659
|
-
|
|
11658
|
+
const customConnect = connect4;
|
|
11659
|
+
connect4 = (opts, callback) => customConnect({ ...opts, socketPath }, callback);
|
|
11660
11660
|
}
|
|
11661
11661
|
this[kUrl] = util.parseOrigin(url);
|
|
11662
|
-
this[kConnector] =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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 (
|
|
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
|
|
12281
|
-
|
|
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
|
-
...
|
|
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, 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,
|
|
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 (
|
|
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
|
|
12534
|
-
|
|
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
|
-
...
|
|
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, 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, ...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 (
|
|
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 (
|
|
12634
|
-
|
|
12633
|
+
if (connect3 && typeof connect3 !== "function") {
|
|
12634
|
+
connect3 = { ...connect3 };
|
|
12635
12635
|
}
|
|
12636
|
-
this[kOptions] = { ...util.deepClone(options), maxOrigins, connect };
|
|
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, 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 });
|
|
13442
|
+
this.#client = factory(proxyUrl, { connect: connect3 });
|
|
13443
13443
|
} else {
|
|
13444
|
-
this.#client = new Client(proxyUrl, { connect });
|
|
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
|
|
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,
|
|
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,
|
|
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 });
|
|
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, 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
|
|
15331
|
+
function connect3(opts, callback) {
|
|
15332
15332
|
if (callback === void 0) {
|
|
15333
15333
|
return new Promise((resolve2, reject) => {
|
|
15334
|
-
|
|
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 =
|
|
15351
|
+
module.exports = connect3;
|
|
15352
15352
|
}
|
|
15353
15353
|
});
|
|
15354
15354
|
|
|
@@ -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: readFile15, mkdir:
|
|
16621
|
-
var { dirname:
|
|
16620
|
+
var { writeFile: writeFile12, readFile: readFile15, mkdir: mkdir16 } = __require("fs/promises");
|
|
16621
|
+
var { dirname: dirname14, resolve: resolve2 } = __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();
|
|
@@ -16849,7 +16849,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
16849
16849
|
throw new InvalidArgumentError2("Snapshot path is required");
|
|
16850
16850
|
}
|
|
16851
16851
|
const resolvedPath = resolve2(path3);
|
|
16852
|
-
await
|
|
16852
|
+
await mkdir16(dirname14(resolvedPath), { recursive: true });
|
|
16853
16853
|
const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
|
|
16854
16854
|
hash,
|
|
16855
16855
|
snapshot
|
|
@@ -29193,11 +29193,11 @@ var require_mtime_precision = __commonJS({
|
|
|
29193
29193
|
function probe(file, fs, callback) {
|
|
29194
29194
|
const cachedPrecision = fs[cacheSymbol];
|
|
29195
29195
|
if (cachedPrecision) {
|
|
29196
|
-
return fs.stat(file, (err,
|
|
29196
|
+
return fs.stat(file, (err, stat5) => {
|
|
29197
29197
|
if (err) {
|
|
29198
29198
|
return callback(err);
|
|
29199
29199
|
}
|
|
29200
|
-
callback(null,
|
|
29200
|
+
callback(null, stat5.mtime, cachedPrecision);
|
|
29201
29201
|
});
|
|
29202
29202
|
}
|
|
29203
29203
|
const mtime = new Date(Math.ceil(Date.now() / 1e3) * 1e3 + 5);
|
|
@@ -29205,13 +29205,13 @@ var require_mtime_precision = __commonJS({
|
|
|
29205
29205
|
if (err) {
|
|
29206
29206
|
return callback(err);
|
|
29207
29207
|
}
|
|
29208
|
-
fs.stat(file, (err2,
|
|
29208
|
+
fs.stat(file, (err2, stat5) => {
|
|
29209
29209
|
if (err2) {
|
|
29210
29210
|
return callback(err2);
|
|
29211
29211
|
}
|
|
29212
|
-
const precision =
|
|
29212
|
+
const precision = stat5.mtime.getTime() % 1e3 === 0 ? "s" : "ms";
|
|
29213
29213
|
Object.defineProperty(fs, cacheSymbol, { value: precision });
|
|
29214
|
-
callback(null,
|
|
29214
|
+
callback(null, stat5.mtime, precision);
|
|
29215
29215
|
});
|
|
29216
29216
|
});
|
|
29217
29217
|
}
|
|
@@ -29266,14 +29266,14 @@ var require_lockfile = __commonJS({
|
|
|
29266
29266
|
if (options.stale <= 0) {
|
|
29267
29267
|
return callback(Object.assign(new Error("Lock file is already being held"), { code: "ELOCKED", file }));
|
|
29268
29268
|
}
|
|
29269
|
-
options.fs.stat(lockfilePath, (err2,
|
|
29269
|
+
options.fs.stat(lockfilePath, (err2, stat5) => {
|
|
29270
29270
|
if (err2) {
|
|
29271
29271
|
if (err2.code === "ENOENT") {
|
|
29272
29272
|
return acquireLock(file, { ...options, stale: 0 }, callback);
|
|
29273
29273
|
}
|
|
29274
29274
|
return callback(err2);
|
|
29275
29275
|
}
|
|
29276
|
-
if (!isLockStale(
|
|
29276
|
+
if (!isLockStale(stat5, options)) {
|
|
29277
29277
|
return callback(Object.assign(new Error("Lock file is already being held"), { code: "ELOCKED", file }));
|
|
29278
29278
|
}
|
|
29279
29279
|
removeLock(file, options, (err3) => {
|
|
@@ -29285,8 +29285,8 @@ var require_lockfile = __commonJS({
|
|
|
29285
29285
|
});
|
|
29286
29286
|
});
|
|
29287
29287
|
}
|
|
29288
|
-
function isLockStale(
|
|
29289
|
-
return
|
|
29288
|
+
function isLockStale(stat5, options) {
|
|
29289
|
+
return stat5.mtime.getTime() < Date.now() - options.stale;
|
|
29290
29290
|
}
|
|
29291
29291
|
function removeLock(file, options, callback) {
|
|
29292
29292
|
options.fs.rmdir(getLockFile(file, options), (err) => {
|
|
@@ -29304,7 +29304,7 @@ var require_lockfile = __commonJS({
|
|
|
29304
29304
|
lock2.updateDelay = lock2.updateDelay || options.update;
|
|
29305
29305
|
lock2.updateTimeout = setTimeout(() => {
|
|
29306
29306
|
lock2.updateTimeout = null;
|
|
29307
|
-
options.fs.stat(lock2.lockfilePath, (err,
|
|
29307
|
+
options.fs.stat(lock2.lockfilePath, (err, stat5) => {
|
|
29308
29308
|
const isOverThreshold = lock2.lastUpdate + options.stale < Date.now();
|
|
29309
29309
|
if (err) {
|
|
29310
29310
|
if (err.code === "ENOENT" || isOverThreshold) {
|
|
@@ -29313,7 +29313,7 @@ var require_lockfile = __commonJS({
|
|
|
29313
29313
|
lock2.updateDelay = 1e3;
|
|
29314
29314
|
return updateLock(file, options);
|
|
29315
29315
|
}
|
|
29316
|
-
const isMtimeOurs = lock2.mtime.getTime() ===
|
|
29316
|
+
const isMtimeOurs = lock2.mtime.getTime() === stat5.mtime.getTime();
|
|
29317
29317
|
if (!isMtimeOurs) {
|
|
29318
29318
|
return setLockAsCompromised(
|
|
29319
29319
|
file,
|
|
@@ -29438,11 +29438,11 @@ var require_lockfile = __commonJS({
|
|
|
29438
29438
|
if (err) {
|
|
29439
29439
|
return callback(err);
|
|
29440
29440
|
}
|
|
29441
|
-
options.fs.stat(getLockFile(file2, options), (err2,
|
|
29441
|
+
options.fs.stat(getLockFile(file2, options), (err2, stat5) => {
|
|
29442
29442
|
if (err2) {
|
|
29443
29443
|
return err2.code === "ENOENT" ? callback(null, false) : callback(err2);
|
|
29444
29444
|
}
|
|
29445
|
-
return callback(null, !isLockStale(
|
|
29445
|
+
return callback(null, !isLockStale(stat5, options));
|
|
29446
29446
|
});
|
|
29447
29447
|
});
|
|
29448
29448
|
}
|
|
@@ -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,22 +29992,19 @@ 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");
|
|
30013
30000
|
}
|
|
30001
|
+
function serviceSocketPath(slockHome) {
|
|
30002
|
+
return path2.join(computerDir(slockHome), "run", "service.sock");
|
|
30003
|
+
}
|
|
30004
|
+
function serviceWindowsPipeName(slockHome) {
|
|
30005
|
+
const hash = createHash("sha256").update(computerDir(slockHome)).digest("hex").slice(0, 16);
|
|
30006
|
+
return `\\\\.\\pipe\\slock-computer-${hash}`;
|
|
30007
|
+
}
|
|
30014
30008
|
function serviceVersionPath(slockHome) {
|
|
30015
30009
|
return path2.join(computerDir(slockHome), "service-version.json");
|
|
30016
30010
|
}
|
|
@@ -30237,14 +30231,7 @@ async function readAttachmentAt(path3) {
|
|
|
30237
30231
|
}
|
|
30238
30232
|
async function readServerAttachment(slockHome, serverId) {
|
|
30239
30233
|
if (!isValidServerId(serverId)) return null;
|
|
30240
|
-
|
|
30241
|
-
const legacy = await readAttachmentAt(legacyServerAttachmentPath(slockHome, serverId));
|
|
30242
|
-
if (!current) return legacy;
|
|
30243
|
-
if (!legacy) return current;
|
|
30244
|
-
if (legacy.adoptedFromLegacy === true && current.adoptedFromLegacy !== true && legacy.serverMachineId !== current.serverMachineId) {
|
|
30245
|
-
return legacy;
|
|
30246
|
-
}
|
|
30247
|
-
return current;
|
|
30234
|
+
return readAttachmentAt(serverAttachmentPath(slockHome, serverId));
|
|
30248
30235
|
}
|
|
30249
30236
|
async function writeServerAttachment(slockHome, attachment) {
|
|
30250
30237
|
if (!isValidServerId(attachment.serverId)) return;
|
|
@@ -30505,8 +30492,8 @@ async function runAttach(opts) {
|
|
|
30505
30492
|
// src/setup.ts
|
|
30506
30493
|
init_esm_shims();
|
|
30507
30494
|
var import_undici3 = __toESM(require_undici(), 1);
|
|
30508
|
-
import { chmod as
|
|
30509
|
-
import { dirname as
|
|
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";
|
|
30510
30497
|
|
|
30511
30498
|
// src/lib/migration.ts
|
|
30512
30499
|
init_esm_shims();
|
|
@@ -31032,8 +31019,8 @@ function readComputerVersion(moduleUrl = import.meta.url) {
|
|
|
31032
31019
|
var COMPUTER_VERSION = readComputerVersion();
|
|
31033
31020
|
|
|
31034
31021
|
// src/service.ts
|
|
31035
|
-
import { mkdir as
|
|
31036
|
-
import { dirname as
|
|
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";
|
|
31037
31024
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
31038
31025
|
|
|
31039
31026
|
// src/cleanup.ts
|
|
@@ -31393,6 +31380,53 @@ async function emitRunnerStateTransition(slockHome, serverId, fromState, toState
|
|
|
31393
31380
|
}
|
|
31394
31381
|
}
|
|
31395
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
|
+
|
|
31396
31430
|
// src/services/start.ts
|
|
31397
31431
|
init_esm_shims();
|
|
31398
31432
|
|
|
@@ -32081,218 +32115,1151 @@ async function resolveAndRunSeaUpgrade(opts) {
|
|
|
32081
32115
|
return { ...result, targetVersion };
|
|
32082
32116
|
}
|
|
32083
32117
|
|
|
32084
|
-
// src/
|
|
32085
|
-
|
|
32086
|
-
|
|
32087
|
-
|
|
32088
|
-
|
|
32089
|
-
|
|
32090
|
-
|
|
32091
|
-
|
|
32092
|
-
|
|
32093
|
-
|
|
32094
|
-
|
|
32095
|
-
|
|
32096
|
-
|
|
32097
|
-
|
|
32098
|
-
|
|
32099
|
-
|
|
32118
|
+
// src/internal/ipc-server.ts
|
|
32119
|
+
init_esm_shims();
|
|
32120
|
+
import { connect, createServer } from "net";
|
|
32121
|
+
import { chmod as chmod5, mkdir as mkdir10, stat as stat4, unlink as unlink5 } from "fs/promises";
|
|
32122
|
+
import { dirname as dirname9 } from "path";
|
|
32123
|
+
|
|
32124
|
+
// src/internal/ipc-codec.ts
|
|
32125
|
+
init_esm_shims();
|
|
32126
|
+
|
|
32127
|
+
// src/lib/types.ts
|
|
32128
|
+
init_esm_shims();
|
|
32129
|
+
var ServiceClientError = class extends Error {
|
|
32130
|
+
code;
|
|
32131
|
+
cause;
|
|
32132
|
+
constructor(code, message, cause) {
|
|
32133
|
+
super(message);
|
|
32134
|
+
this.name = "ServiceClientError";
|
|
32135
|
+
this.code = code;
|
|
32136
|
+
if (cause !== void 0) this.cause = cause;
|
|
32100
32137
|
}
|
|
32101
|
-
|
|
32102
|
-
|
|
32103
|
-
|
|
32104
|
-
|
|
32105
|
-
|
|
32106
|
-
|
|
32107
|
-
|
|
32108
|
-
const child = spawn2(command, args, {
|
|
32109
|
-
detached: true,
|
|
32110
|
-
stdio: ["ignore", supLogFd.fd, supLogFd.fd],
|
|
32111
|
-
windowsHide: true,
|
|
32112
|
-
env: { ...process.env, [PARENT_LOCK_HELD_ENV_VAR]: "1" }
|
|
32113
|
-
});
|
|
32114
|
-
child.on("error", (err) => {
|
|
32115
|
-
process.stderr.write(
|
|
32116
|
-
`${JSON.stringify({ ok: false, code: "SUPERVISOR_SPAWN_FAILED", message: err.message })}
|
|
32117
|
-
`
|
|
32118
|
-
);
|
|
32119
|
-
});
|
|
32120
|
-
const pid = child.pid;
|
|
32121
|
-
child.unref();
|
|
32122
|
-
await supLogFd.close();
|
|
32123
|
-
if (!pid) {
|
|
32124
|
-
throw new Error("SUPERVISOR_SPAWN_FAILED: could not spawn the service process");
|
|
32138
|
+
};
|
|
32139
|
+
var StateReaderError = class extends Error {
|
|
32140
|
+
code;
|
|
32141
|
+
constructor(code, message) {
|
|
32142
|
+
super(message);
|
|
32143
|
+
this.name = "StateReaderError";
|
|
32144
|
+
this.code = code;
|
|
32125
32145
|
}
|
|
32126
|
-
|
|
32127
|
-
|
|
32128
|
-
|
|
32129
|
-
var
|
|
32130
|
-
|
|
32131
|
-
|
|
32132
|
-
|
|
32133
|
-
|
|
32134
|
-
|
|
32135
|
-
|
|
32136
|
-
joinPath(dir, "package.json"),
|
|
32137
|
-
`${JSON.stringify({ name: "slock-computer-sea-runtime", version: COMPUTER_VERSION })}
|
|
32138
|
-
`
|
|
32146
|
+
};
|
|
32147
|
+
|
|
32148
|
+
// src/internal/ipc-codec.ts
|
|
32149
|
+
var MAX_FRAME_BYTES = 1024 * 1024;
|
|
32150
|
+
function encodeFrame(payload) {
|
|
32151
|
+
const json = Buffer.from(JSON.stringify(payload), "utf8");
|
|
32152
|
+
if (json.length > MAX_FRAME_BYTES) {
|
|
32153
|
+
throw new ServiceClientError(
|
|
32154
|
+
"IPC_FRAME_TOO_LARGE",
|
|
32155
|
+
`outgoing frame ${json.length} bytes exceeds MAX_FRAME_BYTES ${MAX_FRAME_BYTES}`
|
|
32139
32156
|
);
|
|
32140
|
-
process.env.PI_PACKAGE_DIR = dir;
|
|
32141
|
-
} catch {
|
|
32142
32157
|
}
|
|
32158
|
+
const header = Buffer.alloc(4);
|
|
32159
|
+
header.writeUInt32BE(json.length, 0);
|
|
32160
|
+
return Buffer.concat([header, json]);
|
|
32143
32161
|
}
|
|
32144
|
-
var
|
|
32145
|
-
|
|
32146
|
-
|
|
32147
|
-
|
|
32148
|
-
|
|
32149
|
-
|
|
32150
|
-
const
|
|
32151
|
-
|
|
32152
|
-
|
|
32153
|
-
|
|
32154
|
-
|
|
32155
|
-
|
|
32156
|
-
|
|
32157
|
-
|
|
32158
|
-
(Packaged/npx installs ship the dist already \u2014 this only affects local source-run setups.)
|
|
32159
|
-
`
|
|
32160
|
-
);
|
|
32161
|
-
process.exit(EX_CONFIG_EXIT_CODE);
|
|
32162
|
-
}
|
|
32163
|
-
throw err;
|
|
32164
|
-
}
|
|
32165
|
-
process.env.SLOCK_COMPUTER_VERSION = COMPUTER_VERSION;
|
|
32166
|
-
return new coreMod.DaemonCore({
|
|
32167
|
-
serverUrl: creds.serverUrl,
|
|
32168
|
-
apiKey: creds.apiKey,
|
|
32169
|
-
localTrace: true,
|
|
32170
|
-
// In a SEA single-binary there is no sidecar `slock` CLI script for the
|
|
32171
|
-
// daemon to resolve, so inject the `__cli` sentinel: the agent CLI wrapper
|
|
32172
|
-
// then execs `<exe> __cli "$@"`, re-execing this binary in CLI mode
|
|
32173
|
-
// (runBundledSlockCli). Normal installs leave this unset → the daemon
|
|
32174
|
-
// resolves the bundled CLI dist path as before.
|
|
32175
|
-
slockCliPath: isSeaBinary() ? "__cli" : void 0,
|
|
32176
|
-
// Managed-Computer remote control (server → WS → runner). This runner
|
|
32177
|
-
// is the service's in-process `__run` child, so:
|
|
32178
|
-
// restart → SIGTERM ourselves → runResident's shutdown does
|
|
32179
|
-
// core.stop() + exit 0 → classifyRunnerExit = graceful → the
|
|
32180
|
-
// service supervisor respawns this runner (effective restart).
|
|
32181
|
-
// upgrade (SEA + requestId) → run the SEA upgrade IN-PROCESS so we can
|
|
32182
|
-
// stream `computer:upgrade:progress` over this live WS, then SIGTERM
|
|
32183
|
-
// ourselves; the supervisor respawns the swapped binary and the new
|
|
32184
|
-
// process reports `done` via onComputerUpgradeReconcile on reconnect.
|
|
32185
|
-
// upgrade (non-SEA dev run, or no requestId) → SEA-only no-op. The
|
|
32186
|
-
// npm-install-root upgrade path is retired: a managed Computer is a
|
|
32187
|
-
// SEA single binary; a source/dev run has no binary to self-swap.
|
|
32188
|
-
onComputerControl: (action, ctx) => {
|
|
32189
|
-
if (action === "restart") {
|
|
32190
|
-
process.kill(process.pid, "SIGTERM");
|
|
32191
|
-
return;
|
|
32162
|
+
var FrameDecoder = class {
|
|
32163
|
+
buffer = Buffer.alloc(0);
|
|
32164
|
+
push(chunk) {
|
|
32165
|
+
this.buffer = this.buffer.length === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
|
|
32166
|
+
}
|
|
32167
|
+
drain() {
|
|
32168
|
+
const frames = [];
|
|
32169
|
+
while (this.buffer.length >= 4) {
|
|
32170
|
+
const length = this.buffer.readUInt32BE(0);
|
|
32171
|
+
if (length > MAX_FRAME_BYTES) {
|
|
32172
|
+
throw new ServiceClientError(
|
|
32173
|
+
"IPC_FRAME_TOO_LARGE",
|
|
32174
|
+
`incoming frame ${length} bytes exceeds MAX_FRAME_BYTES ${MAX_FRAME_BYTES}`
|
|
32175
|
+
);
|
|
32192
32176
|
}
|
|
32193
|
-
if (
|
|
32194
|
-
|
|
32177
|
+
if (this.buffer.length < 4 + length) break;
|
|
32178
|
+
const body = this.buffer.subarray(4, 4 + length);
|
|
32179
|
+
this.buffer = this.buffer.subarray(4 + length);
|
|
32180
|
+
let parsed;
|
|
32181
|
+
try {
|
|
32182
|
+
parsed = JSON.parse(body.toString("utf8"));
|
|
32183
|
+
} catch (cause) {
|
|
32184
|
+
throw new ServiceClientError("IPC_MALFORMED_FRAME", "frame body is not valid UTF-8 JSON", cause);
|
|
32195
32185
|
}
|
|
32196
|
-
|
|
32197
|
-
`[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.`
|
|
32198
|
-
);
|
|
32199
|
-
},
|
|
32200
|
-
// Blip-stitch: on every (re)connect, if THIS process booted from a freshly
|
|
32201
|
-
// swapped binary (a pending-upgrade marker is present), report the upgrade
|
|
32202
|
-
// done with the version we actually are now, then clear the marker.
|
|
32203
|
-
onComputerUpgradeReconcile: async (emitDone) => {
|
|
32204
|
-
const slockHome = resolveSlockHome();
|
|
32205
|
-
const marker = await readPendingUpgradeMarker(slockHome);
|
|
32206
|
-
if (!marker) return;
|
|
32207
|
-
const rolledBack = COMPUTER_VERSION !== marker.targetVersion;
|
|
32208
|
-
emitDone({
|
|
32209
|
-
requestId: marker.requestId,
|
|
32210
|
-
ok: !rolledBack,
|
|
32211
|
-
newVersion: COMPUTER_VERSION,
|
|
32212
|
-
...rolledBack ? { rolledBack: true } : {}
|
|
32213
|
-
});
|
|
32214
|
-
await clearPendingUpgradeMarker(slockHome);
|
|
32186
|
+
frames.push(parsed);
|
|
32215
32187
|
}
|
|
32216
|
-
|
|
32188
|
+
return frames;
|
|
32189
|
+
}
|
|
32217
32190
|
};
|
|
32218
|
-
|
|
32219
|
-
|
|
32191
|
+
|
|
32192
|
+
// src/internal/ipc-server.ts
|
|
32193
|
+
var SUPPORTED_PROTOCOL_VERSIONS = [1];
|
|
32194
|
+
var SERVICE_VERSION = "0.0.0";
|
|
32195
|
+
function resolveTransportPath(installRoot) {
|
|
32196
|
+
return process.platform === "win32" ? serviceWindowsPipeName(installRoot) : serviceSocketPath(installRoot);
|
|
32220
32197
|
}
|
|
32221
|
-
async function
|
|
32222
|
-
|
|
32223
|
-
let
|
|
32198
|
+
async function probeAndClearStaleSocket(socketPath) {
|
|
32199
|
+
if (process.platform === "win32") return;
|
|
32200
|
+
let isSocket = false;
|
|
32224
32201
|
try {
|
|
32225
|
-
|
|
32226
|
-
|
|
32227
|
-
requestId,
|
|
32228
|
-
fromVersion: COMPUTER_VERSION,
|
|
32229
|
-
currentBinaryPath: process.execPath,
|
|
32230
|
-
nowIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
32231
|
-
deps: {
|
|
32232
|
-
onProgress: (e) => ctx.emitUpgradeProgress(seaProgressToFrame(e))
|
|
32233
|
-
}
|
|
32234
|
-
});
|
|
32202
|
+
const stats = await stat4(socketPath);
|
|
32203
|
+
isSocket = stats.isSocket();
|
|
32235
32204
|
} catch (err) {
|
|
32236
|
-
|
|
32205
|
+
if (isErrnoException(err) && err.code === "ENOENT") return;
|
|
32237
32206
|
return;
|
|
32238
32207
|
}
|
|
32239
|
-
if (!
|
|
32240
|
-
ctx.emitUpgradeDone({ ok: false, error: result.reason ?? result.failedPhase ?? "upgrade_failed" });
|
|
32208
|
+
if (!isSocket) {
|
|
32241
32209
|
return;
|
|
32242
32210
|
}
|
|
32243
|
-
|
|
32244
|
-
|
|
32211
|
+
const outcome = await new Promise((resolve2) => {
|
|
32212
|
+
const probe = connect({ path: socketPath });
|
|
32213
|
+
const finalize = (o) => {
|
|
32214
|
+
probe.removeAllListeners();
|
|
32215
|
+
probe.destroy();
|
|
32216
|
+
resolve2(o);
|
|
32217
|
+
};
|
|
32218
|
+
const timer = setTimeout(() => finalize("indeterminate"), 500);
|
|
32219
|
+
probe.once("connect", () => {
|
|
32220
|
+
clearTimeout(timer);
|
|
32221
|
+
finalize("connected");
|
|
32222
|
+
});
|
|
32223
|
+
probe.once("error", (err) => {
|
|
32224
|
+
clearTimeout(timer);
|
|
32225
|
+
finalize(err.code === "ECONNREFUSED" ? "refused" : "indeterminate");
|
|
32226
|
+
});
|
|
32227
|
+
});
|
|
32228
|
+
if (outcome !== "refused") {
|
|
32245
32229
|
return;
|
|
32246
32230
|
}
|
|
32247
|
-
|
|
32248
|
-
|
|
32249
|
-
|
|
32250
|
-
|
|
32251
|
-
|
|
32252
|
-
|
|
32253
|
-
return "crash";
|
|
32231
|
+
try {
|
|
32232
|
+
await unlink5(socketPath);
|
|
32233
|
+
} catch (err) {
|
|
32234
|
+
if (isErrnoException(err) && err.code === "ENOENT") return;
|
|
32235
|
+
throw err;
|
|
32236
|
+
}
|
|
32254
32237
|
}
|
|
32255
|
-
|
|
32256
|
-
|
|
32257
|
-
const
|
|
32258
|
-
const
|
|
32259
|
-
|
|
32260
|
-
|
|
32261
|
-
|
|
32262
|
-
|
|
32263
|
-
|
|
32238
|
+
function createIpcServer(options) {
|
|
32239
|
+
const { installRoot, handlers } = options;
|
|
32240
|
+
const transportPath = resolveTransportPath(installRoot);
|
|
32241
|
+
const connections = /* @__PURE__ */ new Set();
|
|
32242
|
+
let server = null;
|
|
32243
|
+
let closed = false;
|
|
32244
|
+
function dispatchFrame(conn, frame) {
|
|
32245
|
+
if (!isRecord(frame) || typeof frame.type !== "string") {
|
|
32246
|
+
writeError(conn, null, "IPC_MALFORMED_FRAME", "frame is missing required `type` field");
|
|
32247
|
+
conn.socket.destroy();
|
|
32248
|
+
return;
|
|
32249
|
+
}
|
|
32250
|
+
if (!conn.handshakeDone) {
|
|
32251
|
+
handleHello(conn, frame);
|
|
32252
|
+
return;
|
|
32253
|
+
}
|
|
32254
|
+
if (frame.type === "request") {
|
|
32255
|
+
void handleRequest(conn, frame);
|
|
32256
|
+
return;
|
|
32257
|
+
}
|
|
32258
|
+
if (frame.type === "ping") {
|
|
32259
|
+
conn.socket.write(encodeFrame({ type: "pong" }));
|
|
32260
|
+
return;
|
|
32261
|
+
}
|
|
32262
|
+
if (frame.type === "pong") {
|
|
32263
|
+
return;
|
|
32264
|
+
}
|
|
32264
32265
|
}
|
|
32265
|
-
|
|
32266
|
-
|
|
32267
|
-
|
|
32268
|
-
|
|
32269
|
-
|
|
32270
|
-
|
|
32271
|
-
|
|
32272
|
-
|
|
32273
|
-
|
|
32274
|
-
|
|
32266
|
+
function handleHello(conn, frame) {
|
|
32267
|
+
if (frame.type !== "hello") {
|
|
32268
|
+
conn.socket.write(encodeFrame({
|
|
32269
|
+
type: "hello-reject",
|
|
32270
|
+
reason: "IPC_PROTOCOL_HANDSHAKE_FAILED",
|
|
32271
|
+
message: `expected hello, got \`${String(frame.type)}\``
|
|
32272
|
+
}));
|
|
32273
|
+
conn.socket.destroy();
|
|
32274
|
+
return;
|
|
32275
|
+
}
|
|
32276
|
+
const requested = Number(frame.protocolVersion);
|
|
32277
|
+
if (!SUPPORTED_PROTOCOL_VERSIONS.includes(requested)) {
|
|
32278
|
+
conn.socket.write(encodeFrame({
|
|
32279
|
+
type: "hello-reject",
|
|
32280
|
+
reason: "IPC_PROTOCOL_VERSION_UNSUPPORTED",
|
|
32281
|
+
supported: [...SUPPORTED_PROTOCOL_VERSIONS]
|
|
32282
|
+
}));
|
|
32283
|
+
conn.socket.destroy();
|
|
32284
|
+
return;
|
|
32285
|
+
}
|
|
32286
|
+
conn.handshakeDone = true;
|
|
32287
|
+
conn.socket.write(encodeFrame({
|
|
32288
|
+
type: "hello-ack",
|
|
32289
|
+
protocolVersion: requested,
|
|
32290
|
+
serviceVersion: SERVICE_VERSION
|
|
32291
|
+
}));
|
|
32292
|
+
}
|
|
32293
|
+
async function handleRequest(conn, frame) {
|
|
32294
|
+
const id = typeof frame.id === "string" ? frame.id : "";
|
|
32295
|
+
const method = typeof frame.method === "string" ? frame.method : "";
|
|
32296
|
+
if (id === "" || method === "") {
|
|
32297
|
+
writeError(conn, id || null, "IPC_MALFORMED_FRAME", "request missing `id` or `method`");
|
|
32298
|
+
return;
|
|
32299
|
+
}
|
|
32300
|
+
const handler = handlers[method];
|
|
32301
|
+
if (!handler) {
|
|
32302
|
+
writeError(conn, id, "IPC_MALFORMED_FRAME", `unknown method \`${method}\``);
|
|
32303
|
+
return;
|
|
32304
|
+
}
|
|
32275
32305
|
try {
|
|
32276
|
-
await
|
|
32277
|
-
|
|
32278
|
-
|
|
32306
|
+
const result = await handler(
|
|
32307
|
+
frame.params
|
|
32308
|
+
);
|
|
32309
|
+
if (conn.socket.destroyed) return;
|
|
32310
|
+
conn.socket.write(encodeFrame({ type: "response", id, result }));
|
|
32311
|
+
} catch (error) {
|
|
32312
|
+
if (conn.socket.destroyed) return;
|
|
32313
|
+
const { code, message } = normalizeHandlerError(error);
|
|
32314
|
+
writeError(conn, id, code, message);
|
|
32279
32315
|
}
|
|
32280
|
-
};
|
|
32281
|
-
process.on("SIGTERM", () => void shutdown());
|
|
32282
|
-
process.on("SIGINT", () => void shutdown());
|
|
32283
|
-
await core.start();
|
|
32284
|
-
}
|
|
32285
|
-
var RECONCILE_INTERVAL_MS = 5e3;
|
|
32286
|
-
var CHILD_RESTART_BACKOFF_MS = 2e3;
|
|
32287
|
-
function canSupervisorSpawnChild(serverId, running, restarting) {
|
|
32288
|
-
return !running.has(serverId) && !restarting.has(serverId);
|
|
32289
|
-
}
|
|
32290
|
-
function formatReadySummary(ready, serverIds, opts) {
|
|
32291
|
-
if (opts.serverId && serverIds.length === 1) {
|
|
32292
|
-
const pid = ready.get(opts.serverId);
|
|
32293
|
-
return `Daemon for server ${opts.serverLabel ?? opts.serverId} is running${pid ? ` (pid ${pid})` : ""}.`;
|
|
32294
32316
|
}
|
|
32295
|
-
|
|
32317
|
+
function writeError(conn, id, code, message) {
|
|
32318
|
+
if (conn.socket.destroyed) return;
|
|
32319
|
+
try {
|
|
32320
|
+
conn.socket.write(encodeFrame({
|
|
32321
|
+
type: "response",
|
|
32322
|
+
id: id ?? "",
|
|
32323
|
+
error: { code, message }
|
|
32324
|
+
}));
|
|
32325
|
+
} catch {
|
|
32326
|
+
}
|
|
32327
|
+
}
|
|
32328
|
+
function attachConnection(socket) {
|
|
32329
|
+
if (closed) {
|
|
32330
|
+
socket.destroy();
|
|
32331
|
+
return;
|
|
32332
|
+
}
|
|
32333
|
+
const conn = {
|
|
32334
|
+
socket,
|
|
32335
|
+
decoder: new FrameDecoder(),
|
|
32336
|
+
handshakeDone: false
|
|
32337
|
+
};
|
|
32338
|
+
connections.add(conn);
|
|
32339
|
+
socket.on("data", (chunk) => {
|
|
32340
|
+
try {
|
|
32341
|
+
conn.decoder.push(chunk);
|
|
32342
|
+
const frames = conn.decoder.drain();
|
|
32343
|
+
for (const frame of frames) dispatchFrame(conn, frame);
|
|
32344
|
+
} catch (error) {
|
|
32345
|
+
const { code, message } = normalizeHandlerError(error);
|
|
32346
|
+
writeError(conn, null, code, message);
|
|
32347
|
+
socket.destroy();
|
|
32348
|
+
}
|
|
32349
|
+
});
|
|
32350
|
+
socket.on("close", () => connections.delete(conn));
|
|
32351
|
+
socket.on("error", () => {
|
|
32352
|
+
});
|
|
32353
|
+
}
|
|
32354
|
+
return {
|
|
32355
|
+
async listen() {
|
|
32356
|
+
if (server) throw new Error("ipc-server: listen() called twice");
|
|
32357
|
+
if (process.platform !== "win32") {
|
|
32358
|
+
await mkdir10(dirname9(transportPath), { recursive: true });
|
|
32359
|
+
await probeAndClearStaleSocket(transportPath);
|
|
32360
|
+
}
|
|
32361
|
+
const s = createServer(attachConnection);
|
|
32362
|
+
server = s;
|
|
32363
|
+
await new Promise((resolve2, reject) => {
|
|
32364
|
+
const onError = (err) => {
|
|
32365
|
+
s.removeListener("error", onError);
|
|
32366
|
+
reject(err);
|
|
32367
|
+
};
|
|
32368
|
+
s.once("error", onError);
|
|
32369
|
+
s.listen(transportPath, () => {
|
|
32370
|
+
s.removeListener("error", onError);
|
|
32371
|
+
resolve2();
|
|
32372
|
+
});
|
|
32373
|
+
});
|
|
32374
|
+
if (process.platform !== "win32") {
|
|
32375
|
+
try {
|
|
32376
|
+
await chmod5(transportPath, 384);
|
|
32377
|
+
} catch {
|
|
32378
|
+
}
|
|
32379
|
+
}
|
|
32380
|
+
return transportPath;
|
|
32381
|
+
},
|
|
32382
|
+
broadcast(event) {
|
|
32383
|
+
if (closed) return;
|
|
32384
|
+
const payload = encodeFrame({ type: "event", kind: event.kind, payload: event.payload });
|
|
32385
|
+
for (const conn of connections) {
|
|
32386
|
+
if (!conn.handshakeDone || conn.socket.destroyed) continue;
|
|
32387
|
+
try {
|
|
32388
|
+
conn.socket.write(payload);
|
|
32389
|
+
} catch {
|
|
32390
|
+
}
|
|
32391
|
+
}
|
|
32392
|
+
},
|
|
32393
|
+
async close() {
|
|
32394
|
+
if (closed) return;
|
|
32395
|
+
closed = true;
|
|
32396
|
+
const s = server;
|
|
32397
|
+
server = null;
|
|
32398
|
+
for (const conn of connections) conn.socket.destroy();
|
|
32399
|
+
connections.clear();
|
|
32400
|
+
if (s) {
|
|
32401
|
+
await new Promise((resolve2) => {
|
|
32402
|
+
s.close(() => resolve2());
|
|
32403
|
+
});
|
|
32404
|
+
}
|
|
32405
|
+
}
|
|
32406
|
+
};
|
|
32407
|
+
}
|
|
32408
|
+
function normalizeHandlerError(error) {
|
|
32409
|
+
if (error instanceof ServiceClientError) {
|
|
32410
|
+
return { code: error.code, message: error.message };
|
|
32411
|
+
}
|
|
32412
|
+
const message = error instanceof Error ? error.message : "handler threw non-Error value";
|
|
32413
|
+
return { code: "IPC_MALFORMED_FRAME", message };
|
|
32414
|
+
}
|
|
32415
|
+
function isRecord(value) {
|
|
32416
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32417
|
+
}
|
|
32418
|
+
function isErrnoException(value) {
|
|
32419
|
+
return value instanceof Error && typeof value.code === "string";
|
|
32420
|
+
}
|
|
32421
|
+
|
|
32422
|
+
// src/lib/readers.ts
|
|
32423
|
+
init_esm_shims();
|
|
32424
|
+
|
|
32425
|
+
// src/status.ts
|
|
32426
|
+
init_esm_shims();
|
|
32427
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
32428
|
+
async function readUserSession(path3) {
|
|
32429
|
+
try {
|
|
32430
|
+
const parsed = JSON.parse(await readFile9(path3, "utf8"));
|
|
32431
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
32432
|
+
return { state: "present", session: parsed, error: null };
|
|
32433
|
+
}
|
|
32434
|
+
return { state: "invalid", session: null, error: "not a JSON object" };
|
|
32435
|
+
} catch (err) {
|
|
32436
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
|
|
32437
|
+
return { state: "missing", session: null, error: null };
|
|
32438
|
+
}
|
|
32439
|
+
return {
|
|
32440
|
+
state: "invalid",
|
|
32441
|
+
session: null,
|
|
32442
|
+
error: err instanceof Error ? err.message : String(err)
|
|
32443
|
+
};
|
|
32444
|
+
}
|
|
32445
|
+
}
|
|
32446
|
+
function str(v) {
|
|
32447
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
32448
|
+
}
|
|
32449
|
+
async function pidStatus(pidfile) {
|
|
32450
|
+
const pid = await readPidfileAt(pidfile);
|
|
32451
|
+
return pid !== null && isProcessAlive2(pid) ? { running: true, pid } : { running: false };
|
|
32452
|
+
}
|
|
32453
|
+
async function serviceState(slockHome) {
|
|
32454
|
+
const { pid } = await findLiveServicePidReadOnly(slockHome);
|
|
32455
|
+
return pid !== null ? { running: true, pid } : { running: false };
|
|
32456
|
+
}
|
|
32457
|
+
async function deriveHealth(slockHome, serverId, daemon) {
|
|
32458
|
+
if (!daemon.running) return "offline";
|
|
32459
|
+
if (await isDegraded(slockHome, serverId)) return "degraded";
|
|
32460
|
+
return "ok";
|
|
32461
|
+
}
|
|
32462
|
+
async function buildStatusReport(installRoot) {
|
|
32463
|
+
const sessionRead = await readUserSession(userSessionPath(installRoot));
|
|
32464
|
+
const session = sessionRead.session;
|
|
32465
|
+
const attachments = await listServerAttachments(installRoot);
|
|
32466
|
+
const service = {
|
|
32467
|
+
...await serviceState(installRoot),
|
|
32468
|
+
logPath: serviceLogPath(installRoot)
|
|
32469
|
+
};
|
|
32470
|
+
const servers = [];
|
|
32471
|
+
for (const a of attachments) {
|
|
32472
|
+
const daemon = await pidStatus(serverRunnerPidPath(installRoot, a.serverId));
|
|
32473
|
+
servers.push({
|
|
32474
|
+
serverId: a.serverId,
|
|
32475
|
+
serverSlug: a.serverSlug ?? null,
|
|
32476
|
+
serverMachineId: a.serverMachineId,
|
|
32477
|
+
serverUrl: a.serverUrl,
|
|
32478
|
+
attachedAt: a.attachedAt ?? null,
|
|
32479
|
+
serverRunnerLogPath: serverRunnerLogPath(installRoot, a.serverId),
|
|
32480
|
+
daemon,
|
|
32481
|
+
health: await deriveHealth(installRoot, a.serverId, daemon)
|
|
32482
|
+
});
|
|
32483
|
+
}
|
|
32484
|
+
const loggedIn = session?.kind === "user-session" && typeof session.accessToken === "string" && session.accessToken.length > 0;
|
|
32485
|
+
return {
|
|
32486
|
+
slockHome: installRoot,
|
|
32487
|
+
loggedIn,
|
|
32488
|
+
userId: session ? str(session.userId) : null,
|
|
32489
|
+
loginServerUrl: session ? str(session.serverUrl) : null,
|
|
32490
|
+
userSessionError: sessionRead.state === "invalid" ? sessionRead.error : null,
|
|
32491
|
+
service,
|
|
32492
|
+
servers
|
|
32493
|
+
};
|
|
32494
|
+
}
|
|
32495
|
+
function pad(s, n) {
|
|
32496
|
+
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
32497
|
+
}
|
|
32498
|
+
async function runStatus(opts) {
|
|
32499
|
+
const report = await buildStatusReport(resolveSlockHome());
|
|
32500
|
+
if (opts.json) {
|
|
32501
|
+
info(JSON.stringify(report, null, 2));
|
|
32502
|
+
return;
|
|
32503
|
+
}
|
|
32504
|
+
info("");
|
|
32505
|
+
info(`SLOCK_HOME: ${report.slockHome}`);
|
|
32506
|
+
const loginDetail = report.userSessionError ? "no \u2014 user session file is invalid; re-run `slock-computer login`" : report.loggedIn ? `yes (user ${report.userId ?? "?"})` : "no \u2014 run `slock-computer login`";
|
|
32507
|
+
info(
|
|
32508
|
+
`Logged in: ${loginDetail}`
|
|
32509
|
+
);
|
|
32510
|
+
if (report.loginServerUrl) info(`Login server: ${report.loginServerUrl}`);
|
|
32511
|
+
info(
|
|
32512
|
+
`Service: ${report.service.running ? `running (pid ${report.service.pid})` : "stopped \u2014 run `slock-computer start`"}`
|
|
32513
|
+
);
|
|
32514
|
+
info(`Service log: ${report.service.logPath}`);
|
|
32515
|
+
info("");
|
|
32516
|
+
if (report.servers.length === 0) {
|
|
32517
|
+
info("Attachments: none \u2014 run `slock-computer attach /<serverSlug>` (e.g. `/myserver`).");
|
|
32518
|
+
} else {
|
|
32519
|
+
info("Attachments:");
|
|
32520
|
+
info(` ${pad("SERVER", 24)}${pad("HEALTH", 12)}${pad("DAEMON", 24)}${pad("MACHINE", 38)}URL`);
|
|
32521
|
+
for (const s of report.servers) {
|
|
32522
|
+
const dcol = s.daemon.running ? `running (pid ${s.daemon.pid})` : "stopped";
|
|
32523
|
+
info(
|
|
32524
|
+
` ${pad(formatServerSlugDisplay(s.serverSlug), 24)}${pad(s.health, 12)}${pad(dcol, 24)}${pad(s.serverMachineId, 38)}${s.serverUrl}`
|
|
32525
|
+
);
|
|
32526
|
+
info(` Server runner log: ${s.serverRunnerLogPath}`);
|
|
32527
|
+
}
|
|
32528
|
+
if (report.servers.some((s) => s.health === "degraded")) {
|
|
32529
|
+
info("");
|
|
32530
|
+
info(
|
|
32531
|
+
" Note: one or more servers are `degraded` (repeated crashes; auto-restart paused)."
|
|
32532
|
+
);
|
|
32533
|
+
info(
|
|
32534
|
+
" Run `slock-computer doctor /<serverSlug> --reset-health` (e.g. `/myserver`) after fixing the underlying issue."
|
|
32535
|
+
);
|
|
32536
|
+
}
|
|
32537
|
+
}
|
|
32538
|
+
info("");
|
|
32539
|
+
}
|
|
32540
|
+
|
|
32541
|
+
// src/lib/readers.ts
|
|
32542
|
+
async function readServiceStatus(installRoot) {
|
|
32543
|
+
return buildStatusReport(installRoot);
|
|
32544
|
+
}
|
|
32545
|
+
async function readRunnerStatus(installRoot, serverId) {
|
|
32546
|
+
const report = await buildStatusReport(installRoot);
|
|
32547
|
+
const server = report.servers.find((s) => s.serverId === serverId);
|
|
32548
|
+
if (!server) {
|
|
32549
|
+
throw new StateReaderError(
|
|
32550
|
+
"NOT_ATTACHED",
|
|
32551
|
+
`Server ${serverId} is not attached to this Computer.`
|
|
32552
|
+
);
|
|
32553
|
+
}
|
|
32554
|
+
const attachment = await readServerAttachment(installRoot, serverId);
|
|
32555
|
+
if (!attachment) {
|
|
32556
|
+
throw new StateReaderError(
|
|
32557
|
+
"INVALID_ATTACHMENT",
|
|
32558
|
+
`Attachment for server ${serverId} is missing or invalid.`
|
|
32559
|
+
);
|
|
32560
|
+
}
|
|
32561
|
+
const client = new RunnersClient(attachment.serverUrl, attachment.apiKey);
|
|
32562
|
+
const result = await client.list();
|
|
32563
|
+
if (result.status === "success") {
|
|
32564
|
+
return { status: "ok", server, whitelist: result.whitelist, runners: result.runners };
|
|
32565
|
+
}
|
|
32566
|
+
if (result.status === "unauthorized") {
|
|
32567
|
+
return { status: "unauthorized", server };
|
|
32568
|
+
}
|
|
32569
|
+
return { status: "error", server, code: result.code };
|
|
32570
|
+
}
|
|
32571
|
+
async function listRunners(installRoot, opts = {}) {
|
|
32572
|
+
const all = await listServerAttachments(installRoot);
|
|
32573
|
+
let subset = all;
|
|
32574
|
+
if (opts.serverId !== void 0) {
|
|
32575
|
+
const found = all.find((a) => a.serverId === opts.serverId);
|
|
32576
|
+
if (!found) {
|
|
32577
|
+
throw new StateReaderError(
|
|
32578
|
+
"NOT_ATTACHED",
|
|
32579
|
+
`Server ${opts.serverId} is not attached to this Computer.`
|
|
32580
|
+
);
|
|
32581
|
+
}
|
|
32582
|
+
subset = [found];
|
|
32583
|
+
}
|
|
32584
|
+
const servers = [];
|
|
32585
|
+
for (const a of subset) {
|
|
32586
|
+
const client = new RunnersClient(a.serverUrl, a.apiKey);
|
|
32587
|
+
const result = await client.list();
|
|
32588
|
+
const idCols = { serverId: a.serverId, serverSlug: a.serverSlug ?? null };
|
|
32589
|
+
if (result.status === "success") {
|
|
32590
|
+
servers.push({
|
|
32591
|
+
...idCols,
|
|
32592
|
+
status: "ok",
|
|
32593
|
+
whitelist: result.whitelist,
|
|
32594
|
+
runners: result.runners
|
|
32595
|
+
});
|
|
32596
|
+
} else if (result.status === "unauthorized") {
|
|
32597
|
+
servers.push({ ...idCols, status: "unauthorized" });
|
|
32598
|
+
} else {
|
|
32599
|
+
servers.push({ ...idCols, status: "error", code: result.code });
|
|
32600
|
+
}
|
|
32601
|
+
}
|
|
32602
|
+
return { servers };
|
|
32603
|
+
}
|
|
32604
|
+
|
|
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) {
|
|
32632
|
+
try {
|
|
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 };
|
|
32640
|
+
} catch {
|
|
32641
|
+
return { ...DEFAULT_STATE };
|
|
32642
|
+
}
|
|
32643
|
+
}
|
|
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 });
|
|
32648
|
+
}
|
|
32649
|
+
function isCrashEntry(value) {
|
|
32650
|
+
if (!value || typeof value !== "object") return false;
|
|
32651
|
+
const obj = value;
|
|
32652
|
+
return typeof obj.at === "string";
|
|
32653
|
+
}
|
|
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
|
+
};
|
|
32662
|
+
try {
|
|
32663
|
+
const path3 = serviceLogPath(slockHome);
|
|
32664
|
+
await mkdir11(dirname10(path3), { recursive: true });
|
|
32665
|
+
await appendFile3(path3, JSON.stringify(entry) + "\n");
|
|
32666
|
+
} catch {
|
|
32667
|
+
}
|
|
32668
|
+
}
|
|
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) {
|
|
32690
|
+
try {
|
|
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(", ")}.`
|
|
32724
|
+
);
|
|
32725
|
+
}
|
|
32726
|
+
return found.serverId;
|
|
32727
|
+
}
|
|
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);
|
|
32793
|
+
}
|
|
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);
|
|
32807
|
+
}
|
|
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.`
|
|
33168
|
+
);
|
|
33169
|
+
},
|
|
33170
|
+
// Blip-stitch: on every (re)connect, if THIS process booted from a freshly
|
|
33171
|
+
// swapped binary (a pending-upgrade marker is present), report the upgrade
|
|
33172
|
+
// done with the version we actually are now, then clear the marker.
|
|
33173
|
+
onComputerUpgradeReconcile: async (emitDone) => {
|
|
33174
|
+
const slockHome = resolveSlockHome();
|
|
33175
|
+
const marker = await readPendingUpgradeMarker(slockHome);
|
|
33176
|
+
if (!marker) return;
|
|
33177
|
+
const rolledBack = COMPUTER_VERSION !== marker.targetVersion;
|
|
33178
|
+
emitDone({
|
|
33179
|
+
requestId: marker.requestId,
|
|
33180
|
+
ok: !rolledBack,
|
|
33181
|
+
newVersion: COMPUTER_VERSION,
|
|
33182
|
+
...rolledBack ? { rolledBack: true } : {}
|
|
33183
|
+
});
|
|
33184
|
+
await clearPendingUpgradeMarker(slockHome);
|
|
33185
|
+
}
|
|
33186
|
+
});
|
|
33187
|
+
};
|
|
33188
|
+
function seaProgressToFrame(e) {
|
|
33189
|
+
return { phase: e.phase, message: e.message, percent: e.percent };
|
|
33190
|
+
}
|
|
33191
|
+
async function runManagedSeaUpgrade(requestId, ctx) {
|
|
33192
|
+
const slockHome = resolveSlockHome();
|
|
33193
|
+
let result;
|
|
33194
|
+
try {
|
|
33195
|
+
result = await resolveAndRunSeaUpgrade({
|
|
33196
|
+
slockHome,
|
|
33197
|
+
requestId,
|
|
33198
|
+
fromVersion: COMPUTER_VERSION,
|
|
33199
|
+
currentBinaryPath: process.execPath,
|
|
33200
|
+
nowIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
33201
|
+
deps: {
|
|
33202
|
+
onProgress: (e) => ctx.emitUpgradeProgress(seaProgressToFrame(e))
|
|
33203
|
+
}
|
|
33204
|
+
});
|
|
33205
|
+
} catch (err) {
|
|
33206
|
+
ctx.emitUpgradeDone({ ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
33207
|
+
return;
|
|
33208
|
+
}
|
|
33209
|
+
if (!result.ok) {
|
|
33210
|
+
ctx.emitUpgradeDone({ ok: false, error: result.reason ?? result.failedPhase ?? "upgrade_failed" });
|
|
33211
|
+
return;
|
|
33212
|
+
}
|
|
33213
|
+
if (result.alreadyCurrent) {
|
|
33214
|
+
ctx.emitUpgradeDone({ ok: true, newVersion: COMPUTER_VERSION });
|
|
33215
|
+
return;
|
|
33216
|
+
}
|
|
33217
|
+
process.kill(process.pid, "SIGTERM");
|
|
33218
|
+
}
|
|
33219
|
+
function classifyRunnerExit(code, signal) {
|
|
33220
|
+
if (code === EX_CONFIG_EXIT_CODE) return "config-error";
|
|
33221
|
+
if (signal === "SIGTERM" || signal === "SIGINT") return "graceful";
|
|
33222
|
+
if (code === 0) return "graceful";
|
|
33223
|
+
return "crash";
|
|
33224
|
+
}
|
|
33225
|
+
async function runResident(serverId, deps = {}) {
|
|
33226
|
+
assertValidServerId(serverId);
|
|
33227
|
+
const slockHome = resolveSlockHome();
|
|
33228
|
+
const a = await readServerAttachment(slockHome, serverId);
|
|
33229
|
+
if (!a) {
|
|
33230
|
+
fail(
|
|
33231
|
+
"NO_ATTACHMENT",
|
|
33232
|
+
`No attachment for server ${serverId}. Run \`slock-computer attach ${serverId}\` first.`
|
|
33233
|
+
);
|
|
33234
|
+
}
|
|
33235
|
+
const core = await (deps.coreFactory ?? defaultCoreFactory)({
|
|
33236
|
+
serverId: a.serverId,
|
|
33237
|
+
serverMachineId: a.serverMachineId,
|
|
33238
|
+
apiKey: a.apiKey,
|
|
33239
|
+
serverUrl: a.serverUrl
|
|
33240
|
+
});
|
|
33241
|
+
let stopping = false;
|
|
33242
|
+
const shutdown = async () => {
|
|
33243
|
+
if (stopping) return;
|
|
33244
|
+
stopping = true;
|
|
33245
|
+
try {
|
|
33246
|
+
await core.stop();
|
|
33247
|
+
} finally {
|
|
33248
|
+
process.exit(0);
|
|
33249
|
+
}
|
|
33250
|
+
};
|
|
33251
|
+
process.on("SIGTERM", () => void shutdown());
|
|
33252
|
+
process.on("SIGINT", () => void shutdown());
|
|
33253
|
+
await core.start();
|
|
33254
|
+
}
|
|
33255
|
+
var RECONCILE_INTERVAL_MS = 5e3;
|
|
33256
|
+
var CHILD_RESTART_BACKOFF_MS = 2e3;
|
|
33257
|
+
function formatReadySummary(ready, serverIds, opts) {
|
|
33258
|
+
if (opts.serverId && serverIds.length === 1) {
|
|
33259
|
+
const pid = ready.get(opts.serverId);
|
|
33260
|
+
return `Daemon for server ${opts.serverLabel ?? opts.serverId} is running${pid ? ` (pid ${pid})` : ""}.`;
|
|
33261
|
+
}
|
|
33262
|
+
return `Daemons for ${serverIds.length} managed server(s) are running.`;
|
|
32296
33263
|
}
|
|
32297
33264
|
async function runServiceStartupRecovery(slockHome) {
|
|
32298
33265
|
const parentHoldsLock = process.env[PARENT_LOCK_HELD_ENV_VAR] === "1";
|
|
@@ -32327,10 +33294,10 @@ async function runServiceStartupRecovery(slockHome) {
|
|
|
32327
33294
|
}
|
|
32328
33295
|
async function resolveServiceIdentity() {
|
|
32329
33296
|
const here = fileURLToPath2(import.meta.url);
|
|
32330
|
-
const installRoot =
|
|
33297
|
+
const installRoot = dirname11(dirname11(here));
|
|
32331
33298
|
let version = null;
|
|
32332
33299
|
try {
|
|
32333
|
-
const raw = await
|
|
33300
|
+
const raw = await readFile11(joinPath(installRoot, "package.json"), "utf8");
|
|
32334
33301
|
const parsed = JSON.parse(raw);
|
|
32335
33302
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
32336
33303
|
version = parsed.version;
|
|
@@ -32353,7 +33320,7 @@ async function writeServiceVersionEvidence(slockHome) {
|
|
|
32353
33320
|
};
|
|
32354
33321
|
const dest = serviceVersionPath(slockHome);
|
|
32355
33322
|
const tmp = `${dest}.tmp`;
|
|
32356
|
-
await
|
|
33323
|
+
await writeFile10(tmp, JSON.stringify(payload) + "\n", { mode: 384 });
|
|
32357
33324
|
await rename3(tmp, dest);
|
|
32358
33325
|
} catch (err) {
|
|
32359
33326
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -32363,19 +33330,75 @@ async function writeServiceVersionEvidence(slockHome) {
|
|
|
32363
33330
|
);
|
|
32364
33331
|
}
|
|
32365
33332
|
}
|
|
33333
|
+
async function startServiceIpcSeam(slockHome, mutations) {
|
|
33334
|
+
const handlers = {
|
|
33335
|
+
"service-status": async () => readServiceStatus(slockHome),
|
|
33336
|
+
"runner-status": async ({ serverId }) => {
|
|
33337
|
+
try {
|
|
33338
|
+
return await readRunnerStatus(slockHome, serverId);
|
|
33339
|
+
} catch (err) {
|
|
33340
|
+
if (err instanceof StateReaderError) {
|
|
33341
|
+
throw new ServiceClientError("IPC_MALFORMED_FRAME", `${err.code}: ${err.message}`);
|
|
33342
|
+
}
|
|
33343
|
+
throw err;
|
|
33344
|
+
}
|
|
33345
|
+
},
|
|
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
|
+
}
|
|
33358
|
+
};
|
|
33359
|
+
const ipc = createIpcServer({ installRoot: slockHome, handlers });
|
|
33360
|
+
try {
|
|
33361
|
+
const transportPath = await ipc.listen();
|
|
33362
|
+
process.stderr.write(`Service: IPC seam listening at ${transportPath}
|
|
33363
|
+
`);
|
|
33364
|
+
} catch (err) {
|
|
33365
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33366
|
+
process.stderr.write(
|
|
33367
|
+
`Service: IPC seam failed to bind (${msg}); continuing without typed-RPC surface.
|
|
33368
|
+
`
|
|
33369
|
+
);
|
|
33370
|
+
}
|
|
33371
|
+
return ipc;
|
|
33372
|
+
}
|
|
32366
33373
|
async function runService() {
|
|
32367
33374
|
const slockHome = resolveSlockHome();
|
|
32368
|
-
await
|
|
33375
|
+
await mkdir12(serviceRunDir(slockHome), { recursive: true });
|
|
32369
33376
|
await runServiceStartupRecovery(slockHome);
|
|
32370
33377
|
await writePidfileAt(servicePidPath(slockHome), process.pid);
|
|
32371
33378
|
await writeServiceVersionEvidence(slockHome);
|
|
32372
|
-
const
|
|
32373
|
-
|
|
33379
|
+
const runners2 = /* @__PURE__ */ new Map();
|
|
33380
|
+
let shuttingDown = false;
|
|
33381
|
+
let inFlightUpgrade = null;
|
|
32374
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
|
+
};
|
|
32375
33388
|
const spawnChild = async (serverId) => {
|
|
32376
|
-
if (
|
|
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);
|
|
32377
33400
|
const logPath = serverRunnerLogPath(slockHome, serverId);
|
|
32378
|
-
await
|
|
33401
|
+
await mkdir12(dirname11(logPath), { recursive: true });
|
|
32379
33402
|
const logFd = await open(logPath, "a");
|
|
32380
33403
|
const { command, args } = buildResidentSpawn("__run", serverId);
|
|
32381
33404
|
const child = spawn2(command, args, {
|
|
@@ -32384,87 +33407,159 @@ async function runService() {
|
|
|
32384
33407
|
env: childEnv
|
|
32385
33408
|
});
|
|
32386
33409
|
await logFd.close();
|
|
32387
|
-
if (!child.pid)
|
|
32388
|
-
|
|
32389
|
-
|
|
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);
|
|
32390
33420
|
await writePidfileAt(serverRunnerPidPath(slockHome, serverId), child.pid);
|
|
32391
33421
|
child.on("exit", (code, signal) => {
|
|
32392
33422
|
void (async () => {
|
|
32393
|
-
|
|
33423
|
+
const rec = runners2.get(serverId);
|
|
33424
|
+
if (!rec) return;
|
|
33425
|
+
rec.child = void 0;
|
|
32394
33426
|
await clearPidfileAt(serverRunnerPidPath(slockHome, serverId));
|
|
32395
|
-
if (
|
|
32396
|
-
|
|
32397
|
-
|
|
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") {
|
|
32398
33436
|
try {
|
|
32399
33437
|
await markFatalConfig(slockHome, serverId, code, signal);
|
|
32400
33438
|
} catch {
|
|
32401
33439
|
}
|
|
33440
|
+
const prev2 = rec.lifecycle;
|
|
33441
|
+
rec.lifecycle = "degraded";
|
|
33442
|
+
emitTransition(serverId, prev2, "degraded", RUNNER_TRIGGER.exitConfigError);
|
|
32402
33443
|
process.stderr.write(
|
|
32403
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.
|
|
32404
33445
|
`
|
|
32405
33446
|
);
|
|
32406
33447
|
return;
|
|
32407
33448
|
}
|
|
32408
|
-
if (
|
|
32409
|
-
restarting.add(serverId);
|
|
33449
|
+
if (exitClass === "crash") {
|
|
32410
33450
|
try {
|
|
32411
|
-
await
|
|
32412
|
-
|
|
32413
|
-
await spawnChild(serverId);
|
|
32414
|
-
}
|
|
32415
|
-
} finally {
|
|
32416
|
-
restarting.delete(serverId);
|
|
33451
|
+
await recordCrash(slockHome, serverId, code, signal);
|
|
33452
|
+
} catch {
|
|
32417
33453
|
}
|
|
32418
|
-
return;
|
|
32419
33454
|
}
|
|
32420
|
-
|
|
32421
|
-
|
|
32422
|
-
|
|
32423
|
-
|
|
32424
|
-
|
|
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") {
|
|
32425
33461
|
process.stderr.write(
|
|
32426
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.
|
|
32427
33463
|
`
|
|
32428
33464
|
);
|
|
32429
33465
|
return;
|
|
32430
33466
|
}
|
|
32431
|
-
|
|
32432
|
-
|
|
32433
|
-
await new Promise((r) => setTimeout(r, CHILD_RESTART_BACKOFF_MS));
|
|
32434
|
-
if (await readServerAttachment(slockHome, serverId) && !shuttingDown) {
|
|
32435
|
-
await spawnChild(serverId);
|
|
32436
|
-
}
|
|
32437
|
-
} finally {
|
|
32438
|
-
restarting.delete(serverId);
|
|
32439
|
-
}
|
|
33467
|
+
rec.backoffUntil = Date.now() + CHILD_RESTART_BACKOFF_MS;
|
|
33468
|
+
scheduleReconcile(CHILD_RESTART_BACKOFF_MS);
|
|
32440
33469
|
})();
|
|
32441
33470
|
});
|
|
32442
33471
|
};
|
|
32443
|
-
const killChild = (
|
|
32444
|
-
|
|
33472
|
+
const killChild = (rec) => {
|
|
33473
|
+
rec.stopping = true;
|
|
32445
33474
|
try {
|
|
32446
|
-
|
|
33475
|
+
rec.child?.kill("SIGTERM");
|
|
32447
33476
|
} catch {
|
|
32448
33477
|
}
|
|
32449
33478
|
};
|
|
32450
33479
|
const reconcile = async () => {
|
|
33480
|
+
if (shuttingDown) return;
|
|
32451
33481
|
const wanted = new Set(await listManagedServerIds(slockHome));
|
|
33482
|
+
const now = Date.now();
|
|
32452
33483
|
for (const id of wanted) {
|
|
32453
|
-
if (
|
|
33484
|
+
if (canSpawn(runners2.get(id), true, now)) await spawnChild(id);
|
|
32454
33485
|
}
|
|
32455
|
-
for (const [id,
|
|
32456
|
-
if (!wanted.has(id)) killChild(
|
|
33486
|
+
for (const [id, rec] of runners2) {
|
|
33487
|
+
if (!wanted.has(id) && rec.child) killChild(rec);
|
|
32457
33488
|
}
|
|
32458
33489
|
};
|
|
32459
|
-
|
|
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
|
+
});
|
|
32460
33551
|
const shutdown = () => {
|
|
32461
33552
|
if (shuttingDown) return;
|
|
32462
33553
|
shuttingDown = true;
|
|
32463
|
-
for (const
|
|
33554
|
+
for (const rec of runners2.values()) if (rec.child) killChild(rec);
|
|
33555
|
+
void ipc.close();
|
|
32464
33556
|
void clearPidfileAt(servicePidPath(slockHome)).then(() => process.exit(0));
|
|
32465
33557
|
};
|
|
32466
33558
|
process.on("SIGTERM", shutdown);
|
|
32467
33559
|
process.on("SIGINT", shutdown);
|
|
33560
|
+
for (const id of await listManagedServerIds(slockHome)) {
|
|
33561
|
+
runners2.set(id, rehydrateRunnerRecord(id, await isDegraded(slockHome, id)));
|
|
33562
|
+
}
|
|
32468
33563
|
await reconcile();
|
|
32469
33564
|
const timer = setInterval(() => {
|
|
32470
33565
|
void reconcile();
|
|
@@ -32586,7 +33681,7 @@ async function runDetach(serverId, serverLabel = serverId) {
|
|
|
32586
33681
|
var USER_SESSION_EXPIRY_LEEWAY_MS = 3e4;
|
|
32587
33682
|
async function readUserSessionAuth(slockHome) {
|
|
32588
33683
|
try {
|
|
32589
|
-
const parsed = JSON.parse(await
|
|
33684
|
+
const parsed = JSON.parse(await readFile12(userSessionPath(slockHome), "utf8"));
|
|
32590
33685
|
return {
|
|
32591
33686
|
accessToken: typeof parsed.accessToken === "string" ? parsed.accessToken : "",
|
|
32592
33687
|
...typeof parsed.serverUrl === "string" ? { serverUrl: parsed.serverUrl } : {}
|
|
@@ -32597,7 +33692,7 @@ async function readUserSessionAuth(slockHome) {
|
|
|
32597
33692
|
}
|
|
32598
33693
|
async function hasValidUserSession(slockHome) {
|
|
32599
33694
|
try {
|
|
32600
|
-
const parsed = JSON.parse(await
|
|
33695
|
+
const parsed = JSON.parse(await readFile12(userSessionPath(slockHome), "utf8"));
|
|
32601
33696
|
return parsed.kind === "user-session" && typeof parsed.accessToken === "string" && parsed.accessToken.length > 0 && !isJwtExpired(parsed.accessToken);
|
|
32602
33697
|
} catch {
|
|
32603
33698
|
return false;
|
|
@@ -32617,7 +33712,7 @@ async function refreshUserSession(slockHome, serverUrl) {
|
|
|
32617
33712
|
const file = userSessionPath(slockHome);
|
|
32618
33713
|
let session;
|
|
32619
33714
|
try {
|
|
32620
|
-
session = JSON.parse(await
|
|
33715
|
+
session = JSON.parse(await readFile12(file, "utf8"));
|
|
32621
33716
|
} catch {
|
|
32622
33717
|
return false;
|
|
32623
33718
|
}
|
|
@@ -32636,8 +33731,8 @@ async function refreshUserSession(slockHome, serverUrl) {
|
|
|
32636
33731
|
if (res.status !== 200 || typeof body?.accessToken !== "string" || typeof body.refreshToken !== "string") {
|
|
32637
33732
|
return false;
|
|
32638
33733
|
}
|
|
32639
|
-
await
|
|
32640
|
-
await
|
|
33734
|
+
await mkdir13(dirname12(file), { recursive: true });
|
|
33735
|
+
await writeFile11(
|
|
32641
33736
|
tmpFile,
|
|
32642
33737
|
JSON.stringify(
|
|
32643
33738
|
{
|
|
@@ -32654,7 +33749,7 @@ async function refreshUserSession(slockHome, serverUrl) {
|
|
|
32654
33749
|
),
|
|
32655
33750
|
{ mode: 384 }
|
|
32656
33751
|
);
|
|
32657
|
-
await
|
|
33752
|
+
await chmod6(tmpFile, 384);
|
|
32658
33753
|
await rename4(tmpFile, file);
|
|
32659
33754
|
return true;
|
|
32660
33755
|
} catch {
|
|
@@ -32923,276 +34018,49 @@ async function runSetup(opts, deps = {}) {
|
|
|
32923
34018
|
if (!validation.ok) {
|
|
32924
34019
|
info(
|
|
32925
34020
|
`Migration: ${validation.code} \u2014 ${manualPathErrorMessage(validation.code, selection.path)} Returning to picker.`
|
|
32926
|
-
);
|
|
32927
|
-
continue pickerLoop;
|
|
32928
|
-
}
|
|
32929
|
-
await runRosterAdoption(opts, validation.candidate, adoptLegacyByFingerprint2);
|
|
32930
|
-
migrated = true;
|
|
32931
|
-
break pickerLoop;
|
|
32932
|
-
} else {
|
|
32933
|
-
info("Migration: fresh attach selected.");
|
|
32934
|
-
break pickerLoop;
|
|
32935
|
-
}
|
|
32936
|
-
}
|
|
32937
|
-
}
|
|
32938
|
-
}
|
|
32939
|
-
if (!migrated) {
|
|
32940
|
-
await attach2({
|
|
32941
|
-
serverSlug: opts.serverSlug,
|
|
32942
|
-
serverUrl: opts.serverUrl,
|
|
32943
|
-
name: opts.name,
|
|
32944
|
-
run: false,
|
|
32945
|
-
orchestrated: true
|
|
32946
|
-
});
|
|
32947
|
-
}
|
|
32948
|
-
attachment = await resolveAttachedServerSlug(slockHome, opts.serverSlug);
|
|
32949
|
-
if (!attachment) {
|
|
32950
|
-
fail(
|
|
32951
|
-
"SETUP_ATTACHMENT_MISSING",
|
|
32952
|
-
`Setup did not produce a local attachment for ${label}. Re-run \`slock-computer attach ${label}\`.`
|
|
32953
|
-
);
|
|
32954
|
-
}
|
|
32955
|
-
}
|
|
32956
|
-
if (opts.start === false) {
|
|
32957
|
-
info(`Start: skipped (--no-start). Run \`slock-computer start ${label}\` when ready.`);
|
|
32958
|
-
return;
|
|
32959
|
-
}
|
|
32960
|
-
await start2({
|
|
32961
|
-
serverId: attachment.serverId,
|
|
32962
|
-
serverLabel: opts.serverSlug,
|
|
32963
|
-
foreground: opts.foreground
|
|
32964
|
-
});
|
|
32965
|
-
}
|
|
32966
|
-
|
|
32967
|
-
// src/status.ts
|
|
32968
|
-
init_esm_shims();
|
|
32969
|
-
import { readFile as readFile11 } from "fs/promises";
|
|
32970
|
-
async function readUserSession(path3) {
|
|
32971
|
-
try {
|
|
32972
|
-
const parsed = JSON.parse(await readFile11(path3, "utf8"));
|
|
32973
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
32974
|
-
return { state: "present", session: parsed, error: null };
|
|
32975
|
-
}
|
|
32976
|
-
return { state: "invalid", session: null, error: "not a JSON object" };
|
|
32977
|
-
} catch (err) {
|
|
32978
|
-
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
|
|
32979
|
-
return { state: "missing", session: null, error: null };
|
|
32980
|
-
}
|
|
32981
|
-
return {
|
|
32982
|
-
state: "invalid",
|
|
32983
|
-
session: null,
|
|
32984
|
-
error: err instanceof Error ? err.message : String(err)
|
|
32985
|
-
};
|
|
32986
|
-
}
|
|
32987
|
-
}
|
|
32988
|
-
function str(v) {
|
|
32989
|
-
return typeof v === "string" && v.length > 0 ? v : null;
|
|
32990
|
-
}
|
|
32991
|
-
async function pidStatus(pidfile) {
|
|
32992
|
-
const pid = await readPidfileAt(pidfile);
|
|
32993
|
-
return pid !== null && isProcessAlive2(pid) ? { running: true, pid } : { running: false };
|
|
32994
|
-
}
|
|
32995
|
-
async function serviceState(slockHome) {
|
|
32996
|
-
const { pid } = await findLiveServicePidReadOnly(slockHome);
|
|
32997
|
-
return pid !== null ? { running: true, pid } : { running: false };
|
|
32998
|
-
}
|
|
32999
|
-
async function deriveHealth(slockHome, serverId, daemon) {
|
|
33000
|
-
if (!daemon.running) return "offline";
|
|
33001
|
-
if (await isDegraded(slockHome, serverId)) return "degraded";
|
|
33002
|
-
return "ok";
|
|
33003
|
-
}
|
|
33004
|
-
async function buildStatusReport(installRoot) {
|
|
33005
|
-
const sessionRead = await readUserSession(userSessionPath(installRoot));
|
|
33006
|
-
const session = sessionRead.session;
|
|
33007
|
-
const attachments = await listServerAttachments(installRoot);
|
|
33008
|
-
const service = {
|
|
33009
|
-
...await serviceState(installRoot),
|
|
33010
|
-
logPath: serviceLogPath(installRoot)
|
|
33011
|
-
};
|
|
33012
|
-
const servers = [];
|
|
33013
|
-
for (const a of attachments) {
|
|
33014
|
-
const daemon = await pidStatus(serverRunnerPidPath(installRoot, a.serverId));
|
|
33015
|
-
servers.push({
|
|
33016
|
-
serverId: a.serverId,
|
|
33017
|
-
serverSlug: a.serverSlug ?? null,
|
|
33018
|
-
serverMachineId: a.serverMachineId,
|
|
33019
|
-
serverUrl: a.serverUrl,
|
|
33020
|
-
attachedAt: a.attachedAt ?? null,
|
|
33021
|
-
serverRunnerLogPath: serverRunnerLogPath(installRoot, a.serverId),
|
|
33022
|
-
daemon,
|
|
33023
|
-
health: await deriveHealth(installRoot, a.serverId, daemon)
|
|
33024
|
-
});
|
|
33025
|
-
}
|
|
33026
|
-
const loggedIn = session?.kind === "user-session" && typeof session.accessToken === "string" && session.accessToken.length > 0;
|
|
33027
|
-
return {
|
|
33028
|
-
slockHome: installRoot,
|
|
33029
|
-
loggedIn,
|
|
33030
|
-
userId: session ? str(session.userId) : null,
|
|
33031
|
-
loginServerUrl: session ? str(session.serverUrl) : null,
|
|
33032
|
-
userSessionError: sessionRead.state === "invalid" ? sessionRead.error : null,
|
|
33033
|
-
service,
|
|
33034
|
-
servers
|
|
33035
|
-
};
|
|
33036
|
-
}
|
|
33037
|
-
function pad(s, n) {
|
|
33038
|
-
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
33039
|
-
}
|
|
33040
|
-
async function runStatus(opts) {
|
|
33041
|
-
const report = await buildStatusReport(resolveSlockHome());
|
|
33042
|
-
if (opts.json) {
|
|
33043
|
-
info(JSON.stringify(report, null, 2));
|
|
33044
|
-
return;
|
|
33045
|
-
}
|
|
33046
|
-
info("");
|
|
33047
|
-
info(`SLOCK_HOME: ${report.slockHome}`);
|
|
33048
|
-
const loginDetail = report.userSessionError ? "no \u2014 user session file is invalid; re-run `slock-computer login`" : report.loggedIn ? `yes (user ${report.userId ?? "?"})` : "no \u2014 run `slock-computer login`";
|
|
33049
|
-
info(
|
|
33050
|
-
`Logged in: ${loginDetail}`
|
|
33051
|
-
);
|
|
33052
|
-
if (report.loginServerUrl) info(`Login server: ${report.loginServerUrl}`);
|
|
33053
|
-
info(
|
|
33054
|
-
`Service: ${report.service.running ? `running (pid ${report.service.pid})` : "stopped \u2014 run `slock-computer start`"}`
|
|
33055
|
-
);
|
|
33056
|
-
info(`Service log: ${report.service.logPath}`);
|
|
33057
|
-
info("");
|
|
33058
|
-
if (report.servers.length === 0) {
|
|
33059
|
-
info("Attachments: none \u2014 run `slock-computer attach /<serverSlug>` (e.g. `/myserver`).");
|
|
33060
|
-
} else {
|
|
33061
|
-
info("Attachments:");
|
|
33062
|
-
info(` ${pad("SERVER", 24)}${pad("HEALTH", 12)}${pad("DAEMON", 24)}${pad("MACHINE", 38)}URL`);
|
|
33063
|
-
for (const s of report.servers) {
|
|
33064
|
-
const dcol = s.daemon.running ? `running (pid ${s.daemon.pid})` : "stopped";
|
|
33065
|
-
info(
|
|
33066
|
-
` ${pad(formatServerSlugDisplay(s.serverSlug), 24)}${pad(s.health, 12)}${pad(dcol, 24)}${pad(s.serverMachineId, 38)}${s.serverUrl}`
|
|
33067
|
-
);
|
|
33068
|
-
info(` Server runner log: ${s.serverRunnerLogPath}`);
|
|
33069
|
-
}
|
|
33070
|
-
if (report.servers.some((s) => s.health === "degraded")) {
|
|
33071
|
-
info("");
|
|
33072
|
-
info(
|
|
33073
|
-
" Note: one or more servers are `degraded` (repeated crashes; auto-restart paused)."
|
|
33074
|
-
);
|
|
33075
|
-
info(
|
|
33076
|
-
" Run `slock-computer doctor /<serverSlug> --reset-health` (e.g. `/myserver`) after fixing the underlying issue."
|
|
33077
|
-
);
|
|
33078
|
-
}
|
|
33079
|
-
}
|
|
33080
|
-
info("");
|
|
33081
|
-
}
|
|
33082
|
-
|
|
33083
|
-
// src/runners.ts
|
|
33084
|
-
init_esm_shims();
|
|
33085
|
-
|
|
33086
|
-
// src/targetServer.ts
|
|
33087
|
-
init_esm_shims();
|
|
33088
|
-
function attachmentLabel(a) {
|
|
33089
|
-
return formatServerSlugDisplay(a.serverSlug);
|
|
33090
|
-
}
|
|
33091
|
-
async function refreshAttachmentSlug(home, attachment) {
|
|
33092
|
-
try {
|
|
33093
|
-
const client = new ComputerAttachClient(attachment.serverUrl, "");
|
|
33094
|
-
const result = await client.preflight(attachment.apiKey);
|
|
33095
|
-
if (!result.ok || !result.serverSlug || result.serverSlug === attachment.serverSlug) {
|
|
33096
|
-
return attachment;
|
|
33097
|
-
}
|
|
33098
|
-
const updated = { ...attachment, serverSlug: result.serverSlug };
|
|
33099
|
-
await writeServerAttachment(home, updated);
|
|
33100
|
-
return updated;
|
|
33101
|
-
} catch {
|
|
33102
|
-
return attachment;
|
|
33103
|
-
}
|
|
33104
|
-
}
|
|
33105
|
-
async function listAttachmentsWithFreshSlugs(home) {
|
|
33106
|
-
const attachments = await listServerAttachments(home);
|
|
33107
|
-
return await Promise.all(attachments.map((a) => refreshAttachmentSlug(home, a)));
|
|
33108
|
-
}
|
|
33109
|
-
async function resolveTargetServerId(opts) {
|
|
33110
|
-
const home = resolveSlockHome();
|
|
33111
|
-
let attachments = await listServerAttachments(home);
|
|
33112
|
-
if (attachments.length === 0) {
|
|
33113
|
-
fail("NO_ATTACHMENT", "No server attachments yet. Run `slock-computer attach /<serverSlug>` (e.g. `/myserver`) first.");
|
|
33114
|
-
}
|
|
33115
|
-
const requested = normalizeServerSlug(opts.server ?? "");
|
|
33116
|
-
if (requested) {
|
|
33117
|
-
let found = attachments.find((a) => a.serverSlug === requested);
|
|
33118
|
-
if (!found) {
|
|
33119
|
-
attachments = await listAttachmentsWithFreshSlugs(home);
|
|
33120
|
-
found = attachments.find((a) => a.serverSlug === requested);
|
|
34021
|
+
);
|
|
34022
|
+
continue pickerLoop;
|
|
34023
|
+
}
|
|
34024
|
+
await runRosterAdoption(opts, validation.candidate, adoptLegacyByFingerprint2);
|
|
34025
|
+
migrated = true;
|
|
34026
|
+
break pickerLoop;
|
|
34027
|
+
} else {
|
|
34028
|
+
info("Migration: fresh attach selected.");
|
|
34029
|
+
break pickerLoop;
|
|
34030
|
+
}
|
|
34031
|
+
}
|
|
34032
|
+
}
|
|
33121
34033
|
}
|
|
33122
|
-
if (!
|
|
33123
|
-
|
|
33124
|
-
|
|
33125
|
-
|
|
33126
|
-
|
|
34034
|
+
if (!migrated) {
|
|
34035
|
+
await attach2({
|
|
34036
|
+
serverSlug: opts.serverSlug,
|
|
34037
|
+
serverUrl: opts.serverUrl,
|
|
34038
|
+
name: opts.name,
|
|
34039
|
+
run: false,
|
|
34040
|
+
orchestrated: true
|
|
34041
|
+
});
|
|
33127
34042
|
}
|
|
33128
|
-
|
|
33129
|
-
|
|
33130
|
-
|
|
33131
|
-
|
|
33132
|
-
|
|
33133
|
-
`Multiple servers attached (${attachments.map(attachmentLabel).join(", ")}). Pass \`--server /<serverSlug>\` (e.g. \`--server ${attachmentLabel(attachments[0])}\`) to choose one.`
|
|
33134
|
-
);
|
|
33135
|
-
}
|
|
33136
|
-
async function resolveTargetAttachment(opts) {
|
|
33137
|
-
const serverId = await resolveTargetServerId(opts);
|
|
33138
|
-
const a = await readServerAttachment(resolveSlockHome(), serverId);
|
|
33139
|
-
if (!a) {
|
|
33140
|
-
const label = opts.server ?? serverId;
|
|
33141
|
-
fail("INVALID_ATTACHMENT", `Attachment for ${label} is missing/invalid. Re-run \`slock-computer attach ${label}\`.`);
|
|
33142
|
-
}
|
|
33143
|
-
return a;
|
|
33144
|
-
}
|
|
33145
|
-
|
|
33146
|
-
// src/lib/readers.ts
|
|
33147
|
-
init_esm_shims();
|
|
33148
|
-
|
|
33149
|
-
// src/lib/types.ts
|
|
33150
|
-
init_esm_shims();
|
|
33151
|
-
var StateReaderError = class extends Error {
|
|
33152
|
-
code;
|
|
33153
|
-
constructor(code, message) {
|
|
33154
|
-
super(message);
|
|
33155
|
-
this.name = "StateReaderError";
|
|
33156
|
-
this.code = code;
|
|
33157
|
-
}
|
|
33158
|
-
};
|
|
33159
|
-
|
|
33160
|
-
// src/lib/readers.ts
|
|
33161
|
-
async function listRunners(installRoot, opts = {}) {
|
|
33162
|
-
const all = await listServerAttachments(installRoot);
|
|
33163
|
-
let subset = all;
|
|
33164
|
-
if (opts.serverId !== void 0) {
|
|
33165
|
-
const found = all.find((a) => a.serverId === opts.serverId);
|
|
33166
|
-
if (!found) {
|
|
33167
|
-
throw new StateReaderError(
|
|
33168
|
-
"NOT_ATTACHED",
|
|
33169
|
-
`Server ${opts.serverId} is not attached to this Computer.`
|
|
34043
|
+
attachment = await resolveAttachedServerSlug(slockHome, opts.serverSlug);
|
|
34044
|
+
if (!attachment) {
|
|
34045
|
+
fail(
|
|
34046
|
+
"SETUP_ATTACHMENT_MISSING",
|
|
34047
|
+
`Setup did not produce a local attachment for ${label}. Re-run \`slock-computer attach ${label}\`.`
|
|
33170
34048
|
);
|
|
33171
34049
|
}
|
|
33172
|
-
subset = [found];
|
|
33173
34050
|
}
|
|
33174
|
-
|
|
33175
|
-
|
|
33176
|
-
|
|
33177
|
-
const result = await client.list();
|
|
33178
|
-
const idCols = { serverId: a.serverId, serverSlug: a.serverSlug ?? null };
|
|
33179
|
-
if (result.status === "success") {
|
|
33180
|
-
servers.push({
|
|
33181
|
-
...idCols,
|
|
33182
|
-
status: "ok",
|
|
33183
|
-
whitelist: result.whitelist,
|
|
33184
|
-
runners: result.runners
|
|
33185
|
-
});
|
|
33186
|
-
} else if (result.status === "unauthorized") {
|
|
33187
|
-
servers.push({ ...idCols, status: "unauthorized" });
|
|
33188
|
-
} else {
|
|
33189
|
-
servers.push({ ...idCols, status: "error", code: result.code });
|
|
33190
|
-
}
|
|
34051
|
+
if (opts.start === false) {
|
|
34052
|
+
info(`Start: skipped (--no-start). Run \`slock-computer start ${label}\` when ready.`);
|
|
34053
|
+
return;
|
|
33191
34054
|
}
|
|
33192
|
-
|
|
34055
|
+
await start2({
|
|
34056
|
+
serverId: attachment.serverId,
|
|
34057
|
+
serverLabel: opts.serverSlug,
|
|
34058
|
+
foreground: opts.foreground
|
|
34059
|
+
});
|
|
33193
34060
|
}
|
|
33194
34061
|
|
|
33195
34062
|
// src/runners.ts
|
|
34063
|
+
init_esm_shims();
|
|
33196
34064
|
async function runRunnersList(opts) {
|
|
33197
34065
|
const serverId = await resolveTargetServerId({ server: opts.server });
|
|
33198
34066
|
const installRoot = resolveSlockHome();
|
|
@@ -33397,14 +34265,14 @@ async function runDoctor(opts) {
|
|
|
33397
34265
|
|
|
33398
34266
|
// src/logs.ts
|
|
33399
34267
|
init_esm_shims();
|
|
33400
|
-
import { readFile as
|
|
34268
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
33401
34269
|
var DEFAULT_LINES = 200;
|
|
33402
34270
|
async function runLogs(opts) {
|
|
33403
34271
|
const home = resolveSlockHome();
|
|
33404
34272
|
const file = opts.service ? serviceLogPath(home) : serverRunnerLogPath(home, await resolveTargetServerId({ server: opts.server }));
|
|
33405
34273
|
let content;
|
|
33406
34274
|
try {
|
|
33407
|
-
content = await
|
|
34275
|
+
content = await readFile13(file, "utf8");
|
|
33408
34276
|
} catch {
|
|
33409
34277
|
fail(
|
|
33410
34278
|
"NO_DAEMON_LOG",
|
|
@@ -33418,166 +34286,16 @@ async function runLogs(opts) {
|
|
|
33418
34286
|
for (const line of tail) info(redactSecrets(line));
|
|
33419
34287
|
}
|
|
33420
34288
|
|
|
33421
|
-
// src/reset.ts
|
|
33422
|
-
init_esm_shims();
|
|
33423
|
-
|
|
33424
|
-
// src/serviceState.ts
|
|
33425
|
-
init_esm_shims();
|
|
33426
|
-
import { mkdir as mkdir12, readFile as readFile13, writeFile as writeFile11, appendFile as appendFile3 } from "fs/promises";
|
|
33427
|
-
import { dirname as dirname11 } from "path";
|
|
33428
|
-
|
|
33429
|
-
// src/lib/state.ts
|
|
33430
|
-
init_esm_shims();
|
|
33431
|
-
var SERVICE_STATE_VALUES = [
|
|
33432
|
-
"starting",
|
|
33433
|
-
"running",
|
|
33434
|
-
"degraded",
|
|
33435
|
-
"stopping",
|
|
33436
|
-
"stopped"
|
|
33437
|
-
];
|
|
33438
|
-
function isServiceState(value) {
|
|
33439
|
-
return typeof value === "string" && SERVICE_STATE_VALUES.includes(value);
|
|
33440
|
-
}
|
|
33441
|
-
|
|
33442
|
-
// src/serviceState.ts
|
|
33443
|
-
var DEFAULT_STATE = {
|
|
33444
|
-
state: "running",
|
|
33445
|
-
crashHistory: []
|
|
33446
|
-
};
|
|
33447
|
-
async function readServiceState(slockHome) {
|
|
33448
|
-
try {
|
|
33449
|
-
const raw = await readFile13(serviceStatePath(slockHome), "utf8");
|
|
33450
|
-
const parsed = JSON.parse(raw);
|
|
33451
|
-
if (!parsed || typeof parsed !== "object") return { ...DEFAULT_STATE };
|
|
33452
|
-
const obj = parsed;
|
|
33453
|
-
const state = isServiceState(obj.state) ? obj.state : "running";
|
|
33454
|
-
const crashHistory = Array.isArray(obj.crashHistory) ? obj.crashHistory.filter(isCrashEntry) : [];
|
|
33455
|
-
return { state, crashHistory };
|
|
33456
|
-
} catch {
|
|
33457
|
-
return { ...DEFAULT_STATE };
|
|
33458
|
-
}
|
|
33459
|
-
}
|
|
33460
|
-
async function writeServiceState(slockHome, file) {
|
|
33461
|
-
const path3 = serviceStatePath(slockHome);
|
|
33462
|
-
await mkdir12(dirname11(path3), { recursive: true });
|
|
33463
|
-
await writeFile11(path3, JSON.stringify(file), { mode: 384 });
|
|
33464
|
-
}
|
|
33465
|
-
function isCrashEntry(value) {
|
|
33466
|
-
if (!value || typeof value !== "object") return false;
|
|
33467
|
-
const obj = value;
|
|
33468
|
-
return typeof obj.at === "string";
|
|
33469
|
-
}
|
|
33470
|
-
async function emitServiceStateTransition(slockHome, fromState, toState, trigger) {
|
|
33471
|
-
const entry = {
|
|
33472
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
33473
|
-
kind: "service-state-changed",
|
|
33474
|
-
fromState,
|
|
33475
|
-
toState,
|
|
33476
|
-
trigger
|
|
33477
|
-
};
|
|
33478
|
-
try {
|
|
33479
|
-
const path3 = serviceLogPath(slockHome);
|
|
33480
|
-
await mkdir12(dirname11(path3), { recursive: true });
|
|
33481
|
-
await appendFile3(path3, JSON.stringify(entry) + "\n");
|
|
33482
|
-
} catch {
|
|
33483
|
-
}
|
|
33484
|
-
}
|
|
33485
|
-
async function clearServiceCrashHistory(slockHome) {
|
|
33486
|
-
const current = await readServiceState(slockHome);
|
|
33487
|
-
const previousState = current.state;
|
|
33488
|
-
const clearedCrashCount = current.crashHistory.length;
|
|
33489
|
-
const next = { state: "running", crashHistory: [] };
|
|
33490
|
-
await writeServiceState(slockHome, next);
|
|
33491
|
-
await emitServiceStateTransition(
|
|
33492
|
-
slockHome,
|
|
33493
|
-
previousState,
|
|
33494
|
-
"running",
|
|
33495
|
-
"reset-service"
|
|
33496
|
-
);
|
|
33497
|
-
return { previousState, clearedCrashCount };
|
|
33498
|
-
}
|
|
33499
|
-
|
|
33500
|
-
// src/reset.ts
|
|
33501
|
-
async function resetService(installRoot) {
|
|
33502
|
-
const { previousState, clearedCrashCount } = await clearServiceCrashHistory(installRoot);
|
|
33503
|
-
return {
|
|
33504
|
-
status: "ok",
|
|
33505
|
-
previousState,
|
|
33506
|
-
clearedCrashCount
|
|
33507
|
-
};
|
|
33508
|
-
}
|
|
33509
|
-
async function resetRunner(installRoot, serverId) {
|
|
33510
|
-
const attachment = await readServerAttachment(installRoot, serverId);
|
|
33511
|
-
if (!attachment) {
|
|
33512
|
-
return { status: "not-found", serverId };
|
|
33513
|
-
}
|
|
33514
|
-
const outcome = await resetRunnerHealth(installRoot, serverId);
|
|
33515
|
-
if (outcome.status === "not-found") {
|
|
33516
|
-
return { status: "not-found", serverId };
|
|
33517
|
-
}
|
|
33518
|
-
return {
|
|
33519
|
-
status: "ok",
|
|
33520
|
-
serverId,
|
|
33521
|
-
previousState: outcome.previousState ?? "running",
|
|
33522
|
-
clearedCrashCount: outcome.clearedCrashCount ?? 0
|
|
33523
|
-
};
|
|
33524
|
-
}
|
|
33525
|
-
async function runReset(opts) {
|
|
33526
|
-
const wantsService = opts.service === true;
|
|
33527
|
-
const wantsRunner = opts.runner === true;
|
|
33528
|
-
if (wantsService && wantsRunner) {
|
|
33529
|
-
fail(
|
|
33530
|
-
"RESET_SCOPE_AMBIGUOUS",
|
|
33531
|
-
"`--service` and `--runner` are mutually exclusive. Pass exactly one."
|
|
33532
|
-
);
|
|
33533
|
-
}
|
|
33534
|
-
if (!wantsService && !wantsRunner) {
|
|
33535
|
-
fail(
|
|
33536
|
-
"RESET_SCOPE_MISSING",
|
|
33537
|
-
"Pass `--service` to clear the service-level crash history, or `--runner` to clear a per-runner crash history."
|
|
33538
|
-
);
|
|
33539
|
-
}
|
|
33540
|
-
const slockHome = resolveSlockHome();
|
|
33541
|
-
if (wantsService) {
|
|
33542
|
-
const result2 = await resetService(slockHome);
|
|
33543
|
-
if (opts.json) {
|
|
33544
|
-
info(JSON.stringify(result2));
|
|
33545
|
-
return;
|
|
33546
|
-
}
|
|
33547
|
-
info(
|
|
33548
|
-
`Reset service crash history (previous state: ${result2.previousState}; cleared ${result2.clearedCrashCount} entr${result2.clearedCrashCount === 1 ? "y" : "ies"}).`
|
|
33549
|
-
);
|
|
33550
|
-
info("Service state transitioned to `running`. Runners were not touched.");
|
|
33551
|
-
return;
|
|
33552
|
-
}
|
|
33553
|
-
const serverId = await resolveTargetServerId({ server: opts.server });
|
|
33554
|
-
const result = await resetRunner(slockHome, serverId);
|
|
33555
|
-
if (result.status === "not-found") {
|
|
33556
|
-
fail(
|
|
33557
|
-
"RESET_RUNNER_NOT_FOUND",
|
|
33558
|
-
`Runner for server ${serverId} was not found.`
|
|
33559
|
-
);
|
|
33560
|
-
}
|
|
33561
|
-
if (opts.json) {
|
|
33562
|
-
info(JSON.stringify(result));
|
|
33563
|
-
return;
|
|
33564
|
-
}
|
|
33565
|
-
info(
|
|
33566
|
-
`Reset runner crash history for server ${result.serverId} (previous state: ${result.previousState}; cleared ${result.clearedCrashCount} entr${result.clearedCrashCount === 1 ? "y" : "ies"}).`
|
|
33567
|
-
);
|
|
33568
|
-
info("Runner state transitioned to `running`. Process was not respawned.");
|
|
33569
|
-
}
|
|
33570
|
-
|
|
33571
34289
|
// src/concurrency.ts
|
|
33572
34290
|
init_esm_shims();
|
|
33573
34291
|
var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
|
|
33574
|
-
import { mkdir as
|
|
34292
|
+
import { mkdir as mkdir14 } from "fs/promises";
|
|
33575
34293
|
import { join as join5 } from "path";
|
|
33576
34294
|
var STALE_LOCK_THRESHOLD_MS = 6e4;
|
|
33577
34295
|
async function withMutationLock(fn) {
|
|
33578
34296
|
const slockHome = resolveSlockHome();
|
|
33579
34297
|
const lockTarget = computerDir(slockHome);
|
|
33580
|
-
await
|
|
34298
|
+
await mkdir14(lockTarget, { recursive: true });
|
|
33581
34299
|
const lockfilePath = join5(lockTarget, ".lock");
|
|
33582
34300
|
let release = null;
|
|
33583
34301
|
try {
|
|
@@ -33618,21 +34336,19 @@ async function withMutationLock(fn) {
|
|
|
33618
34336
|
init_esm_shims();
|
|
33619
34337
|
import { readFile as readFile14, rm as rm4 } from "fs/promises";
|
|
33620
34338
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
33621
|
-
import { dirname as
|
|
34339
|
+
import { dirname as dirname13, join as join6 } from "path";
|
|
33622
34340
|
|
|
33623
34341
|
// src/upgradeLog.ts
|
|
33624
34342
|
init_esm_shims();
|
|
33625
|
-
import { chmod as
|
|
34343
|
+
import { chmod as chmod7, mkdir as mkdir15, open as open2 } from "fs/promises";
|
|
33626
34344
|
var FILE_MODE = 384;
|
|
33627
34345
|
function resolveUpgradeTrigger(raw) {
|
|
33628
34346
|
return raw === "web" || raw === "tray" ? raw : "cli";
|
|
33629
34347
|
}
|
|
33630
34348
|
var UPGRADE_ERROR_CODES = [
|
|
33631
|
-
"UPGRADE_DEPS_CHANGED",
|
|
33632
34349
|
"UPGRADE_NETWORK_FAILED",
|
|
33633
34350
|
"UPGRADE_INTEGRITY_FAILED",
|
|
33634
34351
|
"UPGRADE_SWAP_FAILED",
|
|
33635
|
-
"UPGRADE_RESTART_FAILED",
|
|
33636
34352
|
"UPGRADE_NO_TARGET",
|
|
33637
34353
|
"UPGRADE_ALREADY_RUNNING"
|
|
33638
34354
|
];
|
|
@@ -33670,7 +34386,7 @@ function assertUpgradeLogEntry(entry) {
|
|
|
33670
34386
|
}
|
|
33671
34387
|
async function appendUpgradeLogEntry(slockHome, entry) {
|
|
33672
34388
|
assertUpgradeLogEntry(entry);
|
|
33673
|
-
await
|
|
34389
|
+
await mkdir15(computerDir(slockHome), { recursive: true });
|
|
33674
34390
|
const path3 = upgradeLogPath(slockHome);
|
|
33675
34391
|
const at = entry.at ?? formatUpgradeLogTimestamp();
|
|
33676
34392
|
const fullEntry = { ...entry, at };
|
|
@@ -33682,7 +34398,7 @@ async function appendUpgradeLogEntry(slockHome, entry) {
|
|
|
33682
34398
|
await handle.close();
|
|
33683
34399
|
}
|
|
33684
34400
|
if (process.platform !== "win32") {
|
|
33685
|
-
await
|
|
34401
|
+
await chmod7(path3, FILE_MODE);
|
|
33686
34402
|
}
|
|
33687
34403
|
}
|
|
33688
34404
|
|
|
@@ -33840,7 +34556,7 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
33840
34556
|
}
|
|
33841
34557
|
function defaultCurrentBinaryDir() {
|
|
33842
34558
|
const here = fileURLToPath3(import.meta.url);
|
|
33843
|
-
return
|
|
34559
|
+
return dirname13(dirname13(here));
|
|
33844
34560
|
}
|
|
33845
34561
|
async function defaultCurrentVersion() {
|
|
33846
34562
|
if (isSeaBinary()) return COMPUTER_VERSION;
|
|
@@ -33985,6 +34701,31 @@ program2.command("upgrade").description(
|
|
|
33985
34701
|
async (opts) => {
|
|
33986
34702
|
const slockHome = resolveSlockHome();
|
|
33987
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
|
+
}
|
|
33988
34729
|
try {
|
|
33989
34730
|
await withMutationLock(
|
|
33990
34731
|
() => runUpgradeCli(slockHome, {
|