@slock-ai/computer 0.0.14 → 0.0.16-alpha.0

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.
@@ -1,3 +1,71 @@
1
+ type DaemonState = {
2
+ running: true;
3
+ pid: number;
4
+ } | {
5
+ running: false;
6
+ };
7
+ type ServerHealth = "ok" | "degraded" | "offline";
8
+ interface ServerStatusRow {
9
+ serverId: string;
10
+ serverSlug: string | null;
11
+ serverMachineId: string;
12
+ serverUrl: string;
13
+ attachedAt: string | null;
14
+ daemonLogPath: string;
15
+ daemon: DaemonState;
16
+ /** v6 §11 health enum surfaced in `status` table (PR-H §3.3). */
17
+ health: ServerHealth;
18
+ }
19
+ interface ComputerStatusReport {
20
+ slockHome: string;
21
+ loggedIn: boolean;
22
+ userId: string | null;
23
+ loginServerUrl: string | null;
24
+ userSessionError: string | null;
25
+ service: DaemonState & {
26
+ logPath: string;
27
+ };
28
+ servers: ServerStatusRow[];
29
+ }
30
+
31
+ interface RunnerListItem {
32
+ agentId: string;
33
+ name: string;
34
+ status: string;
35
+ model: string;
36
+ runtime: string;
37
+ }
38
+
39
+ /**
40
+ * Per-runner lifecycle states (§3.2). One state at a time per runner.
41
+ *
42
+ * starting — spawned, not yet reporting ready
43
+ * running — healthy, reporting heartbeats
44
+ * degraded — alive but not satisfying health invariants (e.g. crash
45
+ * budget near breach, see §1/§2)
46
+ * crashed — observed exit / fatal signal
47
+ * stopped — intentional stop (operator-driven via `runners stop` or
48
+ * service shutdown)
49
+ */
50
+ declare const RUNNER_STATE_VALUES: readonly ["starting", "running", "degraded", "crashed", "stopped"];
51
+ type RunnerState = (typeof RUNNER_STATE_VALUES)[number];
52
+ declare function isRunnerState(value: unknown): value is RunnerState;
53
+ /**
54
+ * Service (service) lifecycle states (§3.2). Mirrors runner shape but
55
+ * narrower — the service is a singleton per install root.
56
+ *
57
+ * starting — pidfile written, socket not yet listening
58
+ * running — socket listening, heartbeat active
59
+ * degraded — service alive but one or more invariants violated
60
+ * (e.g. SERVICE_DEGRADED §7.3 crash-budget breach)
61
+ * stopping — graceful shutdown in flight (SERVICE_SHUTTING_DOWN
62
+ * §7.3 emitted to clients)
63
+ * stopped — pidfile cleared, socket closed
64
+ */
65
+ declare const SERVICE_STATE_VALUES: readonly ["starting", "running", "degraded", "stopping", "stopped"];
66
+ type ServiceState = (typeof SERVICE_STATE_VALUES)[number];
67
+ declare function isServiceState(value: unknown): value is ServiceState;
68
+
1
69
  /**
2
70
  * IPC error code family — the seed that `ServiceClient.request` and
3
71
  * `ServiceClient.events` propagate. Forward-only post v9-sign: additions
@@ -7,7 +75,7 @@
7
75
  * lands in a follow-up reconcile commit that also unifies the codes
8
76
  * currently emitted via `ComputerServiceError` across services/*.
9
77
  */
10
- declare const IPC_ERROR_CODES: readonly ["IPC_FRAME_TOO_LARGE", "IPC_PROTOCOL_VERSION_UNSUPPORTED", "IPC_HEARTBEAT_TIMEOUT", "IPC_MALFORMED_FRAME", "IPC_REQUEST_TIMEOUT", "IPC_REQUEST_CANCELED", "IPC_CLIENT_CLOSED"];
78
+ declare const IPC_ERROR_CODES: readonly ["IPC_FRAME_TOO_LARGE", "IPC_PROTOCOL_VERSION_UNSUPPORTED", "IPC_PROTOCOL_HANDSHAKE_FAILED", "IPC_HEARTBEAT_TIMEOUT", "IPC_MALFORMED_FRAME", "IPC_REQUEST_TIMEOUT", "IPC_REQUEST_CANCELED", "IPC_CLIENT_CLOSED"];
11
79
  type IpcErrorCode = (typeof IPC_ERROR_CODES)[number];
