@slock-ai/computer 0.0.5 → 0.0.7

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.
Files changed (2) hide show
  1. package/dist/index.js +111 -18
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -298,6 +298,19 @@ function adoptionLogPath(slockHome) {
298
298
  function channelPath(slockHome) {
299
299
  return path.join(computerDir(slockHome), "channel");
300
300
  }
301
+ function upgradeStagingDir(slockHome, version) {
302
+ return path.join(computerDir(slockHome), "upgrade-staging", version);
303
+ }
304
+ function upgradeSnapshotPath(slockHome) {
305
+ return path.join(computerDir(slockHome), "upgrade-snapshot.json");
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
+ }
301
314
 
302
315
  // src/output.ts
303
316
  var CliExit = class extends Error {
@@ -1405,6 +1418,44 @@ async function runResident(serverId, deps = {}) {
1405
1418
  }
1406
1419
  var RECONCILE_INTERVAL_MS = 5e3;
1407
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
+ }
1408
1459
  async function runSupervisorStartupRecovery(slockHome) {
1409
1460
  const parentHoldsLock = process.env[PARENT_LOCK_HELD_ENV_VAR] === "1";
1410
1461
  try {
@@ -1586,7 +1637,7 @@ async function runSupervise() {
1586
1637
  await new Promise(() => {
1587
1638
  });
1588
1639
  }
1589
- async function runStart(opts = {}, _deps = {}) {
1640
+ async function runStart(opts = {}, deps = {}) {
1590
1641
  const slockHome = resolveSlockHome();
1591
1642
  const attached = await listAttachedServerIds(slockHome);
1592
1643
  if (attached.length === 0) {
@@ -1608,9 +1659,11 @@ async function runStart(opts = {}, _deps = {}) {
1608
1659
  const existing = await readPidfileAt(supervisorPidPath(slockHome));
1609
1660
  if (existing && isProcessAlive2(existing)) {
1610
1661
  info(`Supervisor already running (pid ${existing}).`);
1611
- info(
1612
- opts.serverId ? `Marked server ${opts.serverLabel ?? opts.serverId} as managed; its daemon will be ensured on next reconcile tick.` : `Marked all ${attached.length} attached server(s) as managed; their daemons will be ensured on next reconcile tick.`
1613
- );
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));
1614
1667
  return;
1615
1668
  }
1616
1669
  if (opts.foreground) {
@@ -1622,12 +1675,17 @@ async function runStart(opts = {}, _deps = {}) {
1622
1675
  }
1623
1676
  let pid;
1624
1677
  try {
1625
- pid = await spawnDetachedSupervisor(slockHome);
1678
+ pid = await (deps.spawnDetachedSupervisor ?? spawnDetachedSupervisor)(slockHome);
1626
1679
  } catch (err) {
1627
1680
  const msg = err instanceof Error ? err.message : String(err);
1628
1681
  fail("SUPERVISOR_SPAWN_FAILED", msg);
1629
1682
  }
1630
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));
1631
1689
  info(
1632
1690
  `Managing ${managedTargets.length} of ${attached.length} attached server(s). Logs: ${supervisorLogPath(slockHome)}`
1633
1691
  );
@@ -2642,9 +2700,6 @@ function satisfiesTilde(ver, base) {
2642
2700
  }
2643
2701
 
2644
2702
  // src/upgrade.ts
2645
- function upgradeStagingDir(slockHome, version) {
2646
- return join6(computerDir(slockHome), "upgrade-staging", version);
2647
- }
2648
2703
  async function stagePhase(slockHome, version, deps = {}) {
2649
2704
  const npmPack = deps.npmPack ?? defaultNpmPack;
2650
2705
  const fsReadFile = deps.fsReadFile ?? readFile11;
@@ -2754,9 +2809,6 @@ async function cleanupStaged(slockHome, version) {
2754
2809
  } catch {
2755
2810
  }
2756
2811
  }
2757
- function upgradeSnapshotPath(slockHome) {
2758
- return join6(computerDir(slockHome), "upgrade-snapshot.json");
2759
- }
2760
2812
  async function snapshotPhase(slockHome, snap) {
2761
2813
  const path2 = upgradeSnapshotPath(slockHome);
2762
2814
  const tmp = `${path2}.tmp`;
@@ -3298,6 +3350,26 @@ async function locateStagedTarball(stagedPath) {
3298
3350
  return join6(stagedPath, tgz);
3299
3351
  }
3300
3352
 
3353
+ // src/upgradeLog.ts
3354
+ import { chmod as chmod5, mkdir as mkdir12, open as open2 } from "fs/promises";
3355
+ var FILE_MODE = 384;
3356
+ async function appendUpgradeLogEntry(slockHome, entry) {
3357
+ await mkdir12(computerDir(slockHome), { recursive: true });
3358
+ const path2 = upgradeLogPath(slockHome);
3359
+ const at = entry.at ?? formatUpgradeLogTimestamp();
3360
+ const fullEntry = { ...entry, at };
3361
+ const line = JSON.stringify(fullEntry) + "\n";
3362
+ const handle = await open2(path2, "a", FILE_MODE);
3363
+ try {
3364
+ await handle.write(line);
3365
+ } finally {
3366
+ await handle.close();
3367
+ }
3368
+ if (process.platform !== "win32") {
3369
+ await chmod5(path2, FILE_MODE);
3370
+ }
3371
+ }
3372
+
3301
3373
  // src/upgradeCli.ts
3302
3374
  async function defaultSpawnFreshSupervisor(slockHome) {
3303
3375
  await spawnDetachedSupervisor(slockHome);
@@ -3400,14 +3472,35 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
3400
3472
  spawnFreshSupervisor: () => spawnFreshSupervisor(slockHome)
3401
3473
  }
3402
3474
  });
3475
+ const bundle = (version) => ({
3476
+ computerVersion: version
3477
+ });
3478
+ const logTrigger = opts.trigger ?? "cli";
3403
3479
  if (outcome.ok) {
3404
3480
  info(
3405
3481
  `Upgrade ${fromVersion} \u2192 ${targetVersion} succeeded (health-OK in ${outcome.restart?.healthAfterMs ?? 0}ms).`
3406
3482
  );
3483
+ await appendUpgradeLogEntry(slockHome, {
3484
+ fromBundle: bundle(fromVersion),
3485
+ toBundle: bundle(targetVersion),
3486
+ channel: channel2,
3487
+ trigger: logTrigger,
3488
+ outcome: "ok"
3489
+ }).catch(() => {
3490
+ });
3407
3491
  return;
3408
3492
  }
3409
3493
  const code = mapFailurePhaseToCode(outcome);
3410
3494
  const rolledBack = outcome.rolledBack === true ? " (swap rolled back)" : "";
3495
+ await appendUpgradeLogEntry(slockHome, {
3496
+ fromBundle: bundle(fromVersion),
3497
+ toBundle: bundle(targetVersion),
3498
+ channel: channel2,
3499
+ trigger: logTrigger,
3500
+ outcome: "err",
3501
+ errorCode: code
3502
+ }).catch(() => {
3503
+ });
3411
3504
  fail(
3412
3505
  code,
3413
3506
  `Upgrade failed at phase=${outcome.phase}: ${outcome.reason ?? "unknown"}${rolledBack}.`
@@ -3473,7 +3566,7 @@ async function defaultCurrentVersion() {
3473
3566
  }
3474
3567
 
3475
3568
  // src/upgradeTestHarness.ts
3476
- import { mkdir as mkdir12, readdir as readdir4, stat as stat3, writeFile as writeFile10 } from "fs/promises";
3569
+ import { mkdir as mkdir13, readdir as readdir4, stat as stat3, writeFile as writeFile10 } from "fs/promises";
3477
3570
  import { join as join8 } from "path";
3478
3571
  import { createHash as createHash4 } from "crypto";
3479
3572
  var PHASES = /* @__PURE__ */ new Set([
@@ -3544,7 +3637,7 @@ function buildSimulatedDeps(slockHome, opts) {
3544
3637
  if (opts.simulateFail === "extract") {
3545
3638
  return { exitCode: 1, stderr: "simulated extract failure" };
3546
3639
  }
3547
- await mkdir12(join8(destDir, "package"), { recursive: true });
3640
+ await mkdir13(join8(destDir, "package"), { recursive: true });
3548
3641
  await writeFile10(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
3549
3642
  return { exitCode: 0, stderr: "" };
3550
3643
  },
@@ -3605,7 +3698,7 @@ function buildSimulatedDeps(slockHome, opts) {
3605
3698
  }
3606
3699
  async function arrangeSnapshotFailure(slockHome) {
3607
3700
  const snapshotPath = join8(slockHome, "computer", "upgrade-snapshot.json");
3608
- await mkdir12(snapshotPath, { recursive: true });
3701
+ await mkdir13(snapshotPath, { recursive: true });
3609
3702
  }
3610
3703
  async function pathInfo(path2) {
3611
3704
  try {
@@ -3660,12 +3753,12 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
3660
3753
  process.exitCode = 1;
3661
3754
  return;
3662
3755
  }
3663
- await mkdir12(slockHome, { recursive: true });
3756
+ await mkdir13(slockHome, { recursive: true });
3664
3757
  if (opts.simulateFail === "snapshot") {
3665
3758
  await arrangeSnapshotFailure(slockHome);
3666
3759
  }
3667
3760
  const { opts: upgradeOpts } = buildSimulatedDeps(slockHome, opts);
3668
- await mkdir12(upgradeOpts.currentBinaryDir, { recursive: true });
3761
+ await mkdir13(upgradeOpts.currentBinaryDir, { recursive: true });
3669
3762
  let outcome;
3670
3763
  try {
3671
3764
  outcome = await runUpgrade(slockHome, upgradeOpts);
@@ -3702,7 +3795,7 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
3702
3795
  }
3703
3796
 
3704
3797
  // src/upgradeInstallSmoke.ts
3705
- import { copyFile, mkdir as mkdir13, readFile as readFile13 } from "fs/promises";
3798
+ import { copyFile, mkdir as mkdir14, readFile as readFile13 } from "fs/promises";
3706
3799
  import { createHash as createHash5 } from "crypto";
3707
3800
  import { dirname as dirname11, isAbsolute, join as join9, resolve as pathResolve } from "path";
3708
3801
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -3725,7 +3818,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
3725
3818
  const spawnFreshSupervisor = deps.spawnFreshSupervisor ?? (async (h) => {
3726
3819
  await spawnDetachedSupervisor(h);
3727
3820
  });
3728
- await mkdir13(slockHome, { recursive: true });
3821
+ await mkdir14(slockHome, { recursive: true });
3729
3822
  const outcome = await runUpgrade(slockHome, {
3730
3823
  targetVersion: opts.targetVersion,
3731
3824
  fromVersion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/computer",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Slock Computer — standalone human/local-machine control-plane CLI (login + attach). Distinct from the agent-facing @slock-ai/cli.",
5
5
  "type": "module",
6
6
  "bin": {