@microsandbox/control-client 0.7.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.
Files changed (62) hide show
  1. package/README.md +115 -0
  2. package/dist/attempt.d.ts +19 -0
  3. package/dist/attempt.d.ts.map +1 -0
  4. package/dist/attempt.js +79 -0
  5. package/dist/attempt.js.map +1 -0
  6. package/dist/client.d.ts +9 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +11 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/connection.d.ts +32 -0
  11. package/dist/connection.d.ts.map +1 -0
  12. package/dist/connection.js +103 -0
  13. package/dist/connection.js.map +1 -0
  14. package/dist/dialer.d.ts +16 -0
  15. package/dist/dialer.d.ts.map +1 -0
  16. package/dist/dialer.js +2 -0
  17. package/dist/dialer.js.map +1 -0
  18. package/dist/error.d.ts +13 -0
  19. package/dist/error.d.ts.map +1 -0
  20. package/dist/error.js +23 -0
  21. package/dist/error.js.map +1 -0
  22. package/dist/index.d.ts +15 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +13 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/json-client.d.ts +15 -0
  27. package/dist/json-client.d.ts.map +1 -0
  28. package/dist/json-client.js +22 -0
  29. package/dist/json-client.js.map +1 -0
  30. package/dist/json-reply.d.ts +18 -0
  31. package/dist/json-reply.d.ts.map +1 -0
  32. package/dist/json-reply.js +63 -0
  33. package/dist/json-reply.js.map +1 -0
  34. package/dist/json-session.d.ts +23 -0
  35. package/dist/json-session.d.ts.map +1 -0
  36. package/dist/json-session.js +128 -0
  37. package/dist/json-session.js.map +1 -0
  38. package/dist/json-value.d.ts +18 -0
  39. package/dist/json-value.d.ts.map +1 -0
  40. package/dist/json-value.js +138 -0
  41. package/dist/json-value.js.map +1 -0
  42. package/dist/legacy-request.d.ts +26 -0
  43. package/dist/legacy-request.d.ts.map +1 -0
  44. package/dist/legacy-request.js +77 -0
  45. package/dist/legacy-request.js.map +1 -0
  46. package/dist/node.d.ts +13 -0
  47. package/dist/node.d.ts.map +1 -0
  48. package/dist/node.js +19 -0
  49. package/dist/node.js.map +1 -0
  50. package/dist/protocol.d.ts +13 -0
  51. package/dist/protocol.d.ts.map +1 -0
  52. package/dist/protocol.js +65 -0
  53. package/dist/protocol.js.map +1 -0
  54. package/dist/records.d.ts +100 -0
  55. package/dist/records.d.ts.map +1 -0
  56. package/dist/records.js +110 -0
  57. package/dist/records.js.map +1 -0
  58. package/dist/request.d.ts +50 -0
  59. package/dist/request.d.ts.map +1 -0
  60. package/dist/request.js +86 -0
  61. package/dist/request.js.map +1 -0
  62. package/package.json +26 -0
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # TypeScript control client
2
+
3
+ `ControlClient` is `Client<ControlProtocol>` over the shared framed engine. It sends a bounded `control.hello` directly, negotiates generation and limits, and exposes the original welcome frame. Use it when the peer is known to support framed control. It does not probe JSON or downgrade after failure.
4
+
5
+ The root entry is browser-safe and accepts a caller-owned byte transport or reconnectable connector. The `/node` entry accepts an existing Unix socket or Windows named-pipe path without renaming it.
6
+
7
+ ```ts
8
+ import { GetMemoryState, MiB, SetMemoryTarget, typedMessage } from "@microsandbox/control-client";
9
+ import { connectFramedControl } from "@microsandbox/control-client/node";
10
+
11
+ async function inspectAndResize(path: string) {
12
+ const client = await connectFramedControl(path, { setupTimeoutMs: 10_000 });
13
+ try {
14
+ const response = await client.request(typedMessage("control.memory.state", {}));
15
+ console.log(response.type, response.raw.body);
16
+ const before = await client.requestTyped(new GetMemoryState());
17
+ const accepted = await client.requestTyped(new SetMemoryTarget(MiB(2048)));
18
+ console.log(before.target_mib, accepted.target_mib); // bigint, even for small values
19
+ } finally {
20
+ await client.close();
21
+ }
22
+ }
23
+ ```
24
+
25
+ Checked helpers cover capabilities, memory state/target, CPU state/target, and ordered secret updates. They validate replies and retain the actual response on `ControlClientError`. Generic message requests return peer error frames directly. A target reply reports acceptance and observation; it does not establish guest convergence. Secret batches stop at the first failure and retain earlier completed changes.
26
+
27
+ Use `connectControl` for automatic discovery on the same endpoint. It sends the existing read-only JSON capabilities request, closes that exchange, and opens a fresh framed connection only after an affirmative CBOR advertisement. Setup has one total deadline across both connections. Malformed discovery, timeout, EOF without a reply, or a failed welcome never trigger fallback or replay.
28
+
29
+ ```text
30
+ connectControl(path)
31
+ |
32
+ +-- JSON capabilities --> valid legacy reply --> fresh JSON exchange per operation
33
+ |
34
+ +-- JSON capabilities --> advertises CBOR -----> fresh hello/welcome --> shared framed client
35
+ ```
36
+
37
+ ```ts
38
+ import { GetMemoryState, MiB, SetMemoryTarget, typedMessage } from "@microsandbox/control-client";
39
+ import { connectControl } from "@microsandbox/control-client/node";
40
+
41
+ async function compatibleResize(path: string) {
42
+ const connection = await connectControl(path);
43
+ try {
44
+ const reply = await connection.request(typedMessage("control.memory.state", {}));
45
+ if (reply.kind === "cbor") console.log(reply.frame.raw.body);
46
+ else console.log(reply.reply.raw, reply.reply.value.get("memory"));
47
+ const before = await connection.requestTyped(new GetMemoryState());
48
+ const accepted = await connection.requestTyped(new SetMemoryTarget(MiB(2048)));
49
+ return { before, accepted };
50
+ } finally {
51
+ await connection.close();
52
+ }
53
+ }
54
+ ```
55
+
56
+ `ControlConnection.connectConnector` accepts a repeatable caller connector from the browser-safe root. The connection exposes the read-only `capabilities` discovery snapshot, `mode`, `clone()`, `isClosed()`, `close()`, `request()`, and `requestTyped()`. `framed()` returns the complete generic surface in CBOR mode and fails locally with `unsupported_mode` in JSON mode. Encoded messages likewise fail locally in JSON mode; the adapter translates only known, validated native operations.
57
+
58
+ `JsonReply` contains the original response line, including its delimiter and whitespace, and a lossless `Map` of fields. `JsonNumber.token` retains each original numeric token; checked memory observations become `bigint` without passing through a JavaScript number. Ordinary requests return `ok:false` replies. Checked helpers raise `legacy_remote` with the original reply and unknown batch progress. Neither arbitrary diagnostic strings nor legacy errors are converted into structured CBOR errors.
59
+
60
+ For explicit legacy access, construction is inert and performs no discovery:
61
+
62
+ ```ts
63
+ import { GetCpuState } from "@microsandbox/control-client";
64
+ import { jsonControl } from "@microsandbox/control-client/node";
65
+
66
+ async function inspectLegacy(path: string) {
67
+ const client = jsonControl(path);
68
+ try {
69
+ return await client.requestTyped(new GetCpuState());
70
+ } finally {
71
+ await client.close();
72
+ }
73
+ }
74
+ ```
75
+
76
+ The equivalent root API is `JsonControlClient.fromConnector(connector, options?)`. Ordinary legacy request and response lines keep their existing size contract; only automatic discovery replies have a 64 KiB limit. Preparing a secret batch snapshots its entries without imposing the framed 4 MiB ceiling on JSON. JavaScript strings remain subject to garbage collection; explicit buffer clearing is not a guarantee that all copies have been erased.
77
+
78
+ A standalone automatic JSON connection rediscovers before each fresh operation because an endpoint path cannot establish process identity. `ControlConnection.connectVerifiedConnector` permits discovery reuse when the caller supplies a `VerifiedControlConnector`: `connect` must verify every connected peer against the saved OS process birth identity, and `verifySession` must check the active run and process before each operation. Both receive a deadline and cancellation context. Runtime replacement raises `runtime_changed` before mutation admission and closes the shared handle. This extension point does not implement an SDK database or OS identity verifier by itself.
79
+
80
+ `MiB`, `GiB`, `KiB`, `TiB`, and `Mebibytes` are exported from `@microsandbox/types/size`. Their arithmetic matches the SDK helpers. `SetMemoryTarget` validates integer precision and range before converting to the wire quantity; it also accepts `bigint` for full-width values.
81
+
82
+ All generic operations remain available:
83
+
84
+ | Need | Method |
85
+ |---|---|
86
+ | Native or encoded payload | `request(typedMessage(...))`, `request(encodedMessage(...))` |
87
+ | Exact opaque envelope | `requestRaw(flags, body)` |
88
+ | Message or raw subscription | `openStream(...)`, `openStreamRaw(...)` |
89
+ | Owned sender and receiver | `stream.split()` |
90
+ | Follow-up using an owned ID | `sendOnStream(id, ...)`, `sendRaw(id, ...)` |
91
+ | Exact packet without a subscription | `writeUnchecked(bytes)` |
92
+ | Optional checked unary operation | `requestTyped(request)` |
93
+ | Shared connection ownership | `clone()`, `close()` |
94
+
95
+ ```ts
96
+ import { ControlClient, encodedMessage, type ByteTransport } from "@microsandbox/control-client";
97
+
98
+ async function inspectExtension(transport: ByteTransport, payload: Uint8Array) {
99
+ const client = await ControlClient.connectTransport(transport);
100
+ try {
101
+ const reply = await client.request(encodedMessage("extension.inspect", payload));
102
+ return { name: reply.type, payload: reply.payload, original: reply.raw };
103
+ } finally {
104
+ await client.close();
105
+ }
106
+ }
107
+ ```
108
+
109
+ The default control request deadline is 30 seconds. Expiry abandons the local wait; it does not cancel remote work or retry it. `ClientError.delivery` distinguishes requests that were not admitted from unknown outcomes. Complete idle connections do not expire automatically. Explicit close closes every clone; JavaScript finalization is only best-effort cleanup.
110
+
111
+ From `packages`, run `npm run build`, `npm run typecheck`, and `npm test`. Live validation is deliberately opt-in: set `MSB_CONTROL_TEST_SOCKET` to a disposable framed runtime's socket and run `npm test -w @microsandbox/control-client`. Add `MSB_CONTROL_TEST_MODE=cbor` to run automatic discovery as well. The package serializes test files because live files mutate and restore the same VM's targets. A skipped live test is not platform or runtime compatibility evidence.
112
+
113
+ For a historical JSON-only runtime, set `MSB_CONTROL_TEST_MODE=json` and run only the compatibility live file with `npm exec -w @microsandbox/control-client -- vitest run tests/compatibility-live.test.ts`. It checks automatic and explicit JSON access, concurrent reads, full-width result types, and accepted memory/CPU targets, then restores the original targets. These running-runtime tests do not establish SDK launch compatibility.
114
+
115
+ The additional secret test requires `MSB_CONTROL_TEST_SECRET` naming a dummy fixture initially set to `before` and allowed for `example.invalid`. It exercises partial completion, malformed batches, and legacy JSON failures, then restores those fixture values.
@@ -0,0 +1,19 @@
1
+ import { ClientError, type ByteTransport, type ConnectContext, type Connector } from "@microsandbox/protocol-client";
2
+ import { ControlClientError } from "./error.js";
3
+ /** An absolute control-operation scope, including dial, verification, and I/O. */
4
+ export declare class ControlAttempt {
5
+ #private;
6
+ readonly deadlineMs: number;
7
+ get signal(): AbortSignal;
8
+ get context(): ConnectContext;
9
+ constructor(timeoutMs: number, signals?: readonly (AbortSignal | undefined)[]);
10
+ check(): void;
11
+ remaining(): number;
12
+ run<T>(work: () => Promise<T>): Promise<T>;
13
+ connect(connector: Connector): Promise<ByteTransport>;
14
+ dispose(): void;
15
+ }
16
+ export declare function validateTimeout(value: number): void;
17
+ export declare function controlError(error: unknown): ClientError | ControlClientError;
18
+ export declare function closeTransport(transport: ByteTransport): Promise<void>;
19
+ //# sourceMappingURL=attempt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attempt.d.ts","sourceRoot":"","sources":["../src/attempt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,+BAA+B,CAAC;AACrH,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhD,kFAAkF;AAClF,qBAAa,cAAc;;IAGzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,IAAI,MAAM,IAAI,WAAW,CAAoC;IAC7D,IAAI,OAAO,IAAI,cAAc,CAAiE;gBAElF,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,SAAS,CAAC,WAAW,GAAG,SAAS,CAAC,EAAO;IAgBjF,KAAK,IAAI,IAAI;IAIb,SAAS,IAAI,MAAM;IACb,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAU1C,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC;IAQ3D,OAAO,IAAI,IAAI;CAChB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEnD;AACD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,kBAAkB,CAE7E;AACD,wBAAsB,cAAc,CAAC,SAAS,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E"}
@@ -0,0 +1,79 @@
1
+ import { ClientError } from "@microsandbox/protocol-client";
2
+ import { ControlClientError } from "./error.js";
3
+ /** An absolute control-operation scope, including dial, verification, and I/O. */
4
+ export class ControlAttempt {
5
+ #controller = new AbortController();
6
+ #cleanup = [];
7
+ deadlineMs;
8
+ get signal() { return this.#controller.signal; }
9
+ get context() { return { deadlineMs: this.deadlineMs, signal: this.signal }; }
10
+ constructor(timeoutMs, signals = []) {
11
+ validateTimeout(timeoutMs);
12
+ this.deadlineMs = performance.now() + timeoutMs;
13
+ for (const signal of signals) {
14
+ if (!signal)
15
+ continue;
16
+ const abort = () => this.#controller.abort(signal.reason instanceof ClientError ? signal.reason : new ClientError("cancelled"));
17
+ if (signal.aborted)
18
+ abort();
19
+ else {
20
+ signal.addEventListener("abort", abort, { once: true });
21
+ this.#cleanup.push(() => signal.removeEventListener("abort", abort));
22
+ }
23
+ }
24
+ if (timeoutMs === 0)
25
+ this.#controller.abort(new ClientError("timeout"));
26
+ else {
27
+ const timer = setTimeout(() => this.#controller.abort(new ClientError("timeout")), timeoutMs);
28
+ this.#cleanup.push(() => clearTimeout(timer));
29
+ }
30
+ }
31
+ check() {
32
+ if (performance.now() >= this.deadlineMs && !this.signal.aborted)
33
+ this.#controller.abort(new ClientError("timeout"));
34
+ if (this.signal.aborted)
35
+ throw this.signal.reason;
36
+ }
37
+ remaining() { this.check(); return Math.max(0, this.deadlineMs - performance.now()); }
38
+ async run(work) {
39
+ this.check();
40
+ return new Promise((resolve, reject) => {
41
+ const abort = () => reject(this.signal.reason);
42
+ this.signal.addEventListener("abort", abort, { once: true });
43
+ Promise.resolve().then(() => { this.check(); return work(); }).then(resolve, reject)
44
+ .finally(() => this.signal.removeEventListener("abort", abort));
45
+ if (this.signal.aborted)
46
+ abort();
47
+ });
48
+ }
49
+ async connect(connector) {
50
+ return this.run(() => connector.connect(this.context).then(transport => {
51
+ // Even a connector that ignores cancellation transfers ownership. Close
52
+ // its late result instead of leaving an unobserved live socket behind.
53
+ try {
54
+ this.check();
55
+ }
56
+ catch (error) {
57
+ void closeTransport(transport);
58
+ throw error;
59
+ }
60
+ return transport;
61
+ }));
62
+ }
63
+ dispose() { for (const cleanup of this.#cleanup.splice(0))
64
+ cleanup(); }
65
+ }
66
+ export function validateTimeout(value) {
67
+ if (!Number.isFinite(value) || value < 0 || value > 0x7fffffff)
68
+ throw new ClientError("invalid_options");
69
+ }
70
+ export function controlError(error) {
71
+ return error instanceof ClientError || error instanceof ControlClientError ? error : new ClientError("io");
72
+ }
73
+ export async function closeTransport(transport) {
74
+ try {
75
+ await transport.close();
76
+ }
77
+ catch { /* Preserve the exchange's original outcome. */ }
78
+ }
79
+ //# sourceMappingURL=attempt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attempt.js","sourceRoot":"","sources":["../src/attempt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { Client, type ByteTransport, type Connector, type ConnectOptions } from "@microsandbox/protocol-client";
2
+ import { ControlProtocol } from "./protocol.js";
3
+ /** The full generic native/encoded/raw/stream/packet API, specialized for control. */
4
+ export type ControlClient = Client<ControlProtocol>;
5
+ export declare const ControlClient: {
6
+ connectTransport(transport: ByteTransport, options?: ConnectOptions): Promise<ControlClient>;
7
+ connectConnector(connector: Connector, options?: ConnectOptions): Promise<ControlClient>;
8
+ };
9
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAChH,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,sFAAsF;AACtF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;AACpD,eAAO,MAAM,aAAa;gCACI,aAAa,YAAY,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;gCAGhE,SAAS,YAAY,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAGzF,CAAC"}
package/dist/client.js ADDED
@@ -0,0 +1,11 @@
1
+ import { Client } from "@microsandbox/protocol-client";
2
+ import { ControlProtocol } from "./protocol.js";
3
+ export const ControlClient = {
4
+ connectTransport(transport, options) {
5
+ return Client.connectTransport(transport, new ControlProtocol(), options);
6
+ },
7
+ connectConnector(connector, options) {
8
+ return Client.connectConnector(connector, new ControlProtocol(), options);
9
+ },
10
+ };
11
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA2D,MAAM,+BAA+B,CAAC;AAChH,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAIhD,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,gBAAgB,CAAC,SAAwB,EAAE,OAAwB;QACjE,OAAO,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,eAAe,EAAE,EAAE,OAAO,CAAC,CAAC;IAC5E,CAAC;IACD,gBAAgB,CAAC,SAAoB,EAAE,OAAwB;QAC7D,OAAO,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,eAAe,EAAE,EAAE,OAAO,CAAC,CAAC;IAC5E,CAAC;CACF,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { type ConnectOptions, type Connector, type InboundFrame, type OutboundMessage, type RequestOptions } from "@microsandbox/protocol-client";
2
+ import type { ControlClient } from "./client.js";
3
+ import type { VerifiedControlConnector } from "./dialer.js";
4
+ import type { ControlMode, JsonReply } from "./json-reply.js";
5
+ import { type CheckedControlRequest } from "./legacy-request.js";
6
+ import { type Capabilities } from "./records.js";
7
+ export type ControlReply = {
8
+ kind: "cbor";
9
+ frame: InboundFrame;
10
+ } | {
11
+ kind: "json";
12
+ reply: JsonReply;
13
+ };
14
+ /** Shared automatic discovery; JSON and CBOR keep their actual reply formats. */
15
+ export declare class ControlConnection {
16
+ private readonly session;
17
+ private constructor();
18
+ static connectConnector(connector: Connector, options?: ConnectOptions): Promise<ControlConnection>;
19
+ static connectVerifiedConnector(connector: VerifiedControlConnector, options?: ConnectOptions): Promise<ControlConnection>;
20
+ private static establish;
21
+ clone(): ControlConnection;
22
+ get mode(): ControlMode;
23
+ /** Validated discovery snapshot; GetCapabilities explicitly requests a fresh observation. */
24
+ get capabilities(): Readonly<Capabilities>;
25
+ isClosed(): boolean;
26
+ framed(): ControlClient;
27
+ close(): Promise<void>;
28
+ request(message: OutboundMessage, options?: RequestOptions): Promise<ControlReply>;
29
+ requestTyped<T>(request: CheckedControlRequest<T>, options?: RequestOptions): Promise<T>;
30
+ private framedOperation;
31
+ }
32
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0D,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE1M,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAiB,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAG3E,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAqB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAwD,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAGvG,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,CAAC;AAGtG,iFAAiF;AACjF,qBAAa,iBAAiB;IACR,OAAO,CAAC,QAAQ,CAAC,OAAO;IAA5C,OAAO;IACP,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAGvG,MAAM,CAAC,wBAAwB,CAAC,SAAS,EAAE,wBAAwB,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,iBAAiB,CAAC;mBAGzG,SAAS;IA2B9B,KAAK,IAAI,iBAAiB;IAC1B,IAAI,IAAI,IAAI,WAAW,CAAkD;IACzE,6FAA6F;IAC7F,IAAI,YAAY,IAAI,QAAQ,CAAC,YAAY,CAAC,CAAsC;IAChF,QAAQ,IAAI,OAAO;IACnB,MAAM,IAAI,aAAa;IAIjB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,YAAY,CAAC;IAItF,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;YAIpF,eAAe;CAiB9B"}
@@ -0,0 +1,103 @@
1
+ import { Client, ClientError, defaultLimits } from "@microsandbox/protocol-client";
2
+ import { closeTransport, ControlAttempt, controlError } from "./attempt.js";
3
+ import { ControlClientError } from "./error.js";
4
+ import { JsonSession } from "./json-session.js";
5
+ import { nativeJsonRequest } from "./legacy-request.js";
6
+ import { DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_SETUP_TIMEOUT_MS } from "./records.js";
7
+ import { ControlProtocol } from "./protocol.js";
8
+ /** Shared automatic discovery; JSON and CBOR keep their actual reply formats. */
9
+ export class ControlConnection {
10
+ session;
11
+ constructor(session) {
12
+ this.session = session;
13
+ }
14
+ static connectConnector(connector, options = {}) {
15
+ return this.establish({ connector }, options);
16
+ }
17
+ static connectVerifiedConnector(connector, options = {}) {
18
+ return this.establish({ connector, verifier: connector }, options);
19
+ }
20
+ static async establish(dialer, options) {
21
+ const json = new JsonSession(dialer, options, true);
22
+ const attempt = new ControlAttempt(options.setupTimeoutMs ?? DEFAULT_SETUP_TIMEOUT_MS, [options.signal]);
23
+ let framed;
24
+ let transport;
25
+ try {
26
+ const { mode, capabilities } = await json.discover(attempt);
27
+ if (mode === "cbor") {
28
+ // Discovery finishes and closes its JSON stream. Positive advertisement
29
+ // requires a fresh framed handshake; failures must never downgrade.
30
+ transport = await attempt.connect(dialer.connector);
31
+ if (dialer.verifier)
32
+ await attempt.run(() => dialer.verifier.verifySession(attempt.context));
33
+ const protocol = new ControlProtocol(), owned = transport;
34
+ const established = await attempt.run(() => protocol.establish(owned, { ...attempt.context, limits: defaultLimits(json.options.limits) }));
35
+ attempt.check();
36
+ framed = await Client.fromEstablished(protocol, established);
37
+ transport = undefined; // The generic reader/writer now own the stream.
38
+ }
39
+ attempt.check();
40
+ return new ControlConnection({ dialer, options: json.options, json, framed, closed: new AbortController(), capabilities: Object.freeze(capabilities) });
41
+ }
42
+ catch (error) {
43
+ if (transport)
44
+ await closeTransport(transport);
45
+ await framed?.close();
46
+ await json.close();
47
+ const failure = controlError(error);
48
+ throw failure instanceof ClientError ? failure.withDelivery("not_sent") : failure;
49
+ }
50
+ finally {
51
+ attempt.dispose();
52
+ }
53
+ }
54
+ clone() { return new ControlConnection(this.session); }
55
+ get mode() { return this.session.framed ? "cbor" : "json"; }
56
+ /** Validated discovery snapshot; GetCapabilities explicitly requests a fresh observation. */
57
+ get capabilities() { return this.session.capabilities; }
58
+ isClosed() { return this.session.json.isClosed() || (this.session.framed?.isClosed() ?? false); }
59
+ framed() {
60
+ if (!this.session.framed)
61
+ throw new ControlClientError("unsupported_mode");
62
+ return this.session.framed;
63
+ }
64
+ async close() {
65
+ this.session.closed.abort(new ClientError("closed"));
66
+ await Promise.all([this.session.json.close(), this.session.framed?.close()]);
67
+ }
68
+ async request(message, options = {}) {
69
+ if (!this.session.framed)
70
+ return { kind: "json", reply: await this.session.json.operation(nativeJsonRequest(message), options) };
71
+ return { kind: "cbor", frame: await this.framedOperation(options, remaining => this.session.framed.request(message, remaining)) };
72
+ }
73
+ async requestTyped(request, options = {}) {
74
+ if (!this.session.framed)
75
+ return request.decodeJson(await this.session.json.operation(request.jsonRequest(), options));
76
+ return this.framedOperation(options, remaining => this.session.framed.requestTyped(request, remaining));
77
+ }
78
+ async framedOperation(options, operation) {
79
+ if (this.isClosed())
80
+ throw new ClientError("closed");
81
+ const attempt = new ControlAttempt(options.requestTimeoutMs ?? this.session.options.limits?.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS, [options.signal, this.session.closed.signal]);
82
+ try {
83
+ try {
84
+ const verifier = this.session.dialer.verifier;
85
+ if (verifier)
86
+ await attempt.run(() => verifier.verifySession(attempt.context));
87
+ attempt.check();
88
+ if (this.isClosed())
89
+ throw new ClientError("closed");
90
+ }
91
+ catch (error) {
92
+ await this.close();
93
+ const failure = controlError(error);
94
+ throw failure instanceof ClientError ? failure.withDelivery("not_sent") : failure;
95
+ }
96
+ return await operation({ ...options, signal: attempt.signal, requestTimeoutMs: attempt.remaining() });
97
+ }
98
+ finally {
99
+ attempt.dispose();
100
+ }
101
+ }
102
+ }
103
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { ConnectContext, Connector } from "@microsandbox/protocol-client";
2
+ /**
3
+ * Backend-owned identity seam. connect must verify each connected peer against
4
+ * the saved OS process birth token. verifySession must recheck the active run
5
+ * and process before an operation; a PID or endpoint path alone is insufficient.
6
+ * Both methods reject runtime replacement before returning an owned transport
7
+ * or permitting operation admission, using ControlClientError/runtime_changed.
8
+ */
9
+ export interface VerifiedControlConnector extends Connector {
10
+ verifySession(context: ConnectContext): Promise<void>;
11
+ }
12
+ export type ControlDialer = {
13
+ connector: Connector;
14
+ verifier?: VerifiedControlConnector;
15
+ };
16
+ //# sourceMappingURL=dialer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialer.d.ts","sourceRoot":"","sources":["../src/dialer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/E;;;;;;GAMG;AACH,MAAM,WAAW,wBAAyB,SAAQ,SAAS;IACzD,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,wBAAwB,CAAA;CAAE,CAAC"}
package/dist/dialer.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dialer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialer.js","sourceRoot":"","sources":["../src/dialer.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ import type { InboundFrame } from "@microsandbox/protocol-client";
2
+ import type { ControlError } from "./records.js";
3
+ import type { JsonReply } from "./json-reply.js";
4
+ export type ControlErrorCode = "peer" | "invalid_response" | "unsupported_mode" | "runtime_changed" | "legacy_remote" | "invalid_json_response";
5
+ /** Checked-operation failures retain the real response, without logging its data. */
6
+ export declare class ControlClientError extends Error {
7
+ readonly code: ControlErrorCode;
8
+ readonly response?: (InboundFrame | JsonReply) | undefined;
9
+ readonly peerError?: ControlError | undefined;
10
+ constructor(code: ControlErrorCode, response?: (InboundFrame | JsonReply) | undefined, peerError?: ControlError | undefined);
11
+ get delivery(): "not_sent" | "unknown";
12
+ }
13
+ //# sourceMappingURL=error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,kBAAkB,GAAG,kBAAkB,GAAG,iBAAiB,GAAG,eAAe,GAAG,uBAAuB,CAAC;AAQhJ,qFAAqF;AACrF,qBAAa,kBAAmB,SAAQ,KAAK;IAEzC,QAAQ,CAAC,IAAI,EAAE,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,CAAC,GAAE,YAAY,GAAG,SAAS;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,YAAY;gBAFxB,IAAI,EAAE,gBAAgB,EACtB,QAAQ,CAAC,GAAE,YAAY,GAAG,SAAS,aAAA,EACnC,SAAS,CAAC,EAAE,YAAY,YAAA;IAKnC,IAAI,QAAQ,IAAI,UAAU,GAAG,SAAS,CAErC;CAEF"}
package/dist/error.js ADDED
@@ -0,0 +1,23 @@
1
+ const descriptions = {
2
+ peer: "control operation rejected by peer", invalid_response: "invalid control operation response",
3
+ unsupported_mode: "operation requires framed control", runtime_changed: "runtime session changed",
4
+ legacy_remote: "legacy control operation failed; batch progress is unknown", invalid_json_response: "invalid legacy control response",
5
+ };
6
+ /** Checked-operation failures retain the real response, without logging its data. */
7
+ export class ControlClientError extends Error {
8
+ code;
9
+ response;
10
+ peerError;
11
+ constructor(code, response, peerError) {
12
+ super(descriptions[code]);
13
+ this.code = code;
14
+ this.response = response;
15
+ this.peerError = peerError;
16
+ this.name = "ControlClientError";
17
+ }
18
+ get delivery() {
19
+ return this.code === "unsupported_mode" || this.code === "runtime_changed" ? "not_sent" : "unknown";
20
+ }
21
+ [Symbol.for("nodejs.util.inspect.custom")]() { return "ControlClientError: " + this.message; }
22
+ }
23
+ //# sourceMappingURL=error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAMA,MAAM,YAAY,GAAqC;IACrD,IAAI,EAAE,oCAAoC,EAAE,gBAAgB,EAAE,oCAAoC;IAClG,gBAAgB,EAAE,mCAAmC,EAAE,eAAe,EAAE,yBAAyB;IACjG,aAAa,EAAE,4DAA4D,EAAE,qBAAqB,EAAE,iCAAiC;CACtI,CAAC;AAEF,qFAAqF;AACrF,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAEhC;IACA;IACA;IAHX,YACW,IAAsB,EACtB,QAAmC,EACnC,SAAwB;QAEjC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;QAJjB,SAAI,GAAJ,IAAI,CAAkB;QACtB,aAAQ,GAAR,QAAQ,CAA2B;QACnC,cAAS,GAAT,SAAS,CAAe;QAGjC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;IACD,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,IAAI,KAAK,kBAAkB,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;IACtG,CAAC;IACD,CAAC,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC,KAAa,OAAO,sBAAsB,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;CACvG"}
@@ -0,0 +1,15 @@
1
+ /** Browser-safe control protocol; native transport entry points live in /node. */
2
+ export * from "./client.js";
3
+ export * from "./protocol.js";
4
+ export * from "./records.js";
5
+ export * from "./request.js";
6
+ export * from "./error.js";
7
+ export * from "./connection.js";
8
+ export * from "./json-client.js";
9
+ export { JsonReply, type ControlMode } from "./json-reply.js";
10
+ export { JsonNumber, type JsonObject, type JsonValue } from "./json-value.js";
11
+ export type { CheckedControlRequest, LegacyControlRequest } from "./legacy-request.js";
12
+ export type { VerifiedControlConnector } from "./dialer.js";
13
+ export * from "@microsandbox/types/size";
14
+ export { Client, ClientError, InboundFrame, typedMessage, encodedMessage, type ByteTransport, type Connector, type ConnectOptions, type RequestOptions, type RawFrame, type TypedMessage, type EncodedMessage, type Request, } from "@microsandbox/protocol-client";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC9E,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACvF,YAAY,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5D,cAAc,0BAA0B,CAAC;AACzC,OAAO,EACL,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAC/D,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,cAAc,EAC5E,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,KAAK,OAAO,GACpE,MAAM,+BAA+B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /** Browser-safe control protocol; native transport entry points live in /node. */
2
+ export * from "./client.js";
3
+ export * from "./protocol.js";
4
+ export * from "./records.js";
5
+ export * from "./request.js";
6
+ export * from "./error.js";
7
+ export * from "./connection.js";
8
+ export * from "./json-client.js";
9
+ export { JsonReply } from "./json-reply.js";
10
+ export { JsonNumber } from "./json-value.js";
11
+ export * from "@microsandbox/types/size";
12
+ export { Client, ClientError, InboundFrame, typedMessage, encodedMessage, } from "@microsandbox/protocol-client";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAoB,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAmC,MAAM,iBAAiB,CAAC;AAG9E,cAAc,0BAA0B,CAAC;AACzC,OAAO,EACL,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,GAGhE,MAAM,+BAA+B,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { ConnectOptions, Connector, OutboundMessage, RequestOptions } from "@microsandbox/protocol-client";
2
+ import { type CheckedControlRequest } from "./legacy-request.js";
3
+ import type { JsonReply } from "./json-reply.js";
4
+ /** Explicit JSON unary adapter. Construction is inert and never probes. */
5
+ export declare class JsonControlClient {
6
+ private readonly session;
7
+ private constructor();
8
+ static fromConnector(connector: Connector, options?: ConnectOptions): JsonControlClient;
9
+ clone(): JsonControlClient;
10
+ isClosed(): boolean;
11
+ close(): Promise<void>;
12
+ request(message: OutboundMessage, options?: RequestOptions): Promise<JsonReply>;
13
+ requestTyped<T>(request: CheckedControlRequest<T>, options?: RequestOptions): Promise<T>;
14
+ }
15
+ //# sourceMappingURL=json-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-client.d.ts","sourceRoot":"","sources":["../src/json-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAEhH,OAAO,EAAqB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,2EAA2E;AAC3E,qBAAa,iBAAiB;IACR,OAAO,CAAC,QAAQ,CAAC,OAAO;IAA5C,OAAO;IACP,MAAM,CAAC,aAAa,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,GAAE,cAAmB,GAAG,iBAAiB;IAG3F,KAAK,IAAI,iBAAiB;IAC1B,QAAQ,IAAI,OAAO;IACnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAChB,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC;IAG/E,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CAG/F"}
@@ -0,0 +1,22 @@
1
+ import { JsonSession } from "./json-session.js";
2
+ import { nativeJsonRequest } from "./legacy-request.js";
3
+ /** Explicit JSON unary adapter. Construction is inert and never probes. */
4
+ export class JsonControlClient {
5
+ session;
6
+ constructor(session) {
7
+ this.session = session;
8
+ }
9
+ static fromConnector(connector, options = {}) {
10
+ return new JsonControlClient(new JsonSession({ connector }, options, false));
11
+ }
12
+ clone() { return new JsonControlClient(this.session); }
13
+ isClosed() { return this.session.isClosed(); }
14
+ close() { return this.session.close(); }
15
+ async request(message, options) {
16
+ return this.session.operation(nativeJsonRequest(message), options);
17
+ }
18
+ async requestTyped(request, options) {
19
+ return request.decodeJson(await this.session.operation(request.jsonRequest(), options));
20
+ }
21
+ }
22
+ //# sourceMappingURL=json-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-client.js","sourceRoot":"","sources":["../src/json-client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAA8B,MAAM,qBAAqB,CAAC;AAGpF,2EAA2E;AAC3E,MAAM,OAAO,iBAAiB;IACS;IAArC,YAAqC,OAAoB;QAApB,YAAO,GAAP,OAAO,CAAa;IAAG,CAAC;IAC7D,MAAM,CAAC,aAAa,CAAC,SAAoB,EAAE,UAA0B,EAAE;QACrE,OAAO,IAAI,iBAAiB,CAAC,IAAI,WAAW,CAAC,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAC/E,CAAC;IACD,KAAK,KAAwB,OAAO,IAAI,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1E,QAAQ,KAAc,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACvD,KAAK,KAAoB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACvD,KAAK,CAAC,OAAO,CAAC,OAAwB,EAAE,OAAwB;QAC9D,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;IACrE,CAAC;IACD,KAAK,CAAC,YAAY,CAAI,OAAiC,EAAE,OAAwB;QAC/E,OAAO,OAAO,CAAC,UAAU,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1F,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ import { type JsonObject, type JsonValue } from "./json-value.js";
2
+ import type { Capabilities, CpuState, MemoryState } from "./records.js";
3
+ export type ControlMode = "cbor" | "json";
4
+ /** Actual legacy bytes and an inspectable lossless object; no fabricated frame. */
5
+ export declare class JsonReply {
6
+ #private;
7
+ constructor(raw: Uint8Array);
8
+ get raw(): Uint8Array;
9
+ get value(): JsonObject;
10
+ /** Only a valid affirmative capabilities reply is evidence of a legacy peer. */
11
+ discoveryMode(): ControlMode;
12
+ /** Only checked helpers turn ok:false into an error; progress stays unknown. */
13
+ checked<T>(decode: (value: JsonObject) => T): T;
14
+ }
15
+ export declare function jsonCapabilities(value: JsonValue | undefined): Capabilities;
16
+ export declare function jsonMemory(value: JsonValue | undefined): MemoryState;
17
+ export declare function jsonCpu(value: JsonValue | undefined): CpuState;
18
+ //# sourceMappingURL=json-reply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-reply.d.ts","sourceRoot":"","sources":["../src/json-reply.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmC,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACnG,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAExE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1C,mFAAmF;AACnF,qBAAa,SAAS;;gBAGR,GAAG,EAAE,UAAU;IAI3B,IAAI,GAAG,IAAI,UAAU,CAAsB;IAC3C,IAAI,KAAK,IAAI,UAAU,CAAwB;IAG/C,gFAAgF;IAChF,aAAa,IAAI,WAAW;IAW5B,gFAAgF;IAChF,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,CAAC,GAAG,CAAC;CAMhD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,YAAY,CAQ3E;AACD,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,CAGpE;AACD,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAG9D"}