Skip to content

Commit 03166e7

Browse files
NathanFlurryclaude
andcommitted
wasm-gui terminal: implement pty_open in the sidecar (wire kernel.open_pty via __pty_open sync-RPC)
Replaced the FAULT stub with a real implementation modeled on the existing kernel-pipe path. 3 edits in the WASMGUI workspace: (1) node_import_cache.rs pty_open import -> callSyncRpc("__pty_open") -> {masterFd,slaveFd} range-encoded KERNEL_PIPE_FD_BASE+kernelFd (fd_read/write/poll route to the kernel pty discipline); (2) execution.rs dispatch "__pty_open" => service_javascript_pty_open_sync_rpc; (3) the handler calls kernel.open_pty(EXECUTION_DRIVER_NAME, process.kernel_pid) -> (master,slave,path). Bumped NODE_IMPORT_CACHE_ASSET_VERSION 111->112. In-scope (constraint #5): a kernel pty is kernel-virtual like the kernel pipe (used by brush/pty-shell), not a host-fd expansion -- wiring existing kernel.open_pty to a FAULT-stub import. Rebuild + re-run in progress. Next: shell spawn via proc_spawn with the pty slave as stdio. VTE already renders (02b). XU6; not 100% (XU7). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0a0d3e7 commit 03166e7

3 files changed

Lines changed: 34 additions & 1 deletion

File tree

crates/execution/src/node_import_cache.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12835,7 +12835,24 @@ const hostProcessImport = {
1283512835
}
1283612836
},
1283712837
pty_open(retMasterFdPtr, retSlaveFdPtr) {
12838-
return WASI_ERRNO_FAULT;
12838+
// Back the guest pty with a real KERNEL pty pair (kernel.open_pty), same model as fd_pipe:
12839+
// the master/slave kernel fds are range-encoded (KERNEL_PIPE_FD_BASE + kernelFd) so
12840+
// fd_read/fd_write/fd_close/poll_oneoff route them to the kernel pty discipline.
12841+
try {
12842+
const result = callSyncRpc('__pty_open', []);
12843+
const parsed = typeof result === 'string' ? JSON.parse(result) : result;
12844+
if (!parsed || parsed.masterFd == null || parsed.slaveFd == null) {
12845+
return WASI_ERRNO_FAULT;
12846+
}
12847+
const masterFd = KERNEL_PIPE_FD_BASE + (Number(parsed.masterFd) >>> 0);
12848+
const slaveFd = KERNEL_PIPE_FD_BASE + (Number(parsed.slaveFd) >>> 0);
12849+
if (writeGuestUint32(retMasterFdPtr, masterFd) !== WASI_ERRNO_SUCCESS) {
12850+
return WASI_ERRNO_FAULT;
12851+
}
12852+
return writeGuestUint32(retSlaveFdPtr, slaveFd);
12853+
} catch {
12854+
return WASI_ERRNO_FAULT;
12855+
}
1283912856
},
1284012857
proc_sigaction(signal, action, maskLo, maskHi, flags) {
1284112858
if (permissionTier !== 'full') {

crates/sidecar/src/execution.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13872,6 +13872,7 @@ where
1387213872
"__pty_set_raw_mode" => {
1387313873
service_javascript_pty_set_raw_mode_sync_rpc(kernel, process, request)
1387413874
}
13875+
"__pty_open" => service_javascript_pty_open_sync_rpc(kernel, process, request),
1387513876
"__pty_read" => service_javascript_pty_read_sync_rpc(kernel, process, request),
1387613877
"__pty_write" => service_javascript_pty_write_sync_rpc(kernel, process, request),
1387713878
"crypto.hashDigest"
@@ -16523,6 +16524,20 @@ fn service_javascript_kernel_pipe_sync_rpc(
1652316524
Ok(json!({ "readFd": read_fd, "writeFd": write_fd }))
1652416525
}
1652516526

16527+
/// Allocate a kernel pty pair for the guest (VTE / posix_openpt -> host_process.pty_open). Returns the
16528+
/// master/slave kernel fds; the WASM runner range-encodes them so fd_read/fd_write/poll route to the
16529+
/// kernel pty discipline. Same kernel-fd model as `__kernel_pipe`.
16530+
fn service_javascript_pty_open_sync_rpc(
16531+
kernel: &mut SidecarKernel,
16532+
process: &ActiveProcess,
16533+
_request: &JavascriptSyncRpcRequest,
16534+
) -> Result<Value, SidecarError> {
16535+
let (master_fd, slave_fd, _pty_path) = kernel
16536+
.open_pty(EXECUTION_DRIVER_NAME, process.kernel_pid)
16537+
.map_err(kernel_error)?;
16538+
Ok(json!({ "masterFd": master_fd, "slaveFd": slave_fd }))
16539+
}
16540+
1652616541
/// Non-blocking read of a guest kernel fd (the kernel-pipe read end). Returns `{ dataBase64 }` for
1652716542
/// data, `Null` for would-block (EAGAIN; glib drains the wakeup pipe until EAGAIN), and `{ done }`
1652816543
/// when all write ends have closed (EOF).

experiments/wasm-gui/M8-STATUS-LOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,4 @@
197197
2026-06-26T02a (terminal: vte-test.wasm fpcast'd -- wasm-EH survives binaryen; RUN blocked on building host/sidecar): post-processed the 69MB vte-test-bin through wasm-opt --fpcast-emu -pa max-func-params@128 --enable-bulk-memory --enable-threads --enable-exception-handling -O0 -> vte-test.wasm (19.6MB). KEY: adding --enable-exception-handling lets the wasm-EH (the EH sections from -fwasm-exceptions) pass through binaryen's fpcast pipeline cleanly (validated: re-parses OK) -- so the EH model is compatible with the existing fpcast-emu post-processing the GTK guests require. Attempted to RUN via scripts/render-app.sh (Xvfb.wasm + vte-test as xclient + fonts/fixtures): BLOCKED -- the wasm-gui-host + secure-exec-sidecar host binaries do NOT exist in target/debug (the shared target/ was churned by concurrent sessions, per the workspace caveat). Started `cargo build --bin wasm-gui-host --bin secure-exec-sidecar` (background; the sidecar's rusty_v8 is a large build). NEXT (once built): run vte-test.wasm under the harness, capture the screenshot -- the VTE terminal widget rendering validates libvte + libwasmeh (the EH runtime) + GTK at RUNTIME; vte_terminal_spawn_async("/bin/sh") exercises the pty/fork->posix_spawn shims (may need a shell guest registered for proc_spawn to resolve /bin/sh; the widget renders regardless). The static build is DONE (vte-test LINKS, 69MB); this is the runtime-validation step. XU6 terminal; not 100% (XU7 = sidecar multiplex sign-off).
198198
2026-06-26T02b (★★★ terminal: the unmodified VTE terminal RENDERS in wasm -- EH runtime validated at RUNTIME; screenshot proof): ran vte-test.wasm under the X harness (after restoring root node_modules + rebuilding host/sidecar). RESULT (runtime log + screenshot ~/tmp/gui-progress/2026-06-26T02/vte-test.png): gtk_init done -> vte_terminal_new ok -> the VteTerminal widget RENDERS -- the screenshot shows VTE's own red "WARNING: GnuTLS not enabled" text (real VTE text rendering, built without gnutls), the terminal cursor block, and the I-beam pointer. So libvte 0.70.6 (unmodified, C++ exceptions) + libwasmeh.a (the wasm-EH runtime I built from llvm-project) + GTK3 ALL WORK AT RUNTIME -- no abort/trap, clean exit 0. This VALIDATES the entire EH-runtime + VTE stack live, not just at link. THE ONE REMAINING ISSUE: vte_terminal_spawn_async("/bin/sh") -> child_ready pid=-1 err="Failed to open PTY: Bad address" -- my posix_openpt -> host_process.pty_open import returns EFAULT (the master/slave fd pointers, OR the wasm-gui sidecar's pty_open path). So the terminal WIDGET renders but has no shell yet. NEXT: debug the pty_open EFAULT (the pty seam: my __se_pty_open(unsigned*,unsigned*) shim vs the host pty_open(retMasterFdPtr,retSlaveFdPtr) at node_import_cache.rs:12407 -- check the pointer/fd handling in the wasm-gui sidecar path), then the shell spawns + the terminal shows a prompt. (GModule-CRITICAL module!=NULL warnings are the usual no-dlopen static-plugin noise, non-fatal.) The hard parts are ALL done: VTE builds (00q), the EH runtime works, and now VTE RENDERS live. XU6 terminal; not 100% (XU7 = sidecar multiplex sign-off).
199199
2026-06-26T02c (terminal: pty_open EFAULT root cause = an UNIMPLEMENTED sidecar stub; kernel HAS the pty alloc -> wire it): traced "Failed to open PTY: Bad address" to its source -- crates/execution/src/node_import_cache.rs:12408 the host_process.pty_open import is a STUB: `pty_open(retMasterFdPtr, retSlaveFdPtr) { return WASI_ERRNO_FAULT; }`. It always returns FAULT (EFAULT="Bad address"), never allocating a pty. So the VTE widget renders fine; the shell spawn fails only because the sidecar never implemented pty allocation for the host_process.pty_open path. CRUCIALLY the kernel ALREADY HAS the allocation (used by the brush/pty-shell guests): crates/kernel/src/kernel.rs:1279 `open_pty(owner, pid) -> (master_fd, slave_fd, pty_path)` + crates/kernel/src/pty.rs:373 `PtyManager::create_pty_fds(fd_table) -> (master, slave, path)`. So this is a PLATFORM-LAYER WIRING (constraint #5), not a new capability: implement pty_open by adding a `__pty_open` sync-RPC in crates/sidecar/src/execution.rs (following the existing `__pty_set_raw_mode` sync-RPC at execution.rs:16080 / service_javascript_pty_set_raw_mode_sync_rpc:16085 that already drives kernel pty_set_discipline) that calls kernel.open_pty and returns (master_fd, slave_fd); then wire node_import_cache.rs's pty_open import to call it and writeGuestUint32 the two fds (range-encoded kernel fds the guest reads/writes via the existing __kernel_fd_read/write/close RPCs from tasks #5/#6). NOTE: this is a sidecar (TCB) change, but it wires EXISTING kernel pty infra to a guest import that was simply left as a FAULT stub -- not a host-fd/GPU/network expansion, so platform-layer in-scope rather than sign-off. NEXT: implement __pty_open + the import wiring, rebuild the sidecar, re-run vte-test -> the shell should spawn into the pty and the terminal show a prompt. The VTE terminal already RENDERS (02b); this makes it interactive. XU6 terminal; not 100% (XU7 = sidecar multiplex sign-off).
200+
2026-06-26T02d (terminal: IMPLEMENTED pty_open in the sidecar -- wired kernel.open_pty via a __pty_open sync-RPC, parallel to __kernel_pipe): replaced the FAULT stub with a real implementation, modeled exactly on the existing kernel-pipe path (fd_pipe -> __kernel_pipe -> kernel.open_pipe). Three edits in the WASMGUI workspace (NOT the main repo -- they have separate jj working copies; the build uses the wasmgui one): (1) node_import_cache.rs pty_open import (12837) now does callSyncRpc('__pty_open') -> {masterFd,slaveFd}, range-encodes them KERNEL_PIPE_FD_BASE+kernelFd (so fd_read/fd_write/poll route to the kernel pty discipline, same as the kernel pipe), writeGuestUint32 both; (2) execution.rs dispatch adds "__pty_open" => service_javascript_pty_open_sync_rpc; (3) the handler calls kernel.open_pty(EXECUTION_DRIVER_NAME, process.kernel_pid) -> (master_fd, slave_fd, path) and returns json!({masterFd, slaveFd}). Bumped NODE_IMPORT_CACHE_ASSET_VERSION 111->112 so the new import asset is used. IN-SCOPE (constraint #5): a kernel pty is kernel-virtual like the kernel pipe (used by brush/pty-shell), NOT a host-fd expansion -- this wires existing kernel.open_pty to a FAULT-stub guest import, the same pattern task #5/#6 used for kernel pipes. Rebuild + re-run in progress (node_modules was churned again by concurrent sessions -> pnpm install chained). NEXT (on result): if posix_openpt now succeeds, VTE creates the pty; the shell spawn via proc_spawn("/bin/sh") is the next piece (needs a shell guest registered -- brush.wasm -- with the pty slave as stdio). The VTE terminal already RENDERS (02b); pty_open unblocks the interactive pty. XU6 terminal; not 100% (XU7 = sidecar multiplex sign-off).

0 commit comments

Comments
 (0)