@slock-ai/computer 0.0.6 → 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 +105 -12
  2. 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 = {}, _deps = {}) {
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
- info(
1618
- 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.`
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,12 +1675,17 @@ 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
  );
@@ -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.6",
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": {