12
80
  /**
13
81
  * Runtime narrow guard — exposed so consumers can pattern-match without
@@ -31,41 +99,47 @@ interface RequestOptions {
31
99
  * Typed RPC table — keys are kebab-case wire literals (§5.5), values are
32
100
  * per-method `{ params, result }` pairs. The v9 sign-lock seed listed
33
101
  * here is the §3.2 6-method floor; new methods are additive minor bumps
34
- * (extending this map). Param/result payload shapes are intentionally
35
- * `unknown` in this seed — they are pinned in the follow-up commit that
36
- * lands the state-reader surface (`readServiceStatus` / `readRunnerStatus`
37
- * / `listRunners`) alongside the IPC handler signatures.
102
+ * (extending this map).
103
+ *
104
+ * State-reader results (`service-status` / `runner-status` /
105
+ * `list-runners`) are pinned to the lib reader return types — the
106
+ * D-stage §4 IPC handlers in PR-impl-2 satisfy the wire surface by
107
+ * delegating to those readers (mechanical `satisfies`).
108
+ *
109
+ * Mutation results (`upgrade-start` / `reset-service` / `reset-runner`)
110
+ * remain `unknown` here — they pin alongside the PR-impl-3 §X migration /
111
+ * verb pipeline that owns the corresponding handlers.
38
112
  */
39
113
  interface RequestMethodMap {
40
114
  "service-status": {
41
115
  params: void;
42
- result: unknown;
116
+ result: ServiceStatusResult;
43
117
  };
44
118
  "runner-status": {
45
119
  params: {
46
120
  serverId: string;
47
121
  };
48
- result: unknown;
122
+ result: RunnerStatusResult;
49
123
  };
50
124
  "list-runners": {
51
125
  params: void;
52
- result: unknown;
126
+ result: ListRunnersResult;
53
127
  };
54
128
  "upgrade-start": {
55
129
  params: {
56
- target?: string;
130
+ targetVersion?: string;
57
131
  };
58
- result: unknown;
132
+ result: UpgradeStartResult;
59
133
  };
60
134
  "reset-service": {
61
135
  params: void;
62
- result: unknown;
136
+ result: ResetServiceResult;
63
137
  };
64
138
  "reset-runner": {
65
139
  params: {
66
140
  serverId: string;
67
141
  };
68
- result: unknown;
142
+ result: ResetRunnerResult;
69
143
  };
70
144
  }
71
145
  /**
@@ -148,34 +222,264 @@ interface ConnectServiceOptions {
148
222
  type ConnectService = (installRoot: string, options?: ConnectServiceOptions) => Promise<ServiceClient>;
149
223
 
150
224
  /**
151
- * Per-runner lifecycle states (§3.2). One state at a time per runner.
225
+ * Canonical lib-side name for the `service-status` reader result, aligned
226
+ * with the wire method literal. Identical shape to `ComputerStatusReport`
227
+ * (re-exported above for callers that prefer the historical internal
228
+ * name).
152
229
  *
153
- * starting — spawned, not yet reporting ready
154
- * running — healthy, reporting heartbeats
155
- * degraded — alive but not satisfying health invariants (e.g. crash
156
- * budget near breach, see §1/§2)
157
- * crashed — observed exit / fatal signal
158
- * stopped — intentional stop (operator-driven via `runners stop` or
159
- * supervisor shutdown)
230
+ * SECRET-FREE INVARIANT: the report carries server attachment IDENTITY
231
+ * and pid/liveness derived from filesystem state but NEVER the user
232
+ * access token or any `sk_computer_*` credential (status.ts §3.3.1
233
+ * redline, enforced by `status.test.ts` token-leak guards).
160
234
  */
