@slock-ai/computer 0.0.37 → 0.0.52

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,679 +0,0 @@
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
- serverRunnerLogPath: 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 LegacyMachineRosterEntry {
32
- daemonId: string;
33
- apiKeyFingerprint: string;
34
- machineName: string;
35
- hostname: string | null;
36
- lastSeenAt: string | null;
37
- }
38
- type LegacyMachineRosterResult = {
39
- status: "success";
40
- entries: LegacyMachineRosterEntry[];
41
- } | {
42
- status: "auth_required";
43
- } | {
44
- status: "not_authorized";
45
- } | {
46
- status: "disabled";
47
- } | {
48
- status: "error";
49
- code: string;
50
- };
51
- /** A server the user belongs to. `role` mirrors the server-side
52
- * `server_members.role` enum (`owner | admin | member`). */
53
- interface UserServerEntry {
54
- id: string;
55
- name: string;
56
- slug: string;
57
- role: "owner" | "admin" | "member";
58
- }
59
- type UserServersResult = {
60
- status: "success";
61
- servers: UserServerEntry[];
62
- } | {
63
- status: "auth_required";
64
- } | {
65
- status: "error";
66
- code: string;
67
- };
68
- declare class ServersClient {
69
- private readonly baseUrl;
70
- private readonly accessToken;
71
- constructor(baseUrl: string, accessToken: string);
72
- private url;
73
- list(): Promise<UserServersResult>;
74
- }
75
- declare class LegacyMachinesClient {
76
- private readonly baseUrl;
77
- private readonly accessToken;
78
- constructor(baseUrl: string, accessToken: string);
79
- private url;
80
- list(serverSlug: string): Promise<LegacyMachineRosterResult>;
81
- }
82
- interface RunnerListItem {
83
- agentId: string;
84
- name: string;
85
- status: string;
86
- model: string;
87
- runtime: string;
88
- }
89
-
90
- /**
91
- * Per-runner lifecycle states (§3.2). One state at a time per runner.
92
- *
93
- * starting — spawned, not yet reporting ready
94
- * running — healthy, reporting heartbeats
95
- * degraded — alive but not satisfying health invariants (e.g. crash
96
- * budget near breach, see §1/§2)
97
- * crashed — observed exit / fatal signal
98
- * stopped — intentional stop (operator-driven via `runners stop` or
99
- * service shutdown)
100
- */
101
- declare const RUNNER_STATE_VALUES: readonly ["starting", "running", "degraded", "crashed", "stopped"];
102
- type RunnerState = (typeof RUNNER_STATE_VALUES)[number];
103
- declare function isRunnerState(value: unknown): value is RunnerState;
104
- /**
105
- * Service (service) lifecycle states (§3.2). Mirrors runner shape but
106
- * narrower — the service is a singleton per install root.
107
- *
108
- * starting — pidfile written, socket not yet listening
109
- * running — socket listening, heartbeat active
110
- * degraded — service alive but one or more invariants violated
111
- * (e.g. SERVICE_DEGRADED §7.3 crash-budget breach)
112
- * stopping — graceful shutdown in flight (SERVICE_SHUTTING_DOWN
113
- * §7.3 emitted to clients)
114
- * stopped — pidfile cleared, socket closed
115
- */
116
- declare const SERVICE_STATE_VALUES: readonly ["starting", "running", "degraded", "stopping", "stopped"];
117
- type ServiceState = (typeof SERVICE_STATE_VALUES)[number];
118
- declare function isServiceState(value: unknown): value is ServiceState;
119
-
120
- /**
121
- * IPC error code family — the seed that `ServiceClient.request` and
122
- * `ServiceClient.events` propagate. Forward-only post v9-sign: additions
123
- * are additive minor library bumps; renames / removals are major.
124
- *
125
- * Full §7.3 enumeration (SETUP/ATTACH/MIGRATE/UPGRADE/SERVICE families)
126
- * lands in a follow-up reconcile commit that also unifies the codes
127
- * currently emitted via `ComputerServiceError` across services/*.
128
- */
129
- 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"];
130
- type IpcErrorCode = (typeof IPC_ERROR_CODES)[number];
131
- /**
132
- * Runtime narrow guard — exposed so consumers can pattern-match without
133
- * pulling in a generic `as` cast. The CI gate (§7.4) over a final
134
- * `ERROR_CODES` union will subsume this once the reconcile lands.
135
- */
136
- declare function isIpcErrorCode(value: unknown): value is IpcErrorCode;
137
- /**
138
- * Caller-facing options for `ServiceClient.request`. Pairs active cancel
139
- * (`signal` — primary G-stage unmount path) with passive deadline
140
- * (`timeoutMs` — library-side safety net). On either path the client
141
- * emits an IPC `cancel` frame for the in-flight request id and rejects
142
- * the returned promise with `IPC_REQUEST_CANCELED` or
143
- * `IPC_REQUEST_TIMEOUT` respectively (§4.4).
144
- */
145
- interface RequestOptions {
146
- signal?: AbortSignal;
147
- timeoutMs?: number;
148
- }
149
- /**
150
- * Typed RPC table — keys are kebab-case wire literals (§5.5), values are
151
- * per-method `{ params, result }` pairs. The v9 sign-lock seed listed
152
- * here is the §3.2 6-method floor; new methods are additive minor bumps
153
- * (extending this map).
154
- *
155
- * State-reader results (`service-status` / `runner-status` /
156
- * `list-runners`) are pinned to the lib reader return types — the
157
- * D-stage §4 IPC handlers in PR-impl-2 satisfy the wire surface by
158
- * delegating to those readers (mechanical `satisfies`).
159
- *
160
- * Mutation results (`upgrade-start` / `reset-service` / `reset-runner`)
161
- * remain `unknown` here — they pin alongside the PR-impl-3 §X migration /
162
- * verb pipeline that owns the corresponding handlers.
163
- */
164
- interface RequestMethodMap {
165
- "service-status": {
166
- params: void;
167
- result: ServiceStatusResult;
168
- };
169
- "runner-status": {
170
- params: {
171
- serverId: string;
172
- };
173
- result: RunnerStatusResult;
174
- };
175
- "list-runners": {
176
- params: void;
177
- result: ListRunnersResult;
178
- };
179
- "upgrade-start": {
180
- params: {
181
- targetVersion?: string;
182
- };
183
- result: UpgradeStartResult;
184
- };
185
- "reset-service": {
186
- params: void;
187
- result: ResetServiceResult;
188
- };
189
- "reset-runner": {
190
- params: {
191
- serverId: string;
192
- };
193
- result: ResetRunnerResult;
194
- };
195
- }
196
- /**
197
- * Server-pushed event over `ServiceClient.events`. The discriminant field
198
- * is `kind` (§5.5 — established at v9.7 sign); values are past-participle
199
- * kebab-case literals. The 6-kind enumeration is the v9.8 §4.4 floor.
200
- *
201
- * Payload shapes finalize alongside §4 IPC impl (PR-impl-2, liuliu-owned);
202
- * the discriminator is locked here so consumers can write exhaustive
203
- * switches today.
204
- *
205
- * Note: v9.8 §4.4 has an example block still showing the v9.7-pre field
206
- * name `topic` + present-tense values; §5.5 is the byte-pin source of
207
- * truth (`kind` + past-participle). The §4.4 example will be reconciled
208
- * in a follow-up doc-clean pass.
209
- */
210
- type ServiceEvent = {
211
- kind: "service-state-changed";
212
- payload: unknown;
213
- } | {
214
- kind: "runner-state-changed";
215
- payload: unknown;
216
- } | {
217
- kind: "runner-attached";
218
- payload: unknown;
219
- } | {
220
- kind: "runner-detached";
221
- payload: unknown;
222
- } | {
223
- kind: "upgrade-log-appended";
224
- payload: unknown;
225
- } | {
226
- kind: "heartbeat";
227
- payload: unknown;
228
- };
229
- /**
230
- * Library-side error envelope thrown by `ServiceClient`. `code` is a
231
- * member of the §7 closed-set; `cause` is retained in-process only and
232
- * MUST NOT cross IPC / CLI fail boundaries (§7.4 redaction).
233
- */
234
- declare class ServiceClientError extends Error {
235
- readonly code: IpcErrorCode;
236
- readonly cause?: unknown;
237
- constructor(code: IpcErrorCode, message: string, cause?: unknown);
238
- }
239
- /**
240
- * Typed IPC client. The implementation in PR-impl-2 §4 satisfies this
241
- * interface; this commit only locks the surface.
242
- *
243
- * Termination semantics (§4.7):
244
- * - `events` completes naturally (`Symbol.asyncIterator` return) on any
245
- * socket-close path (graceful local/remote, transient, TCP RST). The
246
- * `for-await` loop exits without throwing — idiomatic reconnect is a
247
- * plain outer `while (!stopped) { … }` wrap, no try/catch.
248
- * - `events` THROWS `ServiceClientError` only on protocol-level
249
- * failures: handshake mismatch, frame parse, frame > 1 MiB.
250
- * The disjoint split lets consumers distinguish "retry" (iterator end)
251
- * from "give up" (throw) without runtime classification.
252
- */
253
- interface ServiceClient {
254
- events: AsyncIterable<ServiceEvent>;
255
- request<M extends keyof RequestMethodMap>(method: M, params: RequestMethodMap[M]["params"], options?: RequestOptions): Promise<RequestMethodMap[M]["result"]>;
256
- close(): Promise<void>;
257
- }
258
- /**
259
- * Connection options. `protocolVersion` lets a consumer opt-in to a
260
- * specific handshake version; the service negotiates highest mutually
261
- * supported (§4.3) and may reject with `IPC_PROTOCOL_VERSION_UNSUPPORTED`.
262
- */
263
- interface ConnectServiceOptions {
264
- protocolVersion?: number;
265
- }
266
- /**
267
- * Open a typed IPC connection to the Computer service. Throws
268
- * `ServiceClientError` on handshake failure. Concrete implementation
269
- * lands in PR-impl-2 §4 (liuliu-owned); the type is locked here so
270
- * `apps/slock-computer-app` and other library consumers can type-check
271
- * call sites before the transport ships.
272
- */
273
- type ConnectService = (installRoot: string, options?: ConnectServiceOptions) => Promise<ServiceClient>;
274
-
275
- /**
276
- * Canonical lib-side name for the `service-status` reader result, aligned
277
- * with the wire method literal. Identical shape to `ComputerStatusReport`
278
- * (re-exported above for callers that prefer the historical internal
279
- * name).
280
- *
281
- * SECRET-FREE INVARIANT: the report carries server attachment IDENTITY
282
- * and pid/liveness derived from filesystem state but NEVER the user
283
- * access token or any `sk_computer_*` credential (status.ts §3.3.1
284
- * redline, enforced by `status.test.ts` token-leak guards).
285
- */
286
- type ServiceStatusResult = ComputerStatusReport;
287
- /**
288
- * Per-server block in `ListRunnersResult.servers[]`. The discriminator
289
- * preserves the underlying `RunnersClient.list()` outcome so a single
290
- * server's `unauthorized` / `error` does NOT mask another server's data.
291
- */
292
- type RunnerListPerServer = {
293
- serverId: string;
294
- serverSlug: string | null;
295
- status: "ok";
296
- whitelist: string[];
297
- runners: RunnerListItem[];
298
- } | {
299
- serverId: string;
300
- serverSlug: string | null;
301
- status: "unauthorized";
302
- } | {
303
- serverId: string;
304
- serverSlug: string | null;
305
- status: "error";
306
- code: string;
307
- };
308
- /**
309
- * Aggregate result of `listRunners(installRoot, opts?)`. When `opts.serverId`
310
- * is omitted, `servers[]` contains one block per attached server (zero
311
- * blocks if the Computer has no attachments). When `opts.serverId` is set,
312
- * `servers[]` contains exactly that server's block, or the call throws
313
- * `StateReaderError("NOT_ATTACHED")` if the id is not attached.
314
- */
315
- interface ListRunnersResult {
316
- servers: RunnerListPerServer[];
317
- }
318
- /**
319
- * Result of `readRunnerStatus(installRoot, serverId)` — per-server state
320
- * row plus the runners running on it. Unauthorized / error discriminate
321
- * the runners-API outcome while still surfacing the `server` row so the
322
- * caller has the daemon state context.
323
- */
324
- type RunnerStatusResult = {
325
- status: "ok";
326
- server: ServerStatusRow;
327
- whitelist: string[];
328
- runners: RunnerListItem[];
329
- } | {
330
- status: "unauthorized";
331
- server: ServerStatusRow;
332
- } | {
333
- status: "error";
334
- server: ServerStatusRow;
335
- code: string;
336
- };
337
- /**
338
- * State-reader error closed set. Library boundary errors only — does NOT
339
- * include the CLI ambient-rule errors (`NO_ATTACHMENT` / `AMBIGUOUS_SERVER`)
340
- * which are v4 §6 ergonomics handled by the CLI wrapper, not the lib
341
- * primitive.
342
- */
343
- declare const STATE_READER_ERROR_CODES: readonly ["NOT_ATTACHED", "INVALID_ATTACHMENT"];
344
- type StateReaderErrorCode = (typeof STATE_READER_ERROR_CODES)[number];
345
- /**
346
- * Library-side error envelope thrown by state-readers. Lib readers
347
- * NEVER call `fail()` / `process.exit` — CLI wrappers translate this to
348
- * `fail(code, message)` at the boundary; D-stage IPC handlers map it to
349
- * an IPC error frame.
350
- */
351
- declare class StateReaderError extends Error {
352
- readonly code: StateReaderErrorCode;
353
- constructor(code: StateReaderErrorCode, message: string);
354
- }
355
-
356
- /**
357
- * Result of `reset-service` (`slock-computer reset --service`). Clears
358
- * the service-level `crashHistory` and transitions service state
359
- * `degraded → running`. MUST NOT kill runners (§1.3).
360
- *
361
- * `previousState` reports the state observed prior to the reset write —
362
- * for callers that want to log/surface "was already running" vs
363
- * "recovered from degraded". `clearedCrashCount` counts the entries
364
- * removed from `service.state.json::crashHistory`.
365
- */
366
- interface ResetServiceResult {
367
- status: "ok";
368
- previousState: ServiceState;
369
- clearedCrashCount: number;
370
- }
371
- /**
372
- * Result of `reset-runner` (`slock-computer reset --runner <slug>`).
373
- * Clears the per-runner `crashHistory` and transitions runner state
374
- * `degraded → running`. MUST NOT respawn or kill the runner process
375
- * (§2.4).
376
- *
377
- * `status: "not-found"` is returned when the resolved `serverId` does
378
- * not correspond to an attached server (the CLI ambient `--server` slug
379
- * resolver returns its own error before the handler is invoked; this
380
- * branch defends the IPC path where the caller passes a serverId
381
- * directly).
382
- */
383
- type ResetRunnerResult = {
384
- status: "ok";
385
- serverId: string;
386
- previousState: RunnerState;
387
- clearedCrashCount: number;
388
- } | {
389
- status: "not-found";
390
- serverId: string;
391
- };
392
- /**
393
- * Result of `upgrade-start` (§12 phase pipeline trigger). The same
394
- * pipeline is funneled by manual CLI / auto scheduler / IPC; this
395
- * result describes the trigger outcome only — the per-phase events
396
- * stream over `ServiceEvent` (`upgrade-log-appended` kind).
397
- *
398
- * `status: "already-running"` is returned when an upgrade is already
399
- * in flight; `upgradeId` then refers to the in-flight upgrade so the
400
- * caller can attach to its event stream instead of starting a new one.
401
- */
402
- interface UpgradeStartResult {
403
- status: "started" | "already-running";
404
- upgradeId: string;
405
- targetVersion: string;
406
- }
407
- /**
408
- * Identity record for a legacy `@slock-ai/daemon` machine candidate
409
- * surfaced by `detectLegacyMigration`. Each candidate is the result of
410
- * intersecting (a) local `<installRoot>/machines/machine-<fp>/owner.json`
411
- * evidence with (b) the server's `GET /api/computer/legacy-machines`
412
- * roster on `apiKeyFingerprint` for the logged-in user + target server.
413
- * The shape is identity-only — it never carries credentials.
414
- *
415
- * `apiKeyFingerprint` is the v9.9 intersection key —
416
- * `sha256(apiKey).slice(0,16)`. The same value is computed locally by
417
- * the legacy daemon (`packages/daemon/src/machineLock.ts`) and stored
418
- * server-side on `daemons.api_key_fingerprint` (handshake-backfilled).
419
- * Stable as long as the legacy api key is unchanged.
420
- *
421
- * `daemonId` is the server's `daemons.id` UUID (display + post-migration
422
- * identity). It is NOT a join key — fingerprint is. Cody redline
423
- * msg=ec68c27f: server daemonId / machineName are display-only.
424
- *
425
- * `localPath` is the absolute path of the local owner.json that
426
- * supplied the fingerprint side of the intersection. Surfaced so the
427
- * picker can show the operator which on-disk legacy install they are
428
- * about to adopt, and so the manual `--migrate-from` flag's three-gate
429
- * validator (path-readable / owner.json-parseable / fingerprint-in-roster)
430
- * can return a candidate that points at the same path the operator
431
- * passed.
432
- *
433
- * `machineName` / `hostname` / `lastSeenAt` come from the server roster
434
- * row — display-only, used for picker presentation. May be empty for
435
- * stale rows; the picker tolerates blanks.
436
- */
437
- interface LegacyMachineCandidate {
438
- apiKeyFingerprint: string;
439
- daemonId: string;
440
- localPath: string;
441
- machineName: string;
442
- hostname?: string;
443
- lastSeenAt?: string;
444
- }
445
- /**
446
- * §X.2 migration detection result — the discriminated union returned by
447
- * `detectLegacyMigration(installRoot, target)`.
448
- *
449
- * Per RFC v9.9 §X.2:
450
- * - `candidates`: zero-or-more legacy daemons survived the
451
- * local-owner-json ∩ server-roster intersection. Empty intersection
452
- * is a valid `candidates` result with `candidates.length === 0` —
453
- * the setup flow's fresh-attach trigger #1 (§X.4). One-or-more
454
- * candidates → setup picker prompts the operator (TTY) or falls
455
- * through to fresh attach (non-TTY).
456
- * - `server-unavailable`: roster fetch failed (network / auth /
457
- * server gate off / 5xx). Setup falls through to fresh attach
458
- * under the documented "best-effort detection" discipline — a
459
- * transiently unreachable server must NOT block fresh setup.
460
- *
461
- * Fingerprint discipline (Cody msg=ec68c27f redline):
462
- * - `apiKeyFingerprint` is treated as a sensitive identity. It is
463
- * scoped to this caller's authenticated request and never logged.
464
- * - The intersection is setup's adoption identity after user login:
465
- * a candidate proves the server has a roster row keyed by the same
466
- * fingerprint that appears in the local owner.json. The server
467
- * still enforces ownership by requiring the session user to own the
468
- * selected machine row; fingerprint is only the selector.
469
- */
470
- /**
471
- * Closed set of `MigrationDetection.kind` discriminator values. Mirrors
472
- * the `IPC_ERROR_CODES` / `RUNNER_STATE_VALUES` `as const` tuple pattern
473
- * so consumers can pattern-match exhaustively and so a future §7.4 CI
474
- * gate can verify the runtime kind against the type union without an
475
- * `as` cast (state-machine-modeling discipline — correlated/mutex
476
- * properties land as a closed-set state-enum).
477
- */
478
- declare const MIGRATION_DETECTION_KINDS: readonly ["candidates", "server-unavailable"];
479
- type MigrationDetectionKind = (typeof MIGRATION_DETECTION_KINDS)[number];
480
- /** Runtime narrow guard for `MigrationDetection.kind`. */
481
- declare function isMigrationDetectionKind(value: unknown): value is MigrationDetectionKind;
482
- type MigrationDetection = {
483
- kind: "candidates";
484
- candidates: LegacyMachineCandidate[];
485
- } | {
486
- kind: "server-unavailable";
487
- };
488
-
489
- /**
490
- * Roster fetcher contract — `LegacyMachinesClient.list(serverSlug)`
491
- * satisfies this structurally. Defining it as an interface here lets
492
- * tests stub the network call without instantiating undici.
493
- */
494
- interface LegacyMachineRosterClient {
495
- list(serverSlug: string): Promise<LegacyMachineRosterResult>;
496
- }
497
- /**
498
- * Detect whether the logged-in user has a legacy `@slock-ai/daemon`
499
- * machine on the target server that matches a local install on this
500
- * host (intersection on `apiKeyFingerprint`).
501
- *
502
- * Returns:
503
- * - `{ kind: "candidates"; candidates: LegacyMachineCandidate[] }` —
504
- * zero-or-more mutually-known legacy daemons. Empty list is the
505
- * §X.4 fresh-attach trigger #1; non-empty drives the picker.
506
- * - `{ kind: "server-unavailable" }` — roster fetch failed
507
- * transiently. Caller falls through to fresh attach.
508
- *
509
- * Lib-pure: no env reads, no terminal IO, no `process.exit`. Local
510
- * filesystem errors are swallowed; only the roster-fetch outcome can
511
- * change the discriminator.
512
- */
513
- declare function detectLegacyMigration(installRoot: string, serverSlug: string, client: LegacyMachineRosterClient): Promise<MigrationDetection>;
514
- /**
515
- * Manual `--migrate-from <path>` validator — the three-gate chain that
516
- * RFC v9.9 §X.4 specifies for the manual escape hatch when the picker's
517
- * intersection set is non-empty but the operator wants to migrate a
518
- * different on-disk install.
519
- *
520
- * Gates (executed in order, hard-fail on first failure):
521
- * 1. `MIGRATE_FROM_NOT_FOUND` — `path` does not exist or is not a
522
- * readable directory / file. Accepts either the `machines/
523
- * machine-<fp>/` directory or its `daemon.lock/owner.json`
524
- * directly; the latter is what doctor / forensic flows surface.
525
- * 2. `MIGRATE_FROM_INVALID` — owner.json is unreadable, malformed,
526
- * or missing a 16-hex-char `apiKeyFingerprint` field.
527
- * 3. `MIGRATE_FROM_NOT_OWNED` — owner.json's fingerprint is not in
528
- * the supplied roster (the logged-in user does not have a
529
- * legacy daemon row with this fingerprint on the target server,
530
- * or the row is already migrated / NULL-fingerprinted).
531
- *
532
- * On success returns the same `LegacyMachineCandidate` shape the
533
- * picker emits, so the setup driver's adoption call is the same code
534
- * path on both branches.
535
- */
536
- type ManualPathValidation = {
537
- ok: true;
538
- candidate: LegacyMachineCandidate;
539
- } | {
540
- ok: false;
541
- code: "MIGRATE_FROM_NOT_FOUND" | "MIGRATE_FROM_INVALID" | "MIGRATE_FROM_NOT_OWNED";
542
- };
543
- declare function validateManualMigratePath(inputPath: string, roster: LegacyMachineRosterEntry[]): Promise<ManualPathValidation>;
544
-
545
- type PickerSelection = {
546
- kind: "candidate";
547
- index: number;
548
- } | {
549
- kind: "fresh";
550
- } | {
551
- kind: "manual";
552
- path: string;
553
- };
554
- /**
555
- * Closed-set enumeration of the §X.4 fresh-attach triggers. Used in
556
- * setup info() lines so QA can grep for "fresh trigger: <name>" and
557
- * test mocks can assert that ONLY these inputs route to fresh attach
558
- * (Cody NIT `msg=9102a817`). The picker function itself only emits
559
- * `explicit-zero` / `eof`; the other three are decided upstream in
560
- * `runSetup` before the picker ever fires.
561
- */
562
- declare const MIGRATION_FRESH_TRIGGERS: readonly ["empty-intersection", "explicit-zero", "eof", "non-tty", "server-unavailable"];
563
- type MigrationFreshTrigger = (typeof MIGRATION_FRESH_TRIGGERS)[number];
564
- /**
565
- * Pure picker grammar — extracted so it can be exercised by unit tests
566
- * without stubbing global stdin/stdout. RFC v9.9 §X.4 (post-Jianwei FAIL
567
- * `msg=ecc2e57e` / Cody NIT `msg=9102a817`):
568
- * - `1`..`N` / `<Enter>` → adopt that candidate (1-indexed; Enter picks
569
- * the first candidate, since the operator already saw the
570
- * intersection list).
571
- * - `0` → fresh attach (Trigger #2: explicit-zero).
572
- * - EOF (Ctrl-D / closed stdin) → fresh attach (Trigger #2: eof).
573
- * - `m` / `M` → emits `manual` and prompts for path on the next line.
574
- * - Anything else → reprompt. The picker NEVER silently falls through
575
- * to fresh attach on unrecognized input — that was the B1/B4 contract
576
- * drift that QA rejected.
577
- */
578
- declare function pickMigrationCandidateFromInput(candidates: LegacyMachineCandidate[], read: () => Promise<{
579
- line: string;
580
- eof: boolean;
581
- }>, write: (s: string) => void): Promise<PickerSelection>;
582
-
583
- /**
584
- * Read the Computer-level aggregate status from `installRoot`. Identical
585
- * shape to `buildStatusReport(installRoot)` — exposed under the
586
- * wire-aligned name `readServiceStatus` for IPC `service-status` parity.
587
- */
588
- declare function readServiceStatus(installRoot: string): Promise<ServiceStatusResult>;
589
- /**
590
- * Read a single attached server's daemon state row plus the runners
591
- * currently running on it. Throws `StateReaderError("NOT_ATTACHED")` if
592
- * `serverId` is not in the attached set, or `"INVALID_ATTACHMENT"` if
593
- * the runner.state.json under that id is unreadable.
594
- */
595
- declare function readRunnerStatus(installRoot: string, serverId: string): Promise<RunnerStatusResult>;
596
- /**
597
- * List runners across attached servers. With no `serverId` filter,
598
- * returns one block per attached server (zero if no attachments). With
599
- * a `serverId` filter, returns exactly that server's block or throws
600
- * `StateReaderError("NOT_ATTACHED")`.
601
- *
602
- * Per-server discriminator preserves `unauthorized` / `error` outcomes
603
- * so consumers can pattern-match without losing context.
604
- */
605
- declare function listRunners(installRoot: string, opts?: {
606
- serverId?: string;
607
- }): Promise<ListRunnersResult>;
608
-
609
- type UpgradeTrigger = "cli" | "web" | "tray";
610
- /**
611
- * v8.3.3 §4.4 (6) closed-set, verbatim 7 values. Adding / renaming /
612
- * deleting any value requires an RFC bump (locked discipline). This
613
- * constant array is the runtime source of truth for `assertUpgradeLogEntry`
614
- * membership check; the type alias below mirrors it.
615
- */
616
- declare const UPGRADE_ERROR_CODES: readonly ["UPGRADE_DEPS_CHANGED", "UPGRADE_NETWORK_FAILED", "UPGRADE_INTEGRITY_FAILED", "UPGRADE_SWAP_FAILED", "UPGRADE_RESTART_FAILED", "UPGRADE_NO_TARGET", "UPGRADE_ALREADY_RUNNING"];
617
- type UpgradeErrorCode = (typeof UPGRADE_ERROR_CODES)[number];
618
- interface UpgradeBundle {
619
- computerVersion: string;
620
- /**
621
- * Forensic-only per v8.3.2 cleanup. May be omitted; consumers MUST
622
- * NOT treat presence/absence as a user-facing contract. The bundled
623
- * daemon version plumbing (read from Computer package
624
- * `dependencies["@slock-ai/daemon"]`) lands as a follow-up; until
625
- * then writers emit just `computerVersion` and downstream readers
626
- * default to "unknown" when the field is absent.
627
- */
628
- bundledDaemonVersion?: string;
629
- }
630
- /**
631
- * v8.3.3 audit-log entry shape, discriminated on `outcome`. Splitting
632
- * the union means:
633
- * - `outcome: "ok"` forbids `errorCode` at the type level
634
- * (`errorCode?: never`)
635
- * - `outcome: "err"` requires `errorCode: UpgradeErrorCode` (closed-set
636
- * 7-value membership enforced by TS)
637
- *
638
- * Together with the runtime `assertUpgradeLogEntry` guard below, this
639
- * eliminates the prior "caller-discipline only" silent contract drift
640
- * (Hao caveat msg=308d99db — `errorCode?: string` allowed any string +
641
- * silent omission on `err` paths).
642
- */
643
- type UpgradeLogEntry = UpgradeLogEntryOk | UpgradeLogEntryErr;
644
- interface UpgradeLogEntryOk {
645
- /** ISO timestamp via formatUpgradeLogTimestamp(); caller may omit to auto-fill. */
646
- at?: string;
647
- fromBundle: UpgradeBundle;
648
- toBundle: UpgradeBundle;
649
- channel: string;
650
- trigger: UpgradeTrigger;
651
- outcome: "ok";
652
- /** Forbidden on ok entries — discriminated-union enforcement. */
653
- errorCode?: never;
654
- }
655
- interface UpgradeLogEntryErr {
656
- at?: string;
657
- fromBundle: UpgradeBundle;
658
- toBundle: UpgradeBundle;
659
- channel: string;
660
- trigger: UpgradeTrigger;
661
- outcome: "err";
662
- /** Required iff outcome === "err"; one of the v8.3.3 closed 7-value set. */
663
- errorCode: UpgradeErrorCode;
664
- }
665
- /**
666
- * Runtime assertion: `entry` is a valid `UpgradeLogEntry`. Throws
667
- * `TypeError` with an actionable message on contract violation. Defense
668
- * in depth against JS-interop callers, JSON-deserialized payloads, and
669
- * future type-system escapes.
670
- *
671
- * Validations:
672
- * - `outcome === "ok"` AND `errorCode` is set → reject
673
- * - `outcome === "err"` AND `errorCode` is missing → reject
674
- * - `outcome === "err"` AND `errorCode` not in 7-value closed set → reject
675
- * - Both bundle objects must have a non-empty `computerVersion` string
676
- */
677
- declare function assertUpgradeLogEntry(entry: UpgradeLogEntry): void;
678
-
679
- export { type ComputerStatusReport, type ConnectService, type ConnectServiceOptions, type DaemonState, IPC_ERROR_CODES, type IpcErrorCode, type LegacyMachineCandidate, type LegacyMachineRosterClient, type LegacyMachineRosterEntry, type LegacyMachineRosterResult, LegacyMachinesClient, type ListRunnersResult, MIGRATION_DETECTION_KINDS, MIGRATION_FRESH_TRIGGERS, type ManualPathValidation, type MigrationDetection, type MigrationDetectionKind, type MigrationFreshTrigger, type PickerSelection, 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, ServersClient, 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, type UserServerEntry, type UserServersResult, assertUpgradeLogEntry, detectLegacyMigration, isIpcErrorCode, isMigrationDetectionKind, isRunnerState, isServiceState, listRunners, pickMigrationCandidateFromInput, readRunnerStatus, readServiceStatus, validateManualMigratePath };