@slock-ai/computer 0.0.6 → 0.0.8
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 +166 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -304,6 +304,13 @@ function upgradeStagingDir(slockHome, version) {
|
|
|
304
304
|
function upgradeSnapshotPath(slockHome) {
|
|
305
305
|
return path.join(computerDir(slockHome), "upgrade-snapshot.json");
|
|
306
306
|
}
|
|
307
|
+
function upgradeLogPath(slockHome) {
|
|
308
|
+
return path.join(computerDir(slockHome), "upgrade.log");
|
|
309
|
+
}
|
|
310
|
+
function formatUpgradeLogTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
311
|
+
const iso = date.toISOString();
|
|
312
|
+
return iso.replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
313
|
+
}
|
|
307
314
|
|
|
308
315
|
// src/output.ts
|
|
309
316
|
var CliExit = class extends Error {
|
|
@@ -1411,6 +1418,44 @@ async function runResident(serverId, deps = {}) {
|
|
|
1411
1418
|
}
|
|
1412
1419
|
var RECONCILE_INTERVAL_MS = 5e3;
|
|
1413
1420
|
var CHILD_RESTART_BACKOFF_MS = 2e3;
|
|
1421
|
+
var START_ENSURE_TIMEOUT_MS = 15e3;
|
|
1422
|
+
var START_ENSURE_POLL_INTERVAL_MS = 100;
|
|
1423
|
+
async function waitForManagedDaemonPids(slockHome, serverIds, deps) {
|
|
1424
|
+
const readPidfile = deps.readPidfile ?? readPidfileAt;
|
|
1425
|
+
const isAlive = deps.isProcessAlive ?? isProcessAlive2;
|
|
1426
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1427
|
+
const timeoutMs = deps.ensureTimeoutMs ?? START_ENSURE_TIMEOUT_MS;
|
|
1428
|
+
const pollIntervalMs = deps.ensurePollIntervalMs ?? START_ENSURE_POLL_INTERVAL_MS;
|
|
1429
|
+
const deadline = Date.now() + timeoutMs;
|
|
1430
|
+
const ready = /* @__PURE__ */ new Map();
|
|
1431
|
+
while (ready.size < serverIds.length) {
|
|
1432
|
+
for (const serverId of serverIds) {
|
|
1433
|
+
if (ready.has(serverId)) continue;
|
|
1434
|
+
const pid = await readPidfile(serverDaemonPidPath(slockHome, serverId));
|
|
1435
|
+
if (pid && isAlive(pid)) ready.set(serverId, pid);
|
|
1436
|
+
}
|
|
1437
|
+
if (ready.size === serverIds.length) return ready;
|
|
1438
|
+
const remaining = deadline - Date.now();
|
|
1439
|
+
if (remaining <= 0) return ready;
|
|
1440
|
+
await sleep2(Math.min(pollIntervalMs, remaining));
|
|
1441
|
+
}
|
|
1442
|
+
return ready;
|
|
1443
|
+
}
|
|
1444
|
+
function formatReadySummary(ready, serverIds, opts) {
|
|
1445
|
+
if (opts.serverId && serverIds.length === 1) {
|
|
1446
|
+
const pid = ready.get(opts.serverId);
|
|
1447
|
+
return `Daemon for server ${opts.serverLabel ?? opts.serverId} is running${pid ? ` (pid ${pid})` : ""}.`;
|
|
1448
|
+
}
|
|
1449
|
+
return `Daemons for ${serverIds.length} managed server(s) are running.`;
|
|
1450
|
+
}
|
|
1451
|
+
function failStartEnsureTimeout(slockHome, serverIds, ready, opts) {
|
|
1452
|
+
const missing = serverIds.filter((id) => !ready.has(id));
|
|
1453
|
+
const target = opts.serverId && missing.length === 1 ? `${opts.serverLabel ?? opts.serverId}` : `${missing.length} daemon(s): ${missing.join(", ")}`;
|
|
1454
|
+
fail(
|
|
1455
|
+
"START_DAEMON_TIMEOUT",
|
|
1456
|
+
`Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${supervisorLogPath(slockHome)} plus per-server daemon logs under ~/.slock/computer/servers/<serverId>/daemon.log.`
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1414
1459
|
async function runSupervisorStartupRecovery(slockHome) {
|
|
1415
1460
|
const parentHoldsLock = process.env[PARENT_LOCK_HELD_ENV_VAR] === "1";
|
|
1416
1461
|
try {
|
|
@@ -1592,7 +1637,7 @@ async function runSupervise() {
|
|
|
1592
1637
|
await new Promise(() => {
|
|
1593
1638
|
});
|
|
1594
1639
|
}
|
|
1595
|
-
async function runStart(opts = {},
|
|
1640
|
+
async function runStart(opts = {}, deps = {}) {
|
|
1596
1641
|
const slockHome = resolveSlockHome();
|
|
1597
1642
|
const attached = await listAttachedServerIds(slockHome);
|
|
1598
1643
|
if (attached.length === 0) {
|
|
@@ -1614,9 +1659,11 @@ async function runStart(opts = {}, _deps = {}) {
|
|
|
1614
1659
|
const existing = await readPidfileAt(supervisorPidPath(slockHome));
|
|
1615
1660
|
if (existing && isProcessAlive2(existing)) {
|
|
1616
1661
|
info(`Supervisor already running (pid ${existing}).`);
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1662
|
+
const ready2 = await waitForManagedDaemonPids(slockHome, managedTargets, deps);
|
|
1663
|
+
if (ready2.size !== managedTargets.length) {
|
|
1664
|
+
failStartEnsureTimeout(slockHome, managedTargets, ready2, opts);
|
|
1665
|
+
}
|
|
1666
|
+
info(formatReadySummary(ready2, managedTargets, opts));
|
|
1620
1667
|
return;
|
|
1621
1668
|
}
|
|
1622
1669
|
if (opts.foreground) {
|
|
@@ -1628,18 +1675,66 @@ async function runStart(opts = {}, _deps = {}) {
|
|
|
1628
1675
|
}
|
|
1629
1676
|
let pid;
|
|
1630
1677
|
try {
|
|
1631
|
-
pid = await spawnDetachedSupervisor(slockHome);
|
|
1678
|
+
pid = await (deps.spawnDetachedSupervisor ?? spawnDetachedSupervisor)(slockHome);
|
|
1632
1679
|
} catch (err) {
|
|
1633
1680
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1634
1681
|
fail("SUPERVISOR_SPAWN_FAILED", msg);
|
|
1635
1682
|
}
|
|
1636
1683
|
info(`Supervisor started (pid ${pid}); keeps running after this terminal closes.`);
|
|
1684
|
+
const ready = await waitForManagedDaemonPids(slockHome, managedTargets, deps);
|
|
1685
|
+
if (ready.size !== managedTargets.length) {
|
|
1686
|
+
failStartEnsureTimeout(slockHome, managedTargets, ready, opts);
|
|
1687
|
+
}
|
|
1688
|
+
info(formatReadySummary(ready, managedTargets, opts));
|
|
1637
1689
|
info(
|
|
1638
1690
|
`Managing ${managedTargets.length} of ${attached.length} attached server(s). Logs: ${supervisorLogPath(slockHome)}`
|
|
1639
1691
|
);
|
|
1640
1692
|
info(`Per-server daemon logs: ~/.slock/computer/servers/<serverId>/daemon.log`);
|
|
1641
1693
|
info(`Check state with \`slock-computer status\`.`);
|
|
1642
1694
|
}
|
|
1695
|
+
async function runStop(deps = {}) {
|
|
1696
|
+
const slockHome = resolveSlockHome();
|
|
1697
|
+
const readPidfile = deps.readPidfile ?? readPidfileAt;
|
|
1698
|
+
const isAlive = deps.isProcessAlive ?? isProcessAlive2;
|
|
1699
|
+
const killer = deps.killSupervisor ?? ((pid2) => {
|
|
1700
|
+
process.kill(pid2, "SIGTERM");
|
|
1701
|
+
});
|
|
1702
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
1703
|
+
const pollIntervalMs = deps.pollIntervalMs ?? 200;
|
|
1704
|
+
const timeoutMs = deps.timeoutMs ?? 5e3;
|
|
1705
|
+
const pidfilePath = supervisorPidPath(slockHome);
|
|
1706
|
+
const pid = await readPidfile(pidfilePath);
|
|
1707
|
+
if (pid === null) {
|
|
1708
|
+
info("Supervisor not running.");
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
if (!isAlive(pid)) {
|
|
1712
|
+
await clearPidfileAt(pidfilePath);
|
|
1713
|
+
info(`Supervisor not running (cleared stale pidfile for pid ${pid}).`);
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
try {
|
|
1717
|
+
killer(pid);
|
|
1718
|
+
} catch (err) {
|
|
1719
|
+
fail(
|
|
1720
|
+
"STOP_SIGNAL_FAILED",
|
|
1721
|
+
`Failed to send SIGTERM to supervisor (pid ${pid}): ${err instanceof Error ? err.message : String(err)}. Check process permissions or run: kill ${pid}`
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
const deadline = Date.now() + timeoutMs;
|
|
1725
|
+
while (Date.now() < deadline) {
|
|
1726
|
+
if (!isAlive(pid)) {
|
|
1727
|
+
await clearPidfileAt(pidfilePath);
|
|
1728
|
+
info(`Stopped supervisor (pid ${pid}).`);
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
await sleep2(pollIntervalMs);
|
|
1732
|
+
}
|
|
1733
|
+
fail(
|
|
1734
|
+
"STOP_TIMEOUT",
|
|
1735
|
+
`Supervisor (pid ${pid}) did not exit within ${timeoutMs}ms after SIGTERM. Force-kill with: kill -9 ${pid}`
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1643
1738
|
async function runDetach(serverId, serverLabel = serverId) {
|
|
1644
1739
|
assertValidServerId(serverId);
|
|
1645
1740
|
const slockHome = resolveSlockHome();
|
|
@@ -3298,7 +3393,30 @@ async function locateStagedTarball(stagedPath) {
|
|
|
3298
3393
|
return join6(stagedPath, tgz);
|
|
3299
3394
|
}
|
|
3300
3395
|
|
|
3396
|
+
// src/upgradeLog.ts
|
|
3397
|
+
import { chmod as chmod5, mkdir as mkdir12, open as open2 } from "fs/promises";
|
|
3398
|
+
var FILE_MODE = 384;
|
|
3399
|
+
async function appendUpgradeLogEntry(slockHome, entry) {
|
|
3400
|
+
await mkdir12(computerDir(slockHome), { recursive: true });
|
|
3401
|
+
const path2 = upgradeLogPath(slockHome);
|
|
3402
|
+
const at = entry.at ?? formatUpgradeLogTimestamp();
|
|
3403
|
+
const fullEntry = { ...entry, at };
|
|
3404
|
+
const line = JSON.stringify(fullEntry) + "\n";
|
|
3405
|
+
const handle = await open2(path2, "a", FILE_MODE);
|
|
3406
|
+
try {
|
|
3407
|
+
await handle.write(line);
|
|
3408
|
+
} finally {
|
|
3409
|
+
await handle.close();
|
|
3410
|
+
}
|
|
3411
|
+
if (process.platform !== "win32") {
|
|
3412
|
+
await chmod5(path2, FILE_MODE);
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3301
3416
|
// src/upgradeCli.ts
|
|
3417
|
+
function isEphemeralNpxContext(binaryDir) {
|
|
3418
|
+
return binaryDir.split(/[\\/]/).includes("_npx");
|
|
3419
|
+
}
|
|
3302
3420
|
async function defaultSpawnFreshSupervisor(slockHome) {
|
|
3303
3421
|
await spawnDetachedSupervisor(slockHome);
|
|
3304
3422
|
}
|
|
@@ -3348,6 +3466,18 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
3348
3466
|
}
|
|
3349
3467
|
const fromVersion = await (deps.currentVersion ?? defaultCurrentVersion)();
|
|
3350
3468
|
const currentBinaryDir = (deps.currentBinaryDir ?? defaultCurrentBinaryDir)();
|
|
3469
|
+
const isEphemeralFn = deps.isEphemeralNpxContext ?? isEphemeralNpxContext;
|
|
3470
|
+
if (isEphemeralFn(currentBinaryDir)) {
|
|
3471
|
+
fail(
|
|
3472
|
+
"UPGRADE_EPHEMERAL_CONTEXT",
|
|
3473
|
+
`Cannot upgrade from an ephemeral npx context (${currentBinaryDir}).
|
|
3474
|
+
The currently installed Computer service runs from a different install root, which this command cannot reach. To upgrade, manually replace the persistent install:
|
|
3475
|
+
npx -y @slock-ai/computer@latest stop
|
|
3476
|
+
rm -rf ~/.npm/_npx/<persistent-hash>/
|
|
3477
|
+
npx -y @slock-ai/computer@latest start
|
|
3478
|
+
(Find the persistent hash via: ls -d ~/.npm/_npx/*/node_modules/@slock-ai/computer)`
|
|
3479
|
+
);
|
|
3480
|
+
}
|
|
3351
3481
|
if (targetVersion === fromVersion) {
|
|
3352
3482
|
info(`Already at ${fromVersion} (channel ${channel2}). Nothing to do.`);
|
|
3353
3483
|
return;
|
|
@@ -3400,14 +3530,35 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
3400
3530
|
spawnFreshSupervisor: () => spawnFreshSupervisor(slockHome)
|
|
3401
3531
|
}
|
|
3402
3532
|
});
|
|
3533
|
+
const bundle = (version) => ({
|
|
3534
|
+
computerVersion: version
|
|
3535
|
+
});
|
|
3536
|
+
const logTrigger = opts.trigger ?? "cli";
|
|
3403
3537
|
if (outcome.ok) {
|
|
3404
3538
|
info(
|
|
3405
3539
|
`Upgrade ${fromVersion} \u2192 ${targetVersion} succeeded (health-OK in ${outcome.restart?.healthAfterMs ?? 0}ms).`
|
|
3406
3540
|
);
|
|
3541
|
+
await appendUpgradeLogEntry(slockHome, {
|
|
3542
|
+
fromBundle: bundle(fromVersion),
|
|
3543
|
+
toBundle: bundle(targetVersion),
|
|
3544
|
+
channel: channel2,
|
|
3545
|
+
trigger: logTrigger,
|
|
3546
|
+
outcome: "ok"
|
|
3547
|
+
}).catch(() => {
|
|
3548
|
+
});
|
|
3407
3549
|
return;
|
|
3408
3550
|
}
|
|
3409
3551
|
const code = mapFailurePhaseToCode(outcome);
|
|
3410
3552
|
const rolledBack = outcome.rolledBack === true ? " (swap rolled back)" : "";
|
|
3553
|
+
await appendUpgradeLogEntry(slockHome, {
|
|
3554
|
+
fromBundle: bundle(fromVersion),
|
|
3555
|
+
toBundle: bundle(targetVersion),
|
|
3556
|
+
channel: channel2,
|
|
3557
|
+
trigger: logTrigger,
|
|
3558
|
+
outcome: "err",
|
|
3559
|
+
errorCode: code
|
|
3560
|
+
}).catch(() => {
|
|
3561
|
+
});
|
|
3411
3562
|
fail(
|
|
3412
3563
|
code,
|
|
3413
3564
|
`Upgrade failed at phase=${outcome.phase}: ${outcome.reason ?? "unknown"}${rolledBack}.`
|
|
@@ -3473,7 +3624,7 @@ async function defaultCurrentVersion() {
|
|
|
3473
3624
|
}
|
|
3474
3625
|
|
|
3475
3626
|
// src/upgradeTestHarness.ts
|
|
3476
|
-
import { mkdir as
|
|
3627
|
+
import { mkdir as mkdir13, readdir as readdir4, stat as stat3, writeFile as writeFile10 } from "fs/promises";
|
|
3477
3628
|
import { join as join8 } from "path";
|
|
3478
3629
|
import { createHash as createHash4 } from "crypto";
|
|
3479
3630
|
var PHASES = /* @__PURE__ */ new Set([
|
|
@@ -3544,7 +3695,7 @@ function buildSimulatedDeps(slockHome, opts) {
|
|
|
3544
3695
|
if (opts.simulateFail === "extract") {
|
|
3545
3696
|
return { exitCode: 1, stderr: "simulated extract failure" };
|
|
3546
3697
|
}
|
|
3547
|
-
await
|
|
3698
|
+
await mkdir13(join8(destDir, "package"), { recursive: true });
|
|
3548
3699
|
await writeFile10(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
|
|
3549
3700
|
return { exitCode: 0, stderr: "" };
|
|
3550
3701
|
},
|
|
@@ -3605,7 +3756,7 @@ function buildSimulatedDeps(slockHome, opts) {
|
|
|
3605
3756
|
}
|
|
3606
3757
|
async function arrangeSnapshotFailure(slockHome) {
|
|
3607
3758
|
const snapshotPath = join8(slockHome, "computer", "upgrade-snapshot.json");
|
|
3608
|
-
await
|
|
3759
|
+
await mkdir13(snapshotPath, { recursive: true });
|
|
3609
3760
|
}
|
|
3610
3761
|
async function pathInfo(path2) {
|
|
3611
3762
|
try {
|
|
@@ -3660,12 +3811,12 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
|
|
|
3660
3811
|
process.exitCode = 1;
|
|
3661
3812
|
return;
|
|
3662
3813
|
}
|
|
3663
|
-
await
|
|
3814
|
+
await mkdir13(slockHome, { recursive: true });
|
|
3664
3815
|
if (opts.simulateFail === "snapshot") {
|
|
3665
3816
|
await arrangeSnapshotFailure(slockHome);
|
|
3666
3817
|
}
|
|
3667
3818
|
const { opts: upgradeOpts } = buildSimulatedDeps(slockHome, opts);
|
|
3668
|
-
await
|
|
3819
|
+
await mkdir13(upgradeOpts.currentBinaryDir, { recursive: true });
|
|
3669
3820
|
let outcome;
|
|
3670
3821
|
try {
|
|
3671
3822
|
outcome = await runUpgrade(slockHome, upgradeOpts);
|
|
@@ -3702,7 +3853,7 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
|
|
|
3702
3853
|
}
|
|
3703
3854
|
|
|
3704
3855
|
// src/upgradeInstallSmoke.ts
|
|
3705
|
-
import { copyFile, mkdir as
|
|
3856
|
+
import { copyFile, mkdir as mkdir14, readFile as readFile13 } from "fs/promises";
|
|
3706
3857
|
import { createHash as createHash5 } from "crypto";
|
|
3707
3858
|
import { dirname as dirname11, isAbsolute, join as join9, resolve as pathResolve } from "path";
|
|
3708
3859
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -3725,7 +3876,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
|
|
|
3725
3876
|
const spawnFreshSupervisor = deps.spawnFreshSupervisor ?? (async (h) => {
|
|
3726
3877
|
await spawnDetachedSupervisor(h);
|
|
3727
3878
|
});
|
|
3728
|
-
await
|
|
3879
|
+
await mkdir14(slockHome, { recursive: true });
|
|
3729
3880
|
const outcome = await runUpgrade(slockHome, {
|
|
3730
3881
|
targetVersion: opts.targetVersion,
|
|
3731
3882
|
fromVersion,
|
|
@@ -3868,6 +4019,9 @@ program.command("start").argument("[serverSlug]", "optional: verify this server
|
|
|
3868
4019
|
})
|
|
3869
4020
|
);
|
|
3870
4021
|
}));
|
|
4022
|
+
program.command("stop").description("Stop the Computer supervisor (and all managed per-server daemons).").action(withCliExit(async () => {
|
|
4023
|
+
await withMutationLock(() => runStop());
|
|
4024
|
+
}));
|
|
3871
4025
|
program.command("doctor").argument("[serverSlug]", "optional: scope detail (recent crashes) to one server").description("Diagnose login + per-server attachments + per-server preflight (no secrets).").option("--json", "emit the machine-readable report").option("--cleanup", "after diagnosis, run the local residue cleanup pass").option("--fix", "alias for --cleanup (same behavior)").option("--reset-health", "clear <serverSlug>'s crash history so supervisor resumes auto-restart").action(
|
|
3872
4026
|
withCliExit(
|
|
3873
4027
|
async (serverSlug, opts) => {
|
package/package.json
CHANGED