feat(terminal): add openTerminal session API for deferred PTY input - #188
Conversation
Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: #187
captureTerminal was batch-only: all interactions had to be known at call time and a 30s default timeout killed the child. openTerminal keeps the PTY open with no implicit timeout, exposes waitFor()/send()/close(), and captureTerminal is now implemented on top of it. Fixes #187
Adds open_terminal/TerminalSession with wait_for, send, close, and finish, makes TerminalCaptureOptions::timeout optional (None keeps a session alive), and reimplements capture_terminal on top of the session.
Working session summaryAll three CI workflows now pass on the branch, and PR #188 is ready (not a draft, mergeable, main is an ancestor, working tree clean). What I fixed in this last stretch (all Windows-only test issues, no production-code changes):
PR: #188 (description now also covers the Rust parity port). Final runs: JavaScript checks ✅, Language parity check ✅, Rust checks ✅. This summary was automatically extracted from the AI working session output. |
🤖 Solution Draft LogThis log file contains the complete execution trace of the AI solution draft process. 💰 Cost: $6.151650📊 Context and tokens usage:Claude Opus 5: (2 sub-sessions)
Total: (3.4K new + 142.2K cache writes + 6.6M cache reads) input tokens, 56.6K output tokens, $6.151650 cost 🤖 Models used:
📎 Log file uploaded as Gist (2798KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
🎉 Auto-mergedThis pull request has been automatically merged by hive-mind.
Auto-merged by hive-mind with --auto-merge flag |
Problem
captureTerminal()has a batch-only lifecycle: every interaction must be known at call time,timeoutMillisecondsdefaults to30_000and thenSIGTERMs the child, and the module exported no handle to write to a live PTY. That rules out flows where the input arrives later and from elsewhere — e.g. a CLI login that prints an authorization URL, the request ends, and a separate later request must type the code into the same still-running process (re-spawning prints a new URL, so it is not a workaround).Fixes #187
Solution
New
openTerminal()export alongsidecaptureTerminal(), exposing the machinery that already existed internally:timeoutMillisecondshas no default foropenTerminal(); passing one opts back into a deadline.captureTerminal()keeps its 30 s default.waitFor(pattern, { idleMilliseconds, timeoutMilliseconds })reuses the same readiness matcher asinteractions(string or RegExp) with the sameidleMillisecondsrestart-on-output semantics; it rejects if it times out or the child exits first.send(interaction | interaction[])accepts the sametext/ named-key /resizevocabulary asinteractions, and an entry may carryafter/idleMillisecondsto wait before being applied.close({ signal, timeoutMilliseconds })signals the child (escalating toSIGKILL), returns the usual result object, and writesartifactDirectoryartifacts.dispose()is the never-throwing cleanup variant;finished()waits for a child that exits on its own.output,transcript,frames,asciicast,running,exitStatus,exited.captureTerminal()is reimplemented on top ofopenTerminal(), so the two paths cannot drift.Reproduction and tests
The issue's scenario is reproduced by
js/tests/fixtures/tui-session-fixture.mjs(prints an auth URL, then blocks on raw-mode input). Before this change it could not be expressed at all — there was no handle to write to a live PTY, and the default timeout would kill the child.New tests in
js/tests/terminal-capture.test.mjs:waitForblocks for the requested quiescence and its owntimeoutMillisecondsrejects;send()s reject once the child exits;dispose()stops a child that never exits on its own;file.Runnable example:
js/examples/tui-session.mjs.Full suite: 762 pass / 28 fail, all 28 pre-existing failures in
jq*tests becausejqis not installed in this environment.Notes
minorchangeset is included to trigger the release.Rust parity
The same restructure is mirrored in
rust/src/terminal/capture.rs:TerminalSession(open_terminal) owns the poll loop and exposeswait_for/send/finished/close,TerminalCaptureOptions::timeoutis nowOption<Duration>(Nonewhile a session is open,Some(30s)forcapture_terminal), andcapture_terminal()isopen + finish.TerminalPatterncarries the text/regex readiness matcher. New tests inrust/tests/terminal_capture.rsmirror the JavaScript ones, plus arust/changelog.dfragment and README section.