|
| 1 | +# `_agent.py` coverage |
| 2 | + |
| 3 | +Valid at: cb61477 |
| 4 | + |
| 5 | +## Recent changes |
| 6 | + |
| 7 | +- cb61477 — added `_call_safely(callback, *args)` helper next to the |
| 8 | + callback type aliases. Replaces three copies of the |
| 9 | + `if cb is not None: try: cb(...); except Exception: pass` pattern |
| 10 | + (two in `_read_agent_stream`, one in `_pump_stream`) with single-line |
| 11 | + calls. Behavior preserved — identical None guard, identical broad |
| 12 | + `Exception` suppression, identical argument-once semantics. |
| 13 | + |
| 14 | +## Shape of the module |
| 15 | + |
| 16 | +- Two execution paths: `_run_agent_streaming` (JSON line stream, used for |
| 17 | + `claude`) and `_run_agent_blocking` (subprocess.Popen with optional |
| 18 | + capture, used for all other agents). |
| 19 | +- `execute_agent` is the single public entry point; selects mode via |
| 20 | + `_supports_stream_json(cmd)` (checks `Path(cmd[0]).stem == CLAUDE_BINARY`). |
| 21 | +- Shared shutdown sequence is centralized in `_cleanup_agent`: |
| 22 | + 1. `_ensure_process_dead` (SIGTERM → SIGKILL via `_try_graceful_group_kill`, |
| 23 | + then `proc.kill()`). |
| 24 | + 2. `_close_pipes` (raw `os.close` on stdout/stderr fds to unblock readers). |
| 25 | + 3. `_drain_readers` (bounded join on reader/writer threads). |
| 26 | + 4. `_finalize_pipes` (Python-level `pipe.close()` for GC hygiene). |
| 27 | +- Thread spawning uses `_start_writer_thread` / `_start_pump_thread` to |
| 28 | + centralize the `Thread(..., daemon=True); .start()` boilerplate. |
| 29 | + |
| 30 | +## Verified live (grepped, confirmed used) |
| 31 | + |
| 32 | +- `CLAUDE_BINARY` — public; imported by `_console_emitter.py` for display |
| 33 | + logic (see backlog note about consolidating `_is_claude_command` / |
| 34 | + `_supports_stream_json`; deferred until a third caller appears). |
| 35 | +- `_STDOUT`, `_STDERR` — used in `_run_agent_streaming` / |
| 36 | + `_run_agent_blocking` stderr pump calls and inside `_read_agent_stream`. |
| 37 | +- `_SIGTERM_GRACE_PERIOD`, `_THREAD_JOIN_TIMEOUT`, `_PROCESS_WAIT_TIMEOUT` |
| 38 | + — each referenced exactly once; constants kept near usage as the |
| 39 | + project convention prefers. |
| 40 | +- `AgentResult`, `_StreamResult` — returned from streaming/blocking paths |
| 41 | + and consumed by `engine.py`. |
| 42 | + |
| 43 | +## Potential future wins (not yet taken) |
| 44 | + |
| 45 | +- `_run_agent_streaming` and `_run_agent_blocking` both finish with the |
| 46 | + same "`stdout = "".join(...); stderr = "".join(...); log_file = |
| 47 | + _write_log(...); return AgentResult(...)`" tail, but the shape of the |
| 48 | + intermediate state differs (tuple vs list|None), so extracting would |
| 49 | + mostly move arguments around. Revisit only if a third execution path |
| 50 | + appears. |
| 51 | +- The two `if proc.stdin/stdout/stderr is None: raise RuntimeError(...)` |
| 52 | + guards just after `Popen` could use a single helper, but `subprocess` |
| 53 | + guarantees these are non-None when `PIPE` is passed — the guards exist |
| 54 | + mainly to narrow for the type checker, and a helper would make the |
| 55 | + narrow less explicit. Leave as-is. |
0 commit comments