/* @jsxRuntime classic */ /* @jsx React.createElement */ // Orchestra — Context Map (Bench Phase-3, task 86banqh9k) // Read-only visualization: Knowledge Base entry → affected Wright → category (the Bench), // with freshness/dirty state. "Reconcile" on a dirty Wright jumps to The Bench (Operating Context). // Loaded by index.html as a separate text/babel script; exposes window.ContextMapPage. (function () { const { useState, useEffect, useMemo } = React; // ── API helper (mirrors app.jsx's api()) ────────────────────────────────── function toCamel(s) { return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase()); } function deepCamel(obj) { if (Array.isArray(obj)) return obj.map(deepCamel); if (obj && typeof obj === 'object') { const out = {}; for (const k of Object.keys(obj)) out[toCamel(k)] = deepCamel(obj[k]); return out; } return obj; } async function api(method, path) { const res = await fetch(path, { method }); if (!res.ok) { const t = await res.text().catch(() => ''); throw new Error(`${res.status}: ${t}`); } return deepCamel(await res.json()); } // Category slugs come back from the Bench summary as camelCased dict keys // (e.g. "data_model" → "dataModel" after deepCamel). Humanize for display. function camelToLabel(s) { if (!s) return 'General'; const spaced = s.replace(/([A-Z])/g, ' $1'); return (spaced.charAt(0).toUpperCase() + spaced.slice(1)).trim(); } function trunc(s, n) { return s && s.length > n ? s.slice(0, n - 1) + '…' : (s || ''); } function bezier(x1, y1, x2, y2) { const cx = (x1 + x2) / 2; return `M${x1},${y1} C${cx},${y1} ${cx},${y2} ${x2},${y2}`; } function ContextMapPage({ agents, navigateTo }) { const [summaries, setSummaries] = useState(null); // null = loading const [dirty, setDirty] = useState([]); const [kbEntries, setKbEntries] = useState([]); useEffect(() => { injectStyles(); Promise.all([ api('GET', '/api/bench').catch(() => ({ summaries: [] })), api('GET', '/api/bench/dirty').catch(() => ({ dirty: [] })), api('GET', '/api/org-context/entries').catch(() => []), ]).then(([benchRes, dirtyRes, entries]) => { setSummaries(benchRes.summaries || []); setDirty(dirtyRes.dirty || []); setKbEntries(entries || []); }); }, []); const agentRoles = {}; (agents || []).forEach(a => { agentRoles[a.id] = a.role || a.id; }); const dirtySet = useMemo(() => { const s = new Set(); dirty.forEach(d => s.add(`${d.agentId}::${toCamel(d.category || '')}`)); return s; }, [dirty]); const dirtyAgentIds = useMemo(() => new Set(dirty.map(d => d.agentId)), [dirty]); const kbById = {}; kbEntries.forEach(e => { kbById[e.id] = e; }); const dirtyEntryIds = useMemo(() => { const s = new Set(); dirty.forEach(d => { if (d.sourceEntryId) s.add(d.sourceEntryId); }); return s; }, [dirty]); const liveKbNodes = Array.from(dirtyEntryIds) .map(id => kbById[id]) .filter(e => e && e.kind !== 'group'); const activeAgents = (summaries || []).filter(s => s.totalCount > 0); const wrightIds = activeAgents.map(s => s.agentId); const categorySet = {}; activeAgents.forEach(s => { Object.entries(s.categories || {}).forEach(([cat, count]) => { categorySet[cat] = (categorySet[cat] || 0) + count; }); }); const categoryNames = Object.keys(categorySet).sort(); if (summaries === null) { return (
Context Map
Loading…
); } if (wrightIds.length === 0) { return (
Context Map
Visualizes Knowledge Base → Wright → category edges feeding the Bench.
No Bench activity yet. Items appear here once Builds complete review gates, or the Knowledge Base propagates entries into a Wright's Bench.
); } // ── SVG layout: 3 columns — pending KB entries | Wrights | categories ── const NODE_W = 178, NODE_H = 34, ROW_GAP = 10, PAD_Y = 24, PAD_X = 20, COL_GAP = 140; const COL1_X = PAD_X; const COL2_X = COL1_X + NODE_W + COL_GAP; const COL3_X = COL2_X + NODE_W + COL_GAP; const SVG_W = COL3_X + NODE_W + PAD_X; const kbIds = liveKbNodes.map(e => e.id); const blockH = (arr) => (arr.length ? arr.length * (NODE_H + ROW_GAP) - ROW_GAP : 0); const SVG_H = Math.max(blockH(kbIds), blockH(wrightIds), blockH(categoryNames), NODE_H) + PAD_Y * 2; function colYs(ids) { const ys = {}; const startY = (SVG_H - blockH(ids)) / 2; ids.forEach((id, i) => { ys[id] = startY + i * (NODE_H + ROW_GAP) + NODE_H / 2; }); return ys; } const kbYs = colYs(kbIds); const wrightYs = colYs(wrightIds); const catYs = colYs(categoryNames); const kbEdges = []; dirty.forEach(d => { if (d.sourceEntryId && kbById[d.sourceEntryId] && kbYs[d.sourceEntryId] !== undefined && wrightYs[d.agentId] !== undefined) { kbEdges.push({ kbId: d.sourceEntryId, agentId: d.agentId }); } }); const catEdges = []; activeAgents.forEach(s => { Object.keys(s.categories || {}).forEach(cat => { if (catYs[cat] !== undefined) { catEdges.push({ agentId: s.agentId, cat, dirty: dirtySet.has(`${s.agentId}::${cat}`) }); } }); }); return (
Context Map
Knowledge Base → Wright → category edges feeding the Bench. Read-only — amber dashed edges are dirty (pending reconcile); gold edges are fresh.
{dirtyAgentIds.size > 0 && ( )}
{kbEdges.map(({ kbId, agentId }) => ( ))} {catEdges.map(({ agentId, cat, dirty: isDirty }) => ( ))} {liveKbNodes.map(e => ( {trunc(e.title || `Entry #${e.id}`, 22)} ))} {wrightIds.map(id => { const isDirty = dirtyAgentIds.has(id); return ( { if (isDirty && navigateTo) navigateTo('bench'); }}> {trunc(agentRoles[id] || id, isDirty ? 16 : 22)} {isDirty && ( )} ); })} {categoryNames.map(cat => ( {trunc(camelToLabel(cat), 20)} {categorySet[cat]} ))}
Left: Knowledge Base entries pending propagation ({liveKbNodes.length}) · Middle: Wrights ({wrightIds.length}) · Right: categories — the Bench ({categoryNames.length}) · Click a dirty (⟳) Wright to reconcile.
{liveKbNodes.length === 0 && (
No Knowledge Base entries are currently pending propagation — every Wright's Bench reflects the latest Knowledge Base state. Wright→category edges below show existing Bench contents.
)}
); } function injectStyles() { if (document.getElementById('cm-styles')) return; const el = document.createElement('style'); el.id = 'cm-styles'; el.textContent = ` .cm-root { height:100%; overflow-y:auto; overflow-x:hidden; padding:24px; } .cm-head-row { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; flex-wrap:wrap; margin-bottom:16px; } .cm-title { font-weight:600; font-size:1.05rem; } .cm-sub { font-size:0.82rem; color:#748090; margin-top:4px; line-height:1.55; max-width:620px; } .cm-loading { color:#748090; font-size:0.85rem; margin-top:8px; } .cm-empty { background:#0f1525; border:1px solid #1b2640; border-radius:10px; padding:24px; color:#748090; font-size:0.85rem; line-height:1.6; max-width:520px; margin-top:16px; } .cm-caption { font-size:0.75rem; color:#748090; margin-top:10px; } .cm-note { font-size:0.78rem; color:#748090; margin-top:14px; max-width:600px; line-height:1.55; } `; document.head.appendChild(el); } window.ContextMapPage = ContextMapPage; })();