161
- declare const RUNNER_STATE_VALUES: readonly ["starting", "running", "degraded", "crashed", "stopped"];
162
- type RunnerState = (typeof RUNNER_STATE_VALUES)[number];
163
- declare function isRunnerState(value: unknown): value is RunnerState;
235
+ type ServiceStatusResult = ComputerStatusReport;
164
236
  /**
165
- * Service (supervisor) lifecycle states (§3.2). Mirrors runner shape but
166
- * narrower the service is a singleton per install root.
237
+ * Per-server block in `ListRunnersResult.servers[]`. The discriminator
238
+ * preserves the underlying `RunnersClient.list()` outcome so a single
239
+ * server's `unauthorized` / `error` does NOT mask another server's data.
240
+ */
241
+ type RunnerListPerServer = {
242
+ serverId: string;
243
+ serverSlug: string | null;
244
+ status: "ok";
245
+ whitelist: string[];
246
+ runners: RunnerListItem[];
247
+ } | {
248
+ serverId: string;
249
+ serverSlug: string | null;
250
+ status: "unauthorized";
251
+ } | {
252
+ serverId: string;
253
+ serverSlug: string | null;
254
+ status: "error";
255
+ code: string;
256
+ };
257
+ /**
258
+ * Aggregate result of `listRunners(installRoot, opts?)`. When `opts.serverId`
259
+ * is omitted, `servers[]` contains one block per attached server (zero
260
+ * blocks if the Computer has no attachments). When `opts.serverId` is set,
261
+ * `servers[]` contains exactly that server's block, or the call throws
262
+ * `StateReaderError("NOT_ATTACHED")` if the id is not attached.
263
+ */
264
+ interface ListRunnersResult {
265
+ servers: RunnerListPerServer[];
266
+ }
267
+ /**
268
+ * Result of `readRunnerStatus(installRoot, serverId)` — per-server state
269
+ * row plus the runners running on it. Unauthorized / error discriminate
270
+ * the runners-API outcome while still surfacing the `server` row so the
271
+ * caller has the daemon state context.
272
+ */
273
+ type RunnerStatusResult = {
274
+ status: "ok";
275
+ server: ServerStatusRow;
276
+ whitelist: string[];
277
+ runners: RunnerListItem[];
278
+ } | {
279
+ status: "unauthorized";
280
+ server: ServerStatusRow;
281
+ } | {
282
+ status: "error";
283
+ server: ServerStatusRow;
284
+ code: string;
285
+ };
286
+ /**
287
+ * State-reader error closed set. Library boundary errors only — does NOT
288
+ * include the CLI ambient-rule errors (`NO_ATTACHMENT` / `AMBIGUOUS_SERVER`)
289
+ * which are v4 §6 ergonomics handled by the CLI wrapper, not the lib
290
+ * primitive.
291
+ */
292
+ declare const STATE_READER_ERROR_CODES: readonly ["NOT_ATTACHED", "INVALID_ATTACHMENT"];
293
+ type StateReaderErrorCode = (typeof STATE_READER_ERROR_CODES)[number];
294
+ /**
295
+ * Library-side error envelope thrown by state-readers. Lib readers
296
+ * NEVER call `fail()` / `process.exit` — CLI wrappers translate this to
297
+ * `fail(code, message)` at the boundary; D-stage IPC handlers map it to
298
+ * an IPC error frame.
299
+ */
300
+ declare class StateReaderError extends Error {
301
+ readonly code: StateReaderErrorCode;
302
+ constructor(code: StateReaderErrorCode, message: string);
303
+ }
304
+
305
+ /**
306
+ * Result of `reset-service` (`slock-computer reset --service`). Clears
307
+ * the service-level `crashHistory` and transitions service state
308
+ * `degraded → running`. MUST NOT kill runners (§1.3).
167
309
  *
168
- * starting — pidfile written, socket not yet listening
169
- * running — socket listening, heartbeat active
170
- * degraded — service alive but one or more invariants violated
171
- * (e.g. SERVICE_DEGRADED §7.3 crash-budget breach)
172
- * stopping — graceful shutdown in flight (SERVICE_SHUTTING_DOWN
173
- * §7.3 emitted to clients)
174
- * stopped — pidfile cleared, socket closed
310
+ * `previousState` reports the state observed prior to the reset write —
311
+ * for callers that want to log/surface "was already running" vs
312
+ * "recovered from degraded". `clearedCrashCount` counts the entries
313
+ * removed from `service.state.json::crashHistory`.
175
314
  */
