The Day/Night menu lists every claimed agent with a checkbox and an All box; Drain, Suspend and Resume act on the ticked agents only, are disabled when none is ticked, and the agent popup carries the same three buttons for that one agent. Each action is passkey-signed with its agent list, night.py takes --target per session and keeps a per-agent part, and each chosen agent gets a call naming the action and its skill. The night skills say how to read a per-agent night. 🎯 Fix: 90 % 💥 Break: 5 % 💸 Cost: 106 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 81 🚦 Gate: ⚠️ cost 106 % > 100: operator approved the commit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
4.3 KiB
JavaScript
116 lines
4.3 KiB
JavaScript
const navButtons = new Map();
|
|
|
|
function tabButton(id, label, hint) {
|
|
const badge = Object.hasOwn(state.badges, id) ? h("span", {class: "badge", hidden: true}) : null;
|
|
const button = h("button", {type: "button", id: `tab-${id}`, class: "icon-only", "aria-controls": "main", ...named(label, hint ? `${label} (${hint})` : label), on: {click: () => go(id, null)}}, icon(Nav.ICONS[id]), badge);
|
|
navButtons.set(id, {button, badge});
|
|
return button;
|
|
}
|
|
|
|
document.getElementById("tabs").append(...Nav.TABS.map(([id, label], index) => h("div", {class: "tab-group"},
|
|
tabButton(id, label, `key ${index + 1}`),
|
|
Nav.SUBS[id] ? h("div", {class: "subtabs"}, Nav.SUBS[id].map(([sub, name]) => tabButton(sub, name, ""))) : null)));
|
|
|
|
function measure() {
|
|
document.documentElement.style.setProperty("--head", `${document.getElementById("head").offsetHeight}px`);
|
|
}
|
|
|
|
function go(screen, view) {
|
|
const hash = Nav.hashOf(screen, view || ui.views[screen] || null);
|
|
if (window.location.hash === hash) {
|
|
apply();
|
|
} else {
|
|
window.location.hash = hash;
|
|
}
|
|
}
|
|
|
|
function apply() {
|
|
const route = Nav.route(window.location.hash);
|
|
if (route.redirect) {
|
|
window.history.replaceState(null, "", route.redirect);
|
|
}
|
|
ui.screen = route.screen;
|
|
if (route.view) {
|
|
ui.views[route.screen] = route.view;
|
|
}
|
|
if (document.startViewTransition && drawn.screen && !calm.matches) {
|
|
document.startViewTransition(() => render(true));
|
|
} else {
|
|
render(true);
|
|
}
|
|
}
|
|
|
|
function signature() {
|
|
if (ui.screen === "agents") {
|
|
return JSON.stringify([ui.views.agents]);
|
|
}
|
|
return JSON.stringify(ui.screen === "artifacts" || ui.screen === "tasks" ? [ui.views[ui.screen]] : ui.screen === "rules" ? state.rules : []);
|
|
}
|
|
|
|
function screenOf() {
|
|
return {stream: streamScreen, inbox: tasksScreen, rules: rulesScreen, radio: radioScreen, agents: agentsScreen, artifacts: artifactsScreen, tasks: tasksScreen}[ui.screen]();
|
|
}
|
|
|
|
function render(force) {
|
|
for (const [id, {button}] of navButtons) {
|
|
button.setAttribute("aria-current", ui.screen === id ? "page" : "false");
|
|
}
|
|
const sig = signature();
|
|
if (force || drawn.screen !== ui.screen || drawn.sig !== sig) {
|
|
graphDom = null;
|
|
stream.dom = null;
|
|
tree.dom = null;
|
|
radioDom = null;
|
|
inboxDom = null;
|
|
inboxSig = "";
|
|
taskView.dom = null;
|
|
const main = document.getElementById("main");
|
|
main.setAttribute("aria-labelledby", `tab-${ui.screen}`);
|
|
main.replaceChildren(screenOf());
|
|
document.body.classList.toggle("fit", ["tasks", "inbox"].includes(ui.screen) || (["agents", "artifacts"].includes(ui.screen) && !["tree", "hierarchy"].includes(ui.views[ui.screen])));
|
|
drawn.screen = ui.screen;
|
|
drawn.sig = sig;
|
|
if (g3.host && !g3.host.isConnected) {
|
|
dispose3d();
|
|
}
|
|
}
|
|
stage();
|
|
drawSpawn();
|
|
drawHierarchy(force);
|
|
drawToolbar();
|
|
drawInbox();
|
|
drawRadio();
|
|
drawTasks();
|
|
drawChoices();
|
|
drawStatus();
|
|
tick();
|
|
placePopup();
|
|
}
|
|
|
|
function drawStatus() {
|
|
for (const [id, count] of Object.entries(state.badges)) {
|
|
const {badge} = navButtons.get(id);
|
|
badge.textContent = String(count);
|
|
badge.hidden = count === 0;
|
|
badge.className = `badge${id === "inbox" && count ? " urgent" : ""}`;
|
|
}
|
|
const banner = document.getElementById("mayday");
|
|
const silence = state.silence;
|
|
banner.hidden = !silence;
|
|
banner.replaceChildren(...(silence ? [h("p", {class: "text"}, `MAYDAY · ${dash(silence.by)}: ${silence.text}`), iconBtn("bi-x-octagon", "cancel", "btn small", () => confirmAct("clear-mayday", []), "cancel: end the mayday (radio.py clear)")] : []));
|
|
document.title = `${state.badges.inbox ? `(${state.badges.inbox}) ` : ""}Agent Orchestrator`;
|
|
window.setTimeout(measure, 0);
|
|
}
|
|
|
|
function toggleLeitstand() {
|
|
const on = !document.body.classList.contains("leitstand");
|
|
document.body.classList.toggle("leitstand", on);
|
|
if (on && document.documentElement.requestFullscreen && !document.fullscreenElement) {
|
|
document.documentElement.requestFullscreen().catch(() => null);
|
|
}
|
|
if (!on && document.fullscreenElement) {
|
|
document.exitFullscreen().catch(() => null);
|
|
}
|
|
window.setTimeout(measure, 50);
|
|
}
|