Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499// Wire and mailbox protocol shared by kernel and runtime.// This file is the boundary contract; nothing here may appear on the public Kernel API.// Step-1 subset of the SyscallSpec table in DESIGN.md; the table grows per step.
import type { HostToken } from "./vurls";import { SIGNALS, type InstallReport, type KeepReport, type ProcessRuntime, type Signal, type Substitution, type WinSize } from "./public-types";
export { SIGNALS };export type { InstallReport, KeepReport, ProcessRuntime, Signal, Substitution, WinSize };
export type SyscallName = | "open" | "read" | "write" | "close" | "stat" | "fstat" | "mkdir" | "readdir" | "unlink" | "rmdir" | "rename" | "symlink" | "readlink" | "realpath" | "resolve" | "loadModule" | "loadEsmSync" | "stripTypes" | "exit" | "listen" | "unlisten" | "kill" | "sigaction" | "ttySetRaw" | "ttyGetSize" | "newThread" | "spawn" | "watch" | "unwatch" | "ftruncate" | "utimes" | "futimes" | "npm" | "npmLookup";
export type FsErrno = | "ENOENT" | "EEXIST" | "ENOTDIR" | "EISDIR" | "EBADF" | "EINVAL" | "ENOTEMPTY" | "EMFILE" | "EIO" | "EAGAIN" | "EADDRINUSE" | "ESRCH" | "ENOTTY" | "ELOOP";
export interface FsError { code: FsErrno; syscall: string; path?: string; dest?: string;}
export type FsResult<T> = { ok: true; val: T } | { ok: false; err: FsError };
export type OpenFlags = { read: boolean; write: boolean; append: boolean; trunc: boolean; create: boolean; excl: boolean;};
export type FileKind = "file" | "dir" | "symlink";
export type StatRecord = { kind: FileKind; ino: number; size: number; mode: number; mtimeMs: number;};
/** One directory entry: readdir returns names AND their kind so callers can * distinguish files from dirs without a stat per entry. */export type DirentRecord = { name: string; kind: FileKind };
export type ResolvedModule = | { kind: "builtin"; id: string } | { kind: "file"; path: string; format: "cjs" | "json" | "esm" } | { kind: "missing"; code: "ERR_MODULE_NOT_FOUND" | "ERR_UNKNOWN_BUILTIN_MODULE" | "ERR_PACKAGE_PATH_NOT_EXPORTED" | "ERR_PACKAGE_IMPORT_NOT_DEFINED" | "ERR_INVALID_PACKAGE_CONFIG" | "ERR_INVALID_PACKAGE_TARGET" | "ERR_UNSUPPORTED_DIR_IMPORT" | "ERR_DLOPEN_FAILED" };
/** One export of an ES module in function form: its own binding (`local` is the * identifier in the code), a re-export of a request's export (`imported` null for * `export * as name`), or `export * from` a request. */export type EsmExport = | { kind: "local"; name: string; local: string } | { kind: "reexport"; name: string; request: number; imported: string | null } | { kind: "star"; request: number };
/** The local an `export default <expression>` binds: "export default" and * "const __nib_d=" are both 14 characters. */export const ESM_DEFAULT_LOCAL = "__nib_d";
/** The value the code's i-th import binding takes: a request's export, or its * namespace when `imported` is null. */export interface EsmBinding { request: number; imported: string | null }
/** An ES module made synchronously evaluable for require() (kernel esm-sync.ts). */export interface EsmSyncModule { /** `(function (__nib_meta, __nib_def, __nib_bind, __nibI) { … })`: calling it * declares the import bindings and returns the module body as a function. */ code: Uint8Array; /** Each module request in source order, resolved with the import conditions. A * CommonJS or builtin request carries its named exports (cjs-module-lexer's, as * Node's are, or the builtin's own). */ requests: { specifier: string; resolved: ResolvedModule; names?: readonly string[] }[]; bindings: EsmBinding[]; exports: EsmExport[];}
export interface SyscallSpec { open: { args: { path: string; flags: OpenFlags; mode: number }; ret: { fd: number } }; read: { args: { fd: number; len: number; pos: number | null }; ret: { bytes: Uint8Array; eof: boolean } }; write: { args: { fd: number; data: Uint8Array; pos: number | null }; ret: { written: number } }; close: { args: { fd: number }; ret: null }; stat: { args: { path: string; follow: boolean }; ret: StatRecord }; fstat: { args: { fd: number }; ret: StatRecord }; readdir: { args: { path: string }; ret: DirentRecord[] }; unlink: { args: { path: string }; ret: null }; rmdir: { args: { path: string; recursive: boolean }; ret: null }; rename: { args: { from: string; to: string }; ret: null }; // A link holding `target` verbatim; the target need not exist. symlink: { args: { target: string; path: string }; ret: null }; readlink: { args: { path: string }; ret: { target: string } }; // The path with every symlink in it resolved, as realpath(3) walks it. realpath: { args: { path: string }; ret: { path: string } }; mkdir: { args: { path: string; mode: number; recursive: boolean }; ret: { path: string | null } }; // Set (or grow/shrink) a file's length by fd, as ftruncate(2); fs.truncate(path, …) // opens the path first and runs this on the resulting fd, as Node's does. ftruncate: { args: { fd: number; len: number }; ret: null }; // Set a path's stored time (the one time field behind atime/mtime/ctime/birthtime); // `follow` is false for lutimes. Node's atime has nothing to land in, so it is // accepted and dropped — only mtime is kept. utimes: { args: { path: string; follow: boolean; mtimeMs: number }; ret: null }; futimes: { args: { fd: number; mtimeMs: number }; ret: null }; resolve: { args: { specifier: string; parent: string; kind: "require" | "import" }; ret: ResolvedModule }; loadModule:{ args: { path: string }; ret: { source: Uint8Array; format: "cjs" | "json" | "esm" } }; loadEsmSync:{ args: { path: string }; ret: EsmSyncModule }; // module.stripTypeScriptTypes: the same engine loadModule uses for a .ts path, run // on a caller-supplied string instead of a file (no node_modules/.tsx loader rules — // those are about *which files* strip, not what the engine does to a given string). // The outcome is itself a result (not FsError: a SyntaxError here isn't a POSIX code). stripTypes: { args: { code: string }; ret: { ok: true; js: string } | { ok: false; code: string; message: string } }; exit: { args: { code: number }; ret: never }; listen: { args: { port: number }; ret: null }; unlisten: { args: { port: number }; ret: null }; // A signal to `pid` (the caller may name itself); null only checks that pid exists, as // signal 0 does. Never replies when it ends the caller. kill: { args: { pid: number; sig: Signal | null }; ret: null }; // The caller has (or no longer has) a listener for `sig`: the input to signalAction. sigaction: { args: { sig: Signal; handled: boolean }; ret: null }; // ENOTTY unless the caller's stdio is a pty. ttySetRaw: { args: { raw: boolean }; ret: null }; ttyGetSize:{ args: Record<string, never>; ret: WinSize }; // A thread id for a worker the caller is about to start (startThread): sync, because // `new Worker()` returns with its threadId set. Ids are per process and never reused. newThread: { args: Record<string, never>; ret: { tid: number } }; // A child process running `node <entry>`, owned by the calling thread: its piped // output and its exit come back to that thread (childStdio, childExited). spawn: { args: { entry: string; argv: string[]; env: Record<string, string>; cwd: string; stdio: ChildStdio }; ret: { pid: number } }; // An inotify-style watch on one file or directory (not recursive), owned by the // calling thread: its events come back to that thread as fsEvents. watch: { args: { path: string }; ret: { wid: number } }; unwatch: { args: { wid: number }; ret: null }; // An install in the kernel for the caller (the guest npm command). Async only: it // answers once every fetch is done, and the kernel serves other calls meanwhile. The // call itself succeeds; npm's own failure (ERESOLVE, EUSAGE...) is the outcome's. npm: { args: InstallRequest; ret: InstallOutcome }; // libnpmexec's question for `npm exec` and npx, async as `npm` is: does node_modules // under `path` have a package for `spec`, or which version would install for it. npmLookup: { args: ExecLookupRequest; ret: ExecLookup };}
/** One fs.watch event: Node's eventType and the filename it reports. */export type WatchEvent = [type: "rename" | "change", filename: string];
/** Where each of a child's stdio fds goes. `inherit` is the parent process's own fd; * a child's stdin cannot inherit (nothing routes the parent's input onward). */export interface ChildStdio { stdin: "pipe" | "ignore"; stdout: "pipe" | "inherit" | "ignore"; stderr: "pipe" | "inherit" | "ignore";}
// ---- threads (node:worker_threads) ----// A thread is a runtime worker inside its parent's process: same pid, a snapshot of the// parent's env and cwd, its own loop and mailbox. The kernel starts it and reports its// end; everything between the parent and the thread (messages, stdio, its uncaught// error) travels on a port pair the parent creates, never through the kernel.
/** One end of a node:worker_threads port pair as it crosses threads: the native port is * the doorbell and carries messages with transferables; the two shared queues carry * everything else, readable synchronously (receiveMessageOnPort). `rx`/`tx` are named * from this end; the counters are this end's position in each direction's sequence. */export interface PortDescriptor { readonly nibPort: true; native: MessagePort; rx: SharedArrayBuffer; tx: SharedArrayBuffer; rxSeq: number; txSeq: number;}
/** What only a thread's init carries. `workerData` and `environmentData` were cloned * natively from the parent, so ports in them arrive as PortDescriptors. */export interface ThreadInit { tid: number; name: string; /** Source to run as the entry script (`eval: true`); null runs the init's `entry`. */ eval: string | null; workerData: unknown; environmentData: Array<[key: unknown, value: unknown]>; /** The thread's end of its channel to the parent (parentPort and the internal records). */ port: PortDescriptor;}
/** How a thread ended, as its parent hears it from the kernel. */export interface ThreadExit { tid: number; code: number; /** Ended from outside (terminate(), its parent or its process ending): records it sent * natively and still in flight are not waited for. */ terminated: boolean;}
// ---- signals ----// Signal itself (and SIGNALS) lives in shared/public-types.ts — see the comment there.
/** What a signal does to a process with no listener for it. There is no job control, so * nothing can keep a stopped process stopped: a stop lasts no time, and the process is * continued (sent SIGCONT) at once. */export const SIGNAL_DEFAULT: Record<Signal, "terminate" | "ignore" | "stop"> = { SIGHUP: "terminate", SIGINT: "terminate", SIGQUIT: "terminate", SIGILL: "terminate", SIGTRAP: "terminate", SIGABRT: "terminate", SIGBUS: "terminate", SIGFPE: "terminate", SIGKILL: "terminate", SIGUSR1: "terminate", SIGSEGV: "terminate", SIGUSR2: "terminate", SIGPIPE: "terminate", SIGALRM: "terminate", SIGTERM: "terminate", SIGCHLD: "ignore", SIGCONT: "ignore", SIGSTOP: "stop", SIGTSTP: "stop", SIGTTIN: "stop", SIGTTOU: "stop", SIGURG: "ignore", SIGXCPU: "terminate", SIGXFSZ: "terminate", SIGVTALRM: "terminate", SIGPROF: "terminate", SIGWINCH: "ignore", SIGIO: "terminate", SIGPWR: "terminate", SIGSYS: "terminate",};
export const isSignal = (name: unknown): name is Signal => typeof name === "string" && Object.hasOwn(SIGNALS, name);
/** The delivery rule: SIGKILL always terminates and SIGSTOP always stops; a listener * takes any other signal; without one the default action applies. */export function signalAction(sig: Signal, handled: boolean): "terminate" | "deliver" | "ignore" | "stop" { if (sig === "SIGKILL") return "terminate"; if (sig === "SIGSTOP") return "stop"; if (handled) return "deliver"; return SIGNAL_DEFAULT[sig];}
/** How a process ended: an exit code, or the signal that terminated it. */export type ExitStatus = { code: number; signal: null } | { code: null; signal: Signal };
/** The shell's number for an exit status: the code, or 128 + the signal's number. */export const exitNumber = (s: ExitStatus): number => (s.signal === null ? s.code : 128 + SIGNALS[s.signal]);
// ---- terminals ----// WinSize lives in shared/public-types.ts — see the comment there.
/** The termios flags the line discipline honours (src/kernel/pty.ts). */export interface Termios { /** Canonical mode: line editing, and input delivered a line at a time. */ icanon: boolean; echo: boolean; /** ^C raises SIGINT and ^\ raises SIGQUIT in the foreground process. */ isig: boolean; /** CR becomes NL on input. */ icrnl: boolean; /** NL becomes CR NL on output. */ onlcr: boolean;}
/** A process's stdio: pipes to the host (stdin, stdout and stderr apart), or all three * fds on one kernel pty whose master side the host holds. */export type StdioSpec = { kind: "pipe" } | { kind: "pty"; size: WinSize };
// ---- guest http over the SW virtual origin (/~/@<host>/http/<port>/...) ----
export type HttpHeaders = Array<[name: string, value: string]>; // lowercased names, order keptexport interface HttpRequestRecord { method: string; url: string /* path+query, starts with "/" */; headers: HttpHeaders; body: Uint8Array | null }export interface HttpResponseRecord { status: number; headers: HttpHeaders; body: Uint8Array }
// ---- raw connections (a page WebSocket upgrading to a guest server) ----// A connection is one MessagePort carrying bytes both ways, end to end between the page// and the runtime worker that owns the port: Uint8Array is data, null is end-of-stream// (a FIN). Nothing between them relays it; each hop only transfers the port.
export type ConnectionData = Uint8Array | null;/** What the page reads on a connection: data, or (first, from the kernel) the port the * kernel uses to say the owning process exited, since a dead worker cannot close it. */export type ConnectionMessage = ConnectionData | { t: "watch"; port: MessagePort };/** On a watch port: the process that owned the connection is gone (close code 1006). */export type WatchMessage = { t: "exited" };
/** A whole HTTP/1.1 response that closes the connection, for answers no guest wrote. */export function rawHttpResponse(status: number, reason: string, text: string): Uint8Array { const body = new TextEncoder().encode(text); const head = `HTTP/1.1 ${status} ${reason}\r\ncontent-type: text/plain; charset=utf-8\r\ncontent-length: ${body.length}\r\nconnection: close\r\n\r\n`; const out = new Uint8Array(head.length + body.length); out.set(new TextEncoder().encode(head)); out.set(body, head.length); return out;}
export type Req = { t: "req"; id: number; op: SyscallName; args?: unknown; sync?: true };export type Res = { t: "res"; id: number; ok: boolean; val?: unknown; err?: FsError };
// ---- control plane ----
/** One file the host seeds into the VFS. A snapshot's recorded `mtimeMs` becomes the * file's mtime; without one the file gets the seed time. */export interface SeedEntry { path: string; data: string | Uint8Array; mtimeMs?: number }
export type SaveType = "prod" | "dev" | "optional" | "peer";
/** One npm command the kernel's installer runs in `cwd`, as the guest npm parsed it; * runtime.install() is `install` with nothing to add. */export type InstallRequest = | { command: "install"; cwd: string; /** Package specs as typed: "zod", "zod@3", "z@npm:zod@^3". */ add: string[]; /** false is --no-save: neither package.json nor package-lock.json is written. */ save: false | { /** -D, -O, --save-peer, -P; null saves where package.json has it, else dependencies. */ type: SaveType | null; /** -E: the version without npm's `^` save-prefix. */ exact: boolean; }; /** dev.nibling.npm.getClosure gateways (registry.ts), in order, to fill the metadata * cache from before resolving. Unset is the kernel's configured list; empty is none. */ gateways?: string[]; } | { command: "uninstall"; cwd: string; remove: string[]; save: boolean } /** Install package-lock.json as it is, which package.json must agree with; writes neither. */ | { command: "ci"; cwd: string };
/** An npm error as npm's error-message.js reads it off the thrown error. */export interface InstallError { message: string; code?: string; errno?: number; syscall?: string; path?: string; /** "name@spec" for an E404: the package the registry does not have. */ pkgid?: string;}
/** A spec as `npm exec --package` or npx was given it, looked up in node_modules under * `path`; `npx` when that is an npx cache dir, which takes no older version. */export interface ExecLookupRequest { spec: string; path: string; npx: boolean;}
/** What libnpmexec reads off a package to run its bin: package.json's, or the manifest's. */export interface ExecPackage { name: string; version: string; /** Normalized as read-package-json does: a string bin is the unscoped name's. */ bin: Record<string, string>;}
/** `found` is a package node_modules has; otherwise `pkg` is the manifest to install. */export type ExecLookup = { ok: true; found: boolean; pkg: ExecPackage } | { ok: false; error: InstallError };
/** Restore the directories a key holds into the FS, or save them from it under the key. */export type KeepOp = "restore" | "save";
// KeepReport lives in shared/public-types.ts: the host API returns it.
export type KeepOutcome = { ok: true; report: KeepReport } | { ok: false; error: string };
export type InstallOutcome = { ok: true; report: InstallReport } | { ok: false; error: InstallError };
// InstallReport lives in shared/public-types.ts: the host API returns it.
// Host fs ops (DESIGN §host-fs): the page never blocks, so host-side fs is ASYNC// port RPC — not the SAB mailbox, which exists so GUEST code can block. Each op is// answered by exactly one `fsResult` carrying the same id.export type HostFsOp = | "readFile" | "writeFile" | "mkdir" | "readdir" | "stat" | "unlink" | "rmdir" | "rename" | "symlink" | "readlink";
export type HostToKernel = | { t: "admin"; port: MessagePort; runtimeUrl: string } | { t: "spawn"; id: number; entry: string; argv: string[]; env: Record<string, string>; cwd: string; stdio: StdioSpec; runtime: ProcessRuntime } | { t: "seed"; entries: SeedEntry[] } // Signal the process spawned with this id; a no-op once it has exited. | { t: "kill"; id: number; sig: Signal } // Signal any live process by pid, a guest's child included. | { t: "killPid"; pid: number; sig: Signal } // Bytes for a pipe process's stdin; null closes it (EOF). Ignored for a pty process. | { t: "stdin"; id: number; bytes: Uint8Array | null } // Keystrokes into a pty's master side, through the line discipline. | { t: "ptyInput"; id: number; bytes: Uint8Array } | { t: "ptyResize"; id: number; size: WinSize } // Host fs call: op + op-shaped args (shaped by host/index.ts, trusted at the port). | { t: "fs"; id: number; op: HostFsOp; args: unknown } // ESM bridge: the host relays one SW virtual-origin fetch. `id` is host-local and // echoed on the `source` reply; kind/path come from the /~/ URL (shared/vurls.ts). | { t: "moduleSource"; id: number; kind: "vfs" | "builtin" | "cjs"; path: string } // The SW's bridge id rides every hop unchanged (SW → host → kernel → runtime → back). | { t: "httpRequest"; id: number; port: number; req: HttpRequestRecord } // A raw connection to the guest server on `port` (the socket MessagePort is transferred). | { t: "connect"; port: number; socket: MessagePort } // Install node_modules from `${cwd}/package-lock.json`; answered by one `installed`. | { t: "install"; id: number; cwd: string } // Restore or save kept directories (kernel/kept-dirs.ts); answered by one `kept`. | { t: "keep"; id: number; op: KeepOp; key: string; paths: string[] } // Report guest writes to files under these path prefixes as `guestWrite`; [] stops reporting. | { t: "observeWrites"; prefixes: string[] };
export type KernelToHost = | { t: "ready" } | { t: "spawned"; id: number; pid: number } // A pty process's output and its echo arrive on "stdout": the master side. | { t: "stdio"; id: number; stream: "stdout" | "stderr"; bytes: Uint8Array } | { t: "exited"; id: number; status: ExitStatus } // Reply to one `fs` request. Errors travel as FsError; the host maps them to // Node-flavoured Error objects (shared/nodeerr.ts). | { t: "fsResult"; id: number; ok: boolean; val?: unknown; err?: FsError } // Emitted after ANY successful filesystem mutation from ANY writer (guest // syscalls, host fs ops, seed). Sound because the kernel is the single writer. // This is a demo-harness notification piggybacking on that invariant — NOT // fs.watch (per-watcher event delivery stays step-8 scope). | { t: "fsChanged" } // A guest finished writing an observed file: it closed an fd it wrote through, or renamed a // file onto the path. `data` is the whole file; `gen` names this state of it, and `readGen` // the state the process last read at this path (null when it never read it). | { t: "guestWrite"; path: string; pid: number; argv: string[]; data: Uint8Array; gen: number; readGen: number | null } // Module bytes for a moduleSource request; ok:false → the SW answers 404. | { t: "source"; id: number; ok: boolean; bytes?: Uint8Array } // res:null → no live listener (or it exited mid-request); the SW answers 502. | { t: "httpResponse"; id: number; res: HttpResponseRecord | null; error?: string } // Every process the kernel starts, the host's and guests' children; ppid is null for the host's. | { t: "procStarted"; pid: number; ppid: number | null; argv: string[]; cwd: string } // Sent after the process's ports are unlistened. | { t: "procExited"; pid: number } | { t: "listening"; port: number; pid: number } | { t: "unlistened"; port: number; pid: number } | ({ t: "installed"; id: number } & InstallOutcome) | ({ t: "kept"; id: number } & KeepOutcome);
// SW ↔ host bridge (one MessageChannel per host page). `id` is the SW's; it is echoed back.export type BridgeRequest = | { t: "get"; id: number; url: string } | { t: "http"; id: number; port: number; req: HttpRequestRecord } | { t: "connect"; port: number; socket: MessagePort };export type BridgeReply = | { t: "source"; id: number; ok: boolean; bytes?: Uint8Array } | { t: "httpResult"; id: number; res: HttpResponseRecord | null; error?: string };
// Control messages on the SW's own channel (navigator.serviceWorker), outside any bridge.export type HostToSw = | { t: "bridge"; host: HostToken; port: MessagePort } /** Take control of this uncontrolled page: a hard reload bypasses the worker while * it is already active, so its activate-time claim never reaches the page. */ | { t: "claim" } /** Are you the runtime's worker? Answered with a pong on `port`: a site worker that * composes dist/runtime/sw.js via importScripts() runs from its own URL, yet is ours. */ | { t: "ping"; port: MessagePort };/** From an app page (the WebSocket shim): open a raw connection to its guest server. * `path` is the WebSocket URL's pathname, `page` the document URL, both used only to * find the server when the SW has no record of the page. */export type PageToSw = { t: "connect"; socket: MessagePort; path: string; page: string };export type SwToHost = | { t: "rebridge" } /** The answer to a ping, on the ping's port rather than the SW's own channel. */ | { t: "pong" };
export type KernelToRuntime = | { t: "init"; /** The owning host page, named by every module URL the runtime builds. */ host: HostToken; port: MessagePort; pid: number; entry: string; argv: string[]; env: Record<string, string>; cwd: string; /** The pty's size when stdio is a terminal; null for pipes. */ tty: WinSize | null; /** Set when this worker is a worker_threads thread rather than a process's main thread. */ thread: ThreadInit | null; /** The process's, so every thread of a process has the same one. */ runtime: ProcessRuntime; } // A thread this runtime started has ended. | ({ t: "threadExited" } & ThreadExit) // Output a child this thread spawned wrote to a piped fd, and its end. | { t: "childStdio"; pid: number; stream: "stdout" | "stderr"; bytes: Uint8Array } | { t: "childExited"; pid: number; status: ExitStatus } // Events for a watch this thread holds, in the order they happened. | { t: "fsEvents"; wid: number; events: WatchEvent[] } | { t: "httpRequest"; id: number; port: number; req: HttpRequestRecord } // A raw connection; `id` is the kernel's, echoed by connectionClosed. | { t: "connect"; id: number; port: number; socket: MessagePort } // A wake-up: the signals themselves are pending in the mailbox (shared/mailbox.ts). | { t: "signal" } // Input for fd 0, in order; null is EOF. | { t: "stdin"; bytes: Uint8Array | null };
export type KernelToRuntimeInit = Extract<KernelToRuntime, { t: "init" }>;
export type RuntimeToKernel = | { t: "attach"; mailbox: SharedArrayBuffer } | Req | { t: "stdio"; stream: "stdout" | "stderr"; bytes: Uint8Array } | { t: "httpResponse"; id: number; res: HttpResponseRecord } | { t: "connectionClosed"; id: number } // Start thread `tid` (from newThread). `transfer` lists every transferable inside // `thread`, so the kernel can pass them on to the new worker. | { t: "startThread"; entry: string; argv: string[]; env: Record<string, string>; cwd: string; thread: ThreadInit; transfer: Transferable[] } | { t: "terminateThread"; tid: number } // Bytes for the piped stdin of a child this thread spawned; null closes it (EOF). | { t: "childStdin"; pid: number; bytes: Uint8Array | null };