feat(orchestrator): rename kvb-hub to Agent Orchestrator with the phase 2 surface
Surface (requirement 001 phase 2): navigation Stream (Inbox, Regeln) / Agents / Artifacts with keys 1-3 and hash routes (#graph and #tree redirect), header with title, command palette and settings only, server-side badges (unsnoozed inbox, beats within 900 s, open jobs), a Mayday banner with "aufheben", four stream chips with agent:/art:/seit: search, Filter and time menus, the inbox in main, an agents toolbar (Funk an alle with Newsletter, Broadcast, Pan-Pan, Security, Mayday; Priorität; Tag/Nacht), Post or Call in the agent panel, Ambiente in the settings and read-aloud over the seven classes with Mayday locked. Rename (decision 9): hub/ is orchestrator/, the package kvb_orchestrator, the unit kvb-orchestrator-runner.service, scripts/orchestrator.sh, environment KVB_ORCHESTRATOR_*, make targets orchestrator-*, the MCP server, image and compose project kvb-orchestrator, the guard header X-Kvb-Orchestrator and the message marker [kvb-orchestrator msg <id>]. install runs migrate first: it stops the old container, removes its image and code, moves state, approvals and pinned keys, swaps the MCP registration, the PermissionRequest hook and the runner unit, and changes nothing on a second run (scratch-HOME test with stub docker, claude and systemctl). Saved views fall back to the old localStorage key once. MEDUSA acceptances carried to the renamed paths: 82 rows whose lines are unchanged (reason prefixed "renamed from <old path>, line unchanged"), and 7 rows whose text changed only in the product name, the package name, a temp prefix or a new class argument, each upheld by one kvb-think-criticize skeptic (approval.py:60, messaging.py:53, server.py:56, tests/hub_support.py:64, passkey.py:205, page-agent.js:75, language.py:46). Cost 101 %: the longer name adds 15 tokens of skill text; the operator approved it with the commit. 🎯 Fix: 92 % 💥 Break: 4 % 💸 Cost: 101 % ⏱️ Time: – 🛡️ Security: 100 % 💰 Value: ? 🏆 Score: 87 🚦 Gate: ⚠️ cost 101 % > 100: ask the operator before the commit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
54f3725aa1
commit
6f940b4415
125 files changed
+1725
-782
No files matched your search
@@ -0,0 +1,144 @@
|
||||
function aggregate() {
|
||||
const links = new Map();
|
||||
const add = (source, target, cls) => {
|
||||
const key = `${source}\u0000${target}\u0000${cls}`;
|
||||
const link = links.get(key) || {source, target, cls, count: 0};
|
||||
link.count += 1;
|
||||
links.set(key, link);
|
||||
};
|
||||
for (const row of state.radio) {
|
||||
for (const target of row.source ? (row.targets.length ? row.targets : ["*"]) : []) {
|
||||
add(row.source, target, row.class);
|
||||
}
|
||||
}
|
||||
for (const grant of state.grants) {
|
||||
add(chairKey(), grant.target, "grant");
|
||||
}
|
||||
const order = (link) => (tone(link.cls) === "alarm" ? 1e6 : 0) + link.count;
|
||||
return [...links.values()].sort((a, b) => order(a) - order(b));
|
||||
}
|
||||
|
||||
const width = (count) => Math.min(1.25 + Math.log2(1 + count) * 1.6, 9);
|
||||
const tone = (cls) => Space.TONES[cls] || "direct";
|
||||
|
||||
function defs() {
|
||||
return s("defs", {}, LEGEND.map(([name]) => s("marker", {id: `tip-${name}`, viewBox: "0 0 10 10", refX: 8, refY: 5, markerWidth: 9, markerHeight: 9, markerUnits: "userSpaceOnUse", orient: "auto-start-reverse", class: `t-${name}`},
|
||||
s("path", {class: "tip", d: "M0,0 L10,5 L0,10 z"}))));
|
||||
}
|
||||
|
||||
function toward(point, aim, gap) {
|
||||
const dx = aim.x - point.x;
|
||||
const dy = aim.y - point.y;
|
||||
const length = Math.hypot(dx, dy) || 1;
|
||||
return {x: point.x + (dx / length) * gap, y: point.y + (dy / length) * gap};
|
||||
}
|
||||
|
||||
function radiusOf(id) {
|
||||
const node = state.graph.nodes.find((entry) => entry.id === id);
|
||||
return node ? RADIUS[node.kind] : RADIUS.session;
|
||||
}
|
||||
|
||||
function targets() {
|
||||
const plan = state.graph.layout;
|
||||
const order = [HUMAN, ...plan.order];
|
||||
return new Map(order.map((id, index) => {
|
||||
const spot = plan.orbit[id] || {angle: -Math.PI / 2, radius: 0};
|
||||
return [id, {angle: spot.angle, radius: spot.radius, chord: -Math.PI / 2 + (2 * Math.PI * index) / order.length}];
|
||||
}));
|
||||
}
|
||||
|
||||
function step(now) {
|
||||
const progress = motion.duration ? Math.min((now - motion.start) / motion.duration, 1) : 1;
|
||||
const eased = Space.ease(progress);
|
||||
motion.current = new Map([...motion.target].map(([id, to]) => {
|
||||
const from = motion.from.get(id) || to;
|
||||
return [id, {angle: Space.turn(from.angle, to.angle, eased), radius: from.radius + (to.radius - from.radius) * eased, chord: Space.turn(from.chord, to.chord, eased)}];
|
||||
}));
|
||||
if (ui.views.agents !== "swimlane") {
|
||||
stage();
|
||||
}
|
||||
motion.frame = progress < 1 ? window.requestAnimationFrame(step) : 0;
|
||||
}
|
||||
|
||||
function relayout(duration) {
|
||||
const next = targets();
|
||||
motion.from = new Map([...next.keys()].map((id) => [id, motion.current.get(id) || next.get(id)]));
|
||||
motion.target = next;
|
||||
motion.start = performance.now();
|
||||
motion.duration = calm.matches ? 0 : duration;
|
||||
if (!motion.frame) {
|
||||
motion.frame = window.requestAnimationFrame(step);
|
||||
}
|
||||
}
|
||||
|
||||
function spotOf(id, fresh) {
|
||||
return motion.current.get(id) || fresh.get(id) || {angle: -Math.PI / 2, radius: RINGS[1], chord: -Math.PI / 2};
|
||||
}
|
||||
|
||||
function layout(nodes) {
|
||||
const fresh = targets();
|
||||
const spots = new Map();
|
||||
for (const node of nodes.filter((entry) => entry.kind !== "subagent")) {
|
||||
const at = spotOf(node.id, fresh);
|
||||
const radius = node.kind === "operator" ? 0 : at.radius;
|
||||
spots.set(node.id, {x: radius * Math.cos(at.angle), y: radius * Math.sin(at.angle), angle: at.angle});
|
||||
}
|
||||
const moons = new Map();
|
||||
for (const node of nodes.filter((entry) => entry.kind === "subagent")) {
|
||||
moons.set(node.parent, [...(moons.get(node.parent) || []), node]);
|
||||
}
|
||||
for (const [parent, list] of moons) {
|
||||
const base = spots.get(parent) || spots.get(chairKey()) || {x: 0, y: 0, angle: -Math.PI / 2};
|
||||
list.forEach((node, index) => spots.set(node.id, Space.moon(base, index, list.length)));
|
||||
}
|
||||
return spots;
|
||||
}
|
||||
|
||||
function caption(node, at, r, gap) {
|
||||
if (node.kind === "subagent") {
|
||||
return null;
|
||||
}
|
||||
if (gap === null) {
|
||||
return s("text", {class: "label", y: r + 17, "text-anchor": "middle"}, node.label);
|
||||
}
|
||||
const cos = Math.cos(at.angle);
|
||||
const sin = Math.sin(at.angle);
|
||||
const anchor = cos > 0.3 ? "start" : cos < -0.3 ? "end" : "middle";
|
||||
return s("text", {class: "label", x: fixed(cos * gap), y: fixed(sin * (gap + 3)), dy: "0.35em", "text-anchor": anchor}, node.label);
|
||||
}
|
||||
|
||||
function select(id) {
|
||||
ui.node = ui.node === id ? null : id;
|
||||
stage();
|
||||
drawSide();
|
||||
const node = state.graph.nodes.find((entry) => entry.id === id);
|
||||
if (ui.node !== null && node && node.kind !== "operator") {
|
||||
openAgent(node.kind === "subagent" ? node.parent : id);
|
||||
}
|
||||
}
|
||||
|
||||
function nodeMark(node, at, gap) {
|
||||
const r = RADIUS[node.kind];
|
||||
const classes = ["node", `k-${node.kind}`, ui.node === node.id ? "selected" : "", node.claims.length ? "claimed" : ""].filter(Boolean).join(" ");
|
||||
const tasks = node.tasks.length ? ` · ${node.tasks.length} Aufgaben` : "";
|
||||
return s("g", {
|
||||
class: classes, transform: `translate(${fixed(at.x)} ${fixed(at.y)})`, tabindex: 0, role: "button", "aria-label": `${node.label}, ${NODE_KINDS[node.kind]}`,
|
||||
on: {
|
||||
click: () => select(node.id),
|
||||
keydown: (event) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
select(node.id);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
s("title", {}, `${node.label} · ${NODE_KINDS[node.kind]}${node.parent ? ` von ${labelOf(node.parent)}` : ""}${tasks}`),
|
||||
s("circle", {r}),
|
||||
node.kind === "subagent" ? null : s("text", {class: "initial", "text-anchor": "middle", dy: "0.35em"}, node.label.slice(0, 1).toUpperCase()),
|
||||
caption(node, at, r, gap));
|
||||
}
|
||||
|
||||
function chartFrame(size, label) {
|
||||
return s("svg", {viewBox: `${-size} ${-size} ${2 * size} ${2 * size}`, class: "chart", role: "img", "aria-label": label}, defs());
|
||||
}
|
||||
Reference in new issue
Block a user