2026-11-06 22:20:06 +00:00
from __future__ import annotations
2026-11-07 19:59:47 +00:00
import array
2026-11-07 15:03:14 +00:00
import base64
2026-11-07 19:59:47 +00:00
import contextlib
import http.server
2026-11-06 22:20:06 +00:00
import json
2026-11-07 19:59:47 +00:00
import math
2026-11-06 22:20:06 +00:00
import os
import shutil
2026-11-08 13:37:02 +00:00
import ssl
2026-11-06 22:20:06 +00:00
import subprocess
import sys
import tempfile
2026-11-07 15:03:14 +00:00
import threading
2026-11-06 22:20:06 +00:00
import time
import unittest
2026-11-07 15:03:14 +00:00
from collections.abc import Callable
2026-11-06 22:20:06 +00:00
from pathlib import Path
2026-11-07 19:59:47 +00:00
from unittest import mock
2026-11-08 13:37:02 +00:00
from urllib.parse import urlsplit
2026-11-06 22:20:06 +00:00
HERE = Path ( __file__ ) . resolve () . parent
sys . path . insert ( 0 , str ( HERE . parent ))
from hub_case import HubCase
2026-11-07 19:59:47 +00:00
from hub_engines import wav_bytes
2026-11-09 06:53:55 +00:00
from hub_support import ASK , HOST_STATE , OPTIONS , RUNNER , iso , verifier
2026-11-07 19:59:47 +00:00
from kvb_orchestrator import skill_scripts
2026-11-07 15:03:14 +00:00
from test_hub_cleanup import DONE_TIP , FAKE_PKGMGR , host
2026-11-07 17:46:39 +00:00
from test_hub_offices import COUNCIL_SCRIPT
from test_hub_offices import host as office_host
2026-11-06 22:20:06 +00:00
2026-11-07 15:03:14 +00:00
OUT = Path ( os . environ . get ( "E2E_OUT" , "/tmp/orchestrator-phase4-shots" ))
2026-11-06 22:20:06 +00:00
WIDTHS = (( 1920 , 1080 ), ( 1366 , 768 ), ( 390 , 844 ))
2026-11-07 15:03:14 +00:00
VIEWS = ( "orbit" , "chord" , "swimlane" , "tree" , "3d" )
2026-11-09 06:53:55 +00:00
TASK_VIEWS = ( "list" , "board" , "rasci" , "gantt" , "burn" )
2026-11-07 23:43:26 +00:00
SCREENS = [ "stream" , "stream/inbox" , "stream/rules" , "stream/radio" , * ( f " { tab } / { view } " for tab in ( "agents" , "artifacts" ) for view in VIEWS ), "agents/hierarchy" , * ( f "tasks/ { view } " for view in TASK_VIEWS )]
2026-11-08 17:26:51 +00:00
SUBS = [ "Inbox" , "Firewall" , "Radio" ]
2026-11-07 23:43:26 +00:00
TABS = [ "Stream" , "Agents" , "Artifacts" , "Tasks" ]
2026-11-07 15:03:14 +00:00
HELPERS = """
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const until = async (check, ms) => {
const end = Date.now() + ms;
while (Date.now() < end) {
const found = check();
if (found) {
return found;
}
await sleep(200);
}
return null;
};
const all = (selector, root) => [...(root || document).querySelectorAll(selector)];
2026-11-06 22:20:06 +00:00
const text = (node) => (node ? node.textContent.trim() : null);
2026-11-07 15:03:14 +00:00
const name = (node) => (node ? node.getAttribute("aria-label") || text(node) : null);
"""
PROBE = """(async () => {@HELPERS@
2026-11-08 17:26:51 +00:00
await until(() => !document.querySelector("#main .space-note") || !text(document.querySelector("#main .space-note")).startsWith("Loading"), 8000);
2026-11-06 22:20:06 +00:00
const badge = (button) => {
const found = button.querySelector(".badge");
return found && !found.hidden ? text(found) : null;
};
2026-11-07 15:03:14 +00:00
const icons = all("svg.bi").filter((svg) => !svg.closest(".sprite"));
2026-11-06 22:20:06 +00:00
const banner = document.getElementById("mayday");
return {
hash: location.hash,
title: document.title,
h1: text(document.querySelector("h1")),
header: all("header.top button").map((button) => button.id),
2026-11-07 15:03:14 +00:00
tabs: all("#tabs > .tab-group > button").map(name),
subs: all("#tabs .subtabs button").map(name),
2026-11-06 22:20:06 +00:00
badges: Object.fromEntries(all("#tabs button").map((button) => [button.id, badge(button)])),
banner: banner.hidden ? null : text(banner),
rail: Boolean(document.getElementById("rail")),
2026-11-08 17:26:51 +00:00
views: all('#main [aria-label="View"] button').map(name),
layouts: all('#main [aria-label="Layout"] button').map(name),
chips: all('#main [aria-label="Categories"] button').map(name),
2026-11-07 15:03:14 +00:00
menus: all("#main details.menu > summary").map(name),
2026-11-06 22:20:06 +00:00
time: text(document.querySelector("#time > summary")),
replay: Boolean(document.querySelector("#time .replay input[type=range]")),
custom: Boolean(document.querySelector('#main details.menu input[type="datetime-local"]')),
2026-11-07 15:03:14 +00:00
toolbar: all('#main [role="toolbar"] > *').map((node) => name(node.tagName === "DETAILS" ? node.querySelector("summary") : node.querySelector("button") || node)),
2026-11-06 22:20:06 +00:00
toAll: all("#to-all input[type=radio]").map((input) => [input.value, input.checked]),
inbox: all("#main .inbox-list > li").length,
2026-11-07 15:03:14 +00:00
icons: icons.length,
unlabelled: icons.map((svg) => svg.parentElement).filter((owner) => !text(owner) && !(owner.getAttribute("title") && owner.getAttribute("aria-label"))).map((owner) => owner.outerHTML.slice(0, 160)),
broken: all("svg.bi use").filter((use) => !document.getElementById(use.getAttribute("href").slice(1))).map((use) => use.getAttribute("href")),
states: all("#main span.state").map(name),
2026-11-08 17:26:51 +00:00
deletes: all('#main button[aria-label="Delete"], #main button[aria-label="Delete all merged"]').map((button) => [name(button), button.disabled, button.title]),
2026-11-07 15:03:14 +00:00
spawn: all("#main .spawn-tree li").map((item) => [item.dataset.kind, item.dataset.running || null]),
marks: all("#main svg.chart .node").length,
note: text(document.querySelector("#main .space-note")),
canvas: Boolean(document.querySelector("#main .graph3d canvas")),
2026-11-07 17:46:39 +00:00
org: all("#main .org-chart .org-row[data-office]").map((row) => [row.dataset.office, text(row.querySelector(".org-holder"))]),
toggles: all("#main .org-toggle").map((button) => [name(button), button.getAttribute("aria-expanded")]),
order: all("#main .agent-order li").map((item) => item.dataset.name),
2026-11-08 17:26:51 +00:00
taskFilters: all('#main [aria-label="Task filters"] button').map((button) => [button.dataset.filter, button.getAttribute("aria-pressed"), text(button.querySelector(".count"))]),
2026-11-07 23:43:26 +00:00
taskRows: all("#main .task-row").map((row) => [row.dataset.task, row.classList.contains("open-work")]),
columns: all("#main .task-column").map((column) => [column.dataset.column, all(".task-card", column).map((card) => card.dataset.task)]),
matrix: [all("#main table.matrix thead th").length, all("#main table.matrix tbody tr").length, all("#main table.matrix td .letter").map(text).join("")],
flows: all("#main .flow").map((flow) => flow.dataset.flow),
flowEdges: all("#main svg.chart path.flow").map((path) => path.dataset.side),
openWork: all("#main .open-work").length,
2026-11-08 21:18:55 +00:00
panel: all("#drawer, .drawer, #main aside, #main .panel, #main .graph-body").length,
nodeChips: all("#main .space-list, #main .chip[data-node]").length,
2026-11-06 22:20:06 +00:00
wide: document.documentElement.scrollWidth > window.innerWidth + 1,
2026-11-08 13:37:02 +00:00
tall: document.documentElement.scrollHeight > window.innerHeight,
2026-11-06 22:20:06 +00:00
};
2026-11-07 15:03:14 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-07 17:46:39 +00:00
ORG_OPEN = """(async () => {@HELPERS@
const row = await until(() => document.querySelector('#main .org-row[data-office="identity/oidc"]'), 8000);
if (!row) {
return {error: text(document.getElementById("main"))};
}
2026-11-08 17:26:51 +00:00
const toggle = all("#main .org-toggle").find((button) => name(button) === "Collapse: identity");
2026-11-07 17:46:39 +00:00
toggle.focus();
return {focused: document.activeElement.dataset.focus, offices: all("#main .org-row[data-office]").map((entry) => entry.dataset.office)};
})()""" . replace ( "@HELPERS@" , HELPERS )
ORG_FOLDED = """(async () => {@HELPERS@
const toggle = document.activeElement;
const folded = {focused: toggle.dataset.focus, expanded: toggle.getAttribute("aria-expanded"), label: name(toggle), hidden: document.getElementById(toggle.getAttribute("aria-controls")).hidden, seen: all("#main .org-row[data-office]").filter((entry) => entry.offsetParent !== null).map((entry) => entry.dataset.office)};
toggle.click();
await sleep(200);
const before = all("#main .agent-order li").map((item) => item.dataset.name);
all("#main .agent-order li")[1].querySelector('[data-focus^="up:"]').focus();
return {...folded, again: toggle.getAttribute("aria-expanded"), before};
})()""" . replace ( "@HELPERS@" , HELPERS )
ORDER_MOVED = """(async () => {@HELPERS@
const after = all("#main .agent-order li").map((item) => item.dataset.name);
const focused = document.activeElement.dataset.focus || null;
2026-11-08 17:26:51 +00:00
const save = all("#main .agent-order button").find((button) => text(button) === "Save order");
2026-11-07 17:46:39 +00:00
const disabled = save.disabled;
save.click();
2026-11-08 17:26:51 +00:00
const toast = await until(() => all("#toasts .toast").find((box) => text(box).startsWith("Priority")), 8000);
2026-11-07 17:46:39 +00:00
return {after, focused, disabled, toast: text(toast)};
})()""" . replace ( "@HELPERS@" , HELPERS )
OPEN_TAKEOVER = """(async () => {@HELPERS@
document.querySelector('#main [data-focus="takeover:identity/oidc"]').click();
await sleep(400);
2026-11-08 17:26:51 +00:00
return {title: text(document.querySelector(".sheet-head .panel-title")), options: all(".sheet-box select option").map((option) => option.value), work: document.querySelector('.sheet-box input[aria-label="Work"]').value, focused: document.activeElement.tagName};
2026-11-07 17:46:39 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS )
CONFIRM_TAKEOVER = """(async () => {@HELPERS@
2026-11-08 17:26:51 +00:00
all(".sheet-box button").find((button) => name(button) === "Hand over").click();
2026-11-07 17:46:39 +00:00
const done = await until(() => document.querySelector(".sheet-box [data-result]"), 30000);
return {result: done ? done.dataset.result : null, text: text(done), toasts: all("#toasts .toast").map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS )
AFTER_TAKEOVER = """(async () => {@HELPERS@
document.querySelector(".sheet-head button").click();
const row = await until(() => {
const found = document.querySelector('#main .org-row[data-office="identity/oidc"] .org-holder');
return found && !text(found).endsWith("Stage") ? found : null;
}, 15000);
return {holder: text(row)};
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-07 23:43:26 +00:00
INBOX_CHIP = """(async () => {@HELPERS@
2026-11-08 17:26:51 +00:00
const chip = (id) => document.querySelector(`#main [aria-label="Task filters"] button[data-filter="$ {id} "]`);
2026-11-07 23:43:26 +00:00
chip("operator").click();
await until(() => location.hash === "#stream/inbox" && document.querySelector("#main .inbox-list"), 4000);
const inbox = {hash: location.hash, items: all("#main .inbox-list > li").length, counted: Number(text(chip("operator").querySelector(".count"))), pressed: chip("operator").getAttribute("aria-pressed"), current: all("#tabs button[aria-current=page]").map(name)};
2026-11-08 17:26:51 +00:00
chip("grant").click();
2026-11-07 23:43:26 +00:00
await until(() => location.hash.startsWith("#tasks/"), 4000);
await sleep(300);
return {inbox, back: {hash: location.hash, rows: all("#main .task-row").map((row) => [row.dataset.task, text(row.querySelector(".pill"))])}};
})()""" . replace ( "@HELPERS@" , HELPERS )
PIN = """(async () => {@HELPERS@
const row = await until(() => all("#main .task-row").find((item) => item.dataset.task === "work/docs"), 8000);
if (!row) {
return {error: text(document.getElementById("main"))};
}
2026-11-08 17:26:51 +00:00
row.querySelector('button[aria-label="Priority"]').click();
2026-11-07 23:43:26 +00:00
await sleep(400);
2026-11-08 17:26:51 +00:00
const field = document.querySelector('.sheet-box input[aria-label="Priority"]');
2026-11-07 23:43:26 +00:00
field.value = "2";
field.dispatchEvent(new Event("input", {bubbles: true}));
const preview = all(".sheet-box .task-edit .command code").map(text);
2026-11-08 17:26:51 +00:00
all(".sheet-box button").find((button) => name(button) === "Pin").click();
2026-11-07 23:43:26 +00:00
const done = await until(() => document.querySelector(".sheet-box [data-jobs]"), 20000);
return {title: text(document.querySelector(".sheet-head .panel-title")), preview, jobs: done ? done.dataset.jobs : null, toasts: all("#toasts .toast").map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-07 15:03:14 +00:00
ACTIONS = """(async () => {@HELPERS@
2026-11-06 22:20:06 +00:00
const press = async (key) => {
document.dispatchEvent(new KeyboardEvent("keydown", {key, bubbles: true}));
await sleep(300);
return location.hash;
};
2026-11-07 23:43:26 +00:00
const found = {keys: [await press("2"), await press("3"), await press("4"), await press("1")]};
2026-11-06 22:20:06 +00:00
document.getElementById("settings-open").click();
await sleep(300);
2026-11-07 15:03:14 +00:00
found.ambience = all(".sheet-box form > section:first-child button").map((button) => button.id);
2026-11-08 17:26:51 +00:00
found.speech = all('.sheet-box [aria-label="Classes"] input').map((input) => [input.parentElement.textContent.trim(), input.checked, input.disabled]);
2026-11-07 15:03:14 +00:00
found.settingsUnlabelled = all(".sheet-box svg.bi").map((svg) => svg.parentElement).filter((owner) => !text(owner) && !(owner.getAttribute("title") && owner.getAttribute("aria-label"))).length;
2026-11-06 22:20:06 +00:00
document.querySelector(".sheet-head button").click();
document.getElementById("palette-open").click();
await sleep(300);
2026-11-07 15:03:14 +00:00
found.palette = all(".palette-list li > span:first-child").map(text);
2026-11-06 22:20:06 +00:00
document.dispatchEvent(new KeyboardEvent("keydown", {key: "Escape", bubbles: true}));
await sleep(200);
2026-11-08 17:26:51 +00:00
document.querySelector('#main [aria-label="Categories"] button:nth-child(2)').click();
2026-11-06 22:20:06 +00:00
await sleep(900);
2026-11-08 17:26:51 +00:00
document.querySelector('#main button.linkish[title^="Open "]').click();
2026-11-07 15:03:14 +00:00
await sleep(500);
2026-11-08 21:18:55 +00:00
found.popup = all("#popup input[type=radio]").map((input) => [input.value, input.checked]);
found.popupUnlabelled = all("#popup svg.bi").map((svg) => svg.parentElement).filter((owner) => !text(owner) && !(owner.getAttribute("title") && owner.getAttribute("aria-label"))).length;
found.centred = [document.getElementById("popup").open, document.querySelector("#popup .leader line").getAttribute("display")];
2026-11-07 15:03:14 +00:00
return found;
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-08 21:18:55 +00:00
POPUPS = """
const dialog = document.getElementById("popup");
const box = dialog.querySelector(".popup-box");
const line = dialog.querySelector(".leader line");
const click = (target) => target.dispatchEvent(new MouseEvent("click", {bubbles: true, cancelable: true}));
const tint = document.createElement("i");
tint.style.color = "var(--alarm)";
document.body.append(tint);
const red = getComputedStyle(tint).color;
tint.remove();
const tall = () => document.documentElement.scrollHeight > innerHeight;
const look = (node) => ({picked: node.classList.contains("picked"), size: Math.round(node.querySelector("circle").getBoundingClientRect().width), red: getComputedStyle(node.querySelector("circle")).fill === red});
const opened = () => {
const rect = box.getBoundingClientRect();
const ends = line.getAttribute("display") === "none" ? null : ["x1", "y1", "x2", "y2"].map((key) => Number(line.getAttribute(key)));
return {
open: dialog.open, fade: [dialog.classList.contains("fade"), getComputedStyle(dialog).animationName], title: text(box.querySelector(".panel-title")), opacity: Number(getComputedStyle(box).opacity), line: ends,
onBox: Boolean(ends) && (Math.abs(ends[2] - rect.left) < 1 || Math.abs(ends[2] - rect.right) < 1 || Math.abs(ends[3] - rect.top) < 1 || Math.abs(ends[3] - rect.bottom) < 1),
inside: rect.left >= 0 && rect.top >= 0 && rect.right <= innerWidth && rect.bottom <= innerHeight,
tall: tall(), wide: document.documentElement.scrollWidth > innerWidth + 1, panel: all("#drawer, .drawer, #main aside, #main .panel, #main .graph-body").length,
};
};
const away = async (pick) => {
const outside = document.elementFromPoint(2, innerHeight - 2);
click(outside);
await sleep(250);
return {...look(pick()), open: dialog.open, outside: outside === dialog};
};
"""
POPUP_ORBIT = """(async () => {@HELPERS@@POPUPS@
const pick = () => document.querySelector('#main svg .node[data-pick="s2"]');
if (!(await until(pick, 8000))) {
return {error: text(document.getElementById("main"))};
}
const before = look(pick());
const flat = !tall();
click(pick());
await sleep(400);
2026-11-08 22:06:31 +00:00
const art = box.querySelector(".popup-head .avatar.big");
const face = art ? {key: art.dataset.face, svg: Boolean(art.querySelector("svg.face-art .mouth")), text: art.textContent} : null;
const shown = {...opened(), ...look(pick()), sections: all("h3", box).map((head) => head.firstChild.textContent), composer: Boolean(box.querySelector("form textarea")), offices: text(box.querySelector(".popup-body p")), face};
2026-11-08 21:18:55 +00:00
const gone = await away(pick);
click(pick());
await sleep(300);
const again = dialog.open;
(document.activeElement || document.body).dispatchEvent(new KeyboardEvent("keydown", {key: "Escape", bubbles: true}));
await sleep(250);
const escaped = {...look(pick()), open: dialog.open, again};
click(pick());
await sleep(500);
return {before, flat, shown, gone, escaped, last: dialog.open};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@POPUPS@" , POPUPS )
POPUP_ARTIFACT = """(async () => {@HELPERS@@POPUPS@
const first = await until(() => document.querySelector('#main svg .node[data-pick^="branch:"]'), 8000);
if (!first) {
return {error: text(document.getElementById("main"))};
}
const id = first.dataset.pick;
const pick = () => all("#main svg .node[data-pick]").find((node) => node.dataset.pick === id);
const before = look(pick());
const flat = !tall();
click(pick());
await sleep(400);
const shown = {...opened(), ...look(pick()), label: first.getAttribute("aria-label").split(",")[0]};
const gone = await away(pick);
click(pick());
await sleep(400);
return {before, flat, shown, gone, last: dialog.open};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@POPUPS@" , POPUPS )
2026-11-07 15:03:14 +00:00
PICK_3D = """(async () => {@HELPERS@
2026-11-08 21:18:55 +00:00
const field = await until(() => document.querySelector('#main input[aria-label="Find a node"]'), 8000);
const ready = await until(() => document.querySelector("#main .graph3d canvas") && !text(document.querySelector("#main .space-note")).startsWith("Loading"), 8000);
if (!field || !ready) {
2026-11-07 15:03:14 +00:00
return {error: text(document.querySelector("#main .graph"))};
}
2026-11-08 21:18:55 +00:00
const type = async (value) => {
field.value = value;
field.dispatchEvent(new Event("input", {bubbles: true}));
await sleep(150);
return all("#main .hits .chip").map((chip) => [chip.dataset.hit, text(chip)]);
};
const tree = await (await fetch("/api/tree", {cache: "no-store"})).json();
const found = {empty: await type(""), twins: tree.repos.flatMap((repo) => repo.branches).filter((branch) => branch.name === "main").length, twin: await type("main"), hits: await type("@QUERY@")};
found.flat = document.documentElement.scrollHeight <= innerHeight;
const chip = all("#main .hits .chip").find((node) => node.dataset.hit.startsWith("@PREFIX@"));
2026-11-07 15:03:14 +00:00
if (!chip) {
2026-11-08 21:18:55 +00:00
return {error: found};
2026-11-07 15:03:14 +00:00
}
chip.click();
await sleep(600);
2026-11-08 21:18:55 +00:00
const dialog = document.getElementById("popup");
const line = dialog.querySelector(".leader line");
const host = () => document.querySelector("#main .graph3d");
found.opened = {
open: dialog.open, title: text(dialog.querySelector(".popup-box .panel-title")), label: text(chip), hit: chip.dataset.hit, picked: host().dataset.picked || null,
line: line.getAttribute("display") !== "none", lists: all("#main .space-list, #main .chip[data-node]").length, flat: document.documentElement.scrollHeight <= innerHeight,
};
dialog.dispatchEvent(new MouseEvent("click", {bubbles: true}));
await sleep(300);
found.closed = {open: dialog.open, picked: host().dataset.picked || null};
chip.click();
await sleep(600);
return found;
2026-11-07 15:03:14 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS )
CALL = """(async () => {@HELPERS@
2026-11-08 17:26:51 +00:00
document.querySelector('#main [aria-label="Categories"] button[aria-label="Radio"]').click();
const someone = await until(() => document.querySelector('#main button.linkish[title^="Open "]'), 8000);
2026-11-06 22:20:06 +00:00
someone.click();
await sleep(500);
2026-11-08 21:18:55 +00:00
document.querySelector('#popup input[type=radio][value="call"]').click();
const field = document.querySelector("#popup textarea");
2026-11-07 15:03:14 +00:00
field.value = "E2E call ping";
field.dispatchEvent(new Event("input", {bubbles: true}));
2026-11-08 21:18:55 +00:00
document.querySelector('#popup button[type="submit"]').click();
2026-11-08 17:26:51 +00:00
const entry = await until(() => all("#main .entry").find((row) => row.textContent.includes("Read") && row.textContent.includes("E2E call ping")), 20000);
2026-11-07 15:03:14 +00:00
return {to: text(someone), receipt: entry ? text(entry) : null, sent: all("#main .entry").filter((row) => row.textContent.includes("E2E call ping")).length};
})()""" . replace ( "@HELPERS@" , HELPERS )
OPEN_DELETE = """(async () => {@HELPERS@
const card = await until(() => all("#main details.repo").find((details) => details.textContent.includes("github.com/me/demo")), 8000);
if (!card) {
return {error: text(document.getElementById("main"))};
}
card.open = true;
await sleep(300);
2026-11-08 17:26:51 +00:00
const every = document.querySelector('#main button[aria-label="Delete all merged"]');
const buttons = all('button[aria-label="Delete"]', card);
2026-11-07 15:03:14 +00:00
const states = all("span.state", card).map(name);
buttons.find((button) => !button.disabled).click();
await sleep(500);
2026-11-08 17:26:51 +00:00
return {states, every: [every.disabled, every.title], buttons: buttons.map((button) => [button.disabled, button.title]), title: text(document.querySelector(".sheet-head .panel-title")), dry: all('.sheet-box [aria-label="Dry run"] pre').map(text)};
2026-11-07 15:03:14 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS )
CONFIRM = """(async () => {@HELPERS@
2026-11-08 17:26:51 +00:00
all(".sheet-box button").find((button) => name(button) === "Delete").click();
2026-11-07 15:03:14 +00:00
const done = await until(() => document.querySelector(".sheet-box [data-result]"), 30000);
const job = document.querySelector(".sheet-box [data-cleanup]");
return {job: job ? job.dataset.cleanup : null, result: done ? done.dataset.result : null, text: text(done), toasts: all("#toasts .toast").map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS )
AFTER = """(async () => {@HELPERS@
document.querySelector(".sheet-head button").click();
2026-11-08 17:26:51 +00:00
const past = await until(() => all("#main details.repo").find((details) => details.textContent.includes("Recently deleted")), 15000);
2026-11-07 15:03:14 +00:00
return {past: past ? [past.querySelector("[data-result]").dataset.result, name(past.querySelector("span.state"))] : null, branches: all("#main details.repo").filter((details) => details !== past).flatMap((details) => all(".branch-name strong", details)).map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-07 19:59:47 +00:00
OVERLAP = """
const shown = (node) => node.getClientRects().length > 0 && node.getBoundingClientRect().width > 0;
const words = (label) => [...label.childNodes].filter((node) => node.nodeType === 3).map((node) => node.textContent).join("").trim();
const called = (node) => node.getAttribute("aria-label") || (node.closest("label") ? words(node.closest("label")) : "") || text(node) || node.type;
const clashes = (root) => {
const boxes = all("input, select, textarea, button", root).filter(shown).map((node) => ({node, box: node.getBoundingClientRect()}));
const found = [];
boxes.forEach((a, index) => boxes.slice(index + 1).forEach((b) => {
const x = Math.min(a.box.right, b.box.right) - Math.max(a.box.left, b.box.left);
const y = Math.min(a.box.bottom, b.box.bottom) - Math.max(a.box.top, b.box.top);
if (x > 0.5 && y > 0.5) {
found.push(`${called(a.node)} × ${called(b.node)}`);
}
}));
const outside = boxes.filter(( {box} ) => box.right > root.getBoundingClientRect().right + 0.5).map(( {node} ) => called(node));
return {found, outside};
};
"""
RULES_OVERLAP = """(async () => {@HELPERS@@CLASHES@
const open = document.querySelector(".sheet-head button");
if (open) {
open.click();
}
const rules = await until(() => document.querySelector("#main .rule-form"), 8000);
const kind = rules.querySelector("select");
const found = {} ;
for (const value of ["decision", "review", "ask"]) {
kind.value = value;
kind.dispatchEvent(new Event("change", {bubbles: true}));
await sleep(150);
found[value] = {...clashes(rules), labels: all("label", rules).filter(shown).map(words)};
}
return found;
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@CLASHES@" , OVERLAP )
SETTINGS_OVERLAP = """(async () => {@HELPERS@@CLASHES@
document.getElementById("settings-open").click();
const settings = await until(() => document.querySelector(".sheet-box form.settings"), 8000);
2026-11-08 17:26:51 +00:00
all("button", settings).find((button) => text(button) === "+ Station").click();
2026-11-07 19:59:47 +00:00
const theme = all("select", settings).find((select) => [...select.options].some((option) => option.value === "custom"));
theme.value = "custom";
theme.dispatchEvent(new Event("change", {bubbles: true}));
await sleep(250);
2026-11-08 17:26:51 +00:00
const place = all("section", settings).find((section) => text(section.querySelector("h3")) === "Location");
2026-11-07 19:59:47 +00:00
place.scrollIntoView({block: "start"});
return clashes(settings);
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@CLASHES@" , OVERLAP )
MEDIA_LOG = """() => {
const keep = (entry) => {
try {
const list = JSON.parse(sessionStorage.getItem("e2e-media") || "[]");
list.push({...entry, page: Math.round(performance.timeOrigin)});
sessionStorage.setItem("e2e-media", JSON.stringify(list));
} catch (error) {
return;
}
};
const play = HTMLMediaElement.prototype.play;
HTMLMediaElement.prototype.play = function () {
window.e2eAudio = this;
const src = this.getAttribute("src") || "";
const result = play.call(this);
result.then(() => keep({play: "ok", src}), (error) => keep({play: error.name, src}));
return result;
};
document.addEventListener("securitypolicyviolation", (event) => keep({csp: event.effectiveDirective, blocked: event.blockedURI}));
}"""
MEDIA = """
const log = () => JSON.parse(sessionStorage.getItem("e2e-media") || "[]");
const sample = () => (window.e2eAudio ? {paused: window.e2eAudio.paused, time: window.e2eAudio.currentTime, error: window.e2eAudio.error ? window.e2eAudio.error.code : null, src: window.e2eAudio.getAttribute("src")} : null);
const advancing = async () => {
const first = sample();
await sleep(1500);
const second = sample();
return {first, second, playing: Boolean(first && second && !second.paused && second.time - first.time > 0.5)};
};
"""
ADD_STATION = """(async () => {@HELPERS@
const clock = (date) => `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
document.getElementById("settings-open").click();
const form = await until(() => document.querySelector(".sheet-box form.settings"), 8000);
2026-11-08 17:26:51 +00:00
all("button", form).find((button) => text(button) === "+ Station").click();
2026-11-07 19:59:47 +00:00
await sleep(200);
const row = all(".stream-row", form).pop();
const [label, address, start, end] = all("input.field", row);
const set = (input, value) => {
input.value = value;
input.dispatchEvent(new Event("input", {bubbles: true}));
};
set(label, "@NAME@");
set(address, "@STATION@");
set(start, clock(new Date(Date.now() - 3600000)));
set(end, clock(new Date(Date.now() + 3600000)));
all('.days input[type="checkbox"]', row).forEach((box) => {
box.checked = true;
});
all("button", form).find((button) => button.type === "submit").click();
2026-11-08 17:26:51 +00:00
await until(() => all("#toasts .toast").map(text).find((toast) => toast.startsWith("New station")), 8000);
2026-11-07 19:59:47 +00:00
return {toasts: all("#toasts .toast").map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-08 17:26:51 +00:00
STATION_NAME = "E2E tone with a very long station name that fits on no single line"
2026-11-08 13:37:02 +00:00
DLF_PATH = "/dlf/01/high/aac/stream.aac?aggregator=web"
DLF_NODE = "/ard/dlf/01/aac/192/stream.aac?aggregator=web"
2026-11-07 19:59:47 +00:00
RADIO_STATE = """(async () => {@HELPERS@@MEDIA@
await until(() => log().some((entry) => entry.play), 8000);
return {log: log(), media: await advancing(), toasts: all("#toasts .toast").map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@MEDIA@" , MEDIA )
RADIO_VIEW = """(async () => {@HELPERS@@MEDIA@
location.hash = "#stream/radio";
const row = await until(() => document.querySelector('#main [data-station="0"]'), 8000);
if (!row) {
const initial = JSON.parse(document.getElementById("state").textContent);
return {error: "no station row", log: log(), wrapped: !String(HTMLMediaElement.prototype.play).includes("[native code]"), streams: initial.settings.streams, media: initial.media, now: new Date().toString()};
}
const label = row.querySelector(".station-name");
const style = getComputedStyle(label);
return {
state: row.dataset.state,
note: text(row.querySelector(".station-note")),
title: label.title,
line: [style.whiteSpace, style.textOverflow, label.scrollWidth > label.clientWidth, label.getBoundingClientRect().height < 2 * parseFloat(style.lineHeight)],
media: await advancing(),
log: log(),
toasts: all("#toasts .toast").map(text),
};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@MEDIA@" , MEDIA )
RADIO_CLICK = """(async () => {@HELPERS@@MEDIA@
const row = document.querySelector('#main [data-station="0"]');
if (!row) {
return {error: "no station row", log: log(), media: await advancing()};
}
const play = row.querySelector('button[data-action="play"]');
const clicked = Boolean(sample() && sample().paused && play);
if (clicked) {
play.click();
}
await until(() => sample() && !sample().paused, 6000);
const media = await advancing();
const now = document.querySelector('#main [data-station="0"]');
return {clicked, state: now.dataset.state, note: text(now.querySelector(".station-note")), media, log: log()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@MEDIA@" , MEDIA )
SPEAKING = """
const caption = () => {
const node = document.querySelector("#main .radio-caption");
return node && !node.hidden ? node : null;
};
const ring = (chip) => (chip ? getComputedStyle(chip.querySelector(".speaker-face"), "::after").animationName : null);
2026-11-08 22:06:31 +00:00
const mouth = (art) => (art ? [art.classList.contains("speaking"), getComputedStyle(art.querySelector(".mouth")).animationName] : null);
const faces = () => ({
speaking: mouth(document.querySelector('#main .speakers .avatar[data-face="s2"]')),
silent: mouth(document.querySelector('#main .speakers .avatar[data-face="s1"]')),
keys: [...new Set(all("#main .avatar.speaking").map((art) => art.dataset.face))],
});
2026-11-07 19:59:47 +00:00
const cue = async (word) => {
await until(() => document.querySelector("#main .speakers .speaker"), 8000);
await fetch("/api/tts", {method: "POST", headers: {"X-Kvb-Orchestrator": "1", "Content-Type": "application/json"}, body: JSON.stringify({text: word, voice: ""}), cache: "no-store"});
const found = await until(caption, 25000);
const chip = document.querySelector("#main .speaker.speaking");
2026-11-08 22:06:31 +00:00
return found ? {who: text(found.querySelector("strong")), line: text(found.querySelector(".caption-line")), chip: chip ? chip.dataset.key : null, ring: ring(chip), faces: faces()} : {error: "no caption", toasts: all("#toasts .toast").map(text)};
2026-11-07 19:59:47 +00:00
};
"""
SPEAK = """(async () => {@HELPERS@@SPEAKING@
return cue("@CUE@");
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@SPEAKING@" , SPEAKING )
SPEAK_GRAPH = """(async () => {@HELPERS@@SPEAKING@
location.hash = "#agents/orbit";
const node = await until(() => document.querySelector("#main svg .node.speaking"), 6000);
2026-11-08 22:06:31 +00:00
const silent = document.querySelector('#main svg .node[data-pick="s1"] .mouth');
return {
node: node ? node.getAttribute("aria-label") : null, ring: node ? getComputedStyle(node.querySelector("circle")).animationName : null,
mouth: node ? getComputedStyle(node.querySelector(".mouth")).animationName : null, silent: silent ? getComputedStyle(silent).animationName : null,
};
2026-11-07 19:59:47 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@SPEAKING@" , SPEAKING )
SPEAK_END = """(async () => {@HELPERS@@SPEAKING@
const ended = Boolean(await until(() => !document.querySelector("#main svg .node") || !document.querySelector("#main svg .node.speaking"), 20000));
location.hash = "#stream/radio";
await until(() => document.querySelector("#main .radio-caption"), 6000);
await sleep(300);
2026-11-08 22:06:31 +00:00
return {ended, caption: Boolean(caption()), chips: all("#main .speaker.speaking").length, faces: all("#main .avatar.speaking").length};
2026-11-07 19:59:47 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@SPEAKING@" , SPEAKING )
COMPOSE = """(async () => {@HELPERS@
const form = await until(() => document.querySelector("#main form.composer"), 8000);
if (!form) {
return {error: "no composer"};
}
const [channel, recipient] = all("select", form);
await until(() => [...recipient.options].some((option) => option.value === "@TO@"), 8000);
channel.value = "call";
channel.dispatchEvent(new Event("change", {bubbles: true}));
recipient.value = "@TO@";
const record = all("button", form).find((button) => button.hasAttribute("aria-pressed"));
record.click();
await sleep(1500);
const recording = record.getAttribute("aria-pressed");
record.click();
const field = form.querySelector("textarea");
await until(() => field.value && form.querySelector('.voice-note input[type="checkbox"]'), 60000);
const replay = form.querySelector(".voice-note audio");
const attach = form.querySelector('.voice-note input[type="checkbox"]');
return {
channels: all("option", channel).map((option) => option.value),
recipients: all("option", recipient).map((option) => option.value),
recording,
text: field.value,
replay: replay ? replay.getAttribute("src") : null,
attach: attach ? [text(attach.closest("label")), attach.checked] : null,
status: text(form.querySelector(".composer-status")),
toasts: all("#toasts .toast").map(text),
clashes: (() => {@CLASHES@
return clashes(form);
})(),
};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@CLASHES@" , OVERLAP )
COMPOSE_SEND = """(async () => {@HELPERS@
const form = document.querySelector("#main form.composer");
const field = form.querySelector("textarea");
2026-11-08 17:26:51 +00:00
field.value = `$ {field.value} please`;
2026-11-07 19:59:47 +00:00
field.dispatchEvent(new Event("input", {bubbles: true}));
form.querySelector('.voice-note input[type="checkbox"]').checked = @ATTACH@;
const before = all("#toasts .toast").length;
form.querySelector('button[type="submit"]').click();
2026-11-08 17:26:51 +00:00
const sent = await until(() => all("#toasts .toast").slice(before).map(text).find((toast) => toast.startsWith("Call to")), 20000);
2026-11-07 19:59:47 +00:00
return {sent, left: field.value, voice: Boolean(form.querySelector(".voice-note audio"))};
})()""" . replace ( "@HELPERS@" , HELPERS )
CLEAR_MAYDAY = """(async () => {@HELPERS@
window.confirm = () => true;
2026-11-08 17:26:51 +00:00
const button = await until(() => all('#main [role="toolbar"] button').find((node) => name(node) === "Cancel mayday" && !node.disabled), 8000);
2026-11-07 19:59:47 +00:00
if (!button) {
2026-11-08 17:26:51 +00:00
return {error: "no enabled Cancel mayday button"};
2026-11-07 19:59:47 +00:00
}
button.click();
2026-11-08 17:26:51 +00:00
const done = await until(() => all("#toasts .toast").map(text).find((toast) => toast.startsWith("Cancel mayday")), 15000);
2026-11-07 19:59:47 +00:00
return {done, toasts: all("#toasts .toast").map(text)};
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-08 23:18:18 +00:00
NIGHT_MENU = """(async () => {@HELPERS@
const questions = [];
window.confirm = (question) => {
questions.push(question);
return true;
};
let asked = 0;
const get = navigator.credentials.get.bind(navigator.credentials);
navigator.credentials.get = (options) => {
asked += 1;
return @SIGNS@ ? get(options) : Promise.reject(new DOMException("The operator canceled.", "NotAllowedError"));
};
const posted = [];
const send = window.fetch.bind(window);
window.fetch = (path, init) => {
if (String(path).startsWith("/api/board/")) {
const body = JSON.parse(init.body);
posted.push([String(path), Object.keys(body), body.action || null]);
}
return send(path, init);
};
const menu = await until(() => all('#main [role="toolbar"] details.menu').find((node) => name(node.querySelector("summary")) === "Day/Night"), 8000);
if (!menu) {
return {error: text(document.getElementById("main"))};
}
menu.open = true;
const buttons = all(".actions button", menu);
const runs = [];
for (const button of buttons) {
const label = name(button);
const [before, prompts, sent] = [asked, questions.length, posted.length];
const shown = new Set(all("#toasts .toast"));
button.click();
const toast = await until(() => all("#toasts .toast").filter((box) => !shown.has(box)).map(text).find((entry) => entry.startsWith(`$ {label} :`)), 15000);
runs.push({label, question: questions.slice(prompts), asked: asked - before, posted: posted.slice(sent), toast});
}
return {labels: buttons.map(name), runs};
})()""" . replace ( "@HELPERS@" , HELPERS )
2026-11-07 19:59:47 +00:00
def tone ( seconds : int , rate : int ) -> bytes :
frames = array . array ( "h" , ( int ( 6000 * math . sin ( 2 * math . pi * 440 * n / rate )) for n in range ( seconds * rate )))
return wav_bytes ( 1 , 2 , rate , frames . tobytes ())
class Station ( http . server . BaseHTTPRequestHandler ):
def do_GET ( self ) -> None :
2026-11-08 13:37:02 +00:00
if self . server . moved :
self . send_response ( 302 )
self . send_header ( "Location" , self . server . moved )
self . send_header ( "Content-Length" , "0" )
self . end_headers ()
return
2026-11-07 19:59:47 +00:00
body = self . server . body
self . send_response ( 200 )
self . send_header ( "Content-Type" , "audio/wav" )
self . send_header ( "Content-Length" , str ( len ( body )))
self . end_headers ()
with contextlib . suppress ( OSError ):
self . wfile . write ( body )
def log_message ( self , * args : object ) -> None :
return
2026-11-07 15:03:14 +00:00
2026-11-07 18:14:21 +00:00
LABELS = """
const unlabelled = (root) => all("svg.bi", root).filter((svg) => !svg.closest(".sprite")).map((svg) => svg.parentElement).filter((owner) => !text(owner) && !(owner.getAttribute("title") && owner.getAttribute("aria-label"))).map((owner) => owner.outerHTML.slice(0, 160));
const wide = () => document.documentElement.scrollWidth > window.innerWidth + 1;
const rowOf = (words) => all("#main .inbox-list > li").find((li) => li.textContent.includes(words));
const press = (root, label) => all("button", root).find((button) => name(button) === label).click();
const out = () => document.querySelector("#main .console .console-out");
const atEnd = () => out().scrollTop + out().clientHeight >= out().scrollHeight - 12;
const line = () => text(document.querySelector("#main .console .console-status"));
"""
RUN_START = """(async () => {@HELPERS@@LABELS@
2026-11-08 17:26:51 +00:00
const row = await until(() => rowOf("Row"), 8000);
2026-11-07 18:14:21 +00:00
if (!row) {
return {error: text(document.getElementById("main"))};
}
2026-11-08 17:26:51 +00:00
press(row, "Run");
const toast = await until(() => all("#toasts .toast.wait").find((box) => box.textContent.includes("Run")), 8000);
2026-11-07 18:14:21 +00:00
const queued = await until(() => out() && line(), 15000);
2026-11-08 17:26:51 +00:00
await until(() => out().textContent.includes("Row 60"), 20000);
2026-11-07 18:14:21 +00:00
return {toast: text(toast), queued, running: line(), overflows: out().scrollHeight > out().clientHeight, followed: atEnd()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
RUN_HELD = """(async () => {@HELPERS@@LABELS@
out().scrollTop = 0;
await sleep(300);
2026-11-08 17:26:51 +00:00
await until(() => out().textContent.includes("Row 80"), 20000);
2026-11-07 18:14:21 +00:00
await sleep(300);
return {held: out().scrollTop, running: line()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
RUN_DONE = """(async () => {@HELPERS@@LABELS@
out().scrollTop = out().scrollHeight;
await until(() => document.querySelector("#main .console").dataset.status === "done", 20000);
await sleep(500);
const lines = out().textContent.trim().split(" \\ n");
return {done: line(), last: lines[lines.length - 1], lines: lines.length, followed: atEnd(), actions: all(".item-actions button", out().closest("li")).map(name)};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
LAYOUT = """(async () => {@HELPERS@@LABELS@
2026-11-08 17:26:51 +00:00
await until(() => document.querySelector("#main .console") && rowOf("Rule request"), 8000);
const rule = rowOf("Rule request");
2026-11-07 18:14:21 +00:00
return {
kinds: all("#main .inbox-list > li").map((li) => text(li.querySelector(".pill"))),
splits: all("#main .deny-split").map((group) => all("button", group).map(name)),
console: line(),
followed: atEnd(),
fits: out().getBoundingClientRect().right <= window.innerWidth,
dry: text(rule.querySelector(".dry-run-count")),
fields: all(".rule-request dt", rule).map(text),
2026-11-08 17:26:51 +00:00
accept: all("button", rule).filter((button) => name(button) === "Accept").length,
2026-11-07 18:14:21 +00:00
unlabelled: unlabelled(document),
wide: wide(),
};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
DENY_SHEET = """(async () => {@HELPERS@@LABELS@
const row = await until(() => rowOf("git push"), 8000);
2026-11-08 17:26:51 +00:00
press(row, "Deny via …");
2026-11-07 18:14:21 +00:00
const box = await until(() => document.querySelector(".sheet-box"), 4000);
return {
title: text(box.querySelector(".panel-title")),
channels: all(".deny-channels input[type=radio]", box).map((input) => [input.value, input.checked]),
hints: all(".deny-channel small", box).map(text),
reason: Boolean(box.querySelector("textarea")),
unlabelled: unlabelled(box),
wide: wide(),
};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
DENY = """(async () => {@HELPERS@@LABELS@
const row = await until(() => rowOf("Bash: make deploy"), 8000);
2026-11-08 17:26:51 +00:00
press(row, "Deny via …");
2026-11-07 18:14:21 +00:00
const box = await until(() => document.querySelector(".sheet-box"), 4000);
box.querySelector('input[value="call"]').click();
2026-11-08 17:26:51 +00:00
box.querySelector("textarea").value = "make the gate green first";
press(box.querySelector(".actions"), "Deny");
const undo = await until(() => all("#toasts .toast.wait").find((toast) => toast.textContent.includes("Denial")), 8000);
2026-11-07 18:14:21 +00:00
return {undo: text(undo), sheet: !document.getElementById("sheet").hidden};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
DENIED = """(async () => {@HELPERS@@LABELS@
return {gone: Boolean(await until(() => !rowOf("Bash: make deploy"), 20000))};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
ACCEPT = """(async () => {@HELPERS@@LABELS@
2026-11-08 17:26:51 +00:00
press(rowOf("Rule request"), "Accept");
const toast = await until(() => all("#toasts .toast").find((box) => box.textContent.includes("Created: Rule")), 15000);
await until(() => !rowOf("Rule request"), 8000);
return {toast: text(toast), left: Boolean(rowOf("Rule request"))};
2026-11-07 18:14:21 +00:00
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
2026-11-09 06:53:55 +00:00
WATCH = """
const questions = [];
window.confirm = (question) => {
questions.push(question);
return true;
};
let asked = 0;
const get = navigator.credentials.get.bind(navigator.credentials);
navigator.credentials.get = (options) => {
asked += 1;
return @SIGNS@ ? get(options) : Promise.reject(new DOMException("The operator canceled.", "NotAllowedError"));
};
const posted = [];
const send = window.fetch.bind(window);
window.fetch = (path, init) => {
if (["/api/choices/sign", "/api/choices", "/api/sign", "/api/inbox/approve"].includes(String(path))) {
const body = JSON.parse(init.body);
posted.push([String(path), Object.keys(body).sort(), body.class || null, body.to || null]);
}
return send(path, init);
};
const bar = () => document.querySelector('#main [aria-label="Request choices"]');
const fresh = async (shown, prefix) => until(() => all("#toasts .toast").filter((box) => !shown.has(box)).map(text).find((entry) => entry.startsWith(prefix)), 20000);
const fits = () => ({tall: document.documentElement.scrollHeight > window.innerHeight, wide: document.documentElement.scrollWidth > window.innerWidth + 1});
"""
CHOICE_VIEW = """(async () => {@HELPERS@@LABELS@
const found = await until(() => document.querySelector('#main [aria-label="Request choices"]') && all("#main .inbox-list > li .options").length === @CARDS@ && document.querySelector('#main [aria-label="Request choices"] details.menu input[type="checkbox"]'), 8000);
if (!found) {
return {error: text(document.getElementById("main"))};
}
const box = document.querySelector('#main [aria-label="Request choices"]');
const menu = box.querySelector("details.menu");
const chip = document.querySelector('#main [aria-label="Task filters"] button[data-filter="operator"]');
return {
kinds: [...new Set(all("#main .inbox-list > li").map((li) => text(li.querySelector(".pill"))))].sort(),
filter: [text(chip).replace(/[0-9]+$/, ""), chip.getAttribute("aria-pressed")],
channels: all("button[data-channel]", box).map((button) => [button.dataset.channel, name(button), button.disabled]),
fields: all("textarea, input:not([type=checkbox])", box).length,
agents: all('input[type="checkbox"]', menu).map((input) => input.value),
multi: [name(menu.querySelector("summary")), text(menu.querySelector("summary .count")), all('input[type="checkbox"]', menu).filter((input) => input.checked).length],
cards: all("#main .inbox-list > li").filter((li) => li.querySelector(".options")).map((li) => [text(li.querySelector(".pill")), all(".option strong", li).map(text)]),
choices: all("#main [data-column], #main .group-title, #main h2, #main h3").filter((node) => node.dataset.column === "choices" || text(node) === "Choices").length,
unlabelled: unlabelled(document),
tall: document.documentElement.scrollHeight > window.innerHeight,
wide: wide(),
};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@LABELS@" , LABELS )
CHOICE_SEND = """(async () => {@HELPERS@@WATCH@
const menu = bar().querySelector("details.menu");
menu.open = true;
await sleep(200);
const boxes = all('input[type="checkbox"]', menu).slice(0, 2);
boxes.forEach((box) => box.click());
const counted = text(menu.querySelector("summary .count"));
menu.open = false;
const picked = boxes.map((box) => box.value).sort((a, b) => a.localeCompare(b));
const runs = [];
for (const button of all("button[data-channel]", bar())) {
const [before, prompts, sent] = [asked, questions.length, posted.length];
const shown = new Set(all("#toasts .toast"));
const disabled = button.disabled;
button.click();
const toast = await fresh(shown, "Choice request");
runs.push({channel: button.dataset.channel, disabled, asked: asked - before, question: questions.slice(prompts), posted: posted.slice(sent), toast});
}
return {picked, counted, runs, fields: all("textarea, input:not([type=checkbox])", bar()).length, ...fits()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@WATCH@" , WATCH )
CHOICE_ANSWER = """(async () => {@HELPERS@@WATCH@
const card = await until(() => all("#main .inbox-list > li").find((li) => li.textContent.includes("@QUESTION@")), 8000);
if (!card) {
return {error: text(document.getElementById("main"))};
}
const option = all(".option", card).find((button) => text(button.querySelector("strong")) === "Wait");
const title = option.title;
const shown = new Set(all("#toasts .toast"));
option.click();
const toast = await fresh(shown, "Choice: Wait");
const gone = @SIGNS@ ? Boolean(await until(() => !all("#main .inbox-list > li").some((li) => li.textContent.includes("@QUESTION@")), 20000)) : null;
return {title, asked, posted, toast, gone, ...fits()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@WATCH@" , WATCH )
TASKS_FIT = """(async () => {@HELPERS@
await until(() => document.querySelector('#main [aria-label="Request choices"]') && document.querySelector("#main .tasks-body > *"), 8000);
document.getElementById("toasts").replaceChildren();
await sleep(300);
return {
hash: location.hash,
bar: all('#main [aria-label="Request choices"] button[data-channel]').map((button) => button.dataset.channel),
columns: all("#main .task-column").map((column) => column.dataset.column),
choices: all("#main [data-column], #main .group-title, #main h2, #main h3").filter((node) => node.dataset.column === "choices" || text(node) === "Choices").length,
tall: document.documentElement.scrollHeight > window.innerHeight,
wide: document.documentElement.scrollWidth > window.innerWidth + 1,
};
})()""" . replace ( "@HELPERS@" , HELPERS )
PLAN_HELPERS = """
const fits = () => ({tall: document.documentElement.scrollHeight > window.innerHeight, wide: document.documentElement.scrollWidth > window.innerWidth + 1});
const modes = () => all('#main [aria-label="Estimation"] button').map((button) => [button.dataset.estimation, button.getAttribute("aria-pressed")]);
const requestShown = () => {
const button = document.getElementById("request-estimates");
return button ? !button.hidden && button.getClientRects().length > 0 : null;
};
const choose = async (select, value) => {
select.value = value;
select.dispatchEvent(new Event("change", {bubbles: true}));
await sleep(300);
};
const fresh = async (shown, prefix) => until(() => all("#toasts .toast").filter((box) => !shown.has(box)).map(text).find((entry) => entry.startsWith(prefix)), 20000);
"""
GANTT = """(async () => {@HELPERS@@PLAN_HELPERS@
const svg = await until(() => document.querySelector("#main svg.gantt"), 8000);
if (!svg) {
return {error: text(document.getElementById("main"))};
}
const forecast = document.querySelector('#main select[aria-label="Forecast"]');
const scroll = document.querySelector("#main .gantt-scroll");
const marks = () => all("#main svg.gantt [data-forecast]").map((mark) => [mark.dataset.task, mark.dataset.forecast === "true", Number(mark.dataset.start), Number(mark.dataset.end), mark.tagName.toLowerCase()]);
const found = {
hash: location.hash,
forecast: [forecast.value, all("option", forecast).map((option) => [option.value, text(option)])],
modes: modes(),
request: requestShown(),
bars: marks(),
edges: all("#main svg.gantt path.gantt-edge").map((path) => [path.dataset.kind, path.dataset.from, path.dataset.to, path.getAttribute("marker-end")]),
hatched: all("#main svg.gantt .gantt-bar.forecast").map((bar) => getComputedStyle(bar).fill.includes("gantt-hatch")),
inner: Boolean(scroll && scroll.closest(".tasks-body")),
...fits(),
};
await choose(forecast, "real");
found.real = marks().filter(([, hatched]) => hatched).length;
await choose(forecast, "points");
found.pointsOnly = marks().filter(([, hatched]) => hatched).map(([task, , start, end, tag]) => [task, end - start, tag]);
await choose(forecast, "always");
return found;
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS )
BURN = """(async () => {@HELPERS@@PLAN_HELPERS@
const svg = await until(() => document.querySelector("#main svg.burn"), 8000);
if (!svg) {
return {error: text(document.getElementById("main"))};
}
const scope = document.querySelector('#main select[aria-label="Scope"]');
const series = () => all("#main svg.burn path.burn-line").map((path) => [path.dataset.series, Number(path.dataset.last)]);
const found = {hash: location.hash, modes: modes(), request: requestShown(), scope: [scope.value, all("option", scope).map((option) => option.value)], series: series(), ...fits()};
await choose(scope, "agent:Docs");
found.agent = series();
await choose(scope, "all");
return found;
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS )
POINTS_ON = """(async () => {@HELPERS@@PLAN_HELPERS@
const before = {modes: modes(), request: requestShown()};
all('#main [aria-label="Estimation"] button').find((button) => button.dataset.estimation === "points").click();
const shown = await until(() => requestShown(), 8000);
return {before, modes: modes(), request: Boolean(shown), ...fits()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS )
REQUEST = """(async () => {@HELPERS@@PLAN_HELPERS@
const svg = await until(() => document.querySelector("#main svg.burn") && requestShown(), 8000);
if (!svg) {
return {error: text(document.getElementById("main")), modes: modes()};
}
const series = all("#main svg.burn path.burn-line").map((path) => [path.dataset.series, Number(path.dataset.last)]);
const shown = new Set(all("#toasts .toast"));
document.getElementById("request-estimates").click();
const toast = await fresh(shown, "Request estimates");
return {modes: modes(), series, toast, ...fits()};
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS )
BULK = """(async () => {@HELPERS@@PLAN_HELPERS@
const list = await until(() => all("#main .task-row").length && document.getElementById("bulk-value"), 8000);
if (!list) {
return {error: text(document.getElementById("main"))};
}
let asked = 0;
const get = navigator.credentials.get.bind(navigator.credentials);
navigator.credentials.get = (options) => {
asked += 1;
return @SIGNS@ ? get(options) : Promise.reject(new DOMException("The operator canceled.", "NotAllowedError"));
};
const posted = [];
const send = window.fetch.bind(window);
window.fetch = (path, init) => {
if (["/api/sign", "/api/tasks"].includes(String(path))) {
const body = JSON.parse(init.body);
posted.push([String(path), Object.keys(body).sort(), body.items.map((item) => [item.id, item.argv.join(" ")])]);
}
return send(path, init);
};
const rows = () => all("#main .task-row").map((row) => row.dataset.task);
const sort = document.querySelector('#main select[aria-label="Sort"]');
const direction = document.querySelector("#main button[data-direction]");
const found = {sorts: all("option", sort).map((option) => option.value), prio: rows()};
await choose(sort, "status");
found.status = rows();
direction.click();
await sleep(300);
found.statusDown = [rows(), name(direction)];
direction.click();
await choose(sort, "prio");
found.picks = all("#main .task-row input.task-pick").map((box) => box.closest(".task-row").dataset.task);
for (const task of ["work/next", "work/child"]) {
all("#main .task-row").find((row) => row.dataset.task === task).querySelector("input.task-pick").click();
}
const button = document.getElementById("bulk-value");
found.armed = [button.disabled, text(button.querySelector(".count"))];
await choose(document.querySelector('#main select[aria-label="Business value of the selection"]'), "@VALUE@");
const shown = new Set(all("#toasts .toast"));
button.click();
found.toast = await fresh(shown, "Business value @VALUE@ for 2 tasks");
found.asked = asked;
found.posted = posted;
found.after = [button.disabled, all("#main .task-row input.task-pick:checked").length];
Object.assign(found, fits());
all('#main [aria-label="Estimation"] button').find((entry) => entry.dataset.estimation === "none").click();
found.reset = Boolean(await until(() => requestShown() === false, 8000));
return found;
})()""" . replace ( "@HELPERS@" , HELPERS ) . replace ( "@PLAN_HELPERS@" , PLAN_HELPERS )
PLAN_VALUES = { 1920 : "13" , 1366 : "21" , 390 : "34" }
STATUS_ORDER = [ "queued" , "awaiting" , "claimed" , "running" , "blocked" , "stale" , "done" , "stopped" ]
CHOICE_REQUEST = "send me your open decisions as choices"
CHOICE_CHANNELS = [ "post" , "call" , "newsletter" , "broadcast" , "pan-pan" , "security" , "mayday" ]
SLOW = "seq -f 'Row %g ' 1 60; sleep 4; seq -f 'Row %g ' 61 80; sleep 4; echo finished"
2026-11-07 18:14:21 +00:00
2026-11-07 15:03:14 +00:00
def labelled ( probe : dict ) -> dict :
return { key : probe [ key ] for key in ( "unlabelled" , "broken" )}
2026-11-06 22:20:06 +00:00
@unittest.skipUnless ( shutil . which ( os . environ . get ( "CHROMIUM" , "chromium" )), "no Chromium" )
class TestOrchestratorE2E ( HubCase ):
def setUp ( self ):
super () . setUp ()
OUT . mkdir ( parents = True , exist_ok = True )
now = time . time ()
2026-11-07 23:43:26 +00:00
self . root = self . write_tree ()
feature = f " { self . root } /worktrees/github.com/me/demo/feature"
2026-11-06 22:20:06 +00:00
self . claim ( "s1" , "Skills session" )
self . write_council ({
"work/s2/claim.json" : { "office" : "work/s2" , "session" : "s2" , "runtime" : "claude" , "name" : "Stage" , "since" : iso ( now - 7200 ), "beat" : now - 2000 , "work" : "x" },
2026-11-07 17:46:39 +00:00
"chair/claim.json" : { "office" : "chair" , "session" : "s1" , "runtime" : "claude" , "name" : "Skills session" , "since" : iso ( now - 7200 ), "beat" : now , "work" : "allocate" },
"identity/oidc/claim.json" : { "office" : "identity/oidc" , "session" : "s2" , "runtime" : "claude" , "name" : "Stage" , "since" : iso ( now - 7200 ), "beat" : now - 2000 , "work" : "oidc line" },
2026-11-07 23:43:26 +00:00
"rasci.json" : {
"work/stage" : { "title" : "Stage line" , "status" : "running" , "worker" : "Stage (this session)" , "roles" : { "Stage (this session)" : "RA" }},
"work/feature" : {
2026-11-08 17:26:51 +00:00
"title" : "Show the merge flow in the hub" , "status" : "running" , "worker" : "Skills session" , "objective" : "The operator sees where a branch flows" , "workdir" : feature , "prio" : 1 ,
"roles" : { "Skills session" : "RA" , "Operator" : "C" , "Docs" : "I" }, "key_results" : [{ "text" : "Flow in the tree" , "met" : True }, { "text" : "Flow in Tasks" , "met" : False }],
2026-11-07 23:43:26 +00:00
"estimates" : { "Skills" : { "value" : 8 , "complexity" : 3 , "at" : iso ( now - 600 )}},
},
2026-11-08 17:26:51 +00:00
"work/docs" : { "title" : "Update the README" , "status" : "blocked" , "worker" : "Docs" , "branch" : "main" , "roles" : { "Docs" : "R" , "Maintainer" : "A" , "Skills" : "S" }, "estimates" : { "Docs" : { "value" : 3 , "complexity" : 2 }}},
"work/old" : { "title" : "Old line" , "status" : "done" , "worker" : "Stage" , "end" : iso ( now - 86400 ), "roles" : { "Stage" : "RA" }},
2026-11-07 23:43:26 +00:00
"operator/push" : { "title" : "git push" , "workdir" : "/repo" , "status" : "done" , "roles" : { "Operator" : "R" }},
},
"queue.json" : { "seq" : 3 , "entries" : [{ "id" : "gate-3" , "kind" : "gate" , "task" : "work/feature" , "workdir" : feature , "session" : "s1" , "name" : "Skills" , "status" : "waiting" , "enqueued" : now - 120 , "cmd" : "make test" }]},
2026-11-06 22:20:06 +00:00
"radio-silence.json" : { "since" : now - 60 , "until" : now + 3600 , "by" : "Docs" , "text" : "disk full" },
"news.jsonl" : self . jsonl ([
{ "ts" : now - 600 , "from" : "Skills" , "to" : [ "Stage" ], "text" : "rebase please" , "class" : "post" , "v" : 2 },
{ "ts" : now - 500 , "from" : "Stage" , "to" : [ "Skills" ], "text" : "gate red" , "class" : "call" , "v" : 2 },
{ "ts" : now - 400 , "from" : "Maintainer" , "to" : [], "text" : "main is green" , "class" : "newsletter" , "v" : 2 },
{ "ts" : now - 300 , "from" : "Maintainer" , "to" : [], "text" : "rotate keys" , "class" : "security" , "v" : 2 },
{ "ts" : now - 60 , "from" : "Docs" , "to" : [], "text" : "MAYDAY disk full" , "class" : "mayday" , "v" : 2 },
]),
})
2026-11-07 15:03:14 +00:00
self . subagents ( now )
2026-11-06 22:20:06 +00:00
self . request ( "feat: orchestrator" , "" )
2026-11-09 06:53:55 +00:00
self . create_ask ({ ** ASK , "timeout" : 3600 })
2026-11-06 22:20:06 +00:00
self . base = f "http:// { self . host } /"
2026-11-07 15:03:14 +00:00
def subagents ( self , now : float ) -> None :
def line ( ts : float ) -> dict :
return { "type" : "user" , "sessionId" : "s1" , "timestamp" : iso ( ts ), "message" : { "role" : "user" , "content" : "x" }}
folder = self . projects / "-repo" / "s1" / "subagents"
folder . mkdir ( parents = True )
( self . projects / "-repo" / "s1.jsonl" ) . write_text ( self . jsonl ([ line ( now - 3600 ), line ( now - 30 )]), encoding = "utf-8" )
for ident , finished in (( "abc" , True ), ( "def" , False )):
( folder / f "agent- { ident } .meta.json" ) . write_text ( json . dumps ({ "agentType" : "Explore" , "description" : f "look { ident } " , "spawnDepth" : 1 , "finished" : finished }), encoding = "utf-8" )
( folder / f "agent- { ident } .jsonl" ) . write_text ( self . jsonl ([ line ( now - 1800 ), line ( now - 60 )]), encoding = "utf-8" )
2026-11-07 19:59:47 +00:00
def drive ( self , steps : list [ dict ], authenticator : dict | None , driver : str = "cdp.mjs" , binary : str = os . environ . get ( "CHROMIUM" , "chromium" ), init : str | None = None ) -> dict :
2026-11-06 22:20:06 +00:00
with tempfile . NamedTemporaryFile ( "w" , suffix = ".json" , delete = False ) as handle :
2026-11-07 19:59:47 +00:00
json . dump ({ "steps" : steps , "authenticator" : authenticator , "init" : init }, handle )
2026-11-06 22:20:06 +00:00
self . addCleanup ( os . unlink , handle . name )
2026-11-07 15:03:14 +00:00
done = subprocess . run ([ "node" , str ( HERE / driver ), binary , handle . name ], capture_output = True , text = True , timeout = 900 , check = False )
2026-11-06 22:20:06 +00:00
self . assertEqual ( done . returncode , 0 , done . stderr [ - 2000 :])
return json . loads ( done . stdout )
2026-11-07 15:03:14 +00:00
def beside ( self , work : Callable [[], None ]) -> None :
stop = threading . Event ()
def loop () -> None :
while not stop . wait ( 0.2 ):
work ()
thread = threading . Thread ( target = loop , daemon = True )
thread . start ()
self . addCleanup ( thread . join , 10 )
self . addCleanup ( stop . set )
def shot ( self , name : str ) -> str :
return str ( OUT / f "chromium- { name } .png" )
2026-11-07 19:59:47 +00:00
def authenticator ( self ) -> dict :
signer = self . pin ()
private = "" . join ( line for line in signer . pem . read_text ( encoding = "utf-8" ) . splitlines () if not line . startswith ( "-----" ))
return { "credentialId" : base64 . b64encode ( signer . raw_id ) . decode (), "privateKey" : private , "rpId" : "localhost" }
def speaks ( self , cues : tuple [ str , ... ]) -> None :
hub = self . httpd . hub
2026-11-08 10:23:25 +00:00
hub . settings . put ({ ** hub . settings . get (), "speech" : { "on" : True , "classes" : [ "mayday" , "pan-pan" , "security" ], "muted" : []}}, hub . settings . get ())
2026-11-07 19:59:47 +00:00
spoken = tone ( 6 , 8000 )
def synthesize ( body : bytes ) -> bytes :
word = json . loads ( body )[ "text" ]
if word in cues :
2026-11-08 17:26:51 +00:00
row = { "ts" : time . time (), "from" : "Stage" , "to" : [], "text" : f "Disk almost full ( { word } ) \n second line" , "class" : "security" , "v" : 2 }
2026-11-07 19:59:47 +00:00
with ( self . council / "news.jsonl" ) . open ( "a" , encoding = "utf-8" ) as handle :
handle . write ( self . jsonl ([ row ]))
return spoken
patch = mock . patch . object ( hub . speech , "synthesize" , synthesize )
patch . start ()
self . addCleanup ( patch . stop )
2026-11-08 13:37:02 +00:00
def station ( self , tls : ssl . SSLContext | None = None , moved : str | None = None ) -> str :
"""Args:
tls: serves https with this context instead of plain http.
moved: answers every request with a 302 to this URL instead of
the tone.
"""
2026-11-07 19:59:47 +00:00
httpd = http . server . ThreadingHTTPServer (( "127.0.0.1" , 0 ), Station )
2026-11-08 13:37:02 +00:00
httpd . body , httpd . moved = tone ( 30 , 22050 ), moved
if tls :
httpd . socket = tls . wrap_socket ( httpd . socket , server_side = True )
2026-11-07 19:59:47 +00:00
thread = threading . Thread ( target = httpd . serve_forever , args = ( 0.05 ,), daemon = True )
thread . start ()
self . addCleanup ( httpd . server_close )
self . addCleanup ( thread . join , 10 )
self . addCleanup ( httpd . shutdown )
2026-11-08 13:37:02 +00:00
return f " { 'https' if tls else 'http' } ://127.0.0.1: { httpd . server_address [ 1 ] }{ DLF_PATH if moved else '/tone.wav' } "
def redirected ( self ) -> tuple [ str , list [ str ]]:
"""Returns:
A station that replays the recorded Deutschlandfunk chain, a 302
from st01.sslstream.dlf.de to an f111.rndfnk.com node, as a 302 to
a second https origin, and that origin as its released via.
"""
folder = Path ( tempfile . mkdtemp ())
self . addCleanup ( shutil . rmtree , folder )
key , cert = folder / "key.pem" , folder / "cert.pem"
subprocess . run ([
"openssl" , "req" , "-x509" , "-newkey" , "ec" , "-pkeyopt" , "ec_paramgen_curve:P-256" , "-nodes" , "-subj" , "/CN=127.0.0.1" ,
"-addext" , "subjectAltName=IP:127.0.0.1" , "-days" , "1" , "-keyout" , str ( key ), "-out" , str ( cert ),
], capture_output = True , timeout = 60 , check = True )
tls = ssl . SSLContext ( ssl . PROTOCOL_TLS_SERVER )
tls . load_cert_chain ( cert , key )
node = urlsplit ( self . station ( tls ))
origin = f "https:// { node . netloc } "
return self . station ( tls , f " { origin }{ DLF_NODE } " ), [ origin ]
2026-11-07 19:59:47 +00:00
def check_overlaps ( self , browser : str , driver : str , binary : str ) -> None :
steps = []
for width , height in WIDTHS :
steps . append ({ "width" : width , "height" : height , "url" : f " { self . base } #stream/rules" , "wait" : 900 , "probe" : RULES_OVERLAP , "shot" : str ( OUT / f " { browser } - { width } -rules-form.png" )})
steps . append ({ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : SETTINGS_OVERLAP , "shot" : str ( OUT / f " { browser } - { width } -settings-form.png" )})
data = self . drive ( steps , None , driver , binary )
( OUT / f " { browser } -overlap.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
2026-11-08 17:26:51 +00:00
hidden = { "decision" : ( "Tool (glob)" , "Directory (glob)" ), "review" : ( "Tool (glob)" ,), "ask" : ()}
2026-11-07 19:59:47 +00:00
for index , ( width , _ ) in enumerate ( WIDTHS ):
rules , settings = data [ "results" ][ 2 * index : 2 * index + 2 ]
with self . subTest ( browser = browser , width = width ):
self . assertNotIn ( "error" , rules )
self . assertNotIn ( "error" , settings )
for kind , found in rules . items ():
self . assertEqual (( found [ "found" ], found [ "outside" ]), ([], []), kind )
self . assertEqual ([ label for label in hidden [ kind ] if label in found [ "labels" ]], [], kind )
2026-11-08 17:26:51 +00:00
self . assertIn ( "Directory (glob)" , rules [ "review" ][ "labels" ])
2026-11-07 19:59:47 +00:00
self . assertEqual ( settings , { "found" : [], "outside" : []})
2026-11-08 13:37:02 +00:00
def check_radio ( self , browser : str , driver : str , binary : str , redirected : bool = False ) -> dict :
2026-11-07 19:59:47 +00:00
"""Args:
driver: cdp.mjs saves the station through the settings form and
signs its new origin with a virtual passkey; bidi.mjs has no
virtual authenticator, so the station is saved before the page
loads.
2026-11-08 13:37:02 +00:00
redirected: plays the recorded Deutschlandfunk redirect chain,
saved before the page loads with its resolved via.
2026-11-07 19:59:47 +00:00
"""
2026-11-08 13:37:02 +00:00
station , via = self . redirected () if redirected else ( self . station (), [])
browser = f " { browser } -dlf" if redirected else browser
signs = driver == "cdp.mjs" and not redirected
2026-11-07 19:59:47 +00:00
if signs :
first = { "width" : 1366 , "height" : 768 , "url" : f "http://localhost: { self . port } /#stream" , "wait" : 900 , "probe" : ADD_STATION . replace ( "@STATION@" , station ) . replace ( "@NAME@" , STATION_NAME ), "shot" : None , "gesture" : True }
else :
hub = self . httpd . hub
2026-11-08 13:37:02 +00:00
hub . settings . put ({ ** hub . settings . get (), "streams" : [{ "name" : STATION_NAME , "url" : station , "from" : "00:00" , "to" : "24:00" , "days" : list ( range ( 7 )), "on" : True , "via" : via }]}, hub . settings . get ())
2026-11-07 19:59:47 +00:00
first = { "width" : 1366 , "height" : 768 , "url" : f " { self . base } #stream" , "wait" : 900 , "probe" : None , "shot" : None }
steps = [
first ,
{ "width" : 390 , "height" : 844 , "url" : None , "wait" : 3000 , "probe" : RADIO_VIEW , "shot" : str ( OUT / f " { browser } -390-radio-station.png" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : RADIO_CLICK , "shot" : str ( OUT / f " { browser } -1366-radio-playing.png" ), "gesture" : True },
]
data = self . drive ( steps , self . authenticator () if signs else None , driver , binary , MEDIA_LOG )
( OUT / f " { browser } -radio.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
saved , view , click = data [ "results" ]
if signs :
2026-11-08 17:26:51 +00:00
self . assertTrue ( any ( toast . startswith ( "New station" ) for toast in saved [ "toasts" ]), saved )
2026-11-07 19:59:47 +00:00
self . assertNotIn ( "error" , view )
self . assertEqual ( view [ "title" ], STATION_NAME )
self . assertEqual ( view [ "line" ], [ "nowrap" , "ellipsis" , True , True ])
blocked = [ entry for entry in view [ "log" ] if entry . get ( "play" ) == "NotAllowedError" ]
if not view [ "media" ][ "playing" ]:
self . assertTrue ( blocked , view )
self . assertEqual ( view [ "state" ], "blocked" , view )
2026-11-08 17:26:51 +00:00
self . assertIn ( "Click" , view [ "note" ])
self . assertTrue ( any ( "blocked" in toast for toast in view [ "toasts" ]), view [ "toasts" ])
2026-11-07 19:59:47 +00:00
self . assertTrue ( click [ "clicked" ], click )
self . assertNotIn ( "error" , click )
self . assertTrue ( click [ "media" ][ "playing" ], click )
self . assertEqual ( click [ "state" ], "playing" , click )
self . assertEqual ([ entry for entry in click [ "log" ] if entry . get ( "csp" ) == "media-src" ], [])
return data
2026-11-08 21:18:55 +00:00
def popped ( self , result : dict , title : str ) -> None :
before , shown , gone = result [ "before" ], result [ "shown" ], result [ "gone" ]
self . assertEqual (( before [ "picked" ], before [ "red" ]), ( False , False ))
self . assertEqual (( shown [ "open" ], shown [ "picked" ], shown [ "red" ], shown [ "fade" ], shown [ "title" ]), ( True , True , True , [ True , "fade" ], title ), shown )
self . assertGreater ( shown [ "size" ], before [ "size" ] * 1.4 )
self . assertLess ( shown [ "opacity" ], 1 )
self . assertIsNotNone ( shown [ "line" ], shown )
self . assertTrue ( shown [ "onBox" ] and shown [ "inside" ], shown )
self . assertEqual (( not shown [ "tall" ], shown [ "wide" ], shown [ "panel" ]), ( result [ "flat" ], False , 0 ))
self . assertEqual ( gone , { "picked" : False , "size" : before [ "size" ], "red" : False , "open" : False , "outside" : True })
self . assertTrue ( result [ "last" ])
def check_popups ( self , browser : str , driver : str , binary : str ) -> None :
steps = []
for width , height in WIDTHS :
steps . append ({ "width" : width , "height" : height , "url" : f " { self . base } #agents/orbit" , "wait" : 1200 , "probe" : POPUP_ORBIT , "shot" : str ( OUT / f " { browser } - { width } -agents-orbit-popup.png" )})
steps . append ({ "width" : width , "height" : height , "url" : f " { self . base } #artifacts/orbit" , "wait" : 1200 , "probe" : POPUP_ARTIFACT , "shot" : str ( OUT / f " { browser } - { width } -artifacts-orbit-popup.png" )})
for tab , query , prefix in (( "artifacts" , "feature" , "branch:" ), ( "agents" , "sta" , "s2" )):
steps . append ({ "width" : 1366 , "height" : 768 , "url" : f " { self . base } # { tab } /3d" , "wait" : 900 , "probe" : PICK_3D . replace ( "@QUERY@" , query ) . replace ( "@PREFIX@" , prefix ), "shot" : str ( OUT / f " { browser } -1366- { tab } -3d-picked.png" )})
data = self . drive ( steps , None , driver , binary )
( OUT / f " { browser } -popups.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
results = data [ "results" ]
for index , ( width , _ ) in enumerate ( WIDTHS ):
agent , artifact = results [ 2 * index : 2 * index + 2 ]
with self . subTest ( browser = browser , width = width ):
self . assertNotIn ( "error" , agent )
self . assertNotIn ( "error" , artifact )
self . assertTrue ( agent [ "flat" ], "#agents/orbit scrolls" )
self . popped ( agent , "Stage" )
2026-11-08 22:06:31 +00:00
self . assertEqual ( agent [ "shown" ][ "face" ], { "key" : "s2" , "svg" : True , "text" : "" })
2026-11-08 21:18:55 +00:00
self . assertEqual ( agent [ "shown" ][ "sections" ], [ "Tasks" , "Claims" , "Subagents" , "History" ])
self . assertTrue ( agent [ "shown" ][ "composer" ])
self . assertRegex ( agent [ "shown" ][ "offices" ], r "^Offices: .*identity/oidc" )
escaped = agent [ "escaped" ]
self . assertEqual (( escaped [ "again" ], escaped [ "open" ], escaped [ "picked" ], escaped [ "red" ], escaped [ "size" ]), ( True , False , False , False , agent [ "before" ][ "size" ]))
self . popped ( artifact , artifact [ "shown" ][ "label" ])
for found , tab in zip ( results [ - 2 :], ( "artifacts" , "agents" ), strict = True ):
with self . subTest ( browser = browser , tab = tab ):
self . assertNotIn ( "error" , found )
self . assertEqual ( found [ "empty" ], [])
labels = [ label for _ , label in found [ "hits" ]]
self . assertEqual ( len ( labels ), len ( set ( labels )), labels )
self . assertLessEqual ( len ( labels ), 8 )
opened = found [ "opened" ]
self . assertEqual (( opened [ "open" ], opened [ "title" ], opened [ "picked" ], opened [ "line" ], opened [ "lists" ], opened [ "flat" ]), ( True , opened [ "label" ], opened [ "hit" ], True , 0 , found [ "flat" ]), opened )
self . assertEqual ( found [ "closed" ], { "open" : False , "picked" : None })
artifacts , agents = results [ - 2 :]
self . assertEqual (( artifacts [ "twins" ], [ label for _ , label in artifacts [ "twin" ]]), ( 2 , [ "main" ]))
self . assertTrue ( artifacts [ "opened" ][ "hit" ] . startswith ( "branch:" ), artifacts [ "opened" ])
self . assertEqual (( agents [ "hits" ], agents [ "opened" ][ "title" ]), ([[ "s2" , "Stage" ]], "Stage" ))
def test_chromium_opens_agent_and_node_popups_and_finds_nodes_at_three_widths ( self ):
self . check_popups ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ))
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_opens_agent_and_node_popups_and_finds_nodes_at_three_widths ( self ):
self . check_popups ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
2026-11-07 19:59:47 +00:00
def test_chromium_form_controls_never_overlap ( self ):
self . check_overlaps ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ))
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_form_controls_never_overlap ( self ):
self . check_overlaps ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
def test_chromium_plays_a_saved_station ( self ):
self . check_radio ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ))
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_plays_a_saved_station ( self ):
self . check_radio ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
2026-11-08 13:37:02 +00:00
def test_chromium_plays_deutschlandfunk_through_its_redirect ( self ):
self . check_radio ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ), redirected = True )
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_plays_deutschlandfunk_through_its_redirect ( self ):
self . check_radio ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ), redirected = True )
2026-11-06 22:20:06 +00:00
def test_chromium_walks_every_screen_at_three_widths ( self ):
2026-11-07 15:03:14 +00:00
steps = [{ "width" : width , "height" : height , "url" : f " { self . base } # { screen } " , "wait" : 900 , "probe" : PROBE , "shot" : self . shot ( f " { width } - { screen . replace ( '/' , '-' ) } " )} for width , height in WIDTHS for screen in SCREENS ]
2026-11-06 22:20:06 +00:00
steps += [{ "width" : 1366 , "height" : 768 , "url" : f " { self . base } # { old } " , "wait" : 600 , "probe" : PROBE , "shot" : None } for old in ( "graph" , "graph/chord" , "tree" )]
steps . append ({ "width" : 1366 , "height" : 768 , "url" : f " { self . base } #stream" , "wait" : 900 , "probe" : ACTIONS , "shot" : None })
2026-11-07 15:03:14 +00:00
steps . append ({ "width" : 1366 , "height" : 768 , "url" : f " { self . base } #artifacts/3d" , "wait" : 900 , "probe" : PROBE , "shot" : self . shot ( "1366-artifacts-3d-reduced-motion" ), "media" : [{ "name" : "prefers-reduced-motion" , "value" : "reduce" }]})
2026-11-08 17:26:51 +00:00
steps . append ({ "width" : 1366 , "height" : 768 , "url" : f " { self . base } #tasks/list" , "wait" : 900 , "probe" : INBOX_CHIP , "shot" : self . shot ( "1366-tasks-grant" )})
2026-11-07 15:03:14 +00:00
data = self . drive ( steps , None )
( OUT / "chromium-walk.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
2026-11-06 22:20:06 +00:00
self . assertEqual ( data [ "errors" ], [])
probes = data [ "results" ][: len ( WIDTHS ) * len ( SCREENS )]
for probe , step in zip ( probes , steps , strict = False ):
with self . subTest ( url = step [ "url" ], width = step [ "width" ]):
self . assertNotIn ( "error" , probe )
self . assertEqual (( probe [ "title" ], probe [ "h1" ]), ( "(2) Agent Orchestrator" , "Agent Orchestrator" ))
self . assertEqual ( probe [ "header" ], [ "palette-open" , "settings-open" ])
2026-11-07 23:43:26 +00:00
self . assertEqual (( probe [ "tabs" ], probe [ "subs" ]), ( TABS , SUBS ))
self . assertEqual ( probe [ "badges" ], { "tab-stream" : None , "tab-inbox" : "2" , "tab-rules" : None , "tab-radio" : None , "tab-agents" : "1" , "tab-artifacts" : None , "tab-tasks" : None })
2026-11-07 15:03:14 +00:00
self . assertEqual ( probe [ "banner" ], "MAYDAY · Docs: disk full" )
2026-11-06 22:20:06 +00:00
self . assertFalse ( probe [ "rail" ])
self . assertEqual (( probe [ "time" ], probe [ "replay" ]), ( "24 h ▾" , True ))
2026-11-07 15:03:14 +00:00
self . assertGreater ( probe [ "icons" ], 8 )
self . assertEqual ( labelled ( probe ), { "unlabelled" : [], "broken" : []})
2026-11-06 22:20:06 +00:00
self . assertFalse ( probe [ "wide" ], "the page scrolls sideways" )
2026-11-08 21:18:55 +00:00
self . assertEqual (( probe [ "panel" ], probe [ "nodeChips" ]), ( 0 , 0 ))
2026-11-08 13:37:02 +00:00
self . assertEqual ([ probe [ "tall" ] for probe in probes if probe [ "hash" ] == "#agents/orbit" ], [ False ] * len ( WIDTHS ))
2026-11-06 22:20:06 +00:00
by_hash = { probe [ "hash" ]: probe for probe in probes }
stream = by_hash [ "#stream" ]
2026-11-08 17:26:51 +00:00
self . assertEqual ([ probe [ "chips" ] for probe in probes if probe [ "hash" ] == "#stream" ], [[ "For you" , "Radio" , "Work" , "Transcripts" ]] * len ( WIDTHS ))
2026-11-07 15:03:14 +00:00
self . assertEqual (( stream [ "menus" ], stream [ "custom" ]), ([ "Filter" ], True ))
2026-11-06 22:20:06 +00:00
self . assertEqual ( by_hash [ "#stream/inbox" ][ "inbox" ], 2 )
2026-11-08 17:26:51 +00:00
for tab , own in (( "agents" , [ "Hierarchy" ]), ( "artifacts" , [])):
2026-11-06 22:20:06 +00:00
with self . subTest ( tab = tab ):
2026-11-07 17:46:39 +00:00
self . assertEqual ( by_hash [ f "# { tab } /orbit" ][ "views" ], [ "Orbit" , "Chord" , "Swimlane" , "Tree" , "3D" , * own ])
2026-11-08 17:26:51 +00:00
self . assertEqual ( by_hash [ f "# { tab } /3d" ][ "layouts" ], [ "Force" , "Time" , "Spawn depth" ])
2026-11-07 15:03:14 +00:00
self . assertTrue ( by_hash [ f "# { tab } /3d" ][ "canvas" ], by_hash [ f "# { tab } /3d" ][ "note" ])
2026-11-08 17:26:51 +00:00
self . assertRegex ( by_hash [ f "# { tab } /3d" ][ "note" ], r "^\d+ nodes · \d+ edges · Force$" )
2026-11-06 22:20:06 +00:00
agents = by_hash [ "#agents/orbit" ]
2026-11-08 17:26:51 +00:00
self . assertEqual ( agents [ "toolbar" ], [ "Radio to all" , "Cancel mayday" , "Priority" , "Day/Night" ])
2026-11-06 22:20:06 +00:00
self . assertEqual ( agents [ "toAll" ], [[ "newsletter" , True ], [ "broadcast" , False ], [ "pan-pan" , False ], [ "security" , False ], [ "mayday" , False ]])
2026-11-07 17:46:39 +00:00
hierarchy = by_hash [ "#agents/hierarchy" ]
self . assertEqual ([ office for office , _ in hierarchy [ "org" ]], [ "chair" , "identity/oidc" , "work/s1" , "work/s2" ])
2026-11-08 17:26:51 +00:00
self . assertEqual ( hierarchy [ "toggles" ], [[ "Collapse: Operator" , "true" ], [ "Collapse: Chair" , "true" ], [ "Collapse: identity" , "true" ], [ "Collapse: Presidents" , "true" ]])
2026-11-07 17:46:39 +00:00
self . assertEqual ( hierarchy [ "toolbar" ], agents [ "toolbar" ])
self . assertGreaterEqual ( len ( hierarchy [ "order" ]), 2 )
2026-11-07 15:03:14 +00:00
self . assertEqual ( by_hash [ "#agents/tree" ][ "spawn" ][ 0 ], [ "operator" , None ])
self . assertIn ([ "twig" , "true" ], by_hash [ "#agents/tree" ][ "spawn" ])
self . assertIn ([ "twig" , "false" ], by_hash [ "#agents/tree" ][ "spawn" ])
artifacts = by_hash [ "#artifacts/tree" ]
2026-11-08 17:26:51 +00:00
self . assertEqual ( artifacts [ "deletes" ], [[ "Delete all merged" , True , "Delete all merged blocked: Secret Service locked" ], [ "Delete" , True , "Delete blocked: Secret Service locked" ]])
for state in ( "merged by #12 into main" , "upstream gone" , "protected" , "1 commits unpushed" , "clean" , "not committed: 0 staged, 0 changed, 1 new" , "gc skips: ignored files beyond known caches: notes.txt" ):
2026-11-07 15:03:14 +00:00
with self . subTest ( state = state ):
self . assertIn ( state , artifacts [ "states" ])
for view in ( "orbit" , "chord" ):
with self . subTest ( view = view ):
self . assertGreaterEqual ( by_hash [ f "#artifacts/ { view } " ][ "marks" ], 4 )
2026-11-07 23:43:26 +00:00
self . assertEqual (( artifacts [ "flows" ], artifacts [ "openWork" ]), ([ "file" , "invalid" ], 5 ))
2026-11-08 17:26:51 +00:00
for state in ( "open work: not committed, task open" , "open work: unpushed, task open" , "open work: not merged" , "3 worktrees with open work" ):
2026-11-07 23:43:26 +00:00
with self . subTest ( state = state ):
self . assertIn ( state , artifacts [ "states" ])
self . assertEqual ( by_hash [ "#artifacts/chord" ][ "flowEdges" ], [ "pull" , "push" ])
order = [ "work/feature" , "work/docs" , "work/stage" , "queue/gate-3" , "work/old" , "operator/push" ]
listed = by_hash [ "#tasks/list" ]
2026-11-09 06:53:55 +00:00
self . assertEqual ( listed [ "views" ], [ "List" , "Board" , "RASCI" , "Gantt" , "Burndown and burnup" ])
2026-11-08 17:26:51 +00:00
self . assertEqual ( listed [ "taskFilters" ], [[ "all" , "true" , "6" ], [ "open" , "false" , "4" ], [ "work" , "false" , "4" ], [ "operator" , "false" , "2" ], [ "grant" , "false" , "1" ], [ "ended" , "false" , "2" ]])
2026-11-07 23:43:26 +00:00
self . assertEqual ( listed [ "taskRows" ], [[ task , task not in ( "work/old" , "operator/push" )] for task in order ])
self . assertEqual ( listed [ "flows" ], [ "file" , "file" ])
self . assertEqual ( by_hash [ "#tasks/board" ][ "columns" ], [[ "queued" , [ "queue/gate-3" ]], [ "running" , [ "work/feature" , "work/stage" ]], [ "blocked" , [ "work/docs" ]], [ "done" , [ "work/old" , "operator/push" ]], [ "stopped" , []]])
self . assertEqual ( by_hash [ "#tasks/rasci" ][ "matrix" ], [ 6 , 6 , "CIRARASRARAR" ])
self . assertEqual ([ probe [ "taskFilters" ][ 3 ][ 1 ] for probe in probes if probe [ "hash" ] == "#stream/inbox" ], [ "true" ] * len ( WIDTHS ))
2026-11-07 15:03:14 +00:00
extra = data [ "results" ][ len ( probes ) :]
self . assertEqual ([ probe [ "hash" ] for probe in extra [: 3 ]], [ "#agents/orbit" , "#agents/chord" , "#artifacts/tree" ])
actions = extra [ 3 ]
2026-11-06 22:20:06 +00:00
self . assertNotIn ( "error" , actions )
2026-11-09 06:53:55 +00:00
self . assertEqual ( actions [ "keys" ], [ "#agents/chord" , "#artifacts/tree" , "#tasks/burn" , "#stream" ])
2026-11-06 22:20:06 +00:00
self . assertEqual ( actions [ "ambience" ], [ "sky-look" , "music-toggle" , "leitstand" , "notify" ])
2026-11-08 21:18:55 +00:00
self . assertEqual (( actions [ "settingsUnlabelled" ], actions [ "popupUnlabelled" ]), ( 0 , 0 ))
2026-11-08 17:26:51 +00:00
self . assertEqual ([ name for name , _ , _ in actions [ "speech" ]], [ "Post" , "Call" , "Newsletter" , "Broadcast" , "MAYDAY" , "PAN-PAN" , "Sécurité" ])
2026-11-06 22:20:06 +00:00
self . assertEqual ([ entry [ 1 :] for entry in actions [ "speech" ] if entry [ 0 ] == "MAYDAY" ], [[ True , True ]])
2026-11-08 23:18:18 +00:00
for tool in ( "Radio to all …" , "Cancel mayday" , "Priority …" , "Drain" , "Suspend" , "Resume" , "Firewall …" , "Settings …" , "Look at the sky" , "Toggle control room" , "Enable notifications" ):
2026-11-06 22:20:06 +00:00
with self . subTest ( tool = tool ):
self . assertTrue ( any ( tool in entry for entry in actions [ "palette" ]), actions [ "palette" ])
2026-11-08 21:18:55 +00:00
self . assertEqual ( actions [ "popup" ], [[ "post" , True ], [ "call" , False ]])
self . assertEqual ( actions [ "centred" ], [ True , "none" ])
calm , chips = extra [ 4 :]
2026-11-08 17:26:51 +00:00
self . assertRegex ( calm [ "note" ], r "Force · without motion \(prefers-reduced-motion\)$" )
2026-11-07 23:43:26 +00:00
self . assertEqual ({ key : value for key , value in chips [ "inbox" ] . items () if key != "counted" }, { "hash" : "#stream/inbox" , "items" : chips [ "inbox" ][ "counted" ], "pressed" : "true" , "current" : [ "Inbox" ]})
self . assertGreaterEqual ( chips [ "inbox" ][ "items" ], 1 )
2026-11-08 17:26:51 +00:00
self . assertEqual ( chips [ "back" ], { "hash" : "#tasks/list" , "rows" : [[ "queue/gate-3" , "Queue job" ]]})
2026-11-07 15:03:14 +00:00
def test_chromium_sends_a_call_from_the_agent_panel_and_shows_its_receipt ( self ):
handled = []
def answer () -> None :
for script , args in self . scripts . calls [ len ( handled ) :]:
handled . append ( script )
if script . endswith ( "radio.py" ) and args [: 2 ] == [ "send" , "call" ]:
to , text , now = args [ args . index ( "--to" ) + 1 ], args [ - 1 ], time . time ()
rows = [{ "ts" : now , "from" : "Operator" , "to" : [ to ], "text" : text , "class" : "call" , "v" : 2 }, { "ts" : now + 0.5 , "from" : to , "to" : [ "Operator" ], "text" : text , "class" : "receipt" , "v" : 2 , "call" : now }]
with ( self . council / "news.jsonl" ) . open ( "a" , encoding = "utf-8" ) as handle :
handle . write ( self . jsonl ( rows ))
self . beside ( answer )
2026-11-07 19:59:47 +00:00
local = f "http://localhost: { self . port } /"
data = self . drive ([{ "width" : 1366 , "height" : 768 , "url" : f " { local } #stream" , "wait" : 900 , "probe" : CALL , "shot" : self . shot ( "1366-call-receipt" )}], self . authenticator ())
2026-11-07 15:03:14 +00:00
self . assertEqual ( data [ "errors" ], [])
result = data [ "results" ][ 0 ]
self . assertNotIn ( "error" , result )
self . assertIsNotNone ( result [ "receipt" ], result )
2026-11-08 17:26:51 +00:00
self . assertIn ( "Read" , result [ "receipt" ])
2026-11-07 15:03:14 +00:00
2026-11-07 19:59:47 +00:00
def test_chromium_shows_who_speaks_while_a_message_is_read ( self ):
self . speaks (( "e2e-cue-1" , "e2e-cue-2" ))
steps = [
{ "width" : 1366 , "height" : 768 , "url" : f " { self . base } #stream/radio" , "wait" : 900 , "probe" : SPEAK . replace ( "@CUE@" , "e2e-cue-1" ), "shot" : self . shot ( "1366-radio-speaking" ), "gesture" : True },
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : SPEAK_GRAPH , "shot" : self . shot ( "1366-agents-speaking" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : SPEAK_END , "shot" : None },
{ "width" : 390 , "height" : 844 , "url" : None , "wait" : 0 , "probe" : SPEAK . replace ( "@CUE@" , "e2e-cue-2" ), "shot" : self . shot ( "390-radio-speaking-reduced-motion" ), "gesture" : True , "media" : [{ "name" : "prefers-reduced-motion" , "value" : "reduce" }]},
]
data = self . drive ( steps , None )
( OUT / "chromium-speaking.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
speaking , graph , ended , calm = data [ "results" ]
2026-11-08 22:06:31 +00:00
self . talks ( speaking , graph )
self . assertEqual ( ended , { "ended" : True , "caption" : False , "chips" : 0 , "faces" : 0 })
2026-11-08 17:26:51 +00:00
self . assertEqual (( calm [ "who" ], calm [ "line" ], calm [ "ring" ]), ( "Stage speaking" , "Disk almost full (e2e-cue-2)" , "none" ), calm )
2026-11-08 22:06:31 +00:00
self . assertEqual ( calm [ "faces" ][ "speaking" ], [ True , "none" ], calm )
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_shows_who_speaks_and_moves_only_the_speakers_mouth ( self ):
self . speaks (( "e2e-cue-1" ,))
steps = [
{ "width" : 1366 , "height" : 768 , "url" : f " { self . base } #stream/radio" , "wait" : 900 , "probe" : SPEAK . replace ( "@CUE@" , "e2e-cue-1" ), "shot" : str ( OUT / "firefox-1366-radio-speaking.png" ), "gesture" : True },
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : SPEAK_GRAPH , "shot" : str ( OUT / "firefox-1366-agents-speaking.png" )},
]
data = self . drive ( steps , None , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
( OUT / "firefox-speaking.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "/favicon.ico" not in error ], [])
self . talks ( * data [ "results" ])
def talks ( self , speaking : dict , graph : dict ) -> None :
"""Args:
speaking: the SPEAK probe while Stage (s2) is read aloud.
graph: the SPEAK_GRAPH probe on #agents/orbit right after it.
"""
self . assertEqual (( speaking [ "who" ], speaking [ "line" ], speaking [ "chip" ], speaking [ "ring" ]), ( "Stage speaking" , "Disk almost full (e2e-cue-1)" , "s2" , "speaking" ), speaking )
self . assertEqual ( speaking [ "faces" ], { "speaking" : [ True , "talk" ], "silent" : [ False , "none" ], "keys" : [ "s2" ]}, speaking )
self . assertEqual (( graph [ "node" ] or "" ) . split ( "," )[ 0 ], "Stage" , graph )
self . assertEqual (( graph [ "ring" ], graph [ "mouth" ], graph [ "silent" ]), ( "speaking-node" , "talk" , "none" ), graph )
2026-11-07 19:59:47 +00:00
def test_chromium_records_transcribes_and_sends_a_signed_call_with_voice ( self ):
local = f "http://localhost: { self . port } /"
steps = [
{ "width" : 1366 , "height" : 768 , "url" : f " { local } #stream/radio" , "wait" : 1200 , "probe" : COMPOSE . replace ( "@TO@" , "Stage" ), "shot" : self . shot ( "1366-radio-composer" ), "gesture" : True },
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : COMPOSE_SEND . replace ( "@ATTACH@" , "false" ), "shot" : None },
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : COMPOSE . replace ( "@TO@" , "Stage" ), "shot" : None , "gesture" : True },
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : COMPOSE_SEND . replace ( "@ATTACH@" , "true" ), "shot" : self . shot ( "1366-radio-composer-sent" )},
]
data = self . drive ( steps , self . authenticator ())
( OUT / "chromium-composer.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
composed , plain , again , sent = data [ "results" ]
self . assertNotIn ( "error" , composed )
self . assertEqual ( composed [ "channels" ], [ "post" , "call" , "newsletter" , "broadcast" , "pan-pan" , "security" , "mayday" ])
self . assertIn ( "Stage" , composed [ "recipients" ])
self . assertEqual ( composed [ "recording" ], "true" )
self . assertEqual ( composed [ "text" ], "hello from the mic second line" , composed )
self . assertRegex ( composed [ "replay" ] or "" , r "^/api/voice/[0-9a-f] {16} \.wav$" )
2026-11-08 17:26:51 +00:00
self . assertEqual ( composed [ "attach" ], [ "Attach recording" , False ])
2026-11-07 19:59:47 +00:00
self . assertEqual ( composed [ "clashes" ], { "found" : [], "outside" : []})
2026-11-08 17:26:51 +00:00
self . assertEqual ( again [ "attach" ], [ "Attach recording" , False ])
2026-11-07 19:59:47 +00:00
note = again [ "replay" ] . removeprefix ( "/api/voice/" ) . removesuffix ( ".wav" )
for result in ( plain , sent ):
self . assertEqual (( result [ "left" ], result [ "voice" ]), ( "" , False ), result )
2026-11-08 17:26:51 +00:00
self . assertTrue ( result [ "sent" ] . startswith ( "Call to Stage: sent" ), result )
2026-11-07 19:59:47 +00:00
calls = [ args for script , args in self . scripts . calls if script == skill_scripts . RADIO ]
self . assertEqual ( len ( calls ), 2 , self . scripts . calls )
self . assertEqual ([ call [: 4 ] for call in calls ], [[ "send" , "call" , "--to" , "Stage" ]] * 2 )
2026-11-08 17:26:51 +00:00
self . assertTrue ( calls [ 0 ][ - 1 ] . endswith ( "] hello from the mic second line please" ), calls [ 0 ])
self . assertTrue ( calls [ 1 ][ - 1 ] . endswith ( f "] hello from the mic second line please voice: { HOST_STATE } /voice/ { note } .wav" ), calls [ 1 ])
2026-11-07 19:59:47 +00:00
status , _ , body = self . get ( f "/api/voice/ { note } .wav" )
self . assertEqual (( status , body [: 4 ]), ( 200 , b "RIFF" ))
def test_chromium_clears_the_mayday_only_after_the_passkey_signs ( self ):
local = f "http://localhost: { self . port } /"
data = self . drive ([{ "width" : 1366 , "height" : 768 , "url" : f " { local } #agents/orbit" , "wait" : 1200 , "probe" : CLEAR_MAYDAY , "shot" : self . shot ( "1366-mayday-cleared" )}], self . authenticator ())
self . assertEqual ( data [ "errors" ], [])
result = data [ "results" ][ 0 ]
self . assertNotIn ( "error" , result )
2026-11-08 17:26:51 +00:00
self . assertTrue (( result [ "done" ] or "" ) . startswith ( "Cancel mayday: done" ), result )
2026-11-07 19:59:47 +00:00
self . assertEqual ( self . scripts . calls , [( skill_scripts . RADIO , [ "clear" , "--force" , "--session" , "Operator" ])])
2026-11-08 23:18:18 +00:00
def check_night_menu ( self , browser : str , driver : str , binary : str , signs : bool ) -> None :
"""Args:
signs: cdp.mjs signs every prompt with its virtual passkey; bidi.mjs
has no virtual authenticator, so the probe answers each prompt
the way a canceled passkey dialog does.
"""
authenticator = self . authenticator ()
probe = NIGHT_MENU . replace ( "@SIGNS@" , "true" if signs else "false" )
step = { "width" : 1366 , "height" : 768 , "url" : f "http://localhost: { self . port } /#agents/orbit" , "wait" : 1200 , "probe" : probe , "shot" : str ( OUT / f " { browser } -1366-night-menu.png" )}
data = self . drive ([ step ], authenticator if signs else None , driver , binary )
( OUT / f " { browser } -night-menu.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
found = data [ "results" ][ 0 ]
self . assertNotIn ( "error" , found )
self . assertEqual ( found [ "labels" ], [ "Drain" , "Suspend" , "Resume" ])
for run , action in zip ( found [ "runs" ], ( "drain" , "suspend" , "resume" ), strict = True ):
with self . subTest ( browser = browser , action = action ):
self . assertEqual ( run [ "asked" ], 1 , run )
self . assertEqual ( len ( run [ "question" ]), 1 , run )
self . assertIn ( f "kvb-agent- { action } " , run [ "question" ][ 0 ])
signed = [[ "/api/board/sign" , [ "action" ], action ]]
if signs :
self . assertEqual ( run [ "posted" ], [ * signed , [ f "/api/board/ { action } " , [ "proof" ], None ]])
self . assertTrue (( run [ "toast" ] or "" ) . startswith ( f " { run [ 'label' ] } : done" ), run )
else :
self . assertEqual ( run [ "posted" ], signed )
self . assertTrue (( run [ "toast" ] or "" ) . startswith ( f " { run [ 'label' ] } : not signed" ), run )
nights = [ args for script , args in self . scripts . calls if script == skill_scripts . NIGHT ]
self . assertEqual ( nights , [[ action , "--session" , "Operator" ] for action in ( "drain" , "suspend" , "resume" )] if signs else [])
if not signs :
self . assertEqual ( self . scripts . calls , [])
def test_chromium_runs_drain_suspend_and_resume_only_after_the_passkey_signs ( self ):
self . check_night_menu ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ), signs = True )
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_asks_the_passkey_for_drain_suspend_and_resume_and_runs_nothing_unsigned ( self ):
self . check_night_menu ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ), signs = False )
2026-11-07 15:03:14 +00:00
def test_chromium_deletes_a_merged_branch_after_dry_run_and_signature ( self ):
tree = json . loads ( self . tree_file . read_text ( encoding = "utf-8" ))
tree [ "delete_ready" ], tree [ "delete_reason" ] = True , ""
self . tree_file . write_text ( json . dumps ( tree ), encoding = "utf-8" )
demo = f " { self . root } /repos/github.com/me/demo"
Path ( demo ) . mkdir ( parents = True )
fake = self . outside / "bin" / "pkgmgr"
fake . parent . mkdir ()
fake . write_text ( FAKE_PKGMGR . format ( python = sys . executable ), encoding = "utf-8" )
fake . chmod ( 0o755 )
worktree = f " { self . root } /worktrees/github.com/me/demo/feature-done"
( fake . parent / "plan.json" ) . write_text ( json . dumps ([{ "branch" : "feature/done" , "worktree" : worktree , "local_sha" : DONE_TIP , "head" : DONE_TIP , "state" : "merged-clean" , "skip" : None }]), encoding = "utf-8" )
following = json . loads ( json . dumps ( tree ))
following [ "repos" ][ 0 ][ "items" ] = [ item for item in following [ "repos" ][ 0 ][ "items" ] if item [ "branch" ] != "feature/done" ]
( fake . parent / "next-tree.json" ) . write_text ( json . dumps ( following ), encoding = "utf-8" )
2026-11-07 19:59:47 +00:00
authenticator = self . authenticator ()
2026-11-07 15:03:14 +00:00
trust = verifier . Trust ( approvers = self . approvers , replay = self . replay / "cleanup.json" , origin = self . origin )
runner = host . Cleanup ( trust , self . tree_file , fake )
jobs , results = self . cleanup / "jobs" , self . cleanup / "results"
self . beside ( lambda : any ( jobs . iterdir ()) and host . cycle ( jobs , results , runner ))
local = f "http://localhost: { self . port } /"
steps = [
{ "width" : 1366 , "height" : 768 , "url" : f " { local } #artifacts/tree" , "wait" : 1200 , "probe" : OPEN_DELETE , "shot" : self . shot ( "1366-delete-dry-run" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : CONFIRM , "shot" : self . shot ( "1366-delete-result" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : AFTER , "shot" : self . shot ( "1366-delete-tree-after" )},
]
data = self . drive ( steps , authenticator )
( OUT / "chromium-delete.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
opened , confirmed , after = data [ "results" ]
self . assertNotIn ( "error" , opened )
2026-11-08 17:26:51 +00:00
self . assertEqual ( opened [ "every" ], [ False , "Delete all merged: 1 branches after dry run and signature" ])
self . assertEqual ( opened [ "buttons" ], [[ False , "Delete: remove feature/done locally, after dry run and signature" ]])
self . assertEqual ( opened [ "title" ], "Delete feature/done" )
2026-11-07 15:03:14 +00:00
self . assertEqual ( len ( opened [ "dry" ]), 1 )
2026-11-08 17:26:51 +00:00
self . assertTrue ( opened [ "dry" ][ 0 ] . startswith ( "feature/done @ 5b1e6fd3a7c0: merged-clean, merged by #12" ), opened [ "dry" ])
self . assertIn ( "stays: remote-branch (local only)" , opened [ "dry" ][ 0 ])
self . assertEqual (( confirmed [ "result" ], confirmed [ "text" ]), ( "done" , "feature/donedeleted" ), confirmed )
self . assertEqual ( confirmed [ "toasts" ], [ "Delete: donefeature/done: deleted" ])
self . assertEqual ( after [ "past" ], [ "done" , "deleted" ])
2026-11-07 15:03:14 +00:00
self . assertNotIn ( "feature/done" , after [ "branches" ])
written = [ json . loads ( path . read_text ( encoding = "utf-8" )) for path in results . glob ( "*.json" )]
self . assertEqual ([[( item [ "branch" ], item [ "status" ]) for item in result [ "items" ]] for result in written ], [[( "feature/done" , "done" )]])
calls = [ json . loads ( line )[ "argv" ] for line in ( fake . parent / "calls.jsonl" ) . read_text ( encoding = "utf-8" ) . splitlines ()]
self . assertEqual ( calls , [
[ "branch" , "cleanup" , "--json" , "--local" , "--branch" , "feature/done" ],
[ "branch" , "cleanup" , "--apply" , "--json" , "--local" , "--branch" , "feature/done" ],
[ "branch" , "tree" , "--format" , "json" , "--output" , str ( self . tree_file )],
])
2026-11-06 22:20:06 +00:00
2026-11-07 17:46:39 +00:00
def test_chromium_folds_the_hierarchy_by_keyboard_reorders_agents_and_hands_an_office_over_with_a_signature ( self ):
signer = self . pin ()
trust = verifier . Trust ( approvers = self . approvers , replay = self . replay / "offices.json" , origin = self . origin )
runner = office_host . Offices ( trust , self . council , COUNCIL_SCRIPT , self . outside / "home" )
jobs , results = self . offices / "jobs" , self . offices / "results"
self . beside ( lambda : any ( jobs . iterdir ()) and office_host . cycle ( jobs , results , runner ))
private = "" . join ( line for line in signer . pem . read_text ( encoding = "utf-8" ) . splitlines () if not line . startswith ( "-----" ))
authenticator = { "credentialId" : base64 . b64encode ( signer . raw_id ) . decode (), "privateKey" : private , "rpId" : "localhost" }
local = f "http://localhost: { self . port } /"
steps = [
{ "width" : 1366 , "height" : 768 , "url" : f " { local } #agents/hierarchy" , "wait" : 1200 , "probe" : ORG_OPEN , "shot" : self . shot ( "1366-hierarchy-open" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "keys" : [ "Enter" ], "probe" : ORG_FOLDED , "shot" : None },
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "keys" : [ "Enter" ], "probe" : ORDER_MOVED , "shot" : self . shot ( "1366-hierarchy-order" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : OPEN_TAKEOVER , "shot" : self . shot ( "1366-hierarchy-takeover" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : CONFIRM_TAKEOVER , "shot" : self . shot ( "1366-hierarchy-takeover-result" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : AFTER_TAKEOVER , "shot" : self . shot ( "1366-hierarchy-after" )},
]
data = self . drive ( steps , authenticator )
( OUT / "chromium-hierarchy.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
opened , folded , moved , sheet , confirmed , after = data [ "results" ]
self . assertNotIn ( "error" , opened )
self . assertEqual ( opened [ "focused" ], "toggle:dimension:identity" )
2026-11-08 17:26:51 +00:00
self . assertEqual (( folded [ "focused" ], folded [ "expanded" ], folded [ "label" ], folded [ "hidden" ], folded [ "again" ]), ( "toggle:dimension:identity" , "false" , "Expand: identity" , True , "true" ))
2026-11-07 17:46:39 +00:00
self . assertNotIn ( "identity/oidc" , folded [ "seen" ])
before = folded [ "before" ]
self . assertEqual ( moved [ "after" ], [ before [ 1 ], before [ 0 ], * before [ 2 :]])
self . assertEqual (( moved [ "focused" ], moved [ "disabled" ]), ( f "down: { before [ 1 ] } " , False ))
2026-11-08 17:26:51 +00:00
self . assertTrue ( moved [ "toast" ] . startswith ( "Priority" ), moved )
2026-11-07 17:46:39 +00:00
self . assertIn (( "rasci.py" , [ "agents" , "order" , "," . join ( moved [ "after" ])]), [( Path ( script ) . name , args ) for script , args in self . scripts . calls ])
2026-11-08 17:26:51 +00:00
self . assertEqual (( sheet [ "title" ], sheet [ "options" ], sheet [ "work" ], sheet [ "focused" ]), ( "identity/oidc: Hand over" , [ "s1" ], "oidc line" , "SELECT" ))
self . assertEqual (( confirmed [ "result" ], confirmed [ "text" ]), ( "done" , "identity/oidcdone" ), confirmed )
self . assertIn ( "Hand over: doneidentity/oidc: done" , confirmed [ "toasts" ])
2026-11-07 17:46:39 +00:00
self . assertNotIn ( "error" , after )
claim = json . loads (( self . council / "identity" / "oidc" / "claim.json" ) . read_text ( encoding = "utf-8" ))
self . assertEqual (( claim [ "session" ], claim [ "work" ]), ( "s1" , "oidc line" ))
written = [ json . loads ( path . read_text ( encoding = "utf-8" )) for path in results . glob ( "*.json" )]
self . assertEqual ([[( item [ "office" ], item [ "action" ], item [ "status" ]) for item in result [ "items" ]] for result in written ], [[( "identity/oidc" , "takeover" , "done" )]])
2026-11-07 18:14:21 +00:00
def inbox_work ( self ) -> None :
stage = { "title" : "Stage line" , "status" : "running" , "worker" : "Stage (this session)" , "roles" : { "Stage (this session)" : "RA" }}
2026-11-08 17:26:51 +00:00
greet = { "title" : SLOW , "workdir" : str ( self . outside ), "description" : "counts" , "check" : "manual" , "status" : "queued" , "roles" : { "Operator" : "R" , "Skills session" : "A" }}
2026-11-07 18:14:21 +00:00
push = { "title" : "git push" , "workdir" : "/repo" , "description" : "ship" , "check" : "manual" , "status" : "queued" , "roles" : { "Operator" : "R" , "Skills session" : "A" }}
self . write_council ({ "rasci.json" : { "work/stage" : stage , "operator/greet" : greet , "operator/push" : push }})
arguments = { "kind" : "ask" , "tool" : "Bash" , "pattern" : "make deploy" , "scope" : "/repo*" , "expires" : int ( time . time ()) + 7 * 86400 , "agent" : "Skills" , "why" : "deploy runs after every green gate" }
self . assertFalse ( self . tool ( "rule_request" , arguments )[ "isError" ])
def layout ( self , probe : dict ) -> None :
self . assertNotIn ( "error" , probe )
2026-11-08 17:26:51 +00:00
self . assertEqual ( sorted ( probe [ "kinds" ]), [ "Ask" , "Operator" , "Operator" , "Review" , "Rule request" ])
self . assertIn ([ "Deny" , "Deny via …" ], probe [ "splits" ])
2026-11-07 18:14:21 +00:00
self . assertEqual ( len ( probe [ "splits" ]), 4 )
2026-11-08 17:26:51 +00:00
self . assertEqual ( probe [ "fields" ], [ "Kind" , "Tool (glob)" , "Pattern (glob)" , "Directory (glob)" , "Expires on" ])
self . assertEqual ( probe [ "dry" ], "Dry run: the rule would have granted 1 of 1 asks from the last 7 days." )
2026-11-07 18:14:21 +00:00
self . assertEqual ( probe [ "accept" ], 1 )
self . assertTrue ( probe [ "fits" ] and probe [ "followed" ], probe )
self . assertEqual (( probe [ "unlabelled" ], probe [ "wide" ]), ([], False ))
def test_chromium_runs_a_command_into_its_console_denies_by_call_and_accepts_a_rule ( self ):
self . inbox_work ()
signer = self . pin ()
trust = [ "--approvers" , str ( self . approvers ), "--replay" , str ( self . replay / "runner.json" )]
def run_jobs () -> None :
if any ( job [ "status" ] == "queued" for job in self . httpd . hub . jobs . listed ()):
subprocess . run ([ sys . executable , str ( RUNNER ), "--port" , str ( self . port ), "--interval" , "0.1" , * trust , "--once" ], capture_output = True , timeout = 120 , check = False )
self . beside ( run_jobs )
private = "" . join ( line for line in signer . pem . read_text ( encoding = "utf-8" ) . splitlines () if not line . startswith ( "-----" ))
authenticator = { "credentialId" : base64 . b64encode ( signer . raw_id ) . decode (), "privateKey" : private , "rpId" : "localhost" }
inbox = f "http://localhost: { self . port } /#stream/inbox"
steps = [
{ "width" : 1366 , "height" : 768 , "url" : inbox , "wait" : 1200 , "probe" : RUN_START , "shot" : self . shot ( "1366-inbox-console-running" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : RUN_HELD , "shot" : self . shot ( "1366-inbox-console-scrolled-up" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : RUN_DONE , "shot" : self . shot ( "1366-inbox-console-done" )},
]
for width , height in WIDTHS :
fresh = f "http://localhost: { self . port } /? { width } #stream/inbox"
steps . append ({ "width" : width , "height" : height , "url" : fresh , "wait" : 1200 , "probe" : LAYOUT , "shot" : self . shot ( f " { width } -inbox-console" )})
steps . append ({ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : DENY_SHEET , "shot" : self . shot ( f " { width } -inbox-deny-sheet" )})
steps += [
{ "width" : 1366 , "height" : 768 , "url" : inbox , "wait" : 1200 , "probe" : DENY , "shot" : self . shot ( "1366-inbox-deny-undo" )},
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : DENIED , "shot" : None },
{ "width" : 1366 , "height" : 768 , "url" : None , "wait" : 0 , "probe" : ACCEPT , "shot" : self . shot ( "1366-inbox-rule-accepted" )},
]
data = self . drive ( steps , authenticator )
( OUT / "chromium-inbox.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
started , held , done , * rest = data [ "results" ]
self . assertNotIn ( "error" , started )
2026-11-08 17:26:51 +00:00
self . assertIn ( "takes effect in" , started [ "toast" ])
self . assertTrue ( started [ "running" ] . startswith ( "running" ), started )
2026-11-07 18:14:21 +00:00
self . assertTrue ( started [ "overflows" ] and started [ "followed" ], started )
self . assertLess ( held [ "held" ], 50 , held )
2026-11-08 17:26:51 +00:00
self . assertEqual (( done [ "last" ], done [ "lines" ], done [ "followed" ]), ( "finished" , 81 , True ))
self . assertRegex ( done [ "done" ], r "^doneExit code 0 after \d+ s$" )
self . assertIn ( "Close" , done [ "actions" ])
2026-11-07 18:14:21 +00:00
layouts , sheets = rest [ 0 : 6 : 2 ], rest [ 1 : 6 : 2 ]
for probe , sheet , ( width , _ ) in zip ( layouts , sheets , WIDTHS , strict = True ):
with self . subTest ( width = width ):
self . layout ( probe )
2026-11-08 17:26:51 +00:00
self . assertTrue ( probe [ "console" ] . startswith ( "doneExit code 0" ), probe )
self . assertEqual ( sheet [ "title" ], "Deny" )
2026-11-07 18:14:21 +00:00
self . assertEqual ( sheet [ "channels" ], [[ "post" , True ], [ "call" , False ], [ "newsletter" , False ], [ "broadcast" , False ], [ "pan-pan" , False ], [ "security" , False ]])
2026-11-08 17:26:51 +00:00
self . assertEqual ( sheet [ "hints" ][: 3 ], [ "to Skills, when its turn ends" , "to Skills, at once, wakes it" , "to all, when their turn ends" ])
2026-11-07 18:14:21 +00:00
self . assertEqual (( sheet [ "reason" ], sheet [ "unlabelled" ], sheet [ "wide" ]), ( True , [], False ))
denied , gone , accepted = rest [ 6 :]
2026-11-08 17:26:51 +00:00
self . assertTrue ( denied [ "undo" ] . startswith ( "Denial (Call): Bash: make deploy" ), denied )
2026-11-07 18:14:21 +00:00
self . assertFalse ( denied [ "sheet" ])
self . assertTrue ( gone [ "gone" ])
2026-11-08 17:26:51 +00:00
self . assertEqual (( accepted [ "toast" ], accepted [ "left" ]), ( "Created: Rule: ask Bash make deploy in /repo*" , False ))
2026-11-07 18:14:21 +00:00
ask = self . httpd . hub . asks . listed ()[ 0 ]
2026-11-08 17:26:51 +00:00
self . assertEqual (( ask [ "status" ], ask [ "reason" ], ask [ "channel" ]), ( "denied" , "make the gate green first" , "call" ))
2026-11-07 18:14:21 +00:00
calls = [ args for script , args in self . scripts . calls if script . endswith ( "radio.py" )]
self . assertEqual ( calls [ - 1 ][: 4 ], [ "send" , "call" , "--to" , "Skills session" ])
2026-11-08 17:26:51 +00:00
self . assertTrue ( calls [ - 1 ][ - 1 ] . endswith ( "Reason: make the gate green first" ), calls [ - 1 ])
2026-11-07 18:14:21 +00:00
self . assertEqual ([( rule [ "kind" ], rule [ "pattern" ], rule [ "proof" ] is not None ) for rule in self . httpd . hub . rules . listed ()], [( "ask" , "make deploy" , True )])
self . assertEqual ([( job [ "status" ], job [ "code" ]) for job in self . httpd . hub . jobs . listed ()], [( "done" , 0 )])
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_draws_the_inbox_console_and_the_rule_request_at_three_widths ( self ):
self . inbox_work ()
now = time . time ()
fields = { "task" : "operator/greet" , "command" : SLOW , "directory" : str ( self . outside ), "sha256" : "" , "status" : "done" , "proof" : None }
2026-11-08 17:26:51 +00:00
output = "" . join ( f "Row { number } \n " for number in range ( 1 , 81 )) + "finished \n "
2026-11-07 18:14:21 +00:00
self . httpd . hub . jobs . add ({ ** fields , "code" : 0 , "output" : output , "created_ts" : now - 20 , "started" : now - 19 , "finished" : now - 10 , "dismissed" : False })
shots = [( width , height , OUT / f "firefox- { width } -inbox-console.png" ) for width , height in WIDTHS ]
steps = [{ "width" : width , "height" : height , "url" : f " { self . base } #stream/inbox" , "wait" : 1200 , "probe" : LAYOUT , "shot" : str ( shot )} for width , height , shot in shots ]
data = self . drive ( steps , None , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
( OUT / "firefox-inbox.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
for probe , ( width , _ , shot ) in zip ( data [ "results" ], shots , strict = True ):
with self . subTest ( width = width ):
self . layout ( probe )
2026-11-08 17:26:51 +00:00
self . assertEqual ( probe [ "console" ], "doneExit code 0 after 9 s" )
2026-11-07 18:14:21 +00:00
self . assertTrue ( shot . is_file () and shot . stat () . st_size > 1000 , shot )
2026-11-09 06:53:55 +00:00
def choice_steps ( self , browser : str , signs : bool , questions : dict [ int , str ]) -> list [ dict ]:
flag = "true" if signs else "false"
local = f "http://localhost: { self . port } /"
steps = []
for index , ( width , height ) in enumerate ( WIDTHS ):
cards = len ( WIDTHS ) - index if signs else len ( WIDTHS )
steps += [
{ "width" : width , "height" : height , "url" : f " { local } ? { width } #stream/inbox" , "wait" : 1200 , "probe" : CHOICE_VIEW . replace ( "@CARDS@" , str ( cards )), "shot" : str ( OUT / f " { browser } - { width } -tasks-choices.png" )},
{ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : CHOICE_SEND . replace ( "@SIGNS@" , flag ), "shot" : str ( OUT / f " { browser } - { width } -choice-request.png" )},
{ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : CHOICE_ANSWER . replace ( "@SIGNS@" , flag ) . replace ( "@QUESTION@" , questions [ width ]), "shot" : str ( OUT / f " { browser } - { width } -choice-answer.png" )},
* ({ "width" : width , "height" : height , "url" : f " { local } ? { width } #tasks/ { view } " , "wait" : 900 , "probe" : TASKS_FIT , "shot" : str ( OUT / f " { browser } - { width } -tasks- { view } -fit.png" )} for view in TASK_VIEWS ),
]
return steps
def requested ( self , sent : dict , signs : bool ) -> None :
"""Args:
sent: the CHOICE_SEND probe: two agents ticked, then every channel
button pressed once.
"""
labels = dict ( zip ( CHOICE_CHANNELS , [ "Post" , "Call" , "Newsletter" , "Broadcast" , "Pan-Pan" , "Sécurité" , "Mayday" ], strict = True ))
self . assertEqual ( len ( sent [ "picked" ]), 2 , sent )
self . assertEqual (( sent [ "counted" ], sent [ "fields" ], sent [ "tall" ], sent [ "wide" ]), ( "2" , 0 , False , False ))
self . assertEqual ([ run [ "channel" ] for run in sent [ "runs" ]], CHOICE_CHANNELS )
for run in sent [ "runs" ]:
with self . subTest ( channel = run [ "channel" ]):
to = sent [ "picked" ] if run [ "channel" ] in ( "post" , "call" ) else [ "*" ]
signed = [[ "/api/choices/sign" , [ "class" , "to" ], run [ "channel" ], to ]]
self . assertEqual (( run [ "disabled" ], run [ "asked" ], len ( run [ "question" ])), ( False , 1 , int ( run [ "channel" ] == "mayday" )), run )
title = f "Choice request ( { labels [ run [ 'channel' ]] } ) to { ', ' . join ( to ) if to != [ '*' ] else 'all' } "
self . assertEqual ( run [ "posted" ], [ * signed , [ "/api/choices" , [ "class" , "proof" , "to" ], run [ "channel" ], to ]] if signs else signed )
self . assertTrue (( run [ "toast" ] or "" ) . startswith ( f " { title } : { 'sent' if signs else 'not signed' } " ), run )
def answered ( self , result : dict , signs : bool ) -> None :
self . assertNotIn ( "error" , result )
self . assertEqual (( result [ "title" ], result [ "asked" ], result [ "tall" ], result [ "wide" ]), ( "Wait: your passkey signs this answer, a post to Stage" , 1 , False , False ))
signed = [[ "/api/sign" , [ "items" ], None , None ]]
self . assertEqual ( result [ "posted" ], [ * signed , [ "/api/inbox/approve" , [ "items" , "proof" ], None , None ]] if signs else signed )
self . assertTrue (( result [ "toast" ] or "" ) . startswith ( "Choice: Wait" + ( "" if signs else ": not signed" )), result )
self . assertEqual ( result [ "gone" ], True if signs else None )
def check_choices ( self , browser : str , driver : str , binary : str , signs : bool ) -> None :
"""Args:
signs: cdp.mjs signs every prompt with its virtual passkey; bidi.mjs
has no virtual authenticator, so the probes answer each prompt
the way a canceled passkey dialog does.
"""
self . inbox_work ()
self . create_ask ({ ** ASK , "command" : "make choices" , "timeout" : 3600 })
authenticator = self . authenticator ()
questions = { width : f "Pick a name at { width } px?" for width , _ in WIDTHS }
decisions = { width : self . tool ( "decision_request" , { "question" : question , "options" : OPTIONS , "agent" : "Stage" , "recommended" : "" })[ "structuredContent" ][ "id" ] for width , question in questions . items ()}
data = self . drive ( self . choice_steps ( browser , signs , questions ), authenticator if signs else None , driver , binary )
( OUT / f " { browser } -choices.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
per = 3 + len ( TASK_VIEWS )
for index , ( width , _ ) in enumerate ( WIDTHS ):
view , sent , answer , * fitted = data [ "results" ][ per * index : per * ( index + 1 )]
with self . subTest ( browser = browser , width = width ):
self . assertNotIn ( "error" , view )
self . assertEqual ( view [ "kinds" ], [ "Ask" , "Choice" , "Operator" , "Review" , "Rule request" ])
self . assertEqual ( view [ "filter" ], [ "Waits on Operator" , "true" ])
self . assertEqual ( view [ "channels" ], [[ channel , label , channel in ( "post" , "call" )] for channel , label in zip ( CHOICE_CHANNELS , [ "Post" , "Call" , "Newsletter" , "Broadcast" , "Pan-Pan" , "Sécurité" , "Mayday" ], strict = True )])
self . assertEqual (( view [ "fields" ], view [ "choices" ], view [ "multi" ], view [ "unlabelled" ], view [ "tall" ], view [ "wide" ]), ( 0 , 0 , [ "Agents" , "0" , 0 ], [], False , False ))
self . assertGreaterEqual ( len ( view [ "agents" ]), 2 , view )
self . assertIn ([ "Choice" , [ "Merge now" , "Wait" ]], view [ "cards" ])
self . requested ( sent , signs )
self . answered ( answer , signs )
for fit , name in zip ( fitted , TASK_VIEWS , strict = True ):
self . assertEqual (( fit [ "hash" ], fit [ "bar" ], fit [ "choices" ], fit [ "tall" ], fit [ "wide" ]), ( f "#tasks/ { name } " , CHOICE_CHANNELS , 0 , False , False ), fit )
if name == "board" :
self . assertEqual ( fit [ "columns" ], [ "queued" , "running" , "blocked" , "done" , "stopped" ])
hub = self . httpd . hub
if not signs :
self . assertEqual (( self . scripts . calls , hub . messages . listed ()), ([], []))
self . assertEqual ([ hub . decisions . get ( decision )[ "status" ] for decision in decisions . values ()], [ "pending" ] * len ( WIDTHS ))
return
requests = [ record for record in hub . messages . listed () if record [ "text" ] == CHOICE_REQUEST ]
self . assertEqual (( len ( requests ), sorted ({ record [ "class" ] for record in requests })), ( len ( WIDTHS ) * 9 , sorted ( CHOICE_CHANNELS )))
for record in requests :
self . assertTrue ( self . tool ( "operator_message" , { "id" : record [ "id" ], "text" : CHOICE_REQUEST , "to" : record [ "to" ]})[ "structuredContent" ][ "match" ], record [ "id" ])
radio = [ args for script , args in self . scripts . calls if script == skill_scripts . RADIO ]
self . assertEqual ( len ( radio ), len ( requests ) + len ( WIDTHS ))
for width , decision in decisions . items ():
with self . subTest ( width = width ):
state = self . tool ( "decision_wait" , { "id" : decision , "timeout_seconds" : 0 })[ "structuredContent" ]
self . assertEqual (( state [ "status" ], state [ "choice" ], state [ "answer" ]), ( "decided" , "Wait" , f "Choice for decision { decision } ( { questions [ width ] } ): Wait" ))
found = self . tool ( "operator_message" , { "id" : state [ "message" ], "text" : state [ "answer" ], "to" : "Stage" })[ "structuredContent" ]
self . assertEqual (( found [ "match" ], found [ "class" ], found [ "to" ]), ( True , "post" , "Stage" ))
self . assertIn ([ "post" , "--to" , "Stage" , "--session" , "Operator" , f "[kvb-orchestrator msg { state [ 'message' ] } ] { state [ 'answer' ] } " ], radio )
def test_chromium_requests_choices_on_every_channel_and_signs_a_choice_at_three_widths ( self ):
self . check_choices ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ), signs = True )
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_asks_the_passkey_for_every_choice_request_and_answer_and_sends_nothing_unsigned ( self ):
self . check_choices ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ), signs = False )
def plan_work ( self , now : float ) -> None :
register = json . loads (( self . council / "rasci.json" ) . read_text ( encoding = "utf-8" ))
register [ "work/feature" ][ "start" ] = iso ( now - 1800 )
register [ "work/stage" ][ "start" ] = iso ( now - 900 )
register . update ({
"work/done-a" : { "title" : "Done A" , "status" : "done" , "worker" : "Stage" , "branch" : "feature/base" , "start" : iso ( now - 36000 ), "end" : iso ( now - 28800 ), "roles" : { "Stage" : "RA" }, "estimates" : { "Stage" : { "value" : 5 , "complexity" : 2 }}},
"work/done-b" : { "title" : "Done B" , "status" : "done" , "worker" : "Docs" , "start" : iso ( now - 25200 ), "end" : iso ( now - 10800 ), "roles" : { "Docs" : "RA" }, "estimates" : { "Docs" : { "value" : 3 , "complexity" : 2 }}},
"work/done-c" : { "title" : "Done C" , "status" : "done" , "worker" : "Docs" , "start" : iso ( now - 14400 ), "end" : iso ( now - 10800 ), "roles" : { "Docs" : "RA" }},
"work/next" : { "title" : "Next step" , "status" : "queued" , "after" : [ "work/feature" ], "roles" : { "Skills" : "R" , "Maintainer" : "A" }, "estimates" : { "Skills" : { "value" : 8 , "complexity" : 3 }}},
"work/child" : { "title" : "Child of the base" , "status" : "queued" , "base" : "feature/base" , "roles" : { "Docs" : "RA" }},
})
queue = json . loads (( self . council / "queue.json" ) . read_text ( encoding = "utf-8" ))
queue [ "entries" ] += [
{ "id" : "deploy-4" , "seq" : 4 , "kind" : "deploy" , "task" : "work/stage" , "workdir" : "/repo" , "session" : "s2" , "name" : "Stage" , "hotfix" : None , "status" : "active" , "enqueued" : now - 400 , "granted" : now - 300 , "cmd" : "make deploy" },
{ "id" : "test-5" , "seq" : 5 , "kind" : "test" , "task" : "work/docs" , "workdir" : "/repo" , "session" : "s2" , "name" : "Docs" , "hotfix" : None , "status" : "waiting" , "enqueued" : now - 60 , "granted" : None , "cmd" : "make test" },
]
self . write_council ({ "rasci.json" : register , "queue.json" : queue })
def plan_steps ( self , browser : str , signs : bool ) -> list [ dict ]:
local = f "http://localhost: { self . port } /"
flag = "true" if signs else "false"
steps = []
for width , height in WIDTHS :
bulk = BULK . replace ( "@SIGNS@" , flag ) . replace ( "@VALUE@" , PLAN_VALUES [ width ])
steps += [
{ "width" : width , "height" : height , "url" : f " { local } ? { width } #tasks/gantt" , "wait" : 1200 , "probe" : GANTT , "shot" : str ( OUT / f " { browser } - { width } -tasks-gantt.png" )},
{ "width" : width , "height" : height , "url" : f " { local } ? { width } #tasks/burn" , "wait" : 900 , "probe" : BURN , "shot" : str ( OUT / f " { browser } - { width } -tasks-burn.png" )},
{ "width" : width , "height" : height , "url" : None , "wait" : 0 , "probe" : POINTS_ON , "shot" : None },
{ "width" : width , "height" : height , "url" : f " { local } ? { width } -reloaded#tasks/burn" , "wait" : 1200 , "probe" : REQUEST , "shot" : str ( OUT / f " { browser } - { width } -tasks-burn-points.png" )},
{ "width" : width , "height" : height , "url" : f " { local } ? { width } -points#tasks/gantt" , "wait" : 1200 , "probe" : GANTT , "shot" : str ( OUT / f " { browser } - { width } -tasks-gantt-points.png" )},
{ "width" : width , "height" : height , "url" : f " { local } ? { width } -list#tasks/list" , "wait" : 1200 , "probe" : bulk , "shot" : str ( OUT / f " { browser } - { width } -tasks-business-value.png" )},
]
return steps
def planned ( self , probe : dict , points : bool ) -> None :
hour = 3600
edges = [[ "after" , "work/feature" , "work/next" ], [ "base" , "work/done-a" , "work/child" ], [ "queue" , "queue/deploy-4" , "queue/gate-3" ], [ "queue" , "queue/gate-3" , "queue/test-5" ]]
self . assertEqual ( probe [ "forecast" ], [ "always" , [[ "always" , "always" ], [ "points" , "points only" ], [ "real" , "real only" ]]])
self . assertEqual ( probe [ "modes" ], [[ "none" , str ( not points ) . lower ()], [ "points" , str ( points ) . lower ()]])
self . assertEqual (( probe [ "request" ], probe [ "inner" ]), ( points , True ))
self . assertEqual ( sorted ([ kind , start , end ] for kind , start , end , _ in probe [ "edges" ]), sorted ( edges ))
self . assertEqual ({ tip for * _ , tip in probe [ "edges" ]}, { "url(#gantt-tip)" })
bars = { task : ( forecast , start , end , tag ) for task , forecast , start , end , tag in probe [ "bars" ]}
self . assertNotIn ( "operator/push" , bars )
self . assertEqual (( bars [ "work/old" ][ 0 ], bars [ "work/old" ][ 3 ]), ( False , "circle" ))
self . assertEqual ([ bars [ task ][ 0 ] for task in ( "work/feature" , "work/stage" , "queue/deploy-4" , "work/done-a" )], [ False ] * 4 )
self . assertGreaterEqual ( bars [ "work/feature" ][ 2 ] - bars [ "work/feature" ][ 1 ], 1800 )
forecasts = ( "work/next" , "work/docs" , "work/child" , "queue/gate-3" , "queue/test-5" )
lengths = { task : round ( bars [ task ][ 2 ] - bars [ task ][ 1 ], 3 ) for task in forecasts }
self . assertEqual ( lengths , { "work/next" : ( 4.5 if points else 2 ) * hour , "work/docs" : ( 3 if points else 2 ) * hour , "work/child" : 2 * hour , "queue/gate-3" : 2 * hour , "queue/test-5" : 2 * hour })
self . assertTrue ( all ( bars [ task ][ 0 ] for task in forecasts ))
self . assertEqual (( bars [ "work/next" ][ 1 ], bars [ "queue/test-5" ][ 1 ]), ( bars [ "work/feature" ][ 2 ], bars [ "queue/gate-3" ][ 2 ]))
self . assertTrue ( probe [ "hatched" ] and all ( probe [ "hatched" ]), probe [ "hatched" ])
self . assertEqual ( probe [ "real" ], 0 )
only = { task : ( round ( length , 3 ), tag ) for task , length , tag in probe [ "pointsOnly" ]}
dot = ( 0 , "circle" )
self . assertEqual ( only , { "work/next" : ( 4.5 * hour , "rect" ) if points else dot , "work/docs" : ( 3 * hour , "rect" ) if points else dot , "work/child" : dot , "queue/gate-3" : dot , "queue/test-5" : dot })
def check_plan ( self , browser : str , driver : str , binary : str , signs : bool ) -> None :
"""Args:
signs: cdp.mjs signs the bulk business value with its virtual
passkey; bidi.mjs has no virtual authenticator, so the probe
answers the prompt the way a canceled passkey dialog does.
"""
self . plan_work ( time . time ())
authenticator = self . authenticator ()
data = self . drive ( self . plan_steps ( browser , signs ), authenticator if signs else None , driver , binary )
( OUT / f " { browser } -plan.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ([ error for error in data [ "errors" ] if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
rows = self . snapshot ()[ "tasks" ]
ranked = list ( enumerate ( rows ))
status_up = [ row [ "task" ] for _ , row in sorted ( ranked , key = lambda pair : ( STATUS_ORDER . index ( pair [ 1 ][ "status" ]), pair [ 0 ]))]
status_down = [ row [ "task" ] for _ , row in sorted ( ranked , key = lambda pair : ( - STATUS_ORDER . index ( pair [ 1 ][ "status" ]), pair [ 0 ]))]
for index , ( width , _ ) in enumerate ( WIDTHS ):
gantt , burn , points_on , request , gantt_points , bulk = data [ "results" ][ 6 * index : 6 * ( index + 1 )]
value = PLAN_VALUES [ width ]
items = [[ "work/next" , f "estimate work/next Operator --value { value } " ], [ "work/child" , f "estimate work/child Operator --value { value } " ]]
with self . subTest ( browser = browser , width = width ):
for probe in ( gantt , burn , points_on , request , gantt_points , bulk ):
self . assertNotIn ( "error" , probe )
self . assertEqual (( probe [ "tall" ], probe [ "wide" ]), ( False , False ), probe )
self . planned ( gantt , points = False )
self . planned ( gantt_points , points = True )
self . assertEqual (( burn [ "modes" ], burn [ "request" ]), ([[ "none" , "true" ], [ "points" , "false" ]], False ))
self . assertEqual ( burn [ "scope" ][ 0 ], "all" )
self . assertEqual ( burn [ "scope" ][ 1 ][: 2 ], [ "all" , "filter" ])
self . assertIn ( "agent:Docs" , burn [ "scope" ][ 1 ])
self . assertIn ( "line:identity/oidc" , burn [ "scope" ][ 1 ])
self . assertEqual ( burn [ "series" ], [[ "scope" , 9 ], [ "done" , 4 ], [ "remaining" , 5 ]])
self . assertEqual ( burn [ "agent" ], [[ "scope" , 5 ], [ "done" , 2 ], [ "remaining" , 3 ]])
self . assertEqual ( points_on [ "before" ], { "modes" : [[ "none" , "true" ], [ "points" , "false" ]], "request" : False })
self . assertEqual (( points_on [ "modes" ], points_on [ "request" ]), ([[ "none" , "false" ], [ "points" , "true" ]], True ))
self . assertEqual ( request [ "modes" ], [[ "none" , "false" ], [ "points" , "true" ]])
self . assertEqual ( request [ "series" ], [[ "scope" , 12 ], [ "done" , 4 ], [ "remaining" , 8 ], [ "unestimated" , 2 ]])
self . assertTrue ( request [ "toast" ] . startswith ( f "Request estimates: { 'done' if signs else 'not signed' } " ), request [ "toast" ])
self . assertEqual ( bulk [ "sorts" ], [ "prio" , "wsjf" , "value" , "complexity" , "status" , "start" , "end" , "agent" , "title" ])
self . assertEqual (( bulk [ "prio" ], bulk [ "status" ], bulk [ "statusDown" ]), ([ row [ "task" ] for row in rows ], status_up , [ status_down , "Descending" ]))
self . assertEqual (( bulk [ "picks" ], bulk [ "armed" ]), ([ "work/feature" , "work/next" , "work/docs" , "work/child" , "work/stage" ], [ False , "2" ]))
self . assertTrue ( bulk [ "toast" ] . startswith ( f "Business value { value } for 2 tasks: { 'handed to the runner' if signs else 'not signed' } " ), bulk [ "toast" ])
signed = [[ "/api/sign" , [ "items" ], items ]]
self . assertEqual (( bulk [ "asked" ], bulk [ "posted" ]), ( 1 , [ * signed , [ "/api/tasks" , [ "items" , "proof" ], items ]] if signs else signed ))
self . assertEqual (( bulk [ "after" ], bulk [ "reset" ]), ([ True , 0 ] if signs else [ False , 2 ], True ))
self . assertEqual ([ args for script , args in self . scripts . calls if script == skill_scripts . ESTIMATE ], [[ "request" , "--session" , "Operator" ]] * ( len ( WIDTHS ) if signs else 0 ))
self . assertEqual ( self . httpd . hub . settings . get ()[ "estimation" ], "none" )
jobs = self . runner_jobs ()
if not signs :
self . assertEqual ( jobs , [])
register = json . loads (( self . council / "rasci.json" ) . read_text ( encoding = "utf-8" ))
self . assertEqual ([ task for task , row in register . items () if "Operator" in row . get ( "estimates" , {})], [])
return
self . assertEqual ([ job [ "argv" ][ - 1 ] for job in jobs ], [ PLAN_VALUES [ width ] for width , _ in WIDTHS for _ in range ( 2 )])
self . run_runner ()
register = json . loads (( self . council / "rasci.json" ) . read_text ( encoding = "utf-8" ))
self . assertEqual ({ task : register [ task ][ "estimates" ][ "Operator" ] for task in ( "work/next" , "work/child" )}, { "work/next" : { "value" : 34 , "at" : register [ "work/next" ][ "estimates" ][ "Operator" ][ "at" ]}, "work/child" : { "value" : 34 , "at" : register [ "work/child" ][ "estimates" ][ "Operator" ][ "at" ]}})
rows = { row [ "task" ]: row for row in self . snapshot ()[ "tasks" ]}
self . assertEqual (( rows [ "work/next" ][ "wsjf" ], rows [ "work/child" ][ "wsjf" ], rows [ "work/child" ][ "value" ]), ({ "value" : 34 , "complexity" : 3 , "score" : 11.33 }, None , 34 ))
def test_chromium_draws_the_gantt_and_burn_switches_the_estimation_requests_estimates_and_signs_a_bulk_business_value_at_three_widths ( self ):
self . check_plan ( "chromium" , "cdp.mjs" , os . environ . get ( "CHROMIUM" , "chromium" ), signs = True )
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
def test_firefox_draws_the_gantt_and_burn_and_asks_the_passkey_for_a_bulk_business_value_storing_nothing_unsigned ( self ):
self . check_plan ( "firefox" , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ), signs = False )
2026-11-07 23:43:26 +00:00
def test_chromium_pins_a_task_priority_after_signature_and_the_runner_applies_it ( self ):
signer = self . pin ()
private = "" . join ( line for line in signer . pem . read_text ( encoding = "utf-8" ) . splitlines () if not line . startswith ( "-----" ))
authenticator = { "credentialId" : base64 . b64encode ( signer . raw_id ) . decode (), "privateKey" : private , "rpId" : "localhost" }
local = f "http://localhost: { self . port } /"
data = self . drive ([{ "width" : 1366 , "height" : 768 , "url" : f " { local } #tasks/list" , "wait" : 1200 , "probe" : PIN , "shot" : self . shot ( "1366-tasks-priority-signed" )}], authenticator )
( OUT / "chromium-task-priority.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
self . assertEqual ( data [ "errors" ], [])
pinned = data [ "results" ][ 0 ]
self . assertNotIn ( "error" , pinned )
self . assertEqual (( pinned [ "title" ], pinned [ "preview" ][ 0 ]), ( "work/docs" , "rasci.py set work/docs --prio 2" ))
2026-11-08 17:26:51 +00:00
self . assertEqual ( pinned [ "toasts" ], [ "Priority work/docs: handed to the runnerrasci.py set work/docs --prio 2" ])
2026-11-07 23:43:26 +00:00
jobs = self . runner_jobs ()
self . assertEqual ([( job [ "id" ], job [ "kind" ], job [ "argv" ]) for job in jobs ], [( pinned [ "jobs" ], "task" , [ "set" , "work/docs" , "--prio" , "2" ])])
self . run_runner ()
register = json . loads (( self . council / "rasci.json" ) . read_text ( encoding = "utf-8" ))
self . assertEqual (( register [ "work/docs" ][ "prio" ], register [ "work/feature" ][ "prio" ]), ( 2 , 1 ))
self . assertEqual ([( row [ "task" ], row [ "prio" ]) for row in self . snapshot ()[ "tasks" ][: 2 ]], [( "work/feature" , 1 ), ( "work/docs" , 2 )])
2026-11-06 22:20:06 +00:00
@unittest.skipUnless ( shutil . which ( os . environ . get ( "FIREFOX" , "firefox" )), "no Firefox" )
2026-11-07 15:03:14 +00:00
def test_firefox_walks_every_screen_at_three_widths ( self ):
shots = [( width , height , screen , OUT / f "firefox- { width } - { screen . replace ( '/' , '-' ) } .png" ) for width , height in WIDTHS for screen in SCREENS ]
for * _ , shot in shots :
shot . unlink ( missing_ok = True )
steps = [{ "width" : width , "height" : height , "url" : f " { self . base } # { screen } " , "wait" : 900 , "probe" : PROBE , "shot" : str ( shot )} for width , height , screen , shot in shots ]
data = self . drive ( steps , None , "bidi.mjs" , os . environ . get ( "FIREFOX" , "firefox" ))
( OUT / "firefox-walk.json" ) . write_text ( json . dumps ( data , indent = 1 ), encoding = "utf-8" )
blocked = [ error for error in data [ "errors" ] if error . startswith ( "Content-Security-Policy" )]
self . assertEqual ([ error for error in data [ "errors" ] if error not in blocked ], [])
# 3d-force-graph injects its own <style>, which the unchanged CSP blocks; the page carries those rules itself.
self . assertEqual ([ error for error in blocked if "(style-src-elem)" not in error and "/favicon.ico" not in error ], [])
for probe , ( width , _ , screen , shot ) in zip ( data [ "results" ], shots , strict = True ):
with self . subTest ( width = width , screen = screen ):
self . assertNotIn ( "error" , probe )
self . assertEqual ( probe [ "hash" ], f "# { screen } " )
2026-11-07 23:43:26 +00:00
self . assertEqual (( probe [ "tabs" ], probe [ "subs" ]), ( TABS , SUBS ))
2026-11-07 15:03:14 +00:00
self . assertEqual ( labelled ( probe ), { "unlabelled" : [], "broken" : []})
self . assertFalse ( probe [ "wide" ], "the page scrolls sideways" )
2026-11-08 21:18:55 +00:00
self . assertEqual (( probe [ "panel" ], probe [ "nodeChips" ]), ( 0 , 0 ))
2026-11-07 15:03:14 +00:00
self . assertTrue ( shot . is_file () and shot . stat () . st_size > 1000 , shot )
if screen . endswith ( "/3d" ):
self . assertTrue ( probe [ "canvas" ], probe [ "note" ])
2026-11-06 22:20:06 +00:00
if __name__ == "__main__" :
unittest . main ()