fix(cli): remove supervise queue highlight
This commit is contained in:
@@ -17,7 +17,6 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
|
||||||
import traceback
|
import traceback
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
@@ -59,7 +58,6 @@ from ._common import PROG
|
|||||||
|
|
||||||
|
|
||||||
_REFRESH_INTERVAL_MS = 1000
|
_REFRESH_INTERVAL_MS = 1000
|
||||||
_NEW_PROPOSAL_HIGHLIGHT_SEC = 5.0
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -99,20 +97,6 @@ def _approval_status(qp: QueuedProposal, verb: str) -> str:
|
|||||||
return base
|
return base
|
||||||
|
|
||||||
|
|
||||||
def _is_recent(
|
|
||||||
proposal_id: str,
|
|
||||||
first_seen: dict[str, float] | None,
|
|
||||||
now: float | None,
|
|
||||||
) -> bool:
|
|
||||||
"""True if `proposal_id` was first seen within the highlight window."""
|
|
||||||
if first_seen is None or now is None:
|
|
||||||
return False
|
|
||||||
started = first_seen.get(proposal_id)
|
|
||||||
if started is None:
|
|
||||||
return False
|
|
||||||
return (now - started) < _NEW_PROPOSAL_HIGHLIGHT_SEC
|
|
||||||
|
|
||||||
|
|
||||||
def _detail_lines(
|
def _detail_lines(
|
||||||
qp: QueuedProposal,
|
qp: QueuedProposal,
|
||||||
*,
|
*,
|
||||||
@@ -388,21 +372,19 @@ def _main_loop(stdscr: "curses._CursesWindow") -> None:
|
|||||||
curses.curs_set(0)
|
curses.curs_set(0)
|
||||||
stdscr.timeout(_REFRESH_INTERVAL_MS)
|
stdscr.timeout(_REFRESH_INTERVAL_MS)
|
||||||
green_attr = _try_init_green()
|
green_attr = _try_init_green()
|
||||||
first_seen: dict[str, float] = {}
|
|
||||||
selected = 0
|
selected = 0
|
||||||
status_line = ""
|
status_line = ""
|
||||||
saw_first_tick = False
|
|
||||||
supervise_pane_id = os.environ.get("TMUX_PANE", "")
|
supervise_pane_id = os.environ.get("TMUX_PANE", "")
|
||||||
|
seen_ids: set[str] = set()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
pending = discover_pending()
|
pending = discover_pending()
|
||||||
if selected >= len(pending):
|
if selected >= len(pending):
|
||||||
selected = max(0, len(pending) - 1)
|
selected = max(0, len(pending) - 1)
|
||||||
|
|
||||||
now = time.monotonic()
|
|
||||||
live_ids = {qp.proposal.id for qp in pending}
|
live_ids = {qp.proposal.id for qp in pending}
|
||||||
newly_arrived = live_ids - first_seen.keys()
|
newly_arrived = live_ids - seen_ids
|
||||||
if saw_first_tick and newly_arrived:
|
if seen_ids and newly_arrived:
|
||||||
try:
|
try:
|
||||||
curses.beep()
|
curses.beep()
|
||||||
except curses.error:
|
except curses.error:
|
||||||
@@ -413,15 +395,11 @@ def _main_loop(stdscr: "curses._CursesWindow") -> None:
|
|||||||
if qp.proposal.id in newly_arrived:
|
if qp.proposal.id in newly_arrived:
|
||||||
selected = i
|
selected = i
|
||||||
break
|
break
|
||||||
for proposal_id in live_ids:
|
seen_ids = live_ids
|
||||||
first_seen.setdefault(proposal_id, now)
|
|
||||||
for stale_id in list(first_seen.keys() - live_ids):
|
|
||||||
del first_seen[stale_id]
|
|
||||||
saw_first_tick = True
|
|
||||||
|
|
||||||
_render(
|
_render(
|
||||||
stdscr, pending, selected, status_line,
|
stdscr, pending, selected, status_line,
|
||||||
first_seen=first_seen, now=now, green_attr=green_attr,
|
green_attr=green_attr,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -478,8 +456,6 @@ def _render(
|
|||||||
selected: int,
|
selected: int,
|
||||||
status_line: str,
|
status_line: str,
|
||||||
*,
|
*,
|
||||||
first_seen: dict[str, float] | None = None,
|
|
||||||
now: float | None = None,
|
|
||||||
green_attr: int = 0,
|
green_attr: int = 0,
|
||||||
) -> None:
|
) -> None:
|
||||||
stdscr.erase()
|
stdscr.erase()
|
||||||
@@ -512,8 +488,6 @@ def _render(
|
|||||||
f"{_proposed_payload_label(p.tool)}"
|
f"{_proposed_payload_label(p.tool)}"
|
||||||
)
|
)
|
||||||
attr = curses.A_REVERSE if i == selected else curses.A_NORMAL
|
attr = curses.A_REVERSE if i == selected else curses.A_NORMAL
|
||||||
if _is_recent(p.id, first_seen, now):
|
|
||||||
attr |= green_attr
|
|
||||||
stdscr.addnstr(row, 0, line, w - 1, attr)
|
stdscr.addnstr(row, 0, line, w - 1, attr)
|
||||||
row += 1
|
row += 1
|
||||||
if row >= h - 3:
|
if row >= h - 3:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
- **Status:** Active
|
- **Status:** Draft
|
||||||
- **Author:** didericis
|
- **Author:** didericis
|
||||||
- **Created:** 2026-06-03
|
- **Created:** 2026-06-03
|
||||||
- **Issue:** #174
|
- **Issue:** #174
|
||||||
@@ -127,8 +127,8 @@ problem is everything that got bolted onto that core after.
|
|||||||
dashboard.py` to `bot_bottle/cli/supervise.py`. The dispatcher
|
dashboard.py` to `bot_bottle/cli/supervise.py`. The dispatcher
|
||||||
in `bot_bottle/cli/__init__.py` and the help text both update.
|
in `bot_bottle/cli/__init__.py` and the help text both update.
|
||||||
- **Strip the curses loop to proposal-only.** The remaining
|
- **Strip the curses loop to proposal-only.** The remaining
|
||||||
surface is: list pending proposals (with the new-arrival bell +
|
surface is: list pending proposals (with the new-arrival bell
|
||||||
green highlight from PRD 0013), Enter for detail view,
|
from PRD 0013), Enter for detail view,
|
||||||
`a`/`m`/`r` for approve / modify / reject, `q` to quit. No
|
`a`/`m`/`r` for approve / modify / reject, `q` to quit. No
|
||||||
agents pane, no Tab, no agent picker, no `n`/`x`/`e`/`p`, no
|
agents pane, no Tab, no agent picker, no `n`/`x`/`e`/`p`, no
|
||||||
tmux dispatch, no `bottles` dict on the main loop.
|
tmux dispatch, no `bottles` dict on the main loop.
|
||||||
@@ -141,10 +141,9 @@ problem is everything that got bolted onto that core after.
|
|||||||
`operator_edit_allowlist`, and their imports come out.
|
`operator_edit_allowlist`, and their imports come out.
|
||||||
- **Collapse the model module.** `dashboard_model.py`'s
|
- **Collapse the model module.** `dashboard_model.py`'s
|
||||||
proposal-side helpers (`QueuedProposal`, `discover_pending`,
|
proposal-side helpers (`QueuedProposal`, `discover_pending`,
|
||||||
`_approval_status`, `_is_recent`, `_detail_lines`,
|
`_approval_status`, `_detail_lines`,
|
||||||
`_failed_url_host`, `_proposed_payload_label`,
|
`_failed_url_host`, `_proposed_payload_label`,
|
||||||
`_suffix_for_tool`, `_REFRESH_INTERVAL_MS`,
|
`_suffix_for_tool`, `_REFRESH_INTERVAL_MS`) move back into
|
||||||
`_NEW_PROPOSAL_HIGHLIGHT_SEC`) move back into
|
|
||||||
`supervise.py` (CLI) or into `bot_bottle/supervise.py`
|
`supervise.py` (CLI) or into `bot_bottle/supervise.py`
|
||||||
(the daemon-side module) — wherever they fit. The agents /
|
(the daemon-side module) — wherever they fit. The agents /
|
||||||
picker / tmux helpers in that module (`PANE_*`,
|
picker / tmux helpers in that module (`PANE_*`,
|
||||||
@@ -205,7 +204,7 @@ ripgrep
|
|||||||
view, same as today.
|
view, same as today.
|
||||||
- `q` / Esc quits. There are no dashboard-owned bottles, so no
|
- `q` / Esc quits. There are no dashboard-owned bottles, so no
|
||||||
per-process teardown decision — `q` just exits.
|
per-process teardown decision — `q` just exits.
|
||||||
- The new-arrival bell + green highlight + (if in tmux) the
|
- The new-arrival bell + (if in tmux) the
|
||||||
`tmux select-pane` jump back to the supervise pane stay,
|
`tmux select-pane` jump back to the supervise pane stay,
|
||||||
because they're real wins for the operator's "I was typing at
|
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
|
claude and a proposal landed" case. They don't require any of
|
||||||
@@ -229,7 +228,7 @@ bot_bottle/cli/supervise.py
|
|||||||
- reject(qp, *, reason)
|
- reject(qp, *, reason)
|
||||||
- QueuedProposal, discover_pending
|
- QueuedProposal, discover_pending
|
||||||
- _detail_lines, _approval_status,
|
- _detail_lines, _approval_status,
|
||||||
_is_recent, _failed_url_host,
|
_failed_url_host,
|
||||||
_proposed_payload_label,
|
_proposed_payload_label,
|
||||||
_suffix_for_tool
|
_suffix_for_tool
|
||||||
```
|
```
|
||||||
@@ -262,7 +261,7 @@ a clear pointer forward. No PRD body is rewritten.
|
|||||||
Keep:
|
Keep:
|
||||||
- `tests/cli/test_dashboard*.py` cases that exercise
|
- `tests/cli/test_dashboard*.py` cases that exercise
|
||||||
`discover_pending`, `approve`, `reject`, `_detail_lines`,
|
`discover_pending`, `approve`, `reject`, `_detail_lines`,
|
||||||
`_is_recent`, `_approval_status`, `_failed_url_host`,
|
`_approval_status`, `_failed_url_host`,
|
||||||
`_proposed_payload_label`, `_suffix_for_tool`,
|
`_proposed_payload_label`, `_suffix_for_tool`,
|
||||||
`_modify` / `edit_in_editor`.
|
`_modify` / `edit_in_editor`.
|
||||||
- `tests/cli/test_dashboard_once.py` (or equivalent) — the
|
- `tests/cli/test_dashboard_once.py` (or equivalent) — the
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
"""Unit: supervise's new-proposal highlight window.
|
|
||||||
|
|
||||||
The curses rendering itself is exercised manually; this isolates
|
|
||||||
the pure decision `is the proposal still in its post-arrival
|
|
||||||
highlight window?`"""
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from bot_bottle.cli import supervise as supervise_cli
|
|
||||||
|
|
||||||
|
|
||||||
class TestIsRecent(unittest.TestCase):
|
|
||||||
def test_just_seen_is_recent(self):
|
|
||||||
self.assertTrue(supervise_cli._is_recent("p1", {"p1": 100.0}, now=100.5))
|
|
||||||
|
|
||||||
def test_seen_within_window(self):
|
|
||||||
# Default window is 5s.
|
|
||||||
self.assertTrue(
|
|
||||||
supervise_cli._is_recent("p1", {"p1": 100.0}, now=104.9),
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_seen_past_window_is_not_recent(self):
|
|
||||||
self.assertFalse(
|
|
||||||
supervise_cli._is_recent("p1", {"p1": 100.0}, now=106.0),
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_unknown_proposal_is_not_recent(self):
|
|
||||||
self.assertFalse(
|
|
||||||
supervise_cli._is_recent("p2", {"p1": 100.0}, now=100.5),
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_none_args_safe_default(self):
|
|
||||||
self.assertFalse(supervise_cli._is_recent("p1", None, None))
|
|
||||||
self.assertFalse(supervise_cli._is_recent("p1", {"p1": 100.0}, None))
|
|
||||||
self.assertFalse(supervise_cli._is_recent("p1", None, 100.5))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
Reference in New Issue
Block a user