main
md 57 lines 5.3 KB
Rendered Raw
1 ---
2 log:
3 2026-05-07: Fixed `colab console` piped-stdin handling. Previously a piped invocation (e.g. `echo 'cmd' | colab console -s s`) sent the command and then hung indefinitely because the previous EOF handler emitted a bare `\x04` (Ctrl-D), which the remote `tmux`-wrapped bash treats as a literal character rather than a session terminator. The new handler sends `exit\n` (which bash actually exits on) and then closes the websocket from the client side after a short grace period (`PIPED_EOF_GRACE_SECONDS = 0.5s`) so any tail output (bash `logout`, tmux `[exited]`) makes it back to the user. TTY mode is unchanged: real-terminal EOF is left to the remote shell. Verified live: `echo 'echo HELLO' | colab console -s s` now exits in ~1.2s instead of hanging.
4
5 2026-05-07: Fixed `print_kitty` (used by `colab exec --output-image` and any image-producing exec) to no-op when `sys.stdout.isatty()` is false. The Kitty Graphics Protocol escape sequence is meaningless when stdout is a file or pipe and was visually corrupting captured output (a multi-KB base64 PNG blob would land in log files, grep targets, or showboat captures). Image bytes are still saved to disk via `handle_image`'s file-write path; only the inline-render attempt is suppressed.
6
7 2026-06-04: Bumped the default `--timeout` for `colab exec` from 10s to 30s (and the matching `colab run` default) so brief silent tasks are less likely to hit a premature `TimeoutError`. Explicit `--timeout` overrides are unaffected.
8 ---
9
10 # Design: Execution and Interactive Interaction (`repl`, `exec`, `console`)
11
12 ## Overview
13 Execution involves sending Python code (or shell commands) to the Jupyter kernel running on the Colab VM and processing the stream of output messages.
14
15 ## Approach
16
17 ### 1. REPL (`colab repl`)
18 - **Transport**: WebSockets (using `websockets` library if allowed, or a custom `http.client` based long-polling implementation if we're strictly stdlib).
19 - **Communication**: Jupyter Kernel Messaging Protocol.
20 - `execute_request`: Send code string.
21 - `execute_reply`: Get status.
22 - `iopub.stream`: Capture `stdout` and `stderr`.
23 - **Interactive Mode**: Standard Python `cmd.Cmd` or `code.InteractiveConsole` for local input/output.
24 - **Piping Support**: Detect `sys.stdin.isatty()`. If not a TTY, read all input and send as a single execution request.
25
26 ### 2. Execution (`colab exec`)
27 - **File Handling**:
28 - If file path is local: Read content, send as code.
29 - If file path is remote: Execute `!python <path>`.
30 - **Multi-Modal Output**: Handle `display_data` messages (e.g., `image/png`, `text/html`). For the CLI, we'll save images to temporary files and print their paths, or if the terminal supports it (e.g., iTerm2), inline them.
31 - **Timeout Configuration**: Exposes a `--timeout` flag (default 30s) to allow long-running silent tasks (like model compilation or data downloading) to execute without being prematurely killed.
32
33 ### 3. Console (`colab console`)
34 - **Implementation**: Connects directly to the backend terminal endpoint (`/colab/tty`) via WebSockets using `websocket-client`.
35 - **Interactive**: Bypasses the Jupyter kernel entirely to provide a raw, PTY-backed bash session on the Colab VM.
36 - **Terminal Management**: Configures `sys.stdin` to raw mode using `termios` and `tty`, passing single characters to the socket and writing raw ANSI escape sequences directly to `sys.stdout.buffer`. Hooks into `SIGWINCH` to communicate local terminal dimensions (`cols`/`rows`) to the remote bash environment so output rendering works perfectly during resizing.
37 - **Piped stdin**: Detected via `sys.stdin.isatty()`. When piped, the input characters are forwarded one at a time to the remote pty, and on EOF the client sends `exit\n` and then closes the websocket itself after `PIPED_EOF_GRACE_SECONDS` (0.5s) so the user's shell goodbye text drains back. The remote `/colab/tty` endpoint wraps bash in tmux, which intercepts a bare `\x04` as a literal character — that is why we send `exit\n` rather than Ctrl-D.
38
39 ## Implementation Details
40 - **Kernel Management**: `ColabRuntime` (from `colab-agent`) already handles message signing and message types.
41 - **Output Streaming**: Continuous polling or asynchronous message handling to provide real-time output.
42 - **Piping Example**: `cat script.py | colab exec -s my-session`.
43
44 ## Testing Strategy
45 TDD is mandatory for all execution features.
46
47 ### 1. Mock Kernel Client
48 - **Test Case**: Verify `ColabRuntime` correctly sends an `execute_request` message over the websocket.
49 - **Test Case**: Verify `iopub.stream` messages are correctly handled and printed to `stdout` in real-time.
50 - **Test Case**: Verify `display_data` (specifically `image/png`) triggers the correct local handling (saving or display).
51
52 ### 2. TTY and Piping
53 - **Test Case**: Mock `sys.stdin.isatty()` to verify `colab repl` correctly switches between interactive mode and one-shot piped execution.
54 - **Test Case**: Verify large piped inputs are handled without buffer overflow or truncation.
55 - **Test Case**: `colab console` with piped stdin sends `exit\n` and calls `ws.close()` on EOF (regression: previously sent `\x04` only and hung).
56 - **Test Case**: `colab console` in TTY mode does not synthesize an exit on EOF (the user owns the session lifecycle).
57 - **Test Case**: `print_kitty` is a no-op when `sys.stdout.isatty()` is false (regression: previously emitted ANSI/base64 into pipes and files).