Files
skills/orchestrator/templates/page-spawn.js
T
Kevin Veen-BirkenbachandClaude Opus 5.5 c035373518 feat(orchestrator)!: S0 English everywhere, Sécurité, NATO hashes, ICAO digits
Requirement 001 phase 6, S0:

- Speech: the security class shows as "Sécurité" and is spoken as in
  maritime radio, "say-cure-ee-tay" three times. A hex run of 7 or more
  characters with a letter and a digit (commit, session, message ids) is
  spelled in the NATO alphabet; every other digit is spoken on its own
  in ICAO pronunciation (42305: four two tree zero fife). The spelling
  runs inside clip() before the length cut, so a phrase still fits
  SPEECH_LIMIT; the displayed text keeps its digits and hashes.
- English: every orchestrator label, tooltip, message, error, script
  output, document and test string is English (Regeln -> Firewall,
  Hierarchie -> Hierarchy, Steuerung -> Controls, Ausführen -> Run, ...);
  the page declares lang="en" and formats dates as en-GB. Task filter
  ids are English. German stays only in the skill trigger phrases that
  quote the operator, and as identifiers: the view keys (view=) and the
  leitstand id.
- tests/test_english.py scans every tracked text file for umlauts and
  German words, with an allowlist of the quoted trigger phrases.

BREAKING CHANGE: the Stream search tokens art: and seit: are now kind:
and since:; an old art: or seit: token counts as a plain word.

Evidence: make test 657 OK, ruff and markdownlint clean
(/tmp/s0-make-test-1.log); e2e 17 OK, none skipped (/tmp/s0-e2e-1.log);
MEDUSA 86 files, 24 findings on reworded strings accepted after an
independent skeptic (21 path:line, 0 rejected, no behaviour change in
signing, approving, rules, denying, offices, events), rescan 0 open.

🎯 Fix: 93 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: –
🛡️ Security: 100 %
🏆 Score: 89
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 17:26:51 +00:00

122 lines
5.1 KiB
JavaScript

const spawn = {subs: new Map(), loading: false, dom: null};
const SPAWN_KINDS = {operator: "bi-person-circle", chair: "bi-award", session: "bi-robot", subagent: "bi-cpu", twig: "bi-cpu"};
function sessionRunning(node) {
const life = (spawn.subs.get(node.id) || {}).data;
if (life && life.died) {
return false;
}
return node.claims.some((claim) => claim.beat !== null && claim.beat >= state.now - ACTIVE_BEAT);
}
function twigs(session) {
const data = (spawn.subs.get(session) || {}).data;
const rows = data ? data.subagents : [];
const roots = [];
const open = [];
for (const row of rows) {
const twig = {id: `${session}/${row.id}`, kind: "twig", label: `${row.type}: ${row.description}`, running: !row.finished, row, session, children: []};
open.length = Math.min(open.length, Math.max(row.depth - 1, 0));
(open.length ? open[open.length - 1].children : roots).push(twig);
open.push(twig);
}
return roots;
}
function spawnTree() {
const nodes = state.graph.nodes;
const chair = nodes.find((node) => node.kind === "chair");
const entry = (node) => ({
id: node.id, kind: node.kind, label: node.label, node,
running: node.kind === "operator" ? null : node.kind === "subagent" ? node.tasks.some((task) => task.status === "running") : sessionRunning(node),
children: [],
});
const root = entry(nodes.find((node) => node.kind === "operator"));
const byId = new Map([[root.id, root]]);
if (chair) {
byId.set(chair.id, entry(chair));
root.children.push(byId.get(chair.id));
}
for (const node of nodes.filter((item) => item.kind === "session")) {
byId.set(node.id, entry(node));
(byId.get(chairKey()) || root).children.push(byId.get(node.id));
}
for (const node of nodes.filter((item) => item.kind === "subagent")) {
(byId.get(node.parent) || byId.get(chairKey()) || root).children.push(entry(node));
}
for (const [id, held] of byId) {
if (held.kind === "session" || held.kind === "chair") {
held.children.push(...twigs(id));
}
}
return root;
}
function spawnFlat(root) {
const found = [];
const walk = (entry, depth, parent) => {
found.push({entry, depth, parent});
entry.children.forEach((child) => walk(child, depth + 1, entry));
};
walk(root, 0, null);
return found;
}
async function loadSpawn() {
if (spawn.loading) {
return;
}
const due = state.graph.nodes.filter((node) => (node.kind === "session" || node.kind === "chair") && SESSION_ID.test(node.id))
.filter((node) => !spawn.subs.has(node.id) || Date.now() - spawn.subs.get(node.id).at > SUBS_TTL);
if (!due.length) {
return;
}
spawn.loading = true;
await Promise.all(due.map(async (node) => {
let data = null;
try {
const response = await fetch(`/api/subagents?session=${encodeURIComponent(node.id)}`, {cache: "no-store"});
data = response.ok ? await response.json() : null;
} catch {
data = null;
}
spawn.subs.set(node.id, {at: Date.now(), data});
}));
spawn.loading = false;
drawSpawn();
stage();
}
function spawnItem(entry) {
const running = entry.running === null ? null : entry.running ? chip("bi-play-circle", "running", "ok") : chip("bi-check-circle", "finished", "neutral");
const open = entry.kind === "twig" ? () => focusSub(entry.session, entry.row.id) : entry.kind === "operator" ? null : () => select(entry.id);
const name = open ? h("button", {type: "button", class: "linkish", title: entry.kind === "twig" ? "Show transcript in the stream" : `Open ${entry.label}`, on: {click: open}}, entry.label) : h("strong", {}, entry.label);
const meta = entry.kind === "twig" ? h("span", {class: "muted"}, `Depth ${entry.row.depth} · `, when(entry.row.started)) : h("span", {class: "muted"}, NODE_KINDS[entry.kind]);
return h("li", {"data-kind": entry.kind, "data-running": entry.running === null ? null : String(entry.running)},
h("div", {class: "spawn-row"}, h("span", {class: "state", role: "img", ...named(NODE_KINDS[entry.kind] || "Subagent")}, icon(SPAWN_KINDS[entry.kind])), name, running, meta),
entry.children.length ? h("ul", {class: "spawn-tree"}, entry.children.map(spawnItem)) : null);
}
function spawnScreen() {
const list = h("div", {class: "card spawn"});
spawn.dom = {list};
window.setTimeout(() => {
drawSpawn();
loadSpawn();
}, 0);
return list;
}
function drawSpawn() {
if (!spawn.dom || !spawn.dom.list.isConnected) {
spawn.dom = null;
return;
}
const root = spawnTree();
const all = spawnFlat(root).map(({entry}) => entry).filter((entry) => entry.running !== null);
const running = all.filter((entry) => entry.running).length;
spawn.dom.list.replaceChildren(
h("div", {class: "inbox-head"}, h("h2", {class: "group-title"}, "Spawn tree", counter(all.length)), h("span", {class: "muted"}, `${running} running · ${all.length - running} finished · Operator → Chair → Sessions → Subagents`)),
h("ul", {class: "spawn-tree root"}, spawnItem(root)));
}