176
- declare const SERVICE_STATE_VALUES: readonly ["starting", "running", "degraded", "stopping", "stopped"];
177
- type ServiceState = (typeof SERVICE_STATE_VALUES)[number];
178
- declare function isServiceState(value: unknown): value is ServiceState;
315
+ interface ResetServiceResult {
316
+ status: "ok";
317
+ previousState: ServiceState;
318
+ clearedCrashCount: number;
319
+ }
320
+ /**
321
+ * Result of `reset-runner` (`slock-computer reset --runner <slug>`).
322
+ * Clears the per-runner `crashHistory` and transitions runner state
323
+ * `degraded → running`. MUST NOT respawn or kill the runner process
324
+ * (§2.4).
325
+ *
326
+ * `status: "not-found"` is returned when the resolved `serverId` does
327
+ * not correspond to an attached server (the CLI ambient `--server` slug
328
+ * resolver returns its own error before the handler is invoked; this
329
+ * branch defends the IPC path where the caller passes a serverId
330
+ * directly).
331
+ */
332
+ type ResetRunnerResult = {
333
+ status: "ok";
334
+ serverId: string;
335
+ previousState: RunnerState;
336
+ clearedCrashCount: number;
337
+ } | {
338
+ status: "not-found";
339
+ serverId: string;
340
+ };
341
+ /**
342
+ * Result of `upgrade-start` (§12 phase pipeline trigger). The same
343
+ * pipeline is funneled by manual CLI / auto scheduler / IPC; this
344
+ * result describes the trigger outcome only — the per-phase events
345
+ * stream over `ServiceEvent` (`upgrade-log-appended` kind).
346
+ *
347
+ * `status: "already-running"` is returned when an upgrade is already
348
+ * in flight; `upgradeId` then refers to the in-flight upgrade so the
349
+ * caller can attach to its event stream instead of starting a new one.
350
+ */
351
+ interface UpgradeStartResult {
352
+ status: "started" | "already-running";
353
+ upgradeId: string;
354
+ targetVersion: string;
355
+ }
356
+ /**
357
+ * Identity record for a legacy `@slock-ai/daemon` machine candidate that
358
+ * `detectLegacyMigration` discovered locally on this Computer install
359
+ * root. The shape is identity-only — it never carries credentials.
360
+ *
361
+ * `machineId` is the legacy daemon's lock-id (`machine-<sha256(apiKey)
362
+ * [0..15]>`). It is stable across legacy daemon restarts but changes if
363
+ * the user rotates the api key. The id is what the user would see in
364
+ * `<installRoot>/machines/` directory listings, and is what
365
+ * `AdoptLegacyService.migrate()` uses to find the lock owner file
366
+ * during migration.
367
+ *
368
+ * `machineName` is the host display name observed in the legacy
369
+ * daemon's `daemon.lock/owner.json` (typically `os.hostname()` at
370
+ * legacy daemon spawn time). Best-effort, may be undefined for stale
371
+ * lock files.
372
+ *
373
+ * `serverUrl` is the API server the legacy daemon was attached to.
374
+ * Used by the migrate prompt to surface "you are about to migrate
375
+ * machine X attached to <server>" before the user accepts.
376
+ */
377
+ interface LegacyMachineCandidate {
378
+ machineId: string;
379
+ machineName?: string;
380
+ serverUrl?: string;
381
+ }
382
+ /**
383
+ * §X migration detection result — the discriminated union returned by
384
+ * `detectLegacyMigration(installRoot, loggedInUserId)`.
385
+ *
386
+ * Per RFC §X.2:
387
+ * - `match`: exactly one local legacy daemon machine. The setup flow
388
+ * SHOULD prompt the user to migrate before falling back to fresh
389
+ * setup. Migration itself is delegated to `AdoptLegacyService.
390
+ * migrate()` (preserved as internal helper per §X.6 — 11 typed
391
+ * errors / 4 events / Result discriminator byte-identical).
392
+ * - `no-match`: zero local legacy daemon machines. Fresh setup
393
+ * proceeds without any migrate prompt.
394
+ * - `ambiguous`: two or more local legacy daemon machines. Per §X.2
395
+ * "no auto-listed-pick fallback" — fresh setup proceeds without
396
+ * any migrate prompt; the user is expected to start the legacy
397
+ * daemon they want migrated first to disambiguate.
398
+ *
399
+ * Fingerprint discipline (best-effort non-authoritative — Hao
400
+ * `msg=4d170194`): the candidate set is computed from local
401
+ * `<installRoot>/machines/machine-*` directory state. It identifies
402
+ * "this host has legacy daemon installs" — it does NOT prove ownership.
403
+ * Final adoption still walks the existing
404
+ * `AdoptLegacyService.migrate()` credential validation path; a match
405
+ * is a prompt-gate, not a trust-gate.
406
+ */
407
+ /**
408
+ * Closed set of `MigrationDetection.kind` discriminator values. Mirrors
409
+ * the `IPC_ERROR_CODES` / `RUNNER_STATE_VALUES` `as const` tuple pattern
410
+ * so consumers can pattern-match exhaustively and so a future §7.4 CI
411
+ * gate can verify the runtime kind against the type union without an
412
+ * `as` cast (state-machine-modeling discipline — correlated/mutex
413
+ * properties land as a closed-set state-enum).
414
+ */
415
+ declare const MIGRATION_DETECTION_KINDS: readonly ["match", "no-match", "ambiguous"];
416
+ type MigrationDetectionKind = (typeof MIGRATION_DETECTION_KINDS)[number];
417
+ /** Runtime narrow guard for `MigrationDetection.kind`. */
418
+ declare function isMigrationDetectionKind(value: unknown): value is MigrationDetectionKind;
419
+ type MigrationDetection = {
420
+ kind: "match";
421
+ machineId: string;
422
+ machineName?: string;
423
+ serverUrl?: string;
424
+ } | {
425
+ kind: "no-match";
426
+ } | {
427
+ kind: "ambiguous";
428
+ candidates: LegacyMachineCandidate[];
429
+ };
430
+
431
+ /**
432
+ * Detect whether `installRoot` already contains legacy
433
+ * `@slock-ai/daemon` machine state that could be migrated to a
434
+ * Computer attachment under the logged-in user.
435
+ *
436
+ * Returns a discriminated `MigrationDetection`:
437
+ * - `match` (exactly one candidate) — caller SHOULD prompt the user
438
+ * to migrate before fresh setup (§X.2 step 3).
439
+ * - `no-match` (zero candidates) — caller proceeds with fresh
440
+ * setup, NO prompt (§X.2 step 5).
441
+ * - `ambiguous` (two-or-more candidates) — caller proceeds with
442
+ * fresh setup, NO prompt (§X.2 step 5; "no auto-listed-pick
443
+ * fallback").
444
+ *
445
+ * Lib-pure: no env reads, no terminal IO, no process.exit. Filesystem
446
+ * errors are swallowed and treated as "no evidence" (per the doc-
447
+ * block above).
448
+ *
449
+ * @param installRoot The Slock home (`SLOCK_HOME`) to scan. Resolved
450
+ * by the CLI wrapper via `resolveSlockHome`.
451
+ * @param loggedInUserId Reserved for the server-roster intersection
452
+ * (forward-compat per §X.2 step 1). v0 does not consume this value;
453
+ * accept the typed argument so consumers of this function can
454
+ * already pass it.
455
+ */
456
+ declare function detectLegacyMigration(installRoot: string, loggedInUserId: string): Promise<MigrationDetection>;
457
+
458
+ /**
459
+ * Read the Computer-level aggregate status from `installRoot`. Identical
460
+ * shape to `buildStatusReport(installRoot)` — exposed under the
461
+ * wire-aligned name `readServiceStatus` for IPC `service-status` parity.
462
+ */
463
+ declare function readServiceStatus(installRoot: string): Promise<ServiceStatusResult>;
464
+ /**
465
+ * Read a single attached server's daemon state row plus the runners
466
+ * currently running on it. Throws `StateReaderError("NOT_ATTACHED")` if
467
+ * `serverId` is not in the attached set, or `"INVALID_ATTACHMENT"` if
468
+ * the runner.state.json under that id is unreadable.
469
+ */
470
+ declare function readRunnerStatus(installRoot: string, serverId: string): Promise<RunnerStatusResult>;
471
+ /**
472
+ * List runners across attached servers. With no `serverId` filter,
473
+ * returns one block per attached server (zero if no attachments). With
474
+ * a `serverId` filter, returns exactly that server's block or throws
475
+ * `StateReaderError("NOT_ATTACHED")`.
476
+ *
477
+ * Per-server discriminator preserves `unauthorized` / `error` outcomes
478
+ * so consumers can pattern-match without losing context.
479
+ */
480
+ declare function listRunners(installRoot: string, opts?: {
481
+ serverId?: string;
482
+ }): Promise<ListRunnersResult>;
179
483
 
