Feat/ts pty handler migration#126
Conversation
|
@luarss Pleas have a review when you get sometime |
There was a problem hiding this comment.
Pull request overview
Migrates the interactive PTY/session layer from Python to a new TypeScript implementation, introducing a TS-based PTY wrapper, buffered output collection, session lifecycle management, and accompanying Vitest coverage.
Changes:
- Added a new interactive subsystem (
PtyHandler,InteractiveSession,CircularBuffer) and error model hierarchy in TypeScript. - Introduced
SessionStateplus interactive result/info interfaces insrc/core/models.ts. - Added comprehensive Vitest coverage for the new interactive components and updated ESLint config to better support the new code/tests.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| typescript/src/interactive/session.ts | New InteractiveSession lifecycle, input queue writer loop, output collection + error detection. |
| typescript/src/interactive/pty_handler.ts | New node-pty wrapper (spawn, write, exit handling, termination, cleanup, command validation). |
| typescript/src/interactive/models.ts | New interactive error hierarchy (SessionError, PTYError, etc.). |
| typescript/src/interactive/buffer.ts | New mutex-protected string chunk circular buffer with async wait-for-data. |
| typescript/src/core/models.ts | New SessionState enum and interactive result/info interfaces. |
| typescript/eslint.config.ts | Ignore coverage/** and enforce @typescript-eslint/no-unused-vars (with _ ignore patterns); relax return-type rule for tests. |
| typescript/tests/interactive/session.test.ts | Tests for session lifecycle, writer loop behavior, output reads, and error detection. |
| typescript/tests/interactive/pty_handler.test.ts | Tests for PTY spawn wiring, onData/onExit behavior, termination, waitForExit, and cleanup semantics. |
| typescript/tests/interactive/command_validation.test.ts | Tests for allowlist + metacharacter/redirection blocking and Settings env parsing. |
| typescript/tests/interactive/buffer.test.ts | Tests for buffer append/drain/eviction, waitForData, stats, and concurrency behavior. |
| .vscode/settings.json | Workspace TS SDK path for consistent editor TypeScript usage. |
| plan.md | Present in PR metadata (currently empty). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Kartik Mittal <100078751+kartikloops@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Kartik Mittal <100078751+kartikloops@users.noreply.github.com>
luarss
left a comment
There was a problem hiding this comment.
Posted some high priorities one to solve, please check @kartikloops
| @@ -0,0 +1,3 @@ | |||
| { | |||
There was a problem hiding this comment.
Without it, every contributor who opens the repo in VS Code gets TypeScript errors or the wrong type-checker, because VS Code defaults to its own bundled TypeScript version rather than the project's. This file pins VS Code to use the TypeScript installed in typescript/node_modules/, so everyone gets consistent IntelliSense and error highlighting that matches what tsc actually sees.
It ensures VS Code uses the same TypeScript the build does.
| logger.debug("PTY handler cleanup completed") | ||
|
|
||
|
|
||
| class MacOSPTYHandler(PTYHandler): |
There was a problem hiding this comment.
Lets avoid this for now - don't need to support macOS for a quick PoC.
| const exited = await this.waitForExit(5000); | ||
| if (exited === null && this._alive) { | ||
| try { | ||
| this._ptyProcess.kill("SIGKILL"); |
There was a problem hiding this comment.
terminateProcess() sends SIGKILL and returns immediately without awaiting; cleanup() then disposes the exit listener before SIGKILL is acknowledged, permanently losing the real exit code.
When the process survives the 5-second SIGTERM window: SIGKILL is sent at line 162 and terminateProcess() returns without awaiting. cleanup() then calls this._exitDisposable?.dispose() (line 179), removing the onExit listener. When SIGKILL is eventually processed, the listener is gone — _exitCode stays null forever. Any pending waitForExit() callers receive null instead of the real exit code (e.g. 137).
| `Command argument ${i} contains shell metacharacters which are not allowed: ${JSON.stringify(arg)}`, | ||
| ); | ||
| } | ||
| if (arg.startsWith(">") || arg.startsWith("<")) { |
There was a problem hiding this comment.
validateCommand does not check for path traversal in arguments — openroad -script ../../etc/malicious.tcl passes all validation.
The argument loop (lines 43–55) checks for shell metacharacters and leading >/<, but never inspects arguments for .. sequences. ['openroad', '-script', '../../etc/malicious.tcl'] contains no flagged characters, so it passes all checks and OpenROAD executes the attacker-controlled Tcl script.
| this._chunks.push(data); | ||
| this._totalSize += data.length; | ||
|
|
||
| while (this._totalSize > this.maxSize && this._chunks.length > 1) { |
There was a problem hiding this comment.
A single append larger than maxSize is never evicted — the buffer permanently exceeds its capacity.
The eviction loop condition is this._chunks.length > 1. When a single large chunk is the only entry, _chunks.length === 1 and the loop never runs, leaving _totalSize above maxSize indefinitely.
|
|
||
| const executable = command[0]!; | ||
|
|
||
| if (path.isAbsolute(executable)) { |
There was a problem hiding this comment.
Python implementation accepts absolute path though. Why is there a divergence ?
Summary
Migrates the interactive PTY layer from Python to TypeScript — source and tests.
New source files
src/interactive/models.ts— session error hierarchy (SessionError,SessionTerminatedError,CommandBlockedError,PTYError)src/interactive/buffer.ts— string-based circular buffer with mutex and async wait-for-datasrc/interactive/pty_handler.ts— wrapsnode-pty, handles spawn, write, graceful termination, and event-driven output viaonData/onExitsrc/interactive/session.ts— session lifecycle (CREATING → ACTIVE → TERMINATED), writer task, input queue, output collectionsrc/core/models.ts—SessionStateenum and result interfacesTests
buffer.test.ts,pty_handler.test.ts,command_validation.test.ts,session.test.ts