Add plexer subsystem, container test infra, and last-resort opencode box backend

- Plexer: always-active terminal multiplexing. NativePlexerController/
  TmuxPlexerController share one interface, chosen by which env var is
  already set (TAI_PLEXER_SOCKET vs TMUX_PANE); NativePlexerBackend is
  the pty.fork()-based wrapper (raw mode, SIGWINCH resize forwarding,
  a Unix-socket control protocol, continuous session logging, a
  Ctrl+A prefix key distinct from tmux's own Ctrl+B). Auto-wraps new
  interactive shells via a .bashrc hook mirroring the existing
  command-not-found install pattern, gated on $TMUX/$TAI_PLEXER_ACTIVE/
  a $HOME/.tai-no-plexer bypass. New plexer_send/read/type/status
  tools, env-gated on a detected controller.
- tests/container.py + docker-compose.yml + tests/docker/Dockerfile:
  a disposable podman/docker image for the tests that must never touch
  a real machine (installing the plexer hook into a real ~/.bashrc, a
  live tmux session). Required suite (test_seal.py, test_tai.py,
  tests/plexer.py, tests/bashrc.py) gates the result; tests/rag.py
  runs as best-effort since its real-LLM dependency can't be satisfied
  inside a disposable container with no nested engine.
- tests/bashrc.py: real end-to-end hook install + bash -i auto-wrap,
  anti-recursion, tmux-active skip, non-interactive skip, a live
  TmuxPlexerController test, and an install/uninstall round-trip.
- tests/plexer.py: NativePlexerBackend under a real pty, plus a
  dedicated robustness pass (file permissions, >64KB payload framing,
  a malformed control request, a genuine self-deadlock fix in the
  write-to-child path).
- tai_test.py: the shared async process-driving framework (pipe/pty
  transport, group-wide kill-and-reap, a StdinBridge for --interactive)
  both test files build on.
- ChatClient last-resort backend: when there's no local opencode CLI
  but the sandbox box is already running, install opencode into it
  (official Node tarball, not apt -- apt's nodejs/npm pull in
  webpack/babel/jest as transitive deps) and route through it instead
  of giving up. Only reachable after the first retry round already
  failed everything else; never creates the box itself. TmuxPlexerController.status()
  fixed to use `list-panes` instead of `display-message -p`, which
  silently fell back to the default pane instead of erroring against
  a dead target.
- Fix 4 pre-existing test_tai.py bugs surfaced by running it in a
  clean environment for the first time: two stale mock signatures
  (subprocess.run's stdout= kwarg, Chat.complete's deadline= kwarg),
  one $TMUX-dependent catalog assertion, one tty-dependent color
  assertion.
- research/: a sourced research archive (Source Dossier/Citation
  Card/Conclusion tiers) backing the above design decisions, with its
  own audit.py consistency checker.
- .gitignore: fix a blanket *.md rule that was about to silently drop
  the entire research/ archive from version control; add __pycache__/
  and the one-off debug transcript this session started from.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 04:36:05 +02:00
co-authored by Claude Sonnet 5
parent f078ab4731
commit 9dbd1fe667
247 changed files with 11187 additions and 26 deletions
+15 -3
View File
@@ -4,8 +4,14 @@
- Use plain `import` and `from` statements in Python files.
- Use the `.py` extension for Python source and test files.
- Keep source and test files at the repository top level.
- Name test files `test_*.py`.
- Keep source files at the repository top level.
- There are two kinds of test, in two different places, on purpose — see "Testing" below for the full split. In short: unit tests are top-level `test_*.py`; integration tests are `tests/<topic>.py` (no `test_` prefix) built on `tai_test.py`.
## Testing
**Unit tests**: `test_tai.py`, `test_seal.py`, at the repository top level, named `test_*.py`, `unittest`-based, import `tai.py` as a module directly, no subprocess, fast. `python3 test_tai.py` / `python3 test_seal.py`, or a single test via `python3 -m unittest test_tai.SomeClass.test_method`.
**Integration tests**: `tests/<topic>.py` (e.g. `tests/rag.py`), deliberately *not* named `test_*.py` and *not* at the top level, so the two kinds are never confused. These drive the real `tai.py` binary as a subprocess (one-shot or REPL) and check actual CLI behavior, using `tai_test.py` (the framework, at the repository root) as the one and only base to build on — never a one-off `subprocess`/`asyncio` harness per file. **They must exercise a real LLM call through the real binary — never a mocked model or a database write standing in for what the agent would have done; the resulting flakiness from a free, rotating backend is accepted, not engineered away.** Full conventions, that rule's full rationale, the required isolation rule (always a throwaway `TAI_HOME`), and why verification must check vault state rather than scrape CLI rendering, are in `tests/README.md` — read it before adding a new one. Run with `python3 tests/<topic>.py` (`--filter`, `--interactive`, `--timeout` all supported).
## Agent Overview
@@ -73,4 +79,10 @@
- Keep code maintainable
- Document decisions
- Version control changes
- Collaborate effectively
- Collaborate effectively
## Research Rule
All external research conducted for this project (web searches, reading documentation to settle a design question, competitive/prior-art analysis) is archived under `research/`, maintained under the auspices of the **Molodetz Institute — Research & Retrieval Division**. The full specification — folder layout, the three required document types (Source Dossier, Source Citation Card, Conclusion Document), required metadata, and the quality bar — lives in `research/README.md`; `research/INDEX.md` is the living registry and statistics page. Read `research/README.md` in full before writing into this archive.
The binding rule, in short: **a research task is not done until it is written into `research/`.** Every source actually consulted gets its own document (Tier 1 if read and synthesized, Tier 2 if only seen in a result listing — never skipped, never inflated past what's actually known), every decision the research produced gets its own Conclusion document in that subject's `conclusions/` folder, and `research/INDEX.md` gets updated in the same pass. Check `research/INDEX.md` for an existing subject before opening a new one.