@ai-ecoverse/slicc-kernel 1.152.2 → 1.153.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.
- package/README.md +26 -2
- package/dist/driver-worker.js +147 -10
- package/dist/driver-worker.js.map +4 -4
- package/dist/index.js +168 -14
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +796 -147
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +147 -10
- package/dist/node-driver-worker.js.map +4 -4
- package/dist/node-process-worker.js +71 -21
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +934 -162
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +71 -21
- package/dist/process-worker.js.map +3 -3
- package/dist/types/client/attach.d.ts +2 -0
- package/dist/types/client/protocol.d.ts +4 -0
- package/dist/types/client/serve-client.d.ts +2 -0
- package/dist/types/embedder-relay.d.ts +58 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/kernel/embedder.d.ts +100 -0
- package/dist/types/kernel/fd-table.d.ts +3 -1
- package/dist/types/kernel/host.d.ts +8 -0
- package/dist/types/kernel/process.d.ts +17 -0
- package/dist/types/launcher.d.ts +13 -0
- package/dist/types/node.d.ts +3 -0
- package/dist/types/process/kernel-streams.d.ts +2 -1
- package/dist/types/process/wasi/wasi-fds.d.ts +1 -0
- package/dist/types/serve.d.ts +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -43,6 +43,7 @@ TypeScript declarations ship in `dist/types/`, and each entry (`.`, `./node`, `.
|
|
|
43
43
|
| `onDenial` | none | `({ pid, uid, op, path, errno, tag? }) => void`: called for every permission check that fails, under `'report'` and `'enforce'` (see [Permissions](#permissions)). `createNodeKernel` takes it too |
|
|
44
44
|
| `sealed` | none | `(req, { signal }) => Promise<object>`: answers requests written to [`/dev/sealed`](#devsealed). `createNodeKernel` takes it too |
|
|
45
45
|
| `clipboard` | none | `{ read(stamp) => Promise<string>, write(text, stamp) => Promise<void> }`: the clipboard behind [`/dev/clipboard`](#devclipboard). `createNodeKernel` takes it too |
|
|
46
|
+
| `requests` | none | `{ kinds, handle(request) }`: the page's answers to [`/dev/embedder`](#devembedder) for the processes it starts. `createNodeKernel` takes it too |
|
|
46
47
|
| `approve` | none | `(req, { signal }) => 'once' \| 'always' \| 'deny'` (or a promise of one): decides a `sudo` request that needs approval (see [Sudo](#sudo)); without it, such a request is denied. `createNodeKernel` takes it too |
|
|
47
48
|
| `cdp` | none | `({ runtime }) => Promise<CdpConnection>`: a browser-level DevTools connection for programs that drive a browser (see [Browser automation](#browser-automation-cdp)) |
|
|
48
49
|
| `facades` | none | `{ link: { port, hook? } }`: kernel-only loopback ports whose connections a hook on the page serves, with the connecting program's identity (see [Page facades](#page-facades)) |
|
|
@@ -86,10 +87,12 @@ Starts `argv` as the session leader on a new terminal and resolves once it is ru
|
|
|
86
87
|
|
|
87
88
|
Stops the kernel worker and every process. Pending and later calls reject.
|
|
88
89
|
|
|
89
|
-
### `kernel.connect({ user, tag }?) → Promise<MessagePort>`
|
|
90
|
+
### `kernel.connect({ user, tag, requests }?) → Promise<MessagePort>`
|
|
90
91
|
|
|
91
92
|
Makes a port for another client of the same kernel. Hand it to a dedicated worker, or through a SharedWorker to another tab: it can be transferred any number of times. All clients share one process table, so a terminal on the page can `ps` and `kill` what a worker started.
|
|
92
93
|
|
|
94
|
+
**`requests`** binds the port's [`/dev/embedder`](#devembedder) requests when the port is made: a hook (`{ kinds, handle }`, as `createKernel`'s) answers for every process the port's client starts, in the embedder's own realm, so a worker client (an agent) never answers or registers requests itself. `'attach'` lets the client that attaches to the port bring its own hook (`attachKernel(port, { requests })`, protocol 1.13); without it, an attach with `requests` fails with `EPERM`. A port with neither answers nothing: its processes' requests are `ENOTSUP`.
|
|
95
|
+
|
|
93
96
|
**`user`** (a name or uid) binds the client to that user; without it the client is root. The binding is a capability: the client's `spawn`, `run` and `openTerminal` run as that user, its `ps` lists only that user's processes, and its `kill` follows POSIX permission. A bound client can't run anything as another user (`code: 'EPERM'`). A root client may pass `user` to `spawn`, `run` or `openTerminal` to run one command as any user. An unknown user rejects with `code: 'ENOENT'`. **`tag`** is the tag (see `kernel.run`) of every process the client starts; its own `spawn`, `run` and `openTerminal` cannot name another. They may add one with their own `tag` option (protocol 1.12; an older kernel answers `ENOSYS`): the process then has both tags, the connection's first, and so does everything it starts. A per-spawn tag only adds; no client, program or child can remove or replace a tag ([#1380](https://github.com/ai-ecoverse/slicc-kernel/issues/1380)).
|
|
94
97
|
|
|
95
98
|
### `kernel.users`
|
|
@@ -166,7 +169,7 @@ Page-only: neither is on an attached client (`attachKernel`, `connect()`), so no
|
|
|
166
169
|
|
|
167
170
|
### `attachKernel(port, options?) → Promise<KernelClient>`
|
|
168
171
|
|
|
169
|
-
Attaches to a kernel over a port from `kernel.connect()`, in any realm: a worker, another tab, or Node. `options.timeoutMs` (10 s by default) bounds the handshake: a port with no kernel behind it rejects with `KernelGoneError`.
|
|
172
|
+
Attaches to a kernel over a port from `kernel.connect()`, in any realm: a worker, another tab, or Node. `options.timeoutMs` (10 s by default) bounds the handshake: a port with no kernel behind it rejects with `KernelGoneError`. `options.requests` (`{ kinds, handle }`) answers the [`/dev/embedder`](#devembedder) requests of the client's processes, when the port was made with `connect({ requests: 'attach' })`.
|
|
170
173
|
|
|
171
174
|
| Client | |
|
|
172
175
|
|---|---|
|
|
@@ -1126,6 +1129,27 @@ A character device (`0666`), there in every `users` mode, through which programs
|
|
|
1126
1129
|
- **Cost:** other files' reads and closes check one property more, and their writes nothing; an Emscripten close checks one flag.
|
|
1127
1130
|
- **On the page,** `createKernel` relays the calls from the kernel worker and back, keeping the error's `name` and `message`.
|
|
1128
1131
|
|
|
1132
|
+
### `/dev/embedder`
|
|
1133
|
+
|
|
1134
|
+
A character device (`0666`, every `users` mode) through which a program asks its embedder's page to do what only a page can: open a URL, capture the screen, play or record audio ([#1527](https://github.com/ai-ecoverse/slicc-kernel/issues/1527)). One open file description is one request, and the reply streams back; plain `sh` with coreutils is enough:
|
|
1135
|
+
|
|
1136
|
+
```sh
|
|
1137
|
+
exec 3<>/dev/embedder
|
|
1138
|
+
{ printf '%s\n' '{"kind":"audio.transcribe","language":"en"}'; cat speech.wav; } >&3
|
|
1139
|
+
cat <&3 > text.txt
|
|
1140
|
+
printf '{"kind":"open.url","url":"%s"}\n' "$url" | cat > /dev/embedder
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
- **The request:** the first line is a JSON object with a `kind` (`a.b-c`: lower-case words with dots, at most 64 bytes) and options (every other key), at most 64 KiB. Everything written after it is the body, streamed to the page as it is written: a write waits while 1 MiB of it is unread by the page. A bad header fails that `write()` with `EINVAL`, and the description stays failed. The request goes to the page when its header line ends.
|
|
1144
|
+
- **The reply:** reads return the page's reply in order, pulled as the program reads, and its end is end-of-file. Opened `O_RDWR`, the first `read()` ends the body. Opened `O_WRONLY` (nobody reads), the last close ends the body and waits for the page to finish, so `close()` returns its error (an Emscripten program's standard output follows `/dev/clipboard`'s exit rule, so `printf … | cat > /dev/embedder` fails with `cat: write error: …`). There is no timeout: a reply may wait for a prompt or a model download; run `timeout` for one.
|
|
1145
|
+
- **Cancel:** a signal during a blocked read, write or `O_WRONLY` close fails it with `EINTR`, and the page's `signal` aborts with reason `interrupted`. Closing the description before the end of the reply aborts it with reason `closed`, and that is no error: it is how `hear` stops recording. A process that ends during a request aborts it too.
|
|
1146
|
+
- **Errors:** the hook's error gives the errno: `NotAllowedError` or `AbortError` (a dismissed prompt, a refused permission) `EACCES`; `NotSupportedError` `ENOTSUP`; `NotFoundError` `ENOENT`; anything else `EIO`. An Emscripten or WASI program first gets `<argv0>: /dev/embedder: <first line of the message>` on standard error, as for `/dev/clipboard`; a JS program has it in the error. `request.progress(line)` from the page writes `<argv0>: <line>` to standard error the same way, at most 4 lines a second.
|
|
1147
|
+
- **Who answers:** the hook of the client the process's session was started through, inherited by its children, forks and `exec`s: `createKernel({ requests })` (and `createNodeKernel`) for the page's own `run` and `openTerminal`, the hook a port was made with (`connect({ requests })`), or the hook its attached client brought (`connect({ requests: 'attach' })`, then `attachKernel(port, { requests })`). Nothing falls back to another client's hook, so a request from another tab's terminal never opens in this one. With no hook, or a kind the hook doesn't list, the request is `ENOTSUP` before the page sees it. A program has no way to register, replace or see a hook.
|
|
1148
|
+
- **The allow-list and policy:** `kinds` maps each kind the hook answers to its policy: `{}`, `{ group: 'video' }`, `{ users: ['alice', 1001] }`. With `users: 'report'` or `'enforce'` the kernel lets a request through only from root, the session's user (the user its port was connected as, or the user `run` or `openTerminal` ran as), a member of the kind's `group`, or a listed user: a process that `sudo -u`'d to someone else gets `EACCES`, and the page never sees it. The handler can still prompt or refuse by the stamp.
|
|
1149
|
+
- **The hook:** `handle({ kind, options, body, stamp, signal, progress })` gets the body as a `ReadableStream<Uint8Array>` and resolves with the reply: a `ReadableStream<Uint8Array>`, bytes, a string, or nothing (an empty reply). `stamp` is `{ pid, uid, gid, groups, users, kind, tag?, tags? }`: the process that sent the header line, with its effective ids and groups.
|
|
1150
|
+
- **Limits:** at most 16 open requests per process (`EMFILE`) and 256 MiB of request windows per kernel, 128 requests at 2 MiB each (`EAGAIN` at `open()` beyond); a closed request frees its share.
|
|
1151
|
+
- `/dev/clipboard` stays as it is, text only. Image clipboards are kinds on this channel (`clipboard.read`, `clipboard.write`), answered by the embedder.
|
|
1152
|
+
|
|
1129
1153
|
### Protected paths
|
|
1130
1154
|
|
|
1131
1155
|
`createKernel({ protect: { paths: ['/node_modules', '/os'] } })` makes those directories read-only for every process, root included, in every `users` mode. They hold code every later process runs, which only the embedder writes, straight to OPFS.
|
package/dist/driver-worker.js
CHANGED
|
@@ -43,6 +43,111 @@ function serveCdpPort(port, hook, request, onDone) {
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// src/embedder-relay.ts
|
|
47
|
+
function fresh(bytes) {
|
|
48
|
+
return bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength ? bytes : bytes.slice();
|
|
49
|
+
}
|
|
50
|
+
function errorOf(err) {
|
|
51
|
+
const name = err?.name;
|
|
52
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
53
|
+
return { name: typeof name === "string" ? name : "Error", message };
|
|
54
|
+
}
|
|
55
|
+
function bytesOf(reply) {
|
|
56
|
+
if (reply instanceof ReadableStream) return reply;
|
|
57
|
+
const bytes = typeof reply === "string" ? new TextEncoder().encode(reply) : reply instanceof Uint8Array ? reply : new Uint8Array(0);
|
|
58
|
+
return new ReadableStream({
|
|
59
|
+
start(controller) {
|
|
60
|
+
if (bytes.length > 0) controller.enqueue(bytes);
|
|
61
|
+
controller.close();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function bodyStream(id, state, post) {
|
|
66
|
+
return new ReadableStream(
|
|
67
|
+
{
|
|
68
|
+
pull: async (controller) => {
|
|
69
|
+
if (state.body.length === 0) post({ id, bodyPull: true });
|
|
70
|
+
while (state.body.length === 0 && !state.abort.signal.aborted) {
|
|
71
|
+
await new Promise((resolve) => {
|
|
72
|
+
state.wakeBody = resolve;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const next = state.body.shift();
|
|
76
|
+
if (next) controller.enqueue(next);
|
|
77
|
+
else controller.close();
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{ highWaterMark: 0 }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
function embedderAnswerer(port, hookFor) {
|
|
84
|
+
const running = /* @__PURE__ */ new Map();
|
|
85
|
+
const post = (message, transfer = []) => port.postMessage({ embedderReply: message }, transfer);
|
|
86
|
+
const fail = (id, err) => {
|
|
87
|
+
running.delete(id);
|
|
88
|
+
post({ id, error: errorOf(err) });
|
|
89
|
+
};
|
|
90
|
+
const start = async (ask) => {
|
|
91
|
+
const state = { abort: new AbortController(), body: [], wakeBody: void 0 };
|
|
92
|
+
running.set(ask.id, state);
|
|
93
|
+
try {
|
|
94
|
+
const hook = hookFor(ask.client);
|
|
95
|
+
if (!hook || !Object.hasOwn(hook.kinds, ask.kind))
|
|
96
|
+
throw Object.assign(new Error(`this page does not answer ${ask.kind}`), {
|
|
97
|
+
name: "NotSupportedError"
|
|
98
|
+
});
|
|
99
|
+
const reply = await hook.handle({
|
|
100
|
+
kind: ask.kind,
|
|
101
|
+
options: ask.options,
|
|
102
|
+
body: bodyStream(ask.id, state, post),
|
|
103
|
+
stamp: ask.stamp,
|
|
104
|
+
signal: state.abort.signal,
|
|
105
|
+
progress: (line) => post({ id: ask.id, progress: String(line) })
|
|
106
|
+
});
|
|
107
|
+
state.reader = bytesOf(reply).getReader();
|
|
108
|
+
post({ id: ask.id, ready: true });
|
|
109
|
+
} catch (err) {
|
|
110
|
+
fail(ask.id, err);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const pull = async (id, state) => {
|
|
114
|
+
try {
|
|
115
|
+
const got = await state.reader.read();
|
|
116
|
+
if (got.done) {
|
|
117
|
+
running.delete(id);
|
|
118
|
+
post({ id, done: true });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const bytes = fresh(got.value);
|
|
122
|
+
post({ id, chunk: bytes }, [bytes.buffer]);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
fail(id, err);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const step = (message) => {
|
|
128
|
+
const state = running.get(message.id);
|
|
129
|
+
if (!state) return;
|
|
130
|
+
if ("body" in message) state.body.push(message.body);
|
|
131
|
+
else if ("bodyEnd" in message) state.body.push(null);
|
|
132
|
+
else if ("pull" in message) void pull(message.id, state);
|
|
133
|
+
else {
|
|
134
|
+
running.delete(message.id);
|
|
135
|
+
state.abort.abort(new DOMException(message.cancel, "AbortError"));
|
|
136
|
+
void state.reader?.cancel(message.cancel).catch(() => void 0);
|
|
137
|
+
}
|
|
138
|
+
state.wakeBody?.();
|
|
139
|
+
};
|
|
140
|
+
return {
|
|
141
|
+
handle(data) {
|
|
142
|
+
const message = data?.embedder;
|
|
143
|
+
if (!message) return false;
|
|
144
|
+
if ("ask" in message) void start(message.ask);
|
|
145
|
+
else step(message);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
46
151
|
// src/facade/port.ts
|
|
47
152
|
function serveFacadePort(port, hook, { name, stamp }, onDone) {
|
|
48
153
|
let conn;
|
|
@@ -93,6 +198,27 @@ function serveFacadePort(port, hook, { name, stamp }, onDone) {
|
|
|
93
198
|
};
|
|
94
199
|
}
|
|
95
200
|
|
|
201
|
+
// src/kernel/pipe.ts
|
|
202
|
+
var PIPE_CAPACITY = 64 * 1024;
|
|
203
|
+
|
|
204
|
+
// src/kernel/embedder.ts
|
|
205
|
+
var EMBEDDER_HEADER_MAX = 64 * 1024;
|
|
206
|
+
var EMBEDDER_WINDOW = 1024 * 1024;
|
|
207
|
+
var EMBEDDER_BUDGET = 256 * 1024 * 1024;
|
|
208
|
+
function kindsOf(hook) {
|
|
209
|
+
if (typeof hook?.handle !== "function" || typeof hook.kinds !== "object" || !hook.kinds)
|
|
210
|
+
throw new TypeError("requests needs kinds and handle");
|
|
211
|
+
return Object.fromEntries(
|
|
212
|
+
Object.entries(hook.kinds).map(([kind, policy]) => [
|
|
213
|
+
kind,
|
|
214
|
+
{
|
|
215
|
+
...policy?.group !== void 0 ? { group: String(policy.group) } : {},
|
|
216
|
+
...policy?.users ? { users: [...policy.users] } : {}
|
|
217
|
+
}
|
|
218
|
+
])
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
96
222
|
// src/kernel/net/ws-queue.ts
|
|
97
223
|
var INBOUND_LIMIT = 16 * 1024 * 1024;
|
|
98
224
|
var MESSAGE_LIMIT = 64 * 1024 * 1024;
|
|
@@ -421,7 +547,7 @@ var KERNEL_SIGINFO = Object.freeze({
|
|
|
421
547
|
});
|
|
422
548
|
|
|
423
549
|
// src/bytes.ts
|
|
424
|
-
function
|
|
550
|
+
function fresh2(bytes, start = 0, end = bytes.length) {
|
|
425
551
|
return new Uint8Array(bytes.subarray(start, end));
|
|
426
552
|
}
|
|
427
553
|
|
|
@@ -478,7 +604,7 @@ function dialStream(port, onClose) {
|
|
|
478
604
|
const writable = new WritableStream({
|
|
479
605
|
write(chunk) {
|
|
480
606
|
if (closed) throw errorWithCode("EPIPE: the connection is closed");
|
|
481
|
-
const copy =
|
|
607
|
+
const copy = fresh2(chunk);
|
|
482
608
|
const acked = new Promise((resolve, reject) => acks.push({ resolve, reject }));
|
|
483
609
|
port.postMessage(copy, [copy.buffer]);
|
|
484
610
|
return acked;
|
|
@@ -665,7 +791,7 @@ async function loopbackFetch(dial, input, options) {
|
|
|
665
791
|
}
|
|
666
792
|
|
|
667
793
|
// src/client/protocol.ts
|
|
668
|
-
var PROTOCOL = [1,
|
|
794
|
+
var PROTOCOL = [1, 13];
|
|
669
795
|
function locksOf(scope = globalThis) {
|
|
670
796
|
const locks = scope.navigator?.locks;
|
|
671
797
|
return typeof locks?.request === "function" ? locks : void 0;
|
|
@@ -693,7 +819,7 @@ var KernelCallError = class extends Error {
|
|
|
693
819
|
}
|
|
694
820
|
};
|
|
695
821
|
var encoder2 = new TextEncoder();
|
|
696
|
-
var
|
|
822
|
+
var bytesOf2 = (data) => typeof data === "string" ? encoder2.encode(data) : data;
|
|
697
823
|
var UNAVAILABLE = {
|
|
698
824
|
manualRedirects: false,
|
|
699
825
|
encodedBodies: false,
|
|
@@ -777,7 +903,9 @@ var Channel = class {
|
|
|
777
903
|
call(call) {
|
|
778
904
|
return this.request(call).done;
|
|
779
905
|
}
|
|
906
|
+
requests;
|
|
780
907
|
receive(data) {
|
|
908
|
+
if (this.requests?.handle(data)) return;
|
|
781
909
|
if (data.traits && data.id === void 0) {
|
|
782
910
|
if (this.remote) this.remote.traits = data.traits;
|
|
783
911
|
for (const listener of [...this.traitsListeners]) listener(data.traits);
|
|
@@ -900,7 +1028,7 @@ async function spawnOn(channel, argv, opts) {
|
|
|
900
1028
|
});
|
|
901
1029
|
const started = Promise.withResolvers();
|
|
902
1030
|
let endedBy;
|
|
903
|
-
const options = { ...rest, ...stdin !== void 0 ? { stdin:
|
|
1031
|
+
const options = { ...rest, ...stdin !== void 0 ? { stdin: bytesOf2(stdin) } : {} };
|
|
904
1032
|
const { done } = channel.request(
|
|
905
1033
|
{ op: "spawn", argv, options },
|
|
906
1034
|
{
|
|
@@ -983,7 +1111,7 @@ async function terminalOn(channel, argv, opts) {
|
|
|
983
1111
|
listener = next;
|
|
984
1112
|
if (next) for (const bytes of backlog.splice(0)) next(bytes);
|
|
985
1113
|
},
|
|
986
|
-
write: (data) => send({ action: "write", bytes:
|
|
1114
|
+
write: (data) => send({ action: "write", bytes: bytesOf2(data) }),
|
|
987
1115
|
resize: (cols, rows) => send({ action: "resize", cols, rows }),
|
|
988
1116
|
signal: (name) => send({ action: "signal", signal: name }),
|
|
989
1117
|
close: () => send({ action: "close" })
|
|
@@ -1045,8 +1173,10 @@ function since(channel, minor, what, run) {
|
|
|
1045
1173
|
}
|
|
1046
1174
|
return run();
|
|
1047
1175
|
}
|
|
1048
|
-
async function greet(channel, lock, timeoutMs) {
|
|
1049
|
-
channel.post({
|
|
1176
|
+
async function greet(channel, lock, timeoutMs, requests) {
|
|
1177
|
+
channel.post({
|
|
1178
|
+
hello: { protocol: PROTOCOL, ...lock ? { lock } : {}, ...requests ? { requests } : {} }
|
|
1179
|
+
});
|
|
1050
1180
|
const timer = setTimeout(
|
|
1051
1181
|
() => channel.end(
|
|
1052
1182
|
new KernelGoneError(`no slicc-kernel answered on this port within ${timeoutMs} ms`)
|
|
@@ -1071,7 +1201,14 @@ async function attachKernel(port, options = {}) {
|
|
|
1071
1201
|
const locks = options.locks === null ? void 0 : options.locks ?? locksOf();
|
|
1072
1202
|
const own = await holdLock(locks);
|
|
1073
1203
|
const channel = new Channel(port, own.release);
|
|
1074
|
-
const
|
|
1204
|
+
const requests = options.requests;
|
|
1205
|
+
if (requests) channel.requests = embedderAnswerer(port, () => requests);
|
|
1206
|
+
const hello = await greet(
|
|
1207
|
+
channel,
|
|
1208
|
+
own.name,
|
|
1209
|
+
options.timeoutMs ?? 1e4,
|
|
1210
|
+
requests ? kindsOf(requests) : void 0
|
|
1211
|
+
);
|
|
1075
1212
|
channel.protocol = hello.protocol;
|
|
1076
1213
|
if (hello.lock && locks) void locks.request(hello.lock, () => channel.end(new KernelGoneError()));
|
|
1077
1214
|
const transport = new RemoteTransport(
|
|
@@ -1234,7 +1371,7 @@ function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
|
|
|
1234
1371
|
(result) => {
|
|
1235
1372
|
if (result instanceof Uint8Array) {
|
|
1236
1373
|
const whole = result.byteOffset === 0 && result.byteLength === result.buffer.byteLength;
|
|
1237
|
-
const bytes = options.owned && whole ? result :
|
|
1374
|
+
const bytes = options.owned && whole ? result : fresh2(result);
|
|
1238
1375
|
reply({ id: data.id, result: bytes }, [bytes.buffer]);
|
|
1239
1376
|
} else reply({ id: data.id, result: result ?? null });
|
|
1240
1377
|
},
|