From 7e663cb0d7758d26cc24e275cbd415ac04a2d473 Mon Sep 17 00:00:00 2001 From: blamouche Date: Fri, 13 Mar 2026 23:37:40 +0100 Subject: [PATCH] Set PTY size explicitly in python bridge fallback --- .prompt-hub/lessons.md | 1 + .prompt-hub/memory.md | 1 + .prompt-hub/releases.md | 4 ++ ...233621-fix-codex-pty-size-python-bridge.md | 19 +++++++ .prompt-hub/version.md | 2 +- pty-bridge.py | 52 ++++++++++++++++++- pty-proxy.js | 8 ++- 7 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 .prompt-hub/todo/todo-20260313-233621-fix-codex-pty-size-python-bridge.md diff --git a/.prompt-hub/lessons.md b/.prompt-hub/lessons.md index 7a8943d..ee6875d 100644 --- a/.prompt-hub/lessons.md +++ b/.prompt-hub/lessons.md @@ -8,3 +8,4 @@ - Proactive terminal handshake injection can be echoed back as raw input in bridge mode; prefer startup-config mitigations over escape-sequence injection. - If injected terminal response sequences are echoed as raw text, stop injection and instead disable Codex startup prompts/warnings via config flags to reduce hidden interactive blockers. - For interactive Codex/TUI output in embedded xterm, avoid forcing `convertEol: true`; it can distort line rendering and split characters across lines. +- Python PTY bridge must set terminal size explicitly (`TIOCSWINSZ`) using requested cols/rows; otherwise interactive TUI output can degrade into letter-per-line rendering. diff --git a/.prompt-hub/memory.md b/.prompt-hub/memory.md index 37b3cf8..9aa9b28 100644 --- a/.prompt-hub/memory.md +++ b/.prompt-hub/memory.md @@ -28,3 +28,4 @@ | 2026-03-13 20:15:06 CET | agent | Added proactive Codex startup terminal handshake (immediate + delayed retry) to address blank bridge startup, with validation and governance updates. | `main.ts`, `.prompt-hub/lessons.md`, `npm run build`, `npm test`, `.prompt-hub/todo/todo-20260313-201421-codex-proactive-handshake.md`, `.prompt-hub/version.md`, `.prompt-hub/releases.md` | success | Commit and push for user retest. | | 2026-03-13 23:24:39 CET | agent | Removed ineffective Codex escape injection (echoed as raw input) and switched to startup prompt-suppression flags in Codex launch command; validated build/tests. | `main.ts`, `.prompt-hub/lessons.md`, `npm run build`, `npm test`, `.prompt-hub/todo/todo-20260313-232410-codex-disable-startup-prompts.md`, `.prompt-hub/version.md`, `.prompt-hub/releases.md` | success | Commit and push for user retest. | | 2026-03-13 23:30:56 CET | agent | Fixed broken split-character Codex rendering by disabling forced xterm EOL conversion (`convertEol: false`); validated build/tests and updated governance files. | `main.ts`, `.prompt-hub/lessons.md`, `npm run build`, `npm test`, `.prompt-hub/todo/todo-20260313-233016-fix-codex-split-characters-output.md`, `.prompt-hub/version.md`, `.prompt-hub/releases.md` | success | Commit and push for user verification. | +| 2026-03-13 23:37:35 CET | agent | Fixed letter-per-line Codex output by propagating cols/rows to python PTY bridge and applying `TIOCSWINSZ` in explicit PTY fork/exec bridge path; validated compile/smoke/build/tests. | `pty-proxy.js`, `pty-bridge.py`, `.prompt-hub/lessons.md`, `python3 -m py_compile pty-bridge.py`, `python3 pty-bridge.py `, `npm run build`, `npm test`, `.prompt-hub/todo/todo-20260313-233621-fix-codex-pty-size-python-bridge.md`, `.prompt-hub/version.md`, `.prompt-hub/releases.md` | success | Commit and push for user retest. | diff --git a/.prompt-hub/releases.md b/.prompt-hub/releases.md index 00cc944..22f3ee2 100644 --- a/.prompt-hub/releases.md +++ b/.prompt-hub/releases.md @@ -1,5 +1,9 @@ # Releases +## 0.1.12 - 2026-03-13 +- Fixed python PTY fallback sizing by propagating requested cols/rows from the proxy and applying them in `pty-bridge.py` via `TIOCSWINSZ` at startup. +- Reworked python bridge execution from `pty.spawn` convenience mode to explicit PTY fork/exec streaming for deterministic terminal sizing. + ## 0.1.11 - 2026-03-13 - Fixed split-character / broken newline rendering in embedded Codex output by disabling forced xterm EOL conversion (`convertEol: false`). diff --git a/.prompt-hub/todo/todo-20260313-233621-fix-codex-pty-size-python-bridge.md b/.prompt-hub/todo/todo-20260313-233621-fix-codex-pty-size-python-bridge.md new file mode 100644 index 0000000..6c3e124 --- /dev/null +++ b/.prompt-hub/todo/todo-20260313-233621-fix-codex-pty-size-python-bridge.md @@ -0,0 +1,19 @@ +# Task: Fix Codex letter-per-line output in python PTY bridge + +## Context +- Codex output appears as one character per line in embedded terminal. +- Likely PTY size negotiation issue in python fallback bridge. + +## Plan +- [x] Pass cols/rows through proxy payload to python bridge. +- [x] Replace bridge spawn path with explicit PTY fork + winsize setup. +- [x] Validate build/tests and update traceability. + +## Review +- Updated `pty-proxy.js` to pass normalized `cols`/`rows` into python bridge payload. +- Reworked `pty-bridge.py` to use explicit PTY fork/exec and apply terminal size (`TIOCSWINSZ`) before streaming. +- Validation: + - `python3 -m py_compile pty-bridge.py` passed. + - `python3 pty-bridge.py ` smoke test passed (`OK_BRIDGE`). + - `npm run build` passed. + - `npm test` passed (`12/12` tests). diff --git a/.prompt-hub/version.md b/.prompt-hub/version.md index 20f4951..0e24a92 100644 --- a/.prompt-hub/version.md +++ b/.prompt-hub/version.md @@ -1 +1 @@ -0.1.11 +0.1.12 diff --git a/pty-bridge.py b/pty-bridge.py index 87045f6..5a7a3ae 100644 --- a/pty-bridge.py +++ b/pty-bridge.py @@ -3,7 +3,11 @@ import base64 import json import os import pty +import fcntl +import select +import struct import sys +import termios def decode_payload(raw: str): @@ -21,6 +25,42 @@ def as_exit_code(status: int) -> int: return 1 +def set_winsize(fd: int, rows: int, cols: int) -> None: + packed = struct.pack("HHHH", rows, cols, 0, 0) + fcntl.ioctl(fd, termios.TIOCSWINSZ, packed) + + +def stream_pty(pid: int, fd: int) -> int: + stdin_open = True + while True: + readers = [fd] + if stdin_open: + readers.append(sys.stdin.fileno()) + + ready, _, _ = select.select(readers, [], []) + if fd in ready: + try: + chunk = os.read(fd, 4096) + except OSError: + chunk = b"" + if not chunk: + break + os.write(sys.stdout.fileno(), chunk) + + if stdin_open and sys.stdin.fileno() in ready: + try: + chunk = os.read(sys.stdin.fileno(), 4096) + except OSError: + chunk = b"" + if not chunk: + stdin_open = False + else: + os.write(fd, chunk) + + _, status = os.waitpid(pid, 0) + return as_exit_code(status) + + def main() -> int: payload = decode_payload(sys.argv[1] if len(sys.argv) > 1 else "") @@ -35,6 +75,10 @@ def main() -> int: os.environ[str(key)] = str(value) launches = payload.get("launches") or [] + cols = int(payload.get("cols") or 120) + rows = int(payload.get("rows") or 30) + cols = max(20, cols) + rows = max(10, rows) if not launches: print("[py-bridge-error] no launch specs provided", file=sys.stderr) return 1 @@ -45,8 +89,12 @@ def main() -> int: args = launch.get("args") or [] argv = [str(file)] + [str(x) for x in args] try: - status = pty.spawn(argv) - return as_exit_code(status) + pid, fd = pty.fork() + if pid == 0: + os.execvpe(str(file), argv, os.environ) + + set_winsize(fd, rows, cols) + return stream_pty(pid, fd) except OSError as error: failures.append(f"{file}: {error}") diff --git a/pty-proxy.js b/pty-proxy.js index a4bd992..629ce8d 100644 --- a/pty-proxy.js +++ b/pty-proxy.js @@ -139,7 +139,9 @@ function spawnPythonBridge(payload, launches) { JSON.stringify({ cwd: payload.cwd, env: payload.env, - launches + launches, + cols: payload.cols, + rows: payload.rows }), "utf8" ).toString("base64"); @@ -185,7 +187,9 @@ async function main() { term = spawnPythonBridge( { cwd: payload.cwd, - env: fallbackEnv + env: fallbackEnv, + cols: Math.max(20, Number(payload.cols) || 120), + rows: Math.max(10, Number(payload.rows) || 30) }, launches );