@slock-ai/computer 0.0.16 → 0.0.17
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 +495 -225
- package/dist/lib/index.d.ts +172 -72
- package/dist/lib/index.js +338 -121
- package/package.json +1 -1
package/dist/lib/index.js
CHANGED
|
@@ -31,73 +31,253 @@ var StateReaderError = class extends Error {
|
|
|
31
31
|
this.code = code;
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
|
-
var MIGRATION_DETECTION_KINDS = ["
|
|
34
|
+
var MIGRATION_DETECTION_KINDS = ["candidates", "server-unavailable"];
|
|
35
35
|
function isMigrationDetectionKind(value) {
|
|
36
36
|
return typeof value === "string" && MIGRATION_DETECTION_KINDS.includes(value);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// src/lib/migration.ts
|
|
40
|
-
import { readdir, readFile } from "fs/promises";
|
|
41
|
-
import { join } from "path";
|
|
40
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
41
|
+
import { join, resolve } from "path";
|
|
42
42
|
var MACHINE_DIR_PREFIX = "machine-";
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
var FINGERPRINT_HEX_RE = /^[0-9a-f]{16}$/;
|
|
44
|
+
async function readLocalOwners(installRoot) {
|
|
45
|
+
const machinesDir = join(installRoot, "machines");
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
entries = await readdir(machinesDir);
|
|
49
|
+
} catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
const owners = [];
|
|
53
|
+
for (const name of entries) {
|
|
54
|
+
if (!name.startsWith(MACHINE_DIR_PREFIX)) continue;
|
|
55
|
+
const ownerPath = join(machinesDir, name, "daemon.lock", "owner.json");
|
|
56
|
+
let raw;
|
|
57
|
+
try {
|
|
58
|
+
raw = await readFile(ownerPath, "utf8");
|
|
59
|
+
} catch {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = JSON.parse(raw);
|
|
65
|
+
} catch {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const fp = parsed.apiKeyFingerprint;
|
|
69
|
+
if (typeof fp !== "string" || !FINGERPRINT_HEX_RE.test(fp)) continue;
|
|
70
|
+
owners.push({ apiKeyFingerprint: fp, localPath: ownerPath });
|
|
71
|
+
}
|
|
72
|
+
return owners;
|
|
73
|
+
}
|
|
74
|
+
function indexLocalByFingerprint(owners) {
|
|
75
|
+
const map = /* @__PURE__ */ new Map();
|
|
76
|
+
for (const owner of owners) {
|
|
77
|
+
if (!map.has(owner.apiKeyFingerprint)) {
|
|
78
|
+
map.set(owner.apiKeyFingerprint, owner);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return map;
|
|
82
|
+
}
|
|
83
|
+
function intersect(localByFp, roster) {
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const entry of roster) {
|
|
86
|
+
const local = localByFp.get(entry.apiKeyFingerprint);
|
|
87
|
+
if (!local) continue;
|
|
88
|
+
out.push({
|
|
89
|
+
apiKeyFingerprint: entry.apiKeyFingerprint,
|
|
90
|
+
daemonId: entry.daemonId,
|
|
91
|
+
localPath: local.localPath,
|
|
92
|
+
machineName: entry.machineName,
|
|
93
|
+
...entry.hostname ? { hostname: entry.hostname } : {},
|
|
94
|
+
...entry.lastSeenAt ? { lastSeenAt: entry.lastSeenAt } : {}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
out.sort((a, b) => {
|
|
98
|
+
const aSeen = a.lastSeenAt ?? "";
|
|
99
|
+
const bSeen = b.lastSeenAt ?? "";
|
|
100
|
+
if (aSeen !== bSeen) {
|
|
101
|
+
if (aSeen === "") return 1;
|
|
102
|
+
if (bSeen === "") return -1;
|
|
103
|
+
return aSeen < bSeen ? 1 : -1;
|
|
104
|
+
}
|
|
105
|
+
return a.apiKeyFingerprint < b.apiKeyFingerprint ? -1 : a.apiKeyFingerprint > b.apiKeyFingerprint ? 1 : 0;
|
|
106
|
+
});
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
async function detectLegacyMigration(installRoot, serverSlug, client) {
|
|
110
|
+
const localOwners = await readLocalOwners(installRoot);
|
|
111
|
+
if (localOwners.length === 0) {
|
|
112
|
+
return { kind: "candidates", candidates: [] };
|
|
113
|
+
}
|
|
114
|
+
const result = await client.list(serverSlug);
|
|
115
|
+
if (result.status !== "success") {
|
|
116
|
+
return { kind: "server-unavailable" };
|
|
117
|
+
}
|
|
118
|
+
const localByFp = indexLocalByFingerprint(localOwners);
|
|
119
|
+
return { kind: "candidates", candidates: intersect(localByFp, result.entries) };
|
|
120
|
+
}
|
|
121
|
+
async function validateManualMigratePath(inputPath, roster) {
|
|
122
|
+
const absInput = resolve(inputPath);
|
|
123
|
+
let stats;
|
|
124
|
+
try {
|
|
125
|
+
stats = await stat(absInput);
|
|
126
|
+
} catch {
|
|
127
|
+
return { ok: false, code: "MIGRATE_FROM_NOT_FOUND" };
|
|
128
|
+
}
|
|
129
|
+
const ownerPath = stats.isDirectory() ? join(absInput, "daemon.lock", "owner.json") : absInput;
|
|
130
|
+
const candidateOwnerPath = await firstReadableOwner([
|
|
131
|
+
ownerPath,
|
|
132
|
+
join(absInput, "owner.json"),
|
|
133
|
+
absInput
|
|
134
|
+
]);
|
|
135
|
+
if (!candidateOwnerPath) {
|
|
136
|
+
return { ok: false, code: "MIGRATE_FROM_INVALID" };
|
|
137
|
+
}
|
|
51
138
|
let raw;
|
|
52
139
|
try {
|
|
53
|
-
raw = await readFile(
|
|
140
|
+
raw = await readFile(candidateOwnerPath, "utf8");
|
|
54
141
|
} catch {
|
|
55
|
-
return {};
|
|
142
|
+
return { ok: false, code: "MIGRATE_FROM_INVALID" };
|
|
56
143
|
}
|
|
57
144
|
let parsed;
|
|
58
145
|
try {
|
|
59
146
|
parsed = JSON.parse(raw);
|
|
60
147
|
} catch {
|
|
61
|
-
return {};
|
|
148
|
+
return { ok: false, code: "MIGRATE_FROM_INVALID" };
|
|
62
149
|
}
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
150
|
+
const fp = parsed.apiKeyFingerprint;
|
|
151
|
+
if (typeof fp !== "string" || !FINGERPRINT_HEX_RE.test(fp)) {
|
|
152
|
+
return { ok: false, code: "MIGRATE_FROM_INVALID" };
|
|
153
|
+
}
|
|
154
|
+
const match = roster.find((r) => r.apiKeyFingerprint === fp);
|
|
155
|
+
if (!match) {
|
|
156
|
+
return { ok: false, code: "MIGRATE_FROM_NOT_OWNED" };
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
ok: true,
|
|
160
|
+
candidate: {
|
|
161
|
+
apiKeyFingerprint: match.apiKeyFingerprint,
|
|
162
|
+
daemonId: match.daemonId,
|
|
163
|
+
localPath: candidateOwnerPath,
|
|
164
|
+
machineName: match.machineName,
|
|
165
|
+
...match.hostname ? { hostname: match.hostname } : {},
|
|
166
|
+
...match.lastSeenAt ? { lastSeenAt: match.lastSeenAt } : {}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
66
169
|
}
|
|
67
|
-
async function
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
170
|
+
async function firstReadableOwner(paths) {
|
|
171
|
+
const seen = /* @__PURE__ */ new Set();
|
|
172
|
+
for (const p of paths) {
|
|
173
|
+
if (seen.has(p)) continue;
|
|
174
|
+
seen.add(p);
|
|
175
|
+
try {
|
|
176
|
+
const st = await stat(p);
|
|
177
|
+
if (st.isFile()) return p;
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
75
180
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/apiClient.ts
|
|
185
|
+
import { fetch } from "undici";
|
|
186
|
+
var LegacyMachinesClient = class {
|
|
187
|
+
constructor(baseUrl, accessToken) {
|
|
188
|
+
this.baseUrl = baseUrl;
|
|
189
|
+
this.accessToken = accessToken;
|
|
190
|
+
}
|
|
191
|
+
url(p) {
|
|
192
|
+
return new URL(p, this.baseUrl).toString();
|
|
193
|
+
}
|
|
194
|
+
async list(serverSlug) {
|
|
195
|
+
let res;
|
|
196
|
+
try {
|
|
197
|
+
res = await fetch(
|
|
198
|
+
this.url(`/api/computer/legacy-machines?serverSlug=${encodeURIComponent(serverSlug)}`),
|
|
199
|
+
{
|
|
200
|
+
method: "GET",
|
|
201
|
+
headers: { Authorization: `Bearer ${this.accessToken}` }
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
} catch {
|
|
205
|
+
return { status: "error", code: "request_failed" };
|
|
206
|
+
}
|
|
207
|
+
const body = await res.json().catch(() => null);
|
|
208
|
+
if (res.status === 200 && body && Array.isArray(body.entries)) {
|
|
209
|
+
const entries = body.entries.map((raw) => {
|
|
210
|
+
if (!raw || typeof raw !== "object") return null;
|
|
211
|
+
const e = raw;
|
|
212
|
+
if (typeof e.daemonId !== "string" || typeof e.apiKeyFingerprint !== "string" || typeof e.machineName !== "string") {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
daemonId: e.daemonId,
|
|
217
|
+
apiKeyFingerprint: e.apiKeyFingerprint,
|
|
218
|
+
machineName: e.machineName,
|
|
219
|
+
hostname: typeof e.hostname === "string" ? e.hostname : null,
|
|
220
|
+
lastSeenAt: typeof e.lastSeenAt === "string" ? e.lastSeenAt : null
|
|
221
|
+
};
|
|
222
|
+
}).filter((entry) => entry !== null);
|
|
223
|
+
return { status: "success", entries };
|
|
224
|
+
}
|
|
225
|
+
if (res.status === 401) return { status: "auth_required" };
|
|
226
|
+
if (res.status === 403) return { status: "not_authorized" };
|
|
227
|
+
if (res.status === 404) return { status: "disabled" };
|
|
228
|
+
const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
|
|
229
|
+
return { status: "error", code };
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
var RunnersClient = class {
|
|
233
|
+
constructor(baseUrl, computerApiKey) {
|
|
234
|
+
this.baseUrl = baseUrl;
|
|
235
|
+
this.computerApiKey = computerApiKey;
|
|
236
|
+
}
|
|
237
|
+
url(p) {
|
|
238
|
+
return new URL(p, this.baseUrl).toString();
|
|
239
|
+
}
|
|
240
|
+
async list() {
|
|
241
|
+
const res = await fetch(this.url("/internal/computer/runners"), {
|
|
242
|
+
method: "GET",
|
|
243
|
+
headers: { Authorization: `Bearer ${this.computerApiKey}` }
|
|
84
244
|
});
|
|
245
|
+
const body = await res.json().catch(() => null);
|
|
246
|
+
if (res.status === 200 && body && Array.isArray(body.runners)) {
|
|
247
|
+
return {
|
|
248
|
+
status: "success",
|
|
249
|
+
whitelist: Array.isArray(body.whitelist) ? body.whitelist : [],
|
|
250
|
+
runners: body.runners
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
|
|
254
|
+
const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
|
|
255
|
+
return { status: "error", code };
|
|
85
256
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
};
|
|
257
|
+
async stop(agentId) {
|
|
258
|
+
const res = await fetch(this.url(`/internal/computer/runners/${encodeURIComponent(agentId)}/stop`), {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: { Authorization: `Bearer ${this.computerApiKey}`, "Content-Type": "application/json" },
|
|
261
|
+
body: "{}"
|
|
262
|
+
});
|
|
263
|
+
if (res.status === 200) return { status: "success" };
|
|
264
|
+
if (res.status === 404) return { status: "not_found" };
|
|
265
|
+
if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
|
|
266
|
+
const body = await res.json().catch(() => null);
|
|
267
|
+
const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
|
|
268
|
+
return { status: "error", code };
|
|
95
269
|
}
|
|
96
|
-
|
|
97
|
-
}
|
|
270
|
+
};
|
|
98
271
|
|
|
99
|
-
// src/
|
|
100
|
-
import { readFile as
|
|
272
|
+
// src/setup.ts
|
|
273
|
+
import { chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename3, rm as rm2, writeFile as writeFile8 } from "fs/promises";
|
|
274
|
+
import { dirname as dirname8 } from "path";
|
|
275
|
+
import { fetch as fetch3 } from "undici";
|
|
276
|
+
|
|
277
|
+
// src/serverState.ts
|
|
278
|
+
import { readFile as readFile2, readdir as readdir2, writeFile, mkdir, unlink, access, chmod } from "fs/promises";
|
|
279
|
+
import { dirname } from "path";
|
|
280
|
+
import { constants as fsConstants } from "fs";
|
|
101
281
|
|
|
102
282
|
// src/paths.ts
|
|
103
283
|
import { createHash } from "crypto";
|
|
@@ -161,9 +341,6 @@ function serviceLogPath(slockHome) {
|
|
|
161
341
|
}
|
|
162
342
|
|
|
163
343
|
// src/serverState.ts
|
|
164
|
-
import { readFile as readFile2, readdir as readdir2, writeFile, mkdir, unlink, access, chmod } from "fs/promises";
|
|
165
|
-
import { dirname } from "path";
|
|
166
|
-
import { constants as fsConstants } from "fs";
|
|
167
344
|
function parseAttachment(raw) {
|
|
168
345
|
try {
|
|
169
346
|
const a = JSON.parse(raw);
|
|
@@ -214,12 +391,37 @@ async function listServerAttachments(slockHome) {
|
|
|
214
391
|
return out;
|
|
215
392
|
}
|
|
216
393
|
|
|
217
|
-
// src/
|
|
218
|
-
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2
|
|
394
|
+
// src/services/attach.ts
|
|
395
|
+
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
219
396
|
import { dirname as dirname2 } from "path";
|
|
397
|
+
|
|
398
|
+
// src/services/login.ts
|
|
399
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
400
|
+
import { dirname as dirname3 } from "path";
|
|
401
|
+
|
|
402
|
+
// src/services/adoptLegacy.ts
|
|
403
|
+
import { chmod as chmod3, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, appendFile, stat as stat2 } from "fs/promises";
|
|
404
|
+
import { createHash as createHash2 } from "crypto";
|
|
405
|
+
import { dirname as dirname4, join as join2 } from "path";
|
|
406
|
+
import { setTimeout as delay } from "timers/promises";
|
|
407
|
+
|
|
408
|
+
// src/service.ts
|
|
409
|
+
import { spawn as spawn2 } from "child_process";
|
|
410
|
+
import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile7, open, rename as rename2 } from "fs/promises";
|
|
411
|
+
import { dirname as dirname7, join as joinPath } from "path";
|
|
412
|
+
import { fileURLToPath } from "url";
|
|
413
|
+
|
|
414
|
+
// src/cleanup.ts
|
|
415
|
+
import { readdir as readdir3, stat as stat3, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
|
|
416
|
+
import { spawn } from "child_process";
|
|
417
|
+
import { join as join3 } from "path";
|
|
418
|
+
|
|
419
|
+
// src/internal/process-primitives.ts
|
|
420
|
+
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, unlink as unlink2 } from "fs/promises";
|
|
421
|
+
import { dirname as dirname5 } from "path";
|
|
220
422
|
async function readPidfileAt(pidfilePath) {
|
|
221
423
|
try {
|
|
222
|
-
const raw = (await
|
|
424
|
+
const raw = (await readFile5(pidfilePath, "utf8")).trim();
|
|
223
425
|
const pid = Number.parseInt(raw, 10);
|
|
224
426
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
225
427
|
} catch {
|
|
@@ -236,6 +438,37 @@ function isProcessAlive(pid) {
|
|
|
236
438
|
}
|
|
237
439
|
}
|
|
238
440
|
|
|
441
|
+
// src/cleanup.ts
|
|
442
|
+
var TMP_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
443
|
+
|
|
444
|
+
// src/health.ts
|
|
445
|
+
import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7, appendFile as appendFile2 } from "fs/promises";
|
|
446
|
+
import { dirname as dirname6 } from "path";
|
|
447
|
+
var CRASH_WINDOW_MS = 6e4;
|
|
448
|
+
var DEGRADED_THRESHOLD = 3;
|
|
449
|
+
async function readHealthFile(slockHome, serverId) {
|
|
450
|
+
if (!isValidServerId(serverId)) return { crashes: [] };
|
|
451
|
+
try {
|
|
452
|
+
const raw = await readFile6(serverHealthPath(slockHome, serverId), "utf8");
|
|
453
|
+
const parsed = JSON.parse(raw);
|
|
454
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.crashes)) {
|
|
455
|
+
return parsed;
|
|
456
|
+
}
|
|
457
|
+
} catch {
|
|
458
|
+
}
|
|
459
|
+
return { crashes: [] };
|
|
460
|
+
}
|
|
461
|
+
async function isDegraded(slockHome, serverId, nowMs = Date.now()) {
|
|
462
|
+
const file = await readHealthFile(slockHome, serverId);
|
|
463
|
+
if (file.fatalConfig) return true;
|
|
464
|
+
const cutoffMs = nowMs - CRASH_WINDOW_MS;
|
|
465
|
+
const recent = file.crashes.filter((c) => {
|
|
466
|
+
const t = new Date(c.at).getTime();
|
|
467
|
+
return Number.isFinite(t) && t >= cutoffMs;
|
|
468
|
+
});
|
|
469
|
+
return recent.length >= DEGRADED_THRESHOLD;
|
|
470
|
+
}
|
|
471
|
+
|
|
239
472
|
// src/internal/service-pid-fallback.ts
|
|
240
473
|
async function findLiveServicePidReadOnly(slockHome, deps = {}) {
|
|
241
474
|
const readPidfile = deps.readPidfile ?? readPidfileAt;
|
|
@@ -271,38 +504,60 @@ async function walkFallback(slockHome, readPidfile, isAlive, clearStale) {
|
|
|
271
504
|
};
|
|
272
505
|
}
|
|
273
506
|
|
|
274
|
-
// src/
|
|
275
|
-
import {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
var
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
507
|
+
// src/services/detach.ts
|
|
508
|
+
import { fetch as fetch2 } from "undici";
|
|
509
|
+
|
|
510
|
+
// src/setup.ts
|
|
511
|
+
var MIGRATION_FRESH_TRIGGERS = [
|
|
512
|
+
"empty-intersection",
|
|
513
|
+
"explicit-zero",
|
|
514
|
+
"eof",
|
|
515
|
+
"non-tty",
|
|
516
|
+
"server-unavailable"
|
|
517
|
+
];
|
|
518
|
+
async function pickMigrationCandidateFromInput(candidates, read, write) {
|
|
519
|
+
write(
|
|
520
|
+
"Migration: detected legacy daemon(s) on this Computer that match the target server.\n"
|
|
521
|
+
);
|
|
522
|
+
candidates.forEach((c, i) => {
|
|
523
|
+
const host = c.hostname ? ` (${c.hostname})` : "";
|
|
524
|
+
const seen = c.lastSeenAt ? ` last seen ${c.lastSeenAt}` : "";
|
|
525
|
+
write(` ${i + 1}. ${c.machineName}${host}${seen}
|
|
526
|
+
`);
|
|
527
|
+
write(` local: ${c.localPath}
|
|
528
|
+
`);
|
|
529
|
+
});
|
|
530
|
+
write(" 0. Fresh attach (skip migration)\n");
|
|
531
|
+
write(" m. Migrate from a different on-disk path\n");
|
|
532
|
+
while (true) {
|
|
533
|
+
write("Choose [1]: ");
|
|
534
|
+
const { line, eof } = await read();
|
|
535
|
+
if (eof) return { kind: "fresh" };
|
|
536
|
+
const trimmed = line.trim();
|
|
537
|
+
const norm = trimmed.toLowerCase();
|
|
538
|
+
if (norm === "m") {
|
|
539
|
+
write("Path to legacy machine dir or owner.json: ");
|
|
540
|
+
const { line: pathLine } = await read();
|
|
541
|
+
return { kind: "manual", path: pathLine.trim() };
|
|
286
542
|
}
|
|
287
|
-
|
|
543
|
+
if (norm.length === 0) return { kind: "candidate", index: 0 };
|
|
544
|
+
if (norm === "0") return { kind: "fresh" };
|
|
545
|
+
const n = Number.parseInt(norm, 10);
|
|
546
|
+
if (Number.isFinite(n) && String(n) === norm && n >= 1 && n <= candidates.length) {
|
|
547
|
+
return { kind: "candidate", index: n - 1 };
|
|
548
|
+
}
|
|
549
|
+
write(
|
|
550
|
+
`Invalid selection "${trimmed}". Enter 1..${candidates.length}, 0, or m.
|
|
551
|
+
`
|
|
552
|
+
);
|
|
288
553
|
}
|
|
289
|
-
return { crashes: [] };
|
|
290
|
-
}
|
|
291
|
-
async function isDegraded(slockHome, serverId, nowMs = Date.now()) {
|
|
292
|
-
const file = await readHealthFile(slockHome, serverId);
|
|
293
|
-
if (file.fatalConfig) return true;
|
|
294
|
-
const cutoffMs = nowMs - CRASH_WINDOW_MS;
|
|
295
|
-
const recent = file.crashes.filter((c) => {
|
|
296
|
-
const t = new Date(c.at).getTime();
|
|
297
|
-
return Number.isFinite(t) && t >= cutoffMs;
|
|
298
|
-
});
|
|
299
|
-
return recent.length >= DEGRADED_THRESHOLD;
|
|
300
554
|
}
|
|
301
555
|
|
|
302
556
|
// src/status.ts
|
|
557
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
303
558
|
async function readUserSession(path2) {
|
|
304
559
|
try {
|
|
305
|
-
const parsed = JSON.parse(await
|
|
560
|
+
const parsed = JSON.parse(await readFile9(path2, "utf8"));
|
|
306
561
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
307
562
|
return { state: "present", session: parsed, error: null };
|
|
308
563
|
}
|
|
@@ -368,48 +623,6 @@ async function buildStatusReport(installRoot) {
|
|
|
368
623
|
};
|
|
369
624
|
}
|
|
370
625
|
|
|
371
|
-
// src/apiClient.ts
|
|
372
|
-
import { fetch } from "undici";
|
|
373
|
-
var RunnersClient = class {
|
|
374
|
-
constructor(baseUrl, computerApiKey) {
|
|
375
|
-
this.baseUrl = baseUrl;
|
|
376
|
-
this.computerApiKey = computerApiKey;
|
|
377
|
-
}
|
|
378
|
-
url(p) {
|
|
379
|
-
return new URL(p, this.baseUrl).toString();
|
|
380
|
-
}
|
|
381
|
-
async list() {
|
|
382
|
-
const res = await fetch(this.url("/internal/computer/runners"), {
|
|
383
|
-
method: "GET",
|
|
384
|
-
headers: { Authorization: `Bearer ${this.computerApiKey}` }
|
|
385
|
-
});
|
|
386
|
-
const body = await res.json().catch(() => null);
|
|
387
|
-
if (res.status === 200 && body && Array.isArray(body.runners)) {
|
|
388
|
-
return {
|
|
389
|
-
status: "success",
|
|
390
|
-
whitelist: Array.isArray(body.whitelist) ? body.whitelist : [],
|
|
391
|
-
runners: body.runners
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
|
|
395
|
-
const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
|
|
396
|
-
return { status: "error", code };
|
|
397
|
-
}
|
|
398
|
-
async stop(agentId) {
|
|
399
|
-
const res = await fetch(this.url(`/internal/computer/runners/${encodeURIComponent(agentId)}/stop`), {
|
|
400
|
-
method: "POST",
|
|
401
|
-
headers: { Authorization: `Bearer ${this.computerApiKey}`, "Content-Type": "application/json" },
|
|
402
|
-
body: "{}"
|
|
403
|
-
});
|
|
404
|
-
if (res.status === 200) return { status: "success" };
|
|
405
|
-
if (res.status === 404) return { status: "not_found" };
|
|
406
|
-
if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
|
|
407
|
-
const body = await res.json().catch(() => null);
|
|
408
|
-
const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
|
|
409
|
-
return { status: "error", code };
|
|
410
|
-
}
|
|
411
|
-
};
|
|
412
|
-
|
|
413
626
|
// src/lib/readers.ts
|
|
414
627
|
async function readServiceStatus(installRoot) {
|
|
415
628
|
return buildStatusReport(installRoot);
|
|
@@ -497,7 +710,7 @@ function isServiceState(value) {
|
|
|
497
710
|
}
|
|
498
711
|
|
|
499
712
|
// src/upgradeLog.ts
|
|
500
|
-
import { chmod as
|
|
713
|
+
import { chmod as chmod5, mkdir as mkdir10, open as open2 } from "fs/promises";
|
|
501
714
|
var UPGRADE_ERROR_CODES = [
|
|
502
715
|
"UPGRADE_DEPS_CHANGED",
|
|
503
716
|
"UPGRADE_NETWORK_FAILED",
|
|
@@ -541,7 +754,9 @@ function assertUpgradeLogEntry(entry) {
|
|
|
541
754
|
}
|
|
542
755
|
export {
|
|
543
756
|
IPC_ERROR_CODES,
|
|
757
|
+
LegacyMachinesClient,
|
|
544
758
|
MIGRATION_DETECTION_KINDS,
|
|
759
|
+
MIGRATION_FRESH_TRIGGERS,
|
|
545
760
|
RUNNER_STATE_VALUES,
|
|
546
761
|
SERVICE_STATE_VALUES,
|
|
547
762
|
STATE_READER_ERROR_CODES,
|
|
@@ -555,6 +770,8 @@ export {
|
|
|
555
770
|
isRunnerState,
|
|
556
771
|
isServiceState,
|
|
557
772
|
listRunners,
|
|
773
|
+
pickMigrationCandidateFromInput,
|
|
558
774
|
readRunnerStatus,
|
|
559
|
-
readServiceStatus
|
|
775
|
+
readServiceStatus,
|
|
776
|
+
validateManualMigratePath
|
|
560
777
|
};
|
package/package.json
CHANGED