Skip to content

Latest commit

 

History

History
155 lines (122 loc) · 11 KB

File metadata and controls

155 lines (122 loc) · 11 KB

agent-tui — Tracker

Working knowledge that doesn't belong in source comments: decisions, follow-ups, open questions, deferred work. Source rots; this file is where the WHY lives.

Current phase

P3 core complete. P0a + P0b + P0 close + P1 + P2 partial + deferred-work sweep + P3 core all shipped on PR #1. P3 core added the typed-Action interceptor, two evaluators (allow-all + binary allowlist), per-snapshot nonced content-boundary delimiters, and the in-process AuditEvent firehose broadcast channel.

Next major chunks: P3 follow-on (OPA-WASM Rego evaluator, auth vault with mlock + kernel-keyring/Keychain, per-pane mpsc queue) or P4 (MCP server + cargo-dist + distribution channels).

Substrate decision

Default engine: alacritty_terminal v0.26.0. Flipped from wezterm-term during P0a because wezterm-term is not published on crates.io — only available as a git dependency, which would block the cargo install agent-tui / npm install -g / brew install distribution promise documented in ADR-TA-012.

Implication: v1 does not have Kitty graphics + Sixel + OSC 8 hyperlinks in the engine. The PNG rasterizer (P1 task) will stub those. The wezterm crate stays as a placeholder; revisit when a published source appears or when libghostty-vt tags stable.

Decisions worth remembering

Topic Decision Rationale
Engine sync Mutex<Box<dyn MasterPty + Send>> — wrap every portable-pty handle in a Mutex Trait bound is Send only; storing the Pane in Arc<> needs Sync
Per-pane queue Deferred from P0 to P1 cycle 13 Mutex on write_input is functionally equivalent until the wait subsystem needs concurrent observers
Doctor Wired to DaemonStatus, not its own command Avoids a wire-protocol change; status payload covers reachability + version + pane count
Snapshot generation GenerationTracker keyed by PaneId lives in handlers/snapshot.rs Moves onto Pane itself when the per-pane queue lands
Signal delivery nix::sys::signal::killpg to the process group via MasterPty::process_group_leader Reaches forwarded shell children, not just immediate child

Deferred work

