Skip to content

Commit 7a3e8e8

Browse files
committed
chore: update PRD and progress for US-026
1 parent 9a3599c commit 7a3e8e8

2 files changed

Lines changed: 29 additions & 1 deletion

File tree

scripts/ralph/prd.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@
435435
"pnpm --filter secure-exec exec vitest run tests/kernel/bridge-gap-behavior.test.ts passes"
436436
],
437437
"priority": 26,
438-
"passes": false,
438+
"passes": true,
439439
"notes": "ROOT CAUSE: packages/nodejs/src/kernel-runtime.ts lines ~355-391: writeStdin() pushes to a buffer array, closeStdin() concatenates and resolves a promise that exec() awaits. Stdin is never delivered to the running sandbox process — it's only available as a complete string AFTER closeStdin(). FIX: For PTY-backed processes (openShell), writeStdin() must deliver data to the sandbox's process.stdin in real-time via the V8 session's sendStreamEvent() method (already exists for child process stdout). The bridge's stdin handler in packages/nodejs/src/bridge/process.ts needs a streaming data path, not just the batched _stdinData global. DO NOT work around this by spawning on the host. DO NOT add skip logic."
440440
},
441441
{

scripts/ralph/progress.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@
6565
- V8 dynamic import callback (`dynamic_import_callback`) uses MODULE_RESOLVE_STATE thread-local — must be initialized before execution for both CJS and ESM modes
6666
- `enable_dynamic_import()` must be called after every isolate create/restore (not captured in snapshots), alongside `disable_wasm()`
6767
- `execute_script()` now takes optional `BridgeCallContext` as 2nd arg for dynamic import support in CJS mode
68+
- Streaming stdin uses _stdinRead bridge handler (async bridge call/response), NOT V8 stream events — stream events have V8 serialization cross-version issues
69+
- buildPtyBridgeHandlers must be called ONCE per executeInternal — the result is shared between dispatch handlers and main bridge handlers to avoid split closure state
70+
- process.exit() inside setTimeout callbacks is silently caught by timer error handler — use process.exit() only in synchronous or stdinDispatch contexts
71+
- PTY stdin flow: shell.write() → PTY master → input buffer → stdin pump (kernel openShell) → proc.writeStdin → onStdinData → resolves _stdinRead promise → readLoop dispatches to stdinDispatch → process.stdin 'data' events
6872

6973
# Ralph Progress Log
7074
Started: Sat Mar 21 02:49:43 AM PDT 2026
@@ -818,3 +822,27 @@ Started: Sat Mar 21 02:49:43 AM PDT 2026
818822
- The __dynamicImport global is still installed in V8 snapshots but never called — removing it would require a snapshot rebuild
819823
- convertEsmToCjs is still needed in loadFileSync for require() of ESM-only packages in CJS exec mode
820824
---
825+
826+
## 2026-03-21 19:13 - US-026
827+
- Fixed streaming stdin delivery from PTY to sandbox process
828+
- Three-layer fix:
829+
1. **Kernel stdin pump** (kernel.ts openShell): Reads from PTY slave input buffer and forwards to driverProcess.writeStdin() — bridges shell.write() to the runtime driver
830+
2. **Bridge handler** (bridge-handlers.ts buildPtyBridgeHandlers): Added `_stdinRead` async bridge handler that returns a Promise resolving with the next stdin chunk. Host resolves when writeStdin delivers data.
831+
3. **Bridge read loop** (bridge/process.ts resume()): When process.stdin.resume() is called on TTY, starts async loop calling _stdinRead repeatedly. Each call creates a pending bridge promise keeping the V8 event loop alive. Data dispatched to stdinDispatch which emits process.stdin 'data' events.
832+
- Also updated stream.rs to route "stdin" → _stdinDispatch (and other missing event types)
833+
- Files changed:
834+
- packages/core/src/kernel/kernel.ts (stdin pump in openShell)
835+
- packages/nodejs/src/bridge-contract.ts (stdinRead key)
836+
- packages/nodejs/src/bridge-handlers.ts (PtyBridgeDeps, _stdinRead handler)
837+
- packages/nodejs/src/bridge/process.ts (_stdinRead loop, stdinDispatch)
838+
- packages/nodejs/src/execution-driver.ts (onStdinReady, deduplicate ptyDeps)
839+
- packages/nodejs/src/kernel-runtime.ts (stdinDeliverFn/stdinEndFn via bridge)
840+
- native/v8-runtime/src/stream.rs (new event type routing)
841+
- packages/secure-exec/tests/kernel/bridge-gap-behavior.test.ts (4 streaming stdin tests)
842+
- **Learnings for future iterations:**
843+
- V8 stream events (sendStreamEvent/dispatch_stream_event) use V8 serialization which may have cross-version issues between host Node.js V8 and Rust V8 crate — prefer bridge call/response for reliable data delivery
844+
- Bridge handlers must be built ONCE and shared — duplicate buildPtyBridgeHandlers calls create separate closures with separate state, causing data to go to the wrong instance
845+
- process.exit() inside setTimeout callbacks is silently caught by the timer error handler — avoid process.exit() in async timer callbacks
846+
- The _stdinRead bridge call pattern keeps the V8 event loop alive via pending promises — each call creates a pending promise until the host resolves it
847+
- buildPtyBridgeHandlers exists in BOTH dispatch handlers (inside buildModuleLoadingBridgeHandlers) and main handlers — must use the same ptyDeps/ptyHandlers instance
848+
---

0 commit comments

Comments
 (0)