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

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.
@@ -0,0 +1,250 @@
1
+ /**
2
+ * IPC error code family — the seed that `ServiceClient.request` and
3
+ * `ServiceClient.events` propagate. Forward-only post v9-sign: additions
4
+ * are additive minor library bumps; renames / removals are major.
5
+ *
6
+ * Full §7.3 enumeration (SETUP/ATTACH/MIGRATE/UPGRADE/SERVICE families)
7
+ * lands in a follow-up reconcile commit that also unifies the codes
8
+ * currently emitted via `ComputerServiceError` across services/*.
9
+ */
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"];
11
+ type IpcErrorCode = (typeof IPC_ERROR_CODES)[number];
12
+ /**
13
+ * Runtime narrow guard — exposed so consumers can pattern-match without
14
+ * pulling in a generic `as` cast. The CI gate (§7.4) over a final
15
+ * `ERROR_CODES` union will subsume this once the reconcile lands.
16
+ */
17
+ declare function isIpcErrorCode(value: unknown): value is IpcErrorCode;
18
+ /**
19
+ * Caller-facing options for `ServiceClient.request`. Pairs active cancel
20
+ * (`signal` — primary G-stage unmount path) with passive deadline
21
+ * (`timeoutMs` — library-side safety net). On either path the client
22
+ * emits an IPC `cancel` frame for the in-flight request id and rejects
23
+ * the returned promise with `IPC_REQUEST_CANCELED` or
24
+ * `IPC_REQUEST_TIMEOUT` respectively (§4.4).
25
+ */
26
+ interface RequestOptions {
27
+ signal?: AbortSignal;
28
+ timeoutMs?: number;
29
+ }
30
+ /**
31
+ * Typed RPC table — keys are kebab-case wire literals (§5.5), values are
32
+ * per-method `{ params, result }` pairs. The v9 sign-lock seed listed
33
+ * 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.
38
+ */
39
+ interface RequestMethodMap {
40
+ "service-status": {
41
+ params: void;
42
+ result: unknown;
43
+ };
44
+ "runner-status": {
45
+ params: {
46
+ serverId: string;
47
+ };
48
+ result: unknown;
49
+ };
50
+ "list-runners": {
51
+ params: void;
52
+ result: unknown;
53
+ };
54
+ "upgrade-start": {
55
+ params: {
56
+ target?: string;
57
+ };
58
+ result: unknown;
59
+ };
60
+ "reset-service": {
61
+ params: void;
62
+ result: unknown;
63
+ };
64
+ "reset-runner": {
65
+ params: {
66
+ serverId: string;
67
+ };
68
+ result: unknown;
69
+ };
70
+ }
71
+ /**
72
+ * Server-pushed event over `ServiceClient.events`. The discriminant field
73
+ * is `kind` (§5.5 — established at v9.7 sign); values are past-participle
74
+ * kebab-case literals. The 6-kind enumeration is the v9.8 §4.4 floor.
75
+ *
76
+ * Payload shapes finalize alongside §4 IPC impl (PR-impl-2, liuliu-owned);
77
+ * the discriminator is locked here so consumers can write exhaustive
78
+ * switches today.
79
+ *
80
+ * Note: v9.8 §4.4 has an example block still showing the v9.7-pre field
81
+ * name `topic` + present-tense values; §5.5 is the byte-pin source of
82
+ * truth (`kind` + past-participle). The §4.4 example will be reconciled
83
+ * in a follow-up doc-clean pass.
84
+ */
85
+ type ServiceEvent = {
86
+ kind: "service-state-changed";
87
+ payload: unknown;
88
+ } | {
89
+ kind: "runner-state-changed";
90
+ payload: unknown;
91
+ } | {
92
+ kind: "runner-attached";
93
+ payload: unknown;
94
+ } | {
95
+ kind: "runner-detached";
96
+ payload: unknown;
97
+ } | {
98
+ kind: "upgrade-log-appended";
99
+ payload: unknown;
100
+ } | {
101
+ kind: "heartbeat";
102
+ payload: unknown;
103
+ };
104
+ /**
105
+ * Library-side error envelope thrown by `ServiceClient`. `code` is a
106
+ * member of the §7 closed-set; `cause` is retained in-process only and
107
+ * MUST NOT cross IPC / CLI fail boundaries (§7.4 redaction).
108
+ */
109
+ declare class ServiceClientError extends Error {
110
+ readonly code: IpcErrorCode;
111
+ readonly cause?: unknown;
112
+ constructor(code: IpcErrorCode, message: string, cause?: unknown);
113
+ }
114
+ /**
115
+ * Typed IPC client. The implementation in PR-impl-2 §4 satisfies this
116
+ * interface; this commit only locks the surface.
117
+ *
118
+ * Termination semantics (§4.7):
119
+ * - `events` completes naturally (`Symbol.asyncIterator` return) on any
120
+ * socket-close path (graceful local/remote, transient, TCP RST). The
121
+ * `for-await` loop exits without throwing — idiomatic reconnect is a
122
+ * plain outer `while (!stopped) { … }` wrap, no try/catch.
123
+ * - `events` THROWS `ServiceClientError` only on protocol-level
124
+ * failures: handshake mismatch, frame parse, frame > 1 MiB.
125
+ * The disjoint split lets consumers distinguish "retry" (iterator end)
126
+ * from "give up" (throw) without runtime classification.
127
+ */
128
+ interface ServiceClient {
129
+ events: AsyncIterable<ServiceEvent>;
130
+ request<M extends keyof RequestMethodMap>(method: M, params: RequestMethodMap[M]["params"], options?: RequestOptions): Promise<RequestMethodMap[M]["result"]>;
131
+ close(): Promise<void>;
132
+ }
133
+ /**
134
+ * Connection options. `protocolVersion` lets a consumer opt-in to a
135
+ * specific handshake version; the service negotiates highest mutually
136
+ * supported (§4.3) and may reject with `IPC_PROTOCOL_VERSION_UNSUPPORTED`.
137
+ */
138
+ interface ConnectServiceOptions {
139
+ protocolVersion?: number;
140
+ }
141
+ /**
142
+ * Open a typed IPC connection to the Computer service. Throws
143
+ * `ServiceClientError` on handshake failure. Concrete implementation
144
+ * lands in PR-impl-2 §4 (liuliu-owned); the type is locked here so
145
+ * `apps/slock-computer-app` and other library consumers can type-check
146
+ * call sites before the transport ships.
147
+ */
148
+ type ConnectService = (installRoot: string, options?: ConnectServiceOptions) => Promise<ServiceClient>;
149
+
150
+ /**
151
+ * Per-runner lifecycle states (§3.2). One state at a time per runner.
152
+ *
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)
160
+ */
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;
164
+ /**
165
+ * Service (supervisor) lifecycle states (§3.2). Mirrors runner shape but
166
+ * narrower — the service is a singleton per install root.
167
+ *
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
175
+ */
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;
179
+
180
+ type UpgradeTrigger = "cli" | "web" | "tray";
181
+ /**
182
+ * v8.3.3 §4.4 (6) closed-set, verbatim 7 values. Adding / renaming /
183
+ * deleting any value requires an RFC bump (locked discipline). This
184
+ * constant array is the runtime source of truth for `assertUpgradeLogEntry`
185
+ * membership check; the type alias below mirrors it.
186
+ */
187
+ 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"];
188
+ type UpgradeErrorCode = (typeof UPGRADE_ERROR_CODES)[number];
189
+ interface UpgradeBundle {
190
+ computerVersion: string;
191
+ /**
192
+ * Forensic-only per v8.3.2 cleanup. May be omitted; consumers MUST
193
+ * NOT treat presence/absence as a user-facing contract. The bundled
194
+ * daemon version plumbing (read from Computer package
195
+ * `dependencies["@slock-ai/daemon"]`) lands as a follow-up; until
196
+ * then writers emit just `computerVersion` and downstream readers
197
+ * default to "unknown" when the field is absent.
198
+ */
199
+ bundledDaemonVersion?: string;
200
+ }
201
+ /**
202
+ * v8.3.3 audit-log entry shape, discriminated on `outcome`. Splitting
203
+ * the union means:
204
+ * - `outcome: "ok"` forbids `errorCode` at the type level
205
+ * (`errorCode?: never`)
206
+ * - `outcome: "err"` requires `errorCode: UpgradeErrorCode` (closed-set
207
+ * 7-value membership enforced by TS)
208
+ *
209
+ * Together with the runtime `assertUpgradeLogEntry` guard below, this
210
+ * eliminates the prior "caller-discipline only" silent contract drift
211
+ * (Hao caveat msg=308d99db — `errorCode?: string` allowed any string +
212
+ * silent omission on `err` paths).
213
+ */
214
+ type UpgradeLogEntry = UpgradeLogEntryOk | UpgradeLogEntryErr;
215
+ interface UpgradeLogEntryOk {
216
+ /** ISO timestamp via formatUpgradeLogTimestamp(); caller may omit to auto-fill. */
217
+ at?: string;
218
+ fromBundle: UpgradeBundle;
219
+ toBundle: UpgradeBundle;
220
+ channel: string;
221
+ trigger: UpgradeTrigger;
222
+ outcome: "ok";
223
+ /** Forbidden on ok entries — discriminated-union enforcement. */
224
+ errorCode?: never;
225
+ }
226
+ interface UpgradeLogEntryErr {
227
+ at?: string;
228
+ fromBundle: UpgradeBundle;
229
+ toBundle: UpgradeBundle;
230
+ channel: string;
231
+ trigger: UpgradeTrigger;
232
+ outcome: "err";
233
+ /** Required iff outcome === "err"; one of the v8.3.3 closed 7-value set. */
234
+ errorCode: UpgradeErrorCode;
235
+ }
236
+ /**
237
+ * Runtime assertion: `entry` is a valid `UpgradeLogEntry`. Throws
238
+ * `TypeError` with an actionable message on contract violation. Defense
239
+ * in depth against JS-interop callers, JSON-deserialized payloads, and
240
+ * future type-system escapes.
241
+ *
242
+ * Validations:
243
+ * - `outcome === "ok"` AND `errorCode` is set → reject
244
+ * - `outcome === "err"` AND `errorCode` is missing → reject
245
+ * - `outcome === "err"` AND `errorCode` not in 7-value closed set → reject
246
+ * - Both bundle objects must have a non-empty `computerVersion` string
247
+ */
248
+ declare function assertUpgradeLogEntry(entry: UpgradeLogEntry): void;
249
+
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 };
@@ -0,0 +1,107 @@
1
+ // src/lib/types.ts
2
+ var IPC_ERROR_CODES = [
3
+ "IPC_FRAME_TOO_LARGE",
4
+ "IPC_PROTOCOL_VERSION_UNSUPPORTED",
5
+ "IPC_HEARTBEAT_TIMEOUT",
6
+ "IPC_MALFORMED_FRAME",
7
+ "IPC_REQUEST_TIMEOUT",
8
+ "IPC_REQUEST_CANCELED",
9
+ "IPC_CLIENT_CLOSED"
10
+ ];
11
+ function isIpcErrorCode(value) {
12
+ return typeof value === "string" && IPC_ERROR_CODES.includes(value);
13
+ }
14
+ var ServiceClientError = class extends Error {
15
+ code;
16
+ cause;
17
+ constructor(code, message, cause) {
18
+ super(message);
19
+ this.name = "ServiceClientError";
20
+ this.code = code;
21
+ if (cause !== void 0) this.cause = cause;
22
+ }
23
+ };
24
+
25
+ // src/lib/state.ts
26
+ var RUNNER_STATE_VALUES = [
27
+ "starting",
28
+ "running",
29
+ "degraded",
30
+ "crashed",
31
+ "stopped"
32
+ ];
33
+ function isRunnerState(value) {
34
+ return typeof value === "string" && RUNNER_STATE_VALUES.includes(value);
35
+ }
36
+ var SERVICE_STATE_VALUES = [
37
+ "starting",
38
+ "running",
39
+ "degraded",
40
+ "stopping",
41
+ "stopped"
42
+ ];
43
+ function isServiceState(value) {
44
+ return typeof value === "string" && SERVICE_STATE_VALUES.includes(value);
45
+ }
46
+
47
+ // src/upgradeLog.ts
48
+ import { chmod, mkdir, open } from "fs/promises";
49
+
50
+ // src/paths.ts
51
+ import { createHash } from "crypto";
52
+ import os from "os";
53
+ import path from "path";
54
+
55
+ // src/upgradeLog.ts
56
+ var UPGRADE_ERROR_CODES = [
57
+ "UPGRADE_DEPS_CHANGED",
58
+ "UPGRADE_NETWORK_FAILED",
59
+ "UPGRADE_INTEGRITY_FAILED",
60
+ "UPGRADE_SWAP_FAILED",
61
+ "UPGRADE_RESTART_FAILED",
62
+ "UPGRADE_NO_TARGET",
63
+ "UPGRADE_ALREADY_RUNNING"
64
+ ];
65
+ var UPGRADE_ERROR_CODE_SET = new Set(UPGRADE_ERROR_CODES);
66
+ function assertUpgradeLogEntry(entry) {
67
+ const e = entry;
68
+ if (e.outcome === "ok") {
69
+ if (e.errorCode !== void 0) {
70
+ throw new TypeError(
71
+ `UpgradeLogEntry contract violation: outcome="ok" must not carry errorCode (got "${e.errorCode}").`
72
+ );
73
+ }
74
+ } else if (e.outcome === "err") {
75
+ if (e.errorCode === void 0) {
76
+ throw new TypeError(
77
+ `UpgradeLogEntry contract violation: outcome="err" requires errorCode from the v8.3.3 closed-set (${UPGRADE_ERROR_CODES.join(" | ")}).`
78
+ );
79
+ }
80
+ if (!UPGRADE_ERROR_CODE_SET.has(e.errorCode)) {
81
+ throw new TypeError(
82
+ `UpgradeLogEntry contract violation: errorCode "${e.errorCode}" is not in the v8.3.3 closed 7-value set (${UPGRADE_ERROR_CODES.join(" | ")}).`
83
+ );
84
+ }
85
+ } else {
86
+ throw new TypeError(
87
+ `UpgradeLogEntry contract violation: outcome must be "ok" or "err" (got "${e.outcome}").`
88
+ );
89
+ }
90
+ if (typeof e.fromBundle?.computerVersion !== "string" || e.fromBundle.computerVersion.length === 0) {
91
+ throw new TypeError(`UpgradeLogEntry contract violation: fromBundle.computerVersion must be a non-empty string.`);
92
+ }
93
+ if (typeof e.toBundle?.computerVersion !== "string" || e.toBundle.computerVersion.length === 0) {
94
+ throw new TypeError(`UpgradeLogEntry contract violation: toBundle.computerVersion must be a non-empty string.`);
95
+ }
96
+ }
97
+ export {
98
+ IPC_ERROR_CODES,
99
+ RUNNER_STATE_VALUES,
100
+ SERVICE_STATE_VALUES,
101
+ ServiceClientError,
102
+ UPGRADE_ERROR_CODES,
103
+ assertUpgradeLogEntry,
104
+ isIpcErrorCode,
105
+ isRunnerState,
106
+ isServiceState
107
+ };
package/package.json CHANGED
@@ -1,11 +1,18 @@
1
1
  {
2
2
  "name": "@slock-ai/computer",
3
- "version": "0.0.13-alpha.0",
3
+ "version": "0.0.14",
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": {
7
7
  "slock-computer": "dist/index.js"
8
8
  },
9
+ "exports": {
10
+ "./lib": {
11
+ "types": "./dist/lib/index.d.ts",
12
+ "default": "./dist/lib/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
9
16
  "files": [
10
17
  "dist"
11
18
  ],
@@ -21,7 +28,7 @@
21
28
  "commander": "^12.1.0",
22
29
  "proper-lockfile": "^4.1.2",
23
30
  "undici": "^7.24.7",
24
- "@slock-ai/daemon": "0.54.2-alpha.0"
31
+ "@slock-ai/daemon": "0.55.0"
25
32
  },
26
33
  "devDependencies": {
27
34
  "@types/node": "^25.5.0",