Still deferred (justified)

  • task 0.4 — Per-pane mpsc::Sender<Command> queue. Re-deferred to P3 with policy. The only thing a real queue adds today is PANE_BUSY semantics under --no-wait, and no flow triggers that yet.
  • PluginAdapter (sub-process JSON-RPC over stdio) — Moved to P4 alongside mcp serve. Both speak stdio JSON-RPC; build the framework once.
  • nvim / tmux built-in adapters — Will land as external plug-ins via PluginAdapter once #2 ships. Avoids dragging nvim --headless / tmux -CC into CI.
  • wezterm engine real impl — Blocked on wezterm-term being published to crates.io. Track only.
  • Windows runtime — cycle W2 — SHIPPED (PR #124), with a mechanism change. Cycle W1 (IPC swap to interprocess) shipped on all platforms; W2 landed signal handling as ConPTY-input ETX (SIGINT) + taskkill /F /T descendant-tree kill (SIGTERM/SIGKILL) instead of the planned GenerateConsoleCtrlEvent (which cannot reach a child on its own pseudoconsole), plus ConPTY DSR render, headless pipe spawn for run/ask/edit, the run | consumer handle-inheritance fix, and windows-latest in the CI matrix. SIGBREAK/SIGQUIT is rejected honestly (no delivery path). Still open: owner-only DACL on the named pipe, real Ctrl-Break delivery — see docs/design/windows-strategy.md.

P-UX6 — timeout fix + small primitives (this PR)

  • Client read-timeout derives from the commandsend_and_recv no longer hardcodes 25s. Wait carries its own deadline (--max), so the read timeout is wait_timeout + 5s margin; everything else keeps the 25s base. Fixes ask … --max 120000 / run --max >25s dying at 25s while the daemon was still working (gaps §3.3). Global --timeout is still unwired — left for a follow-up; deriving from the command is the prescribed fix and is self-contained.
  • snapshot --mode text --keep-color — reconstructs per-cell SGR from fg/bg/attrs (palette / 24-bit / attrs), coalescing same-style runs and resetting per row. Default text stays plain (gaps §3.7). Color→SGR mapping unit-tested; >=256 colors (NamedColor defaults) map to terminal default.
  • session gc[--older-than-days N=7] [--all] [--dry-run]. Reaps dead sessions' sidecars + cast dirs; never reaps a session whose socket still answers (non-spawning liveness probe). Age-gated so a just-crashed session isn't pulled from under a retry (gaps §3.9). Pure planning logic + fs enumeration unit-tested; e2e tokio test.

Deferred from this PR (with reason): wait --ready-for-stdin (needs ptrace/syscall tracing or a recipe startup-delay hint — not a small primitive, gaps §3.5) and uniform --json (cross-cutting output-format refactor touching every verb + the integration suite, gaps §3.13).

Pre-existing red left untouched: xtask docs-coverage fails on 13 un-annotated headings in ai-cli/addressing/vim skills (identical on clean main). Per lesson §7, that's a standalone fix-up, not this PR.

Recently paid down (earlier)

  • Focus trackingpane focus <id> command + tri-state focus (Auto/Focused/Held). Focused-pane death promotes to Held; explicit refocus required.
  • Marker events — Every command emits an m recorder event with {kind, ok, error_code}.
  • Checkpoint events — Per-pane background task pushes s event every 1000 mutations.
  • First-bytes Detect re-attach — PtyChild captures first 512 bytes; spawn fires a deferred re-detect that swaps the attached adapter if a better one wins on populated PaneInfo.
  • OSC 133 raw-byte parser — Pre-engine scanner upgrades PaneState::UnknownShell / Running on FinalTerm shell-integration markers.
  • Stale comment sweep — Removed every lands in P0b / v0.1.0 wires only outline lie.
  • P3 core: typed Action governanceGovernance::check wraps Spawn and Input actions; AllowlistEvaluator enforces --allowed-binaries; AuditEvent broadcast channel fires on every Decision (Allow/Deny/RequireConfirm). New POLICY_DENIED / POLICY_PENDING error paths surface to callers.
  • Per-snapshot nonced content-boundary delimiters — Every snapshot response carries an <<<AGENT_TUI_OUTPUT_xxxxxxxx>>> / <<<END_xxxxxxxx>>> pair with a fresh 8-hex-char nonce so a malicious TUI can't inject a colliding marker.
  • Snapshot --mode cells/hybrid/adapter — Only --mode outline wired in P0a. Cycle 15.
  • State classifier — Returns Unknown unless alt-screen is on. Cycle 16. The 9-state heuristic stack ships with the recorder.
  • Asciicast recorder — Event types exist (agent-tui-recorder); no writer yet. Cycle 17–18.
  • Stale doc comments — CLI flag claims "alacritty lands in P5" (it's the default now); server.rs calls dispatch a "stub matrix" (it isn't anymore). Sweep in cycle 12.
  • CLI engine default — Still Wezterm despite the substrate flip; spawn handler ignores it and instantiates AlacrittyEngine regardless. Cycle 12.
  • wezterm engine crate — Returns the placeholder. Keep as a stub until either wezterm-term is published or libghostty-vt tags. Don't delete — the abstraction validates the trait works for multiple substrates.

Open questions

  • OSC 133 detection precedence. Once the classifier lands, does an OSC 133 marker in scrollback override a current alt-screen flag? Probably yes (shell-in-screen is still shell), but worth a test.
  • Wait under per-pane queue. Does wait --idle block the queue from accepting concurrent reads? Current design: wait should NOT be on the input queue; it subscribes to engine events directly and the snapshot path is already lock-free.
  • Hash window size. RFC pins 256. We can revisit if WAIT_HASH_UNKNOWN shows up in practice — the snapshot-then-wait-on-hash pattern depends on the window covering a typical agent action latency (~2-5 seconds of mutations).
  • Recorder backpressure. RFC says "drop oldest non-g/s on full". Current sketch: bounded mpsc with try_send; on full, drop and bump agent_tui_recorder_dropped_total. Need to confirm tokio's broadcast channel doesn't already do this — it's the closer-fit primitive.

Recent activity

Commit Phase Notes
(next) P3 core Typed Action interceptor, AllowlistEvaluator, AuditEvent firehose, per-snapshot nonced delimiters
5ca9990 Deferred-work sweep Focus tracking, marker/checkpoint events, first-bytes redetect, OSC 133 parser, stale-comment sweep
fcf460c P2 partial AdapterRegistry, sectioned generic outline, claude-code + shell built-ins
e212d9c P1 wait subsystem, cells/hybrid snapshots, classifier, recorder + rotation/retention
d7057f7 P0 close doctor wired to DaemonStatus
3b2239f P0b keymap, press/type quiesce barrier, send_ansi, resize, signal
2140ae9 P0a real engine + PTY + registry + spawn/die/list/snapshot
04a664a scaffolding workspace + RFC + skeleton

Addressing-Model Implementation Tracker (feat/addressing-model)

Live record of the addressing-model rollout. Spec: docs/design/addressing-rfc.md.

Phase plan

# Phase Status Notes
1 Selector grammar + parser (pure lib) in-progress foundation, pure logic
2 Protocol types (state, RoutedStep) todo wire-format additions
3 Daemon: snapshot --select, wait --ref todo
4 Adapter trait: route() todo default impl = identity
5 CLI: press --to, wait --gone todo
6 Built-in adapters emit durable refs todo vim, shell, claude-code
7 Migrate existing tests to new idioms todo no compat constraint
8 Update skills (core, vim, addressing new) todo
9 New integration scenarios todo
10 Pre-push lint + CI green todo

Deferred (for follow-up loops)

  • tmux adapter (v1 limitation, separate phase)
  • Adapter composition (v2, separate RFC)
  • Manifest-driven [routing] blocks (v2)
  • Selector caching API
  • wait --ref stable_ms variant
  • htop adapter (nice to have)
  • fzf adapter (nice to have)
  • claude-code response-streaming vs response-final role split (claude-code adapter now emits @ai-cli.response as a single node; splitting requires per-CLI streaming-end detection)
  • Cell-mode crop for snapshot --select --mode cells

Decisions log (addressing model)

  • Selector parser lives in agent-tui-protocol — pure logic over outline types; no new crate, no new deps.
  • RoutedStep lives in agent-tui-adapter.
  • Bool predicates not in v1 grammar. Use role variants (role=response-streaming) instead.
  • No backward compat constraint per user. Rewrite tests freely.

Open questions (addressing model)

(populated as we go)