diff --git a/docs/prds/0050-strip-dashboard-to-supervisor-tui.md b/docs/prds/0050-strip-dashboard-to-supervisor-tui.md new file mode 100644 index 0000000..8d17173 --- /dev/null +++ b/docs/prds/0050-strip-dashboard-to-supervisor-tui.md @@ -0,0 +1,346 @@ + +- **Status:** Draft +- **Author:** didericis +- **Created:** 2026-06-03 +- **Issue:** #174 + +## Summary + +The `./cli.py dashboard` command has grown from its PRD 0013 roots +(triage supervise proposals) into a parallel-agent control surface +(PRDs 0019/0020/0021): an active-agents pane, agent picker + start, +re-attach, per-bottle stop, tmux split-pane handoff, operator- +initiated `routes`/`pipelock` edits. Each chunk is reasonable on its +own; together they make the dashboard the largest CLI file in the +repo and the thing most likely to break on a rough edge (curses / +tmux / docker-exec / metadata-discovery interactions). + +This PRD reverses that scope creep. The dashboard is reduced to the +**supervise-plane triage TUI** it was in PRDs 0013–0016: list pending +proposals, approve / modify / reject each one, write audit entries, +deliver the response that unblocks the agent's tool call. Everything +that's about *starting / re-entering / stopping* bottles, or about +*operator-initiated* config edits, comes out. The command is renamed +`./cli.py supervise` so the name matches what it does after the cut. + +Future agent-management UX is explicitly punted: if and when a +control surface for parallel agents resurfaces, the working +assumption (per the issue) is that a web GUI — usable from mobile +— is a better second pass than another round of curses iteration. +That decision is not in this PRD's scope; this PRD only removes the +half-built local-curses path so we stop maintaining it. + +## Problem + +Three concrete pains, all downstream of the dashboard's growth: + +1. **Surface area vs. polish.** `dashboard.py` is ~1740 lines; + `dashboard_model.py` adds another ~420. The interactions among + curses, modals, tmux split-pane, docker-exec handoff, agent + provider templates, metadata-driven re-attach, and + ExitStack-free bottle ownership are intricate enough that + shipping the next polish increment costs more than it returns. +2. **No clear ownership of "starts and stops bottles".** Today + that responsibility is split: `./cli.py start` owns one-shot + sessions; the dashboard owns multi-session bottles it started + itself; `./cli.py cleanup` owns everything else. The dashboard + tracking its own `bottles: dict[str, (cm, bottle, identity)]` + that doesn't survive a quit is a confusing third lane. +3. **Wrong target shape for a "manage many agents" UI.** The + parallel-agent experience the dashboard reaches for is mobile- + meaningful — checking in on agents from a phone is the high- + value case — and curses inside an SSH session is the wrong + tool for that. Continuing to polish a local-only TUI delays + the right next investment. + +The triage half of the dashboard isn't suffering from any of these. +Pending proposals are a small, well-scoped, real workload, and the +PRD 0013–0016 surface for handling them is the right shape. The +problem is everything that got bolted onto that core after. + +## Goals / Success Criteria + +1. The supervise TUI starts up, lists pending proposals across all + running bottles, and supports approve / modify / reject + the + `--once` non-interactive mode — exactly as PRDs 0013–0016 + specified, minus everything 0019/0020/0021 added. +2. The CLI subcommand is renamed `supervise` (was `dashboard`). The + old name is not aliased — this PRD is intentionally a + compat/breaking change (the issue carries the + `Compat/Breaking` label). +3. `dashboard.py` shrinks to a single proposal-triage curses loop: + no agents pane, no Tab pane switching, no agent picker, no + start / re-attach / stop verbs, no tmux split-pane, no + `e`/`p` operator-edit verbs, no per-process `bottles` dict. +4. `dashboard_model.py` is collapsed into whatever + `supervise.py` (CLI) needs; the model module is removed if it + has no purpose after the cut. +5. The proposal-side apply paths in `bot_bottle/backend/docker/ + egress_apply.py`, `pipelock_apply.py`, and `capability_apply.py` + are unchanged — they are still called by the approve path. +6. The supervise-sidecar / proposal-queue protocol (PRD 0013) is + unchanged: the agent's experience is identical. +7. The previously-active PRDs that this one undoes are marked + `Superseded by PRD 0049`: + - PRD 0019 — active-agents pane + agent-scoped edit verbs + - PRD 0020 — start / re-attach / stop from the dashboard + - PRD 0021 — tmux split-pane + +## Non-goals + +- **A web GUI for managing agents.** The issue floats this as a + second pass; this PRD does not design or commit to it. The cut + is "remove the path we no longer want to invest in", not + "build the replacement". +- **A separate CLI for operator-initiated routes / pipelock + edits.** Today those edits live as `e` / `p` keys inside the + dashboard. After this PRD they don't exist anywhere — operators + who need ad-hoc edits use the same path the agents do (call the + supervise tool from inside the bottle) or hand-edit the host- + side files and restart the sidecar. Adding a `./cli.py routes + edit ` verb is a follow-up if the loss bites. +- **Removing `./cli.py start` or changing its semantics.** Start + remains the one-shot launch path. PRD 0020's bottle-outlives- + process model is removed; the only path to a long-running + bottle is `./cli.py start` (foreground) plus `cli.py cleanup` + for teardown. +- **Removing the supervise-sidecar protocol or any of the three + block-remediation engines.** PRDs 0013–0016 stay Active. The + agent's view of the world doesn't change. +- **Renaming `dashboard` anywhere other than the CLI entry + point.** The dashboard-related docs (PRDs, decision records, + research notes) keep their historical references — they + describe the state of the world at the time they were written, + and the Status: Superseded line is the marker that the world + has moved on. +- **Migrating the proposal-queue file layout.** The queue still + lives at `~/.bot-bottle/queue//`; the audit log still + lives at `~/.bot-bottle/audit/-.log`. The CLI + surface changes; the on-disk surface does not. + +## Scope + +### In scope + +- **Rename the subcommand.** `./cli.py dashboard` becomes + `./cli.py supervise`. The module moves from `bot_bottle/cli/ + dashboard.py` to `bot_bottle/cli/supervise.py`. The dispatcher + in `bot_bottle/cli/__init__.py` and the help text both update. +- **Strip the curses loop to proposal-only.** The remaining + surface is: list pending proposals (with the new-arrival bell + + green highlight from PRD 0013), Enter for detail view, + `a`/`m`/`r` for approve / modify / reject, `q` to quit. No + agents pane, no Tab, no agent picker, no `n`/`x`/`e`/`p`, no + tmux dispatch, no `bottles` dict on the main loop. +- **Drop unused helpers.** `_picker_modal`, `_preflight_modal`, + `_backend_picker_modal`, `_new_agent_flow`, `_attach_to_bottle`, + `_attach_in_tmux`, `_attach_via_handoff`, `_tmux_*`, + `_ensure_right_pane`, `_redirect_stderr_to_file`, + `_route_op_to_right_pane`, `_stop_bottle_flow`, + `_operator_edit_*_flow`, `operator_edit_routes`, + `operator_edit_allowlist`, and their imports come out. +- **Collapse the model module.** `dashboard_model.py`'s + proposal-side helpers (`QueuedProposal`, `discover_pending`, + `_approval_status`, `_is_recent`, `_detail_lines`, + `_failed_url_host`, `_proposed_payload_label`, + `_suffix_for_tool`, `_REFRESH_INTERVAL_MS`, + `_NEW_PROPOSAL_HIGHLIGHT_SEC`) move back into + `supervise.py` (CLI) or into `bot_bottle/supervise.py` + (the daemon-side module) — wherever they fit. The agents / + picker / tmux helpers in that module (`PANE_*`, + `_filter_agents`, `_running_counts`, `_format_agent_row`, + `_selection_status`, `_selected_agent`, `_bottle_for_slug`, + `_pick_next_after_stop`, `_agent_runtime_args`, + `_build_resume_argv_with_fallback`, `_build_split_pane_argv`, + `_build_respawn_pane_argv`, `_in_tmux`, + `discover_active_agents`) are deleted. +- **Mark superseded PRDs.** The Status line on PRDs 0019, 0020, + and 0021 changes to `Superseded by [PRD 0049](0049-strip- + dashboard-to-supervisor-tui.md)`. +- **Test cleanup.** Any test that targets a removed surface (the + agent picker, the tmux split helpers, the start-from-dashboard + flow, the operator-edit flows, `discover_active_agents`) + comes out. Tests covering proposal triage stay. +- **Help / usage strings.** `bot_bottle/cli/__init__.py`'s usage + block updates the command name and one-liner. + +### Out of scope + +- Any new feature in the supervise TUI. The cut is purely + subtractive (except for the rename). +- Behavior changes in `./cli.py start`, `cli.py cleanup`, + `cli.py resume`, `cli.py list`, `cli.py info`, `cli.py edit`, + `cli.py init` — unchanged. +- Changes to the supervise sidecar (`supervise_server.py`, + `supervise.py` daemon module). The wire protocol stays. +- Changes to the routes / pipelock / capability apply engines. +- Migration helpers, deprecation warnings, or a transitional + `dashboard` alias for `supervise`. The label on the issue says + Compat/Breaking; the rename is a hard cutover. + +## Proposed design + +### Final shape of the TUI + +After this PRD the `./cli.py supervise` curses surface is: + +``` +bot-bottle supervise (3 pending) +───────────────────────────────────────────────────────── +> 03:14:22 [implementer-cy7a6] egress-block abc123… add +github.com/foo + 03:13:55 [researcher-9xqs1] pipelock-block def456… allow +registry.npmjs.org + 03:13:10 [implementer-cy7a6] capability-block ghi789… install +ripgrep + +───────────────────────────────────────────────────────── +[j/k] move [Enter] view [a] approve [m] modify [r] reject [q] quit +``` + +- One pane. No Tab. `j` / `k` / arrows move through the queue. +- Enter opens the existing detail view (justification + + proposed-file body + the green pipelock host-extraction hint). + `a` / `m` / `r` work from both the list view and the detail + view, same as today. +- `q` / Esc quits. There are no dashboard-owned bottles, so no + per-process teardown decision — `q` just exits. +- The new-arrival bell + green highlight + (if in tmux) the + `tmux select-pane` jump back to the supervise pane stay, + because they're real wins for the operator's "I was typing at + claude and a proposal landed" case. They don't require any of + the pane-management code being removed. + +### Code organisation + +After the cut, the CLI module looks roughly like: + +``` +bot_bottle/cli/supervise.py + - cmd_supervise(argv) + - _list_once() # --once mode + - _main_loop(stdscr) # proposal-only + - _render(stdscr, pending, ...) + - _detail_view(stdscr, qp, ...) + - _modify(stdscr, qp) + - _prompt(stdscr, label) + - _write_crash_log(exc) + - approve(qp, *, notes, final_file) + - reject(qp, *, reason) + - QueuedProposal, discover_pending + - _detail_lines, _approval_status, + _is_recent, _failed_url_host, + _proposed_payload_label, + _suffix_for_tool +``` + +`dashboard_model.py` has no purpose once the agents / picker / +tmux helpers are gone, so it is removed and the surviving +proposal-side helpers move into `supervise.py` directly. The +PRD-0013 refactor that split model out (`refactor: extract +dashboard state/model layer into dashboard_model.py`) was +load-bearing for the bigger dashboard surface; with the surface +shrunk back, the split is no longer justified. + +### Removed PRDs: how to mark them + +The three superseded PRDs keep their bodies intact. Only the +Status line at the top changes: + +``` +- **Status:** Superseded by [PRD +0049](0049-strip-dashboard-to-supervisor-tui.md) +``` + +The PRD's own Goals / Success Criteria are left as the historical +record of what the feature shipped — readers tracing back from the +code or the git log land in a PRD that explains what once was, with +a clear pointer forward. No PRD body is rewritten. + +### Tests to keep, tests to remove + +Keep: +- `tests/cli/test_dashboard*.py` cases that exercise + `discover_pending`, `approve`, `reject`, `_detail_lines`, + `_is_recent`, `_approval_status`, `_failed_url_host`, + `_proposed_payload_label`, `_suffix_for_tool`, + `_modify` / `edit_in_editor`. +- `tests/cli/test_dashboard_once.py` (or equivalent) — the + `--once` listing mode. + +Remove: +- Any test of `_picker_modal`, `_preflight_modal`, + `_backend_picker_modal`, `_new_agent_flow`, `_attach_*`, + `_tmux_*`, `_route_op_to_right_pane`, + `_redirect_stderr_to_file`, `_stop_bottle_flow`, + `_operator_edit_*`, `_filter_agents`, `_running_counts`, + `_format_agent_row`, `_selection_status`, + `_selected_agent`, `_bottle_for_slug`, + `_pick_next_after_stop`, `_agent_runtime_args`, + `_build_*_argv`, `discover_active_agents`. +- The test files that exist solely to cover those (e.g., + `test_dashboard_picker.py`, `test_dashboard_tmux.py`, + `test_dashboard_attach.py`, `test_dashboard_agents.py` — + whichever of these exist after the file walk). + +Files are renamed `test_supervise_*.py` to mirror the module +rename. The rename is mechanical; no test logic changes. + +## Implementation chunks + +Sized for a single PR each. + +1. **Strip + rename in one cut.** Move `bot_bottle/cli/ + dashboard.py` to `bot_bottle/cli/supervise.py`, delete the + removed helpers, delete `dashboard_model.py`, inline the + surviving helpers, update the dispatcher + usage in + `bot_bottle/cli/__init__.py`, rename tests to match, mark + PRDs 0019/0020/0021 as superseded. One commit per logical + piece inside the PR (rename, strip, supersede notes, + tests). +2. **Activate PRD 0049.** Flip this PRD's Status line from + Draft to Active in the same PR as chunk 1 once the + implementation lands. (The repo convention is that a PRD's + shipping commit is also the Status flip — see the recent + `docs(prd): activate PRD 0048…` commit shape.) + +The PR closes issue #174. + +## Open questions + +1. **`e` / `p` operator-initiated edits — gone for good or + moved to a separate CLI verb?** The PRD removes them with no + replacement. The simplest replacement is `./cli.py routes + edit ` and `./cli.py pipelock edit `, sharing + the existing `apply_routes_change` / `apply_allowlist_change` + engines. If the loss is felt within the first parallel + run after this lands, that follow-up is a small PR. Leaving + it for a separate PRD so this one stays subtractive. + +2. **`--once` output shape.** The text listing today emits one + proposal per line. Worth keeping exactly as-is for + scripting consumers; this PRD does not change it. Flagging + only because the rename could tempt a tweak. + +3. **Audit-log entry shape for an unprompted edit applied via + a future `routes edit` CLI verb.** Today's + `operator_edit_routes` writes an `ACTION_OPERATOR_EDIT` + audit entry. With those flows removed the constant has no + callers inside this PRD's scope. Keep the constant exported + from `supervise.py` (it's already an `__all__` member) so a + follow-up CLI verb can re-use the same audit shape without + re-introducing dead code first. + +## References + +- Issue +[#174](https://gitea.dideric.is/didericis/bot-bottle/issues/174) + — the request: "strip the dashboard down into just a TUI for + managing agent requests for new egress routes and new + capabilities." +- PRD 0013 — supervise plane foundation (the floor this PRD + reverts the dashboard to). +- PRDs 0014 / 0015 / 0016 — block-remediation engines that the + supervise TUI continues to drive on approve. +- PRDs 0019 / 0020 / 0021 — the bolted-on capabilities this PRD + removes. \ No newline at end of file