Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d074e3b
wasm-gui: M1-M6 desktop recovery — all 26 libs rebuilt, Xvfb/twm rebu…
NathanFlurry Jun 20, 2026
1c2e23e
wasm-gui M6.4: fix sync-RPC fairness (net.poll cap 50ms->3ms)
NathanFlurry Jun 20, 2026
63ee458
wasm-gui M6.4 DONE: robust multi-app desktop (twm + xclock + libX11 w…
NathanFlurry Jun 20, 2026
0150539
wasm-gui M6.1 (input half): host-driven input via XTEST proven
NathanFlurry Jun 20, 2026
66eb007
wasm-gui: M6 Xft + M6-INTERACTIVE desktop + M7 JWM + PNG proof (full …
NathanFlurry Jun 20, 2026
6b9c2cc
kernel: add open_pty_split (M6.3 PTY-spawn foundation)
NathanFlurry Jun 21, 2026
d0cd60d
sidecar: __pty_spawn stdio mode + __pty_read/__pty_write handlers (M6.3)
NathanFlurry Jun 21, 2026
2a4bd97
execution: wasm host_net pty_spawn/pty_read/pty_write imports + bridg…
NathanFlurry Jun 21, 2026
051930a
wasm-gui M6.3: sustained interactive PTY shell session (echo/ping/exi…
NathanFlurry Jun 21, 2026
e5b2129
wasm-gui M6.3: real terminal emulator (suckless st 0.9.2) cross-compi…
NathanFlurry Jun 21, 2026
39136b0
wasm-gui M6.1: working X keyboard in the wasm server (precompiled key…
NathanFlurry Jun 21, 2026
f4836f3
wasm-gui: correct the st live-typing root cause in SPEC (X-event-deli…
NathanFlurry Jun 21, 2026
017a381
wasm-gui M2.3: concurrent libX11 init works (concurrent launch mode +…
NathanFlurry Jun 21, 2026
bda5309
wasm-gui: record st-typing investigation findings (keyboard path bypa…
NathanFlurry Jun 21, 2026
b26f64c
wasm-gui: st-typing — eliminate 3 central dix delivery functions from…
NathanFlurry Jun 21, 2026
006a833
wasm-gui: st-typing root cause CORRECTED — timing/load issue, not rou…
NathanFlurry Jun 21, 2026
739068a
wasm-gui: st-typing isolated to window/connection specifics (+ xpoll-…
NathanFlurry Jun 21, 2026
9e94309
wasm-gui: st-typing crystallized — window events delivered, device ev…
NathanFlurry Jun 21, 2026
cf1c3d2
wasm-gui: st-typing bisection — Xft init and locale eliminated; narro…
NathanFlurry Jun 21, 2026
947fe34
wasm-gui: st-typing — X layer DEFINITIVELY ruled out; cause is st ter…
NathanFlurry Jun 21, 2026
aaa95b6
wasm-gui: st-typing isolated to xinit font/color loading (split tests…
NathanFlurry Jun 21, 2026
30dd81a
wasm-gui: st-typing — no single op is the cause (bisection-by-additio…
NathanFlurry Jun 21, 2026
3905e90
wasm-gui: st-typing — X-call layer exhaustively excluded; only protoc…
NathanFlurry Jun 21, 2026
164b942
wasm-gui: st-typing DEFINITIVELY localized to CLIENT-SIDE (server del…
NathanFlurry Jun 21, 2026
7f2204c
wasm-gui: st-typing refinement — forced read doesn't help; boundary i…
NathanFlurry Jun 21, 2026
62bcd7b
wasm-gui: st-typing pinned to client-side host_net RECV (server flush…
NathanFlurry Jun 21, 2026
39bfe39
wasm-gui: st-typing full recv-chain trace; pinned to host_net net.pol…
NathanFlurry Jun 21, 2026
4582270
wasm-gui: st-typing complete stack trace — every component verified; …
NathanFlurry Jun 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
1,286 changes: 1,230 additions & 56 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
resolver = "2"
members = [
"experiments/wasm-gui/host",
"crates/bridge",
"crates/kernel",
"crates/execution",
Expand Down
12 changes: 12 additions & 0 deletions crates/execution/assets/v8-bridge.source.js
Original file line number Diff line number Diff line change
Expand Up @@ -3943,6 +3943,16 @@ var __bridge = (() => {
classification: "hardened",
rationale: "Host PTY bridge reference for stdin.setRawMode()."
},
{
name: "_ptyReadRaw",
classification: "hardened",
rationale: "Host PTY bridge reference for reading a terminal's PTY master fd (__pty_read)."
},
{
name: "_ptyWriteRaw",
classification: "hardened",
rationale: "Host PTY bridge reference for writing a terminal's PTY master fd (__pty_write)."
},
{
name: "require",
classification: "hardened",
Expand Down Expand Up @@ -6404,6 +6414,8 @@ var __bridge = (() => {
var _processResourceUsage = createBridgeSyncFacade("process.resourceUsage");
var _processVersions = createBridgeSyncFacade("process.versions");
var _kernelPollRaw = createBridgeSyncFacade("_kernelPollRaw");
var _ptyReadRaw = createBridgeSyncFacade("_ptyReadRaw");
var _ptyWriteRaw = createBridgeSyncFacade("_ptyWriteRaw");
function decodeBridgeJson(value) {
return typeof value === "string" ? JSON.parse(value) : value;
}
Expand Down
371 changes: 329 additions & 42 deletions crates/execution/src/node_import_cache.rs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions crates/execution/src/v8_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,8 @@ pub fn map_bridge_method(method: &str) -> (&str, bool) {

// PTY
"_ptySetRawMode" => ("__pty_set_raw_mode", false),
"_ptyReadRaw" => ("__pty_read", false),
"_ptyWriteRaw" => ("__pty_write", false),

// Pass through unknown methods
_ => (method, false),
Expand Down
17 changes: 14 additions & 3 deletions crates/execution/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3339,9 +3339,10 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOsWasiModule =
typeof globalThis?.__agentOsSyncRpc?.callSync === "function"
? globalThis.__agentOsSyncRpc
: null;
const sidecarManagedProcess =
typeof process?.env?.AGENT_OS_SANDBOX_ROOT === "string" &&
process.env.AGENT_OS_SANDBOX_ROOT.length > 0;
// AGENT_OS_SANDBOX_ROOT is scrubbed from the public process.env, so check the hidden
// internal env too (via _sidecarManagedProcess) — otherwise sidecar-managed guests fall to
// the passthrough stdin read, which never receives host write_stdin (kernel pipe) data.
const sidecarManagedProcess = this._sidecarManagedProcess();
if (syncRpc && (sidecarManagedProcess || __agentOsKernelStdioSyncRpcEnabled())) {{
try {{
let chunk = null;
Expand Down Expand Up @@ -4248,6 +4249,16 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOsSyncRpc ===
throw new Error("secure-exec WASM kernel poll bridge is unavailable");
}}
return _kernelPollRaw.applySync(void 0, args);
case "__pty_read":
if (typeof _ptyReadRaw === "undefined") {{
throw new Error("secure-exec WASM pty read bridge is unavailable");
}}
return _ptyReadRaw.applySync(void 0, args);
case "__pty_write":
if (typeof _ptyWriteRaw === "undefined") {{
throw new Error("secure-exec WASM pty write bridge is unavailable");
}}
return _ptyWriteRaw.applySync(void 0, args);
case "child_process.spawn": {{
if (typeof _childProcessSpawnStart === "undefined") {{
throw new Error("secure-exec WASM child_process bridge is unavailable");
Expand Down
58 changes: 58 additions & 0 deletions crates/kernel/src/kernel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1292,6 +1292,64 @@ impl<F: VirtualFileSystem + 'static> KernelVm<F> {
Ok(self.ptys.create_pty_fds(table)?)
}

/// Allocate a PTY pair and split its ends across two processes: the master fd lands in
/// `parent_pid`'s fd table (the terminal emulator reads/writes it) and the slave fd lands in
/// `child_pid`'s fd table (dup'd onto the shell's stdin/stdout/stderr by the caller). This is
/// the kernel primitive behind `__pty_spawn`: unlike `open_pty`, which places both ends in one
/// process, a terminal needs the two ends in distinct processes.
pub fn open_pty_split(
&mut self,
requester_driver: &str,
parent_pid: u32,
child_pid: u32,
) -> KernelResult<(u32, u32, String)> {
self.assert_not_terminated()?;
self.assert_driver_owns(requester_driver, parent_pid)?;
self.assert_driver_owns(requester_driver, child_pid)?;
self.resources
.check_pty_allocation(&self.resource_snapshot())?;
let pty = self.ptys.create_pty();
let mut tables = lock_or_recover(&self.fd_tables);
let master_fd = match tables.get_mut(parent_pid) {
Some(parent_table) => parent_table.open_with(
Arc::clone(&pty.master.description),
FILETYPE_CHARACTER_DEVICE,
None,
)?,
None => {
self.ptys.close(pty.master.description.id());
self.ptys.close(pty.slave.description.id());
return Err(KernelError::no_such_process(parent_pid));
}
};
let slave_fd = match tables.get_mut(child_pid) {
Some(child_table) => match child_table.open_with(
Arc::clone(&pty.slave.description),
FILETYPE_CHARACTER_DEVICE,
None,
) {
Ok(slave_fd) => slave_fd,
Err(error) => {
if let Some(parent_table) = tables.get_mut(parent_pid) {
parent_table.close(master_fd);
}
self.ptys.close(pty.master.description.id());
self.ptys.close(pty.slave.description.id());
return Err(error.into());
}
},
None => {
if let Some(parent_table) = tables.get_mut(parent_pid) {
parent_table.close(master_fd);
}
self.ptys.close(pty.master.description.id());
self.ptys.close(pty.slave.description.id());
return Err(KernelError::no_such_process(child_pid));
}
};
Ok((master_fd, slave_fd, pty.path))
}

pub fn socket_create(
&mut self,
requester_driver: &str,
Expand Down
88 changes: 88 additions & 0 deletions crates/kernel/tests/api_surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,94 @@ fn open_shell_configures_pty_and_exec_uses_shell_driver() {
kernel.waitpid(exec.pid()).expect("wait exec");
}

#[test]
fn open_pty_split_wires_master_in_parent_and_slave_in_child() {
use secure_exec_kernel::pty::LineDisciplineConfig;
let mut config = KernelVmConfig::new("vm-api-pty-split");
config.permissions = Permissions::allow_all();
let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
kernel
.register_driver(CommandDriver::new("shell", ["sh"]))
.expect("register shell");

// Two processes: a terminal (parent, holds the master) and a shell (child, holds the slave).
let terminal = kernel
.exec(
"term",
ExecOptions {
requester_driver: Some(String::from("shell")),
..ExecOptions::default()
},
)
.expect("spawn terminal");
let shell = kernel
.exec(
"shell",
ExecOptions {
requester_driver: Some(String::from("shell")),
..ExecOptions::default()
},
)
.expect("spawn shell");

let (master_fd, slave_fd, path) = kernel
.open_pty_split("shell", terminal.pid(), shell.pid())
.expect("open pty split");
assert!(path.starts_with("/dev/pts/"), "pty path was {path}");

// Raw mode on the slave so the line discipline does not cook/echo our test bytes.
kernel
.pty_set_discipline(
"shell",
shell.pid(),
slave_fd,
LineDisciplineConfig {
canonical: Some(false),
echo: Some(false),
isig: Some(false),
},
)
.expect("set raw mode");

// Input direction: the terminal writes the master, the shell reads it from the slave.
assert_eq!(
kernel
.fd_write("shell", terminal.pid(), master_fd, b"ping")
.expect("write master"),
4
);
assert_eq!(
kernel
.fd_read("shell", shell.pid(), slave_fd, 64)
.expect("read slave"),
b"ping"
);

// Output direction: the shell writes the slave, the terminal reads it from the master.
assert_eq!(
kernel
.fd_write("shell", shell.pid(), slave_fd, b"pong")
.expect("write slave"),
4
);
assert_eq!(
kernel
.fd_read("shell", terminal.pid(), master_fd, 64)
.expect("read master"),
b"pong"
);

// A nonexistent child must fail (and roll back the master fd it tentatively opened).
assert!(kernel
.open_pty_split("shell", terminal.pid(), 999_999)
.is_err());

terminal.finish(0);
shell.finish(0);
kernel.waitpid(terminal.pid()).expect("wait terminal");
kernel.waitpid(shell.pid()).expect("wait shell");
}

#[test]
fn pty_resize_delivers_sigwinch_to_the_foreground_process_group() {
let mut config = KernelVmConfig::new("vm-api-shell");
Expand Down
Loading