feat(supervise): show the bottle's human slug, not its opaque id

Consolidated proposals are keyed by the orchestrator-assigned bottle_id,
so the supervise TUI was rendering a hex id (e.g. 3601cbe883c2786d) as
the bottle name — and the resume hint printed `./cli.py resume <id>`,
which resume can't take (it wants the human identity/slug).

`supervise_pending` now tags each dict with `bottle_label` — the human
slug resolved from registry metadata, falling back to the id when the
bottle is gone. `QueuedProposal` carries the label; every display site
(list rows, detail view, status lines, resume hint) shows it, while
approve/reject still key on `proposal.bottle_slug` (the id).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoEZHDjv84ChoZbozQERhJ
This commit is contained in:
2026-07-16 22:05:21 -04:00
parent 5f59df9e10
commit b1df380ae1
4 changed files with 75 additions and 10 deletions
+15
View File
@@ -226,6 +226,21 @@ class TestOrchestratorSupervise(unittest.TestCase):
self.assertEqual(STATUS_APPROVED, read_response("demo", pid).status)
self.assertEqual([], self.orch.supervise_pending())
def test_pending_carries_human_label(self) -> None:
# The proposal is keyed by bottle_id, but pending dicts also expose the
# bottle's human slug so the operator sees a name, not a hex id.
bottle_id = self._register("codex-dev-a1b2c", "routes: []\n")
self._queue(bottle_id, "routes:\n - host: google.com\n")
pending = self.orch.supervise_pending()
self.assertEqual(bottle_id, pending[0]["bottle_slug"])
self.assertEqual("codex-dev-a1b2c", pending[0]["bottle_label"])
def test_pending_label_falls_back_to_slug_when_bottle_gone(self) -> None:
# No registry record (torn down): label is the id, never empty.
self._queue("ghost-id", "routes:\n - host: google.com\n")
pending = self.orch.supervise_pending()
self.assertEqual("ghost-id", pending[0]["bottle_label"])
def test_approve_by_bottle_id_applies_policy(self) -> None:
# Consolidated reality: the supervise server keys each proposal by the
# orchestrator-assigned bottle_id, not the human slug. Approval must
+17
View File
@@ -78,6 +78,23 @@ class TestDiscoverPending(_ClientMixin, unittest.TestCase):
pending = supervise_cli.discover_pending()
self.assertEqual([early.id, late.id], [qp.proposal.id for qp in pending])
def test_label_comes_from_bottle_label(self) -> None:
# The server tags each dict with the human slug; the CLI displays it
# while the proposal stays keyed by the opaque bottle_id.
client = MagicMock()
d = _proposal("3601cbe883c2786d").to_dict()
d["bottle_label"] = "codex-dev-a1b2c"
client.supervise_pending.return_value = [d]
with patch.object(supervise_cli, "_client", return_value=client):
pending = supervise_cli.discover_pending()
self.assertEqual("codex-dev-a1b2c", pending[0].label)
self.assertEqual("3601cbe883c2786d", pending[0].proposal.bottle_slug)
def test_label_falls_back_to_slug_when_absent(self) -> None:
# Legacy dicts without bottle_label (e.g. an older orchestrator).
self._install_client([_proposal("dev")])
self.assertEqual("dev", supervise_cli.discover_pending()[0].label)
class TestApproveReject(_ClientMixin, unittest.TestCase):
def _qp(self, tool: str = TOOL_EGRESS_ALLOW) -> "supervise_cli.QueuedProposal":