Answer the agents' questions on the page

Menus and permission prompts are read off a seat's screen and shown as
labelled buttons (plus Esc); an agent that stopped with a question gets a
reply box; queue items blocked on a human seat are decisions recorded with
rig queue resolve. Notifications now carry the question itself.

Claude-Session: https://claude.ai/code/session_01QhLkBGEip8wQdaZ54z4AqJ
This commit is contained in:
Asger Geel Weirsøe
2026-10-20 18:09:24 +02:00
parent 69cb255120
commit 150abd9afe
4 changed files with 252 additions and 42 deletions
+85 -3
View File
@@ -50,9 +50,91 @@ class WatcherTest(unittest.TestCase):
w.events(snap(waiting=True))
self.assertEqual(len(w.events(snap(waiting=True))), 1)
def test_prompt_pattern(self):
self.assertTrue(server.PROMPT.search(" Do you want to proceed?\n ❯ 1. Yes"))
self.assertFalse(server.PROMPT.search("❯ yes, launch it"))
def test_gate_notifies_once(self):
w = server.Watcher()
w.events(snap())
gate = {**q("7", "blocked", "operator-human@kernel"), "gate": True}
self.assertEqual(w.events(snap(queue=[gate]))[-1][0], "Beslutning til dig")
self.assertEqual(w.events(snap(queue=[gate])), [])
PERMISSION = """[rig capture: operator-agent@kernel: 23 earlier lines not shown]
● Bash(rig queue show qitem-1 --json)
⎿ Waiting…
────────────────────────────────────────────────────────────────────────────────
Bash command
Tip: auto mode handles these prompts for you — choose "switch to auto mode"
below
Check whether the Chromium libraries are installed
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
│ dpkg-query -W -f='${Package} ${Status}\\n' libatk1.0-0t64
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
This command requires approval
Do you want to proceed?
❯ 1. Yes
2. Yes, and don’t ask again for: dpkg-query *
3. Yes, and switch to auto mode · auto mode handles these prompts for you
4. No
Esc to cancel · Tab to amend
"""
QUESTION = """ - Install location: ~/rigs/workshop.
● Once it's running: I'll give the lead the whole task as a durable work item
(spec file, branch, local commits only, no push). When it's done, I'll report
back the commits and test results.
● Should I launch workshop like this (all Claude, prompts off, in
/home/rig/work/wpp)?
✻ Churned for 1m 24s · done 4:26 PM
──────────────────────────────────────────────────────── operator-agent@kernel ─
❯ yes, launch workshop all-claude
────────────────────────────────────────────────────────────────────────────────
⏵⏵ accept edits on (shift+tab to cycle)
"""
WORKING = """● Bash(cargo test --locked)
⎿ Running…
✻ Crunching… (1m 7s · ↓ 3.3k tokens · thinking)
──────────────────────────────────────────────────────── dev-build@team ─
❯
────────────────────────────────────────────────────────────────────────────────
"""
class ParseAskTest(unittest.TestCase):
def test_permission_prompt(self):
a = server.parse_ask(PERMISSION, idle=False)
self.assertEqual(a["kind"], "menu")
self.assertEqual(a["title"], "Do you want to proceed?")
self.assertEqual([o["key"] for o in a["options"]], ["1", "2", "3", "4"])
self.assertEqual(a["options"][1]["label"], "Yes, and don’t ask again for: dpkg-query *")
def test_question_when_idle(self):
a = server.parse_ask(QUESTION, idle=True)
self.assertEqual(a["kind"], "question")
self.assertTrue(a["text"].startswith("Should I launch workshop"))
self.assertTrue(a["text"].endswith("?"))
def test_question_needs_idle(self):
self.assertIsNone(server.parse_ask(QUESTION, idle=False))
def test_working_is_not_a_question(self):
self.assertIsNone(server.parse_ask(WORKING, idle=True))
self.assertIsNone(server.parse_ask(WORKING, idle=False))
def test_numbered_list_without_cursor_is_not_a_menu(self):
self.assertIsNone(server.parse_ask("● Plan:\n 1. engine\n 2. rooms\n", idle=False))
def test_ask_text_for_notifications(self):
self.assertIn("2. Yes, and", server.ask_text(server.parse_ask(PERMISSION, idle=False)))
def test_seat_names(self):
self.assertTrue(server.SEAT.match("dev-qa@ubl-workshop"))