180
484
  type UpgradeTrigger = "cli" | "web" | "tray";
181
485
  /**
@@ -247,4 +551,4 @@ interface UpgradeLogEntryErr {
247
551
  */
248
552
  declare function assertUpgradeLogEntry(entry: UpgradeLogEntry): void;
249
553
 
250
- export { type ConnectService, type ConnectServiceOptions, IPC_ERROR_CODES, type IpcErrorCode, RUNNER_STATE_VALUES, type RequestMethodMap, type RequestOptions, type RunnerState, SERVICE_STATE_VALUES, type ServiceClient, ServiceClientError, type ServiceEvent, type ServiceState, UPGRADE_ERROR_CODES, type UpgradeBundle, type UpgradeErrorCode, type UpgradeLogEntry, type UpgradeLogEntryErr, type UpgradeLogEntryOk, type UpgradeTrigger, assertUpgradeLogEntry, isIpcErrorCode, isRunnerState, isServiceState };
554
+ export { type ComputerStatusReport, type ConnectService, type ConnectServiceOptions, type DaemonState, IPC_ERROR_CODES, type IpcErrorCode, type LegacyMachineCandidate, type ListRunnersResult, MIGRATION_DETECTION_KINDS, type MigrationDetection, type MigrationDetectionKind, RUNNER_STATE_VALUES, type RequestMethodMap, type RequestOptions, type ResetRunnerResult, type ResetServiceResult, type RunnerListItem as RunnerInfo, type RunnerListPerServer, type RunnerState, type RunnerStatusResult, SERVICE_STATE_VALUES, STATE_READER_ERROR_CODES, type ServerHealth, type ServerStatusRow, type ServiceClient, ServiceClientError, type ServiceEvent, type ServiceState, type ServiceStatusResult, StateReaderError, type StateReaderErrorCode, UPGRADE_ERROR_CODES, type UpgradeBundle, type UpgradeErrorCode, type UpgradeLogEntry, type UpgradeLogEntryErr, type UpgradeLogEntryOk, type UpgradeStartResult, type UpgradeTrigger, assertUpgradeLogEntry, detectLegacyMigration, isIpcErrorCode, isMigrationDetectionKind, isRunnerState, isServiceState, listRunners, readRunnerStatus, readServiceStatus };