@ai-ecoverse/slicc-kernel 1.178.4 → 1.180.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 +20 -4
- package/dist/driver-worker.js +57 -1
- package/dist/driver-worker.js.map +2 -2
- package/dist/index.js +57 -1
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +235 -24
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +57 -1
- package/dist/node-driver-worker.js.map +2 -2
- package/dist/node-process-worker.cjs +1 -1
- package/dist/node-process-worker.js +1 -1
- package/dist/node.js +289 -22
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +1 -1
- package/dist/types/client/attach.d.ts +18 -3
- package/dist/types/client/protocol.d.ts +22 -4
- package/dist/types/index.d.ts +4 -1
- package/dist/types/kernel/jobs.d.ts +2 -1
- package/dist/types/kernel/trace.d.ts +59 -0
- package/dist/types/launcher.d.ts +13 -0
- package/dist/types/node.d.ts +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ Runs `argv[0]` with `argv` as its arguments and resolves when it exits. `stdout`
|
|
|
57
57
|
| option | |
|
|
58
58
|
| --- | --- |
|
|
59
59
|
| `cwd` | working directory, default `/`; created in OPFS if missing. Under `users: 'enforce'`, a `cwd` the user can't search rejects with `code: 'EACCES'` (`cannot change to <cwd>: Permission denied`) and starts nothing, as a `chdir` there fails on Linux; `openTerminal` does the same. A program's spawn with its own `cwd` (`ctx.spawn`, `ctx.sync.run`, a WASIX `posix_spawn` chdir action) is checked the same way and fails with `EACCES`, while a child that inherits its parent's cwd keeps it unchecked, as on Linux |
|
|
60
|
+
| `cwdMissing` | `'create'` (the default) or `'error'`: what happens when `cwd` doesn't exist. `'create'` creates it, with its parents, as described for `cwd`. `'error'` rejects with `code: 'ENOENT'` (`cannot change to <cwd>: No such file or directory`) and starts nothing, as a `chdir` there fails on Linux. A caller that keeps directories in a list to run commands in, and so may name one that was removed since, should pass `'error'`: with `'create'`, its next command brings the removed directory back, empty ([#1808](https://github.com/ai-ecoverse/slicc-kernel/issues/1808)). Any other value rejects with a `TypeError`; `openTerminal` takes it too |
|
|
60
61
|
| `user` | the user to run as, by name or uid (see [Users](#users)); default `root`. An unknown user rejects with `code: 'ENOENT'` |
|
|
61
62
|
| `tag` | a string the kernel keeps with the process and every process it starts (fork, spawn and exec), and hands to `approve` with a [`sudo`](#sudo) request, so the embedder knows which agent asks; nothing a program does changes it. Anything but a string rejects with a `TypeError` |
|
|
62
63
|
| `env` | extra environment, over the user's defaults: `HOME`, `USER`, `LOGNAME`, `PNPM_HOME` and `PATH=$PNPM_HOME/bin:/usr/local/bin:/usr/bin:/bin` (see [Users](#users)), `TZ=<system time zone>` (see [Time zone](#time-zone)), and `PWD=<cwd>` |
|
|
@@ -69,7 +70,7 @@ Starts `argv` as the session leader on a new terminal and resolves once it is ru
|
|
|
69
70
|
|
|
70
71
|
| option | |
|
|
71
72
|
| --- | --- |
|
|
72
|
-
| `cwd`, `user`, `tag`, `env` | as for `run`; `TERM=xterm-256color` and `COLORTERM=truecolor` are added |
|
|
73
|
+
| `cwd`, `cwdMissing`, `user`, `tag`, `env` | as for `run`; `TERM=xterm-256color` and `COLORTERM=truecolor` are added |
|
|
73
74
|
| `cols`, `rows` | initial size, default 80 × 24, so `$COLUMNS` and `$LINES` are right before the first prompt |
|
|
74
75
|
| `onData` | called with each chunk of output as raw bytes (escape sequences included) |
|
|
75
76
|
|
|
@@ -94,7 +95,7 @@ Makes a port for another client of the same kernel. Hand it to a dedicated worke
|
|
|
94
95
|
|
|
95
96
|
**`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`.
|
|
96
97
|
|
|
97
|
-
**`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)).
|
|
98
|
+
**`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. The option may also be a list of at most 8 tags, added in order (protocol 1.15; against an older kernel the client rejects with `code: 'ENOSYS'` before sending anything), so a caller can mark one call's tree with a tag of its own for [`trace`](#clienttracetag-onevents--limit---promise-close-) beside a tag that means something else. 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)).
|
|
98
99
|
|
|
99
100
|
### `kernel.users`
|
|
100
101
|
|
|
@@ -176,7 +177,7 @@ Attaches to a kernel over a port from `kernel.connect()`, in any realm: a worker
|
|
|
176
177
|
|
|
177
178
|
| Client | |
|
|
178
179
|
|---|---|
|
|
179
|
-
| `spawn(argv, { cwd, user, env, stdin, pgid, onStdout, onStderr })` | runs over pipes and resolves with `{ pid, pgid, exited, ended, signal(name) }` once the process has started; `ended` resolves with `{ status, signal }`, `signal` named only when a signal ended the process, as for `kernel.run` (an older kernel never names one); output arrives in chunks, as bytes, and the kernel keeps no copy; every spawn leads its own process group, which `signal` (default `SIGTERM`) signals, unless `pgid` names an existing group to join (protocol 1.3): the process then joins that group and that group's session, as `setpgid` would (a client has no session of its own: each of its spawns and terminals leads one, and `setpgid` cannot cross sessions), and `signal` signals the whole group. The group must have been started by this client (a spawn or a terminal it opened), even if its leader has exited; an unknown group rejects with `code: 'ESRCH'`, another session's with `code: 'EPERM'`, and against a 1.2 kernel the client rejects with `code: 'ENOSYS'` before sending anything, as an older kernel would ignore the option; a program that cannot start rejects with `code: 'ENOENT'`. `user` runs it as another user, which only a root client may (see `kernel.connect`); against a 1.7 kernel the client rejects with `code: 'ENOSYS'` |
|
|
180
|
+
| `spawn(argv, { cwd, cwdMissing, user, env, stdin, pgid, onStdout, onStderr })` | runs over pipes and resolves with `{ pid, pgid, exited, ended, signal(name) }` once the process has started; `ended` resolves with `{ status, signal }`, `signal` named only when a signal ended the process, as for `kernel.run` (an older kernel never names one); output arrives in chunks, as bytes, and the kernel keeps no copy; every spawn leads its own process group, which `signal` (default `SIGTERM`) signals, unless `pgid` names an existing group to join (protocol 1.3): the process then joins that group and that group's session, as `setpgid` would (a client has no session of its own: each of its spawns and terminals leads one, and `setpgid` cannot cross sessions), and `signal` signals the whole group. The group must have been started by this client (a spawn or a terminal it opened), even if its leader has exited; an unknown group rejects with `code: 'ESRCH'`, another session's with `code: 'EPERM'`, and against a 1.2 kernel the client rejects with `code: 'ENOSYS'` before sending anything, as an older kernel would ignore the option; a program that cannot start rejects with `code: 'ENOENT'`. `user` runs it as another user, which only a root client may (see `kernel.connect`); against a 1.7 kernel the client rejects with `code: 'ENOSYS'`. `cwdMissing` is as for `kernel.run` (protocol 1.14); `run` and `openTerminal` take it too, and with `'error'` against a 1.13 kernel, which would create the directory, the client rejects with `code: 'ENOSYS'` before sending anything |
|
|
180
181
|
| `run(argv, options?)` | `spawn` and wait: `{ pid, status, signal, stdout, stderr }` as text, with `onStdout`/`onStderr` streaming text; `signal` as for `kernel.run` |
|
|
181
182
|
| `openTerminal(argv, options?)` | a pty session, as `kernel.openTerminal`, `ended` included |
|
|
182
183
|
| `ps()` | the kernel's processes: `{ pid, ppid, pgid, sid, argv, tty, started, state, rss, utime, stime, uid, gid }`, `state` being `'S'`, `'T'` (stopped) or `'Z'`, `rss` the process's resident memory in bytes (the size of its wasm memory, what `/proc/<pid>/status` shows as `VmRSS`; absent from kernels before 1.61), `utime`/`stime` the CPU time it has used, in milliseconds, in its program and in the kernel's calls (see [`/proc`](#proc); absent for a JS program and from kernels before 1.63) and `uid`/`gid` the effective ids (absent from a 1.7 kernel), and `tag` the process's tag (see `kernel.run`), when it has one, with `tags`, all of them in order, when a spawn added one (see `kernel.connect`), on rows the client's user may see as root or as the same uid. A client bound to a user sees only that user's processes |
|
|
@@ -186,12 +187,27 @@ Attaches to a kernel over a port from `kernel.connect()`, in any realm: a worker
|
|
|
186
187
|
| `fs.watch(paths, { recursive }, onChange)` | resolves with `{ close() }`; `onChange` gets `{ paths }`, the changed paths at, below (or, without `recursive`, directly in) the watched ones, batched per task, or `{ overflow: true }` when there were too many to list, to rescan; it covers every change made through this kernel, by its processes and its clients, and a removal or rename that failed or found nothing to do is none. It also covers what another kernel on the same OPFS root changes (another tab, a worker page) and what a writer outside the kernel reports with [`postFsNotice`](#metadata), as those notices name them: a directory another kernel creates, removes or renames, the file it writes back, and the records of its symlinks, modes, owners and times, and a notice's `trees` as their roots. A peer's notices come only once the kernels share the root (`/proc/sys/kernel/slicc_shared_root` reads `1` from the join handshake on) and only with the shared metadata (not with `metadata: false`), and a writer outside the kernel that posts no notice is never heard. So a client that has to catch a page writing OPFS directly needs that page to call `postFsNotice` after each write. Kernels before this one didn't pass peer and outside changes to a client's watch; they reached only `ctx.fs.watch`. A watch is on names, not files: a watched path need not exist, nor its directories, and one under a regular file is a watch too (every kernel with `watch` does this, protocol 1.1 on), so its creation, by any `mkdir -p` above it, a write or a rename to it, is reported as its path, and so are a removal or rename of it or of a directory above it; the watch goes on hearing the name after that. So a client that waits for a directory to appear watches it, rather than polling `stat` and `readdir`. Under `users`, a watched path the client may not read is `EACCES`, and so is a missing one under a directory the client may not search. Changes are reported by the name they were made under (a file written through a symlink also by its real path), so a client that watches a path under a symlink watches its real path too, for a missing path the real path of its nearest existing directory with the rest of the path after it |
|
|
187
188
|
| `serveCdp(hook, { runtime })` | offers this client's browser to the kernel's programs (protocol 1.5): `hook` is the `cdp` hook of `createKernel`, asked once per program connection; resolves with `{ close() }`, and closing or detaching takes the offer back and closes its connections (see [Browser automation](#browser-automation-cdp)) |
|
|
188
189
|
| `serveFacade(name, hook) → { close() }` | serves a facade that `createKernel({ facades })` declared, the same way as its `hook` (protocol 1.11); the latest registration wins, over the embedder's hook too, and `close()` hands it back. An undeclared name is `EINVAL` |
|
|
190
|
+
| `trace(tag, onEvents, { limit }?)` | reports, as events, every program that a process carrying `tag` runs and every such process's end, with nothing missed (protocol 1.15; against an older kernel the client rejects with `code: 'ENOSYS'`); resolves with `{ close() }` (see [below](#clienttracetag-onevents--limit---promise-close-)) |
|
|
189
191
|
| `onTraits(listener) → () => void` | calls `listener(traits)` each time the kernel announces new transport traits (`kernel.updateTransportTraits()`, protocol 1.10), after `client.transport.traits` has been updated; the function it returns removes the listener |
|
|
190
192
|
| `close({ kill })` | detaches: the client's processes keep running unless `kill` is set, which ends their process groups with `SIGKILL`; its terminals are hung up |
|
|
191
193
|
| `closed` | resolves with the error that ended the client |
|
|
192
194
|
| `users` | the kernel's `users` mode, `'off'`, `'report'` or `'enforce'`; `'off'` from kernels that don't say |
|
|
193
195
|
|
|
194
|
-
When the kernel goes away (the page closed or reloaded, `terminate()`), pending calls and `exited` reject with `KernelGoneError`, and so do later calls. Each side holds a Web Lock and waits on the other's, since a `MessagePort` reports no close in browsers; in Node, the port's `close` event does the same. The first message is a handshake on the protocol version, `1.
|
|
196
|
+
When the kernel goes away (the page closed or reloaded, `terminate()`), pending calls and `exited` reject with `KernelGoneError`, and so do later calls. Each side holds a Web Lock and waits on the other's, since a `MessagePort` reports no close in browsers; in Node, the port's `close` event does the same. The first message is a handshake on the protocol version, `1.15` (`1.0` had no `watch`, `1.1` no mounts, `1.2` no `spawn` into a group, `1.3` no `dial` and `1.4` no `serveCdp`, which reject with `code: 'ENOSYS'` on such a kernel; a `1.5` kernel has no [uplink](#uplink), a `1.6` kernel resolves names through one but connects nothing to it, a `1.7` kernel has no users, a `1.8` kernel has no `sudo` and no tags, a `1.9` kernel never announces new transport traits, a `1.11` kernel has no per-spawn `tag`, a `1.12` kernel takes no `requests` at attach, a `1.13` kernel has no `cwdMissing`, and a `1.14` kernel has no `trace` and takes one tag per spawn, not a list, which the client rejects with `code: 'ENOSYS'` before sending anything): a client or kernel of another major version is refused with an error naming both.
|
|
197
|
+
|
|
198
|
+
#### `client.trace(tag, onEvents, { limit }?) → Promise<{ close() }>`
|
|
199
|
+
|
|
200
|
+
Tells a client which programs a command really ran, without polling `ps()`, which misses a `seq` or a `head` that lives for a millisecond. The kernel runs real bash, so builtins and keywords (`cd`, `for`, `printf`, `echo`) never start a program, and what does is what the command was about (`seq`, `sed`, `node`, the program behind `timeout`). Give each call a tag of its own (the spawn's `tag`, which may be a list, see [`kernel.connect`](#kernelconnect-user-tag-requests---promisemessageport)), subscribe to it before the spawn, and `onEvents` gets every process that carries the tag, in its whole tree, background jobs that outlive the command included:
|
|
201
|
+
|
|
202
|
+
- **`exec`** `{ type, seq, pid, ppid, pgid, argv, exe, uid, at, replaced? }`: a program started, for the first image of a spawn, a child a program spawned (`posix_spawn`, as `timeout` runs its command) and each successful `execve`. `replaced: true` marks an `execve`, whose program keeps the `pid` of the image it replaced (bash runs its last command that way, so `bash -c '…; node x'` ends with an `exec` of `node` under bash's pid). An `execve` that fails (`command not found`) has no event.
|
|
203
|
+
- **`fork`** `{ type, seq, pid, ppid, pgid, argv, exe, uid, at }`: a `fork()`. Its `argv` and `exe` are the parent's, a forked bash for a subshell, a pipeline element or a command it is about to run. A fork that then runs a program has an `exec` with the same `pid` next; one that never has an `exec` ran only builtins, so `type` tells a forked `bash` from a real program.
|
|
204
|
+
- **`exit`** `{ type, seq, pid, status, signal?, utime?, stime?, at }`: a process ended, once per `pid`, after its last image: `status` as `waitpid` would report it (127 for a command that wasn't found, 141 for an unhandled `SIGPIPE`), `signal` the name of the signal that killed it (`'SIGKILL'`), `utime`/`stime` the milliseconds of CPU it used, its images together, as in `ps()` (absent for a JS program).
|
|
205
|
+
|
|
206
|
+
`pid`, `ppid` and `pgid` are the ids `ps()` shows, `ppid` 1 for a spawn's first process; `uid` is the effective uid; `at` is the time in ms since the epoch. The kernel reports from the path every process takes to start and end, so a process that execs and exits within a millisecond is reported like any other. Events come in order: `seq` is one kernel-wide counter, increasing in the order the kernel saw the events (with gaps for events only other subscriptions got), so it orders the execs and exits across a tree more finely than `at`, and for one `pid` the `fork` or `exec` comes before its `exit`. A process that started before the subscription reports only its exit.
|
|
207
|
+
|
|
208
|
+
A client bound to a user sees only the processes of that user it would see in `ps()` (its own real or effective uid; root sees all), so another user's processes with the same tag never reach it, and a program that `sudo` runs as root drops out of its view. A tag matches exactly, any of a process's tags.
|
|
209
|
+
|
|
210
|
+
**Batches and overflow:** `onEvents` gets `{ events, dropped }`, the events since the last batch. The next batch waits until the call returns, or, when it returns a promise, until that settles, so a slow consumer holds the kernel back instead of piling up messages. While it waits, at most `limit` events (4096 by default, at most 65536) wait in the kernel for that subscription; any more are dropped and counted, and the next batch says how many in `dropped` (0 otherwise), so a fork bomb costs a bounded amount of memory, and a consumer that sees `dropped > 0` knows its list is incomplete. `close()` ends the subscription, and so does closing the client. An empty tag or a bad `limit` rejects with `code: 'EINVAL'`.
|
|
195
211
|
|
|
196
212
|
### Headless in Node, for tests
|
|
197
213
|
|
package/dist/driver-worker.js
CHANGED
|
@@ -824,7 +824,7 @@ async function loopbackFetch(dial, input, options) {
|
|
|
824
824
|
}
|
|
825
825
|
|
|
826
826
|
// src/client/protocol.ts
|
|
827
|
-
var PROTOCOL = [1,
|
|
827
|
+
var PROTOCOL = [1, 15];
|
|
828
828
|
function locksOf(scope = globalThis) {
|
|
829
829
|
const locks = scope.navigator?.locks;
|
|
830
830
|
return typeof locks?.request === "function" ? locks : void 0;
|
|
@@ -889,6 +889,7 @@ var Channel = class {
|
|
|
889
889
|
failure;
|
|
890
890
|
pending = /* @__PURE__ */ new Map();
|
|
891
891
|
watching = /* @__PURE__ */ new Map();
|
|
892
|
+
tracing = /* @__PURE__ */ new Map();
|
|
892
893
|
cdpHooks = /* @__PURE__ */ new Map();
|
|
893
894
|
cdpOpen = /* @__PURE__ */ new Set();
|
|
894
895
|
facadeHooks = /* @__PURE__ */ new Map();
|
|
@@ -956,6 +957,10 @@ var Channel = class {
|
|
|
956
957
|
this.watching.get(data.watch)?.(data.change);
|
|
957
958
|
return;
|
|
958
959
|
}
|
|
960
|
+
if (data.trace !== void 0 && data.id === void 0) {
|
|
961
|
+
this.traced(data.trace, data.events ?? [], data.dropped ?? 0);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
959
964
|
if (data.bye !== void 0) {
|
|
960
965
|
this.end(new KernelGoneError(data.bye));
|
|
961
966
|
return;
|
|
@@ -984,6 +989,23 @@ var Channel = class {
|
|
|
984
989
|
if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
|
|
985
990
|
else call.resolve(reply.result);
|
|
986
991
|
}
|
|
992
|
+
traced(trace, events, dropped) {
|
|
993
|
+
const listener = this.tracing.get(trace);
|
|
994
|
+
if (!listener) return;
|
|
995
|
+
const ack = () => {
|
|
996
|
+
if (this.tracing.has(trace)) this.post({ traceAck: trace });
|
|
997
|
+
};
|
|
998
|
+
let consumed;
|
|
999
|
+
try {
|
|
1000
|
+
consumed = listener({ events, dropped });
|
|
1001
|
+
} finally {
|
|
1002
|
+
if (typeof consumed?.then === "function") {
|
|
1003
|
+
void Promise.resolve(consumed).then(ack, ack);
|
|
1004
|
+
} else {
|
|
1005
|
+
ack();
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
987
1009
|
openCdp({ cdpOpen: { registration, ...request }, port }) {
|
|
988
1010
|
const hook = registration === void 0 ? void 0 : this.cdpHooks.get(registration);
|
|
989
1011
|
if (!hook || this.failure) {
|
|
@@ -1046,6 +1068,29 @@ async function serveCdpOn(channel, hook, options) {
|
|
|
1046
1068
|
}
|
|
1047
1069
|
};
|
|
1048
1070
|
}
|
|
1071
|
+
async function traceOn(channel, tag, onEvents, options) {
|
|
1072
|
+
if (typeof onEvents !== "function") throw new TypeError("trace: onEvents must be a function");
|
|
1073
|
+
await since(channel, 15, "trace", async () => {
|
|
1074
|
+
});
|
|
1075
|
+
const { id, done } = channel.request({
|
|
1076
|
+
op: "trace",
|
|
1077
|
+
tag,
|
|
1078
|
+
...options.limit !== void 0 ? { limit: options.limit } : {}
|
|
1079
|
+
});
|
|
1080
|
+
channel.tracing.set(id, onEvents);
|
|
1081
|
+
try {
|
|
1082
|
+
await done;
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
channel.tracing.delete(id);
|
|
1085
|
+
throw error;
|
|
1086
|
+
}
|
|
1087
|
+
return {
|
|
1088
|
+
close: () => {
|
|
1089
|
+
if (!channel.tracing.delete(id)) return;
|
|
1090
|
+
void channel.call({ op: "untrace", trace: id }).catch(() => void 0);
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1049
1094
|
async function dialOn(channel, { port, host }) {
|
|
1050
1095
|
const req = { op: "dial", port, ...host ? { host } : {} };
|
|
1051
1096
|
return dialStream(await since(channel, 4, "dial", () => channel.call(req)));
|
|
@@ -1059,6 +1104,10 @@ async function spawnOn(channel, argv, opts) {
|
|
|
1059
1104
|
});
|
|
1060
1105
|
if (opts.tag !== void 0) await since(channel, 12, "a tag per spawn", async () => {
|
|
1061
1106
|
});
|
|
1107
|
+
if (Array.isArray(opts.tag)) await since(channel, 15, "several tags per spawn", async () => {
|
|
1108
|
+
});
|
|
1109
|
+
if (opts.cwdMissing === "error") await since(channel, 14, "cwdMissing: 'error'", async () => {
|
|
1110
|
+
});
|
|
1062
1111
|
const started = Promise.withResolvers();
|
|
1063
1112
|
let endedBy;
|
|
1064
1113
|
const options = { ...rest, ...stdin !== void 0 ? { stdin: bytesOf2(stdin) } : {} };
|
|
@@ -1118,6 +1167,12 @@ async function terminalOn(channel, argv, opts) {
|
|
|
1118
1167
|
});
|
|
1119
1168
|
if (opts.tag !== void 0) await since(channel, 12, "a tag per terminal", async () => {
|
|
1120
1169
|
});
|
|
1170
|
+
if (Array.isArray(opts.tag)) {
|
|
1171
|
+
await since(channel, 15, "several tags per terminal", async () => {
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
if (opts.cwdMissing === "error") await since(channel, 14, "cwdMissing: 'error'", async () => {
|
|
1175
|
+
});
|
|
1121
1176
|
let listener = onData ?? null;
|
|
1122
1177
|
const backlog = [];
|
|
1123
1178
|
const started = Promise.withResolvers();
|
|
@@ -1275,6 +1330,7 @@ async function attachKernel(port, options = {}) {
|
|
|
1275
1330
|
channel.traitsListeners.add(listener);
|
|
1276
1331
|
return () => channel.traitsListeners.delete(listener);
|
|
1277
1332
|
},
|
|
1333
|
+
trace: (tag, onEvents, options2 = {}) => traceOn(channel, tag, onEvents, options2),
|
|
1278
1334
|
async close(o = {}) {
|
|
1279
1335
|
if (channel.failure) return;
|
|
1280
1336
|
await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
|