/* @jsxRuntime classic */
/* @jsx React.createElement */
// Orchestra — The Bench (Bench Phase-2, task 86banqh7q)
// Per-Wright operating memory surface. Read-only by default;
// Approve/Edit/Dismiss shown when the "advanced" toggle is on.
// Loaded by index.html as a separate text/babel script; exposes window.AgentContextPage.
(function () {
const { useState, useEffect, useCallback } = 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, body) {
const opts = { method, headers: { 'Content-Type': 'application/json' } };
if (body !== undefined) opts.body = JSON.stringify(body);
const res = await fetch(path, opts);
if (!res.ok) { const t = await res.text().catch(() => ''); throw new Error(`${res.status}: ${t}`); }
if (res.status === 204) return null;
return deepCamel(await res.json());
}
// ── Lineage chip text for a learned item ──────────────────────────────────
function lineageText(item) {
if (item.provenance === 'kb-propagation') return 'Propagated from Knowledge Base';
if (item.provenance === 'human') return 'Human-authored';
const runPart = item.sourceRunId ? `Build #${item.sourceRunId}` : 'a Build';
const stagePart = item.sourceStage ? ` · ${item.sourceStage}` : '';
const reviewerPart = item.sourceReviewer ? ` → ${item.sourceReviewer} review gate` : '';
return `Learned from ${runPart}${stagePart}${reviewerPart}`;
}
// ── Category label prettifier ─────────────────────────────────────────────
function catLabel(slug) {
return (slug || 'general').replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
}
// ── Confidence bar ────────────────────────────────────────────────────────
function ConfBar({ value }) {
const pct = Math.round((value || 0) * 100);
const color = pct >= 70 ? '#4ade80' : pct >= 40 ? '#c8a84a' : '#e08080';
return (
);
}
// ── Single Bench item card ────────────────────────────────────────────────
function ItemCard({ item, advanced, onApprove, onDismiss }) {
const isPinned = item.itemType === 'pinned';
return (
{isPinned ? '📌 Pinned' : '💡 Learned'}
{item.category && (
{catLabel(item.category)}
)}
{!isPinned && (
)}
{item.content}
{!isPinned && (
{lineageText(item)}
)}
{advanced && (
{!isPinned && (
)}
)}
);
}
// ── Main page component ────────────────────────────────────────────────────
function AgentContextPage({ agents }) {
const [summaries, setSummaries] = useState({}); // agent_id → summary
const [selAgentId, setSelAgentId] = useState(null);
const [items, setItems] = useState([]);
const [loadingItems, setLoadingItems] = useState(false);
const [advanced, setAdvanced] = useState(false);
const [reconcileResult, setReconcileResult] = useState(null);
const [reconciling, setReconciling] = useState(false);
const [toast, setToast] = useState(null);
useEffect(() => { injectStyles(); }, []);
// Fetch all bench summaries on mount
useEffect(() => {
api('GET', '/api/bench').then(data => {
const map = {};
(data.summaries || []).forEach(s => { map[s.agentId] = s; });
setSummaries(map);
}).catch(() => {});
}, []);
// Fetch items when a Wright is selected
const selectAgent = useCallback((agentId) => {
if (selAgentId === agentId) { setSelAgentId(null); setItems([]); return; }
setSelAgentId(agentId);
setItems([]);
setLoadingItems(true);
api('GET', `/api/bench/${agentId}`)
.then(data => setItems(data.items || []))
.catch(() => setItems([]))
.finally(() => setLoadingItems(false));
}, [selAgentId]);
const showToast = (msg, ok = true) => {
setToast({ msg, ok });
setTimeout(() => setToast(null), 3500);
};
const handleReconcile = async () => {
setReconciling(true);
try {
const r = await api('POST', '/api/bench/reconcile');
setReconcileResult(r);
showToast(`Reconciled: ${r.archived} items archived, ${r.estimatedTokensSaved} tokens saved`);
// Refresh summaries
const data = await api('GET', '/api/bench');
const map = {};
(data.summaries || []).forEach(s => { map[s.agentId] = s; });
setSummaries(map);
} catch (e) {
showToast('Reconcile failed: ' + e.message, false);
} finally {
setReconciling(false);
}
};
const handleApprove = async (itemId) => {
try {
const r = await api('PATCH', `/api/bench/items/${itemId}/approve`);
if (r.ok) {
setItems(prev => prev.map(i => i.id === itemId ? r.item : i));
showToast('Item approved and pinned.');
}
} catch (e) { showToast('Approve failed: ' + e.message, false); }
};
const handleDismiss = async (itemId) => {
try {
const r = await api('DELETE', `/api/bench/items/${itemId}`);
if (r.ok) {
setItems(prev => prev.filter(i => i.id !== itemId));
showToast('Item dismissed.');
}
} catch (e) { showToast('Dismiss failed: ' + e.message, false); }
};
const visibleAgents = (agents || []).filter(a => !a.meta);
const metaAgents = (agents || []).filter(a => a.meta);
const pinnedItems = items.filter(i => i.itemType === 'pinned');
const learnedItems = items.filter(i => i.itemType === 'learned');
const selAgent = selAgentId ? (agents || []).find(a => a.id === selAgentId) : null;
return (
{/* Header */}
The Bench
Per-Wright operating memory — Pinned (human-set) and Learned (auto-captured
at review gates). Select a Wright to inspect its context.
{reconcileResult && (
Last reconcile: {reconcileResult.archived} items archived ·{' '}
~{reconcileResult.estimatedTokensSaved} tokens saved ·{' '}
{reconcileResult.remainingItems} active items remaining
)}
{/* Left rail — Wright list */}
Wrights
{visibleAgents.map(agent => {
const s = summaries[agent.id];
return (
selectAgent(agent.id)}>
{agent.initials || (agent.role || '').slice(0, 2).toUpperCase()}
{agent.role}
{s ? (
📌 {s.pinnedCount} 💡 {s.learnedCount}
) : (
No bench items
)}
);
})}
{metaAgents.length > 0 && (
<>
System
{metaAgents.map(agent => {
const s = summaries[agent.id];
return (
selectAgent(agent.id)}>
{agent.initials || (agent.role || '').slice(0, 2).toUpperCase()}
{agent.role}
{s ? (
📌 {s.pinnedCount} 💡 {s.learnedCount}
) : (
No bench items
)}
);
})}
>
)}
{/* Main panel */}
{!selAgentId ? (
🧠
Select a Wright
Choose a Wright from the left rail to inspect its Pinned and Learned context.
) : loadingItems ? (
) : (
{selAgent ? selAgent.role : selAgentId} — The Bench
📌 {pinnedItems.length} Pinned
💡 {learnedItems.length} Learned
{items.length === 0 ? (
📭
No bench items yet
Items appear here after Builds complete review gates, or when you
pin a note manually (Advanced mode).
{advanced && (
setItems(prev => [item, ...prev])} showToast={showToast} />
)}
) : (
<>
{pinnedItems.length > 0 && (
📌 Pinned
Human-authored context. Confidence stays at 100% and is never decayed.
{pinnedItems.map(item => (
))}
)}
{learnedItems.length > 0 && (
💡 Learned
Auto-captured at review gates. Confidence decays over time; low-confidence items are archived on reconcile.
{learnedItems.map(item => (
))}
)}
{advanced && (
setItems(prev => [item, ...prev])} showToast={showToast} />
)}
>
)}
)}
{/* Toast */}
{toast && (
{toast.msg}
)}
);
}
// ── Pin form (shown in advanced mode) ──────────────────────────────────────
function PinForm({ agentId, onPinned, showToast }) {
const [content, setContent] = useState('');
const [category, setCategory] = useState('');
const [saving, setSaving] = useState(false);
const handlePin = async () => {
if (!content.trim()) return;
setSaving(true);
try {
const r = await (async () => {
const opts = { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: content.trim(), category: category.trim() }) };
const res = await fetch(`/api/bench/${agentId}/pin`, opts);
if (!res.ok) throw new Error(await res.text().catch(() => ''));
return await res.json();
})();
if (r.ok && r.item) {
const item = r.item;
// convert snake_case to camelCase manually for the pinned item
function toCamel(s) { return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase()); }
function dc(o) {
if (Array.isArray(o)) return o.map(dc);
if (o && typeof o === 'object') { const out = {}; for (const k of Object.keys(o)) out[toCamel(k)] = dc(o[k]); return out; }
return o;
}
onPinned(dc(item));
setContent('');
setCategory('');
showToast('Item pinned.');
} else {
showToast(r.message || 'Pin failed', false);
}
} catch (e) { showToast('Pin failed: ' + e.message, false); }
finally { setSaving(false); }
};
return (
);
}
// ── Styles ────────────────────────────────────────────────────────────────
function injectStyles() {
if (document.getElementById('ac-styles')) return;
const el = document.createElement('style');
el.id = 'ac-styles';
el.textContent = `
.ac-root { height:100%; overflow-y:auto; overflow-x:hidden; padding:24px; position:relative; }
.ac-head { margin-bottom:18px; }
.ac-head-row { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; flex-wrap:wrap; }
.ac-title { font-weight:600; font-size:1.05rem; }
.ac-sub { font-size:0.82rem; color:#748090; margin-top:4px; line-height:1.55; max-width:600px; }
.ac-sub b { color:#e8e6df; }
.ac-head-actions { display:flex; align-items:center; gap:12px; flex-shrink:0; flex-wrap:wrap; }
.ac-toggle-wrap { display:flex; align-items:center; gap:6px; cursor:pointer; font-size:0.82rem; color:#748090; }
.ac-toggle-wrap input { cursor:pointer; }
.ac-toggle-label { user-select:none; }
.ac-reconcile-result { margin-top:10px; font-size:0.8rem; color:#748090;
background:#0f1525; border:1px solid #1b2640; border-radius:8px; padding:8px 12px; }
.ac-reconcile-result b { color:#e8e6df; }
.ac-layout { display:flex; gap:16px; align-items:flex-start; }
.ac-rail { width:260px; min-width:220px; flex-shrink:0; display:flex; flex-direction:column; gap:6px; }
.ac-rail-section-title { font-size:0.68rem; text-transform:uppercase; letter-spacing:0.09em;
color:#344054; font-weight:600; padding:4px 2px; }
.ac-wright-row { display:flex; align-items:center; gap:10px; padding:10px 12px;
background:#0f1525; border:1px solid #1b2640; border-radius:10px; cursor:pointer;
transition:all 0.15s; }
.ac-wright-row:hover { border-color:#4a5568; }
.ac-wright-row.active { border-color:#c8a84a; }
.ac-wright-meta { opacity:0.7; }
.ac-wright-avatar { width:28px; height:28px; border-radius:6px; display:flex; align-items:center;
justify-content:center; font-size:0.72rem; font-weight:700; color:#fff; flex-shrink:0; }
.ac-wright-info { min-width:0; }
.ac-wright-name { font-size:0.84rem; font-weight:600; }
.ac-wright-counts { font-size:0.72rem; color:#748090; margin-top:2px; }
.ac-main { flex:1; min-width:0; }
.ac-panel-head { display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:16px; }
.ac-panel-title { font-weight:600; font-size:0.95rem; }
.ac-panel-counts { display:flex; gap:8px; }
.ac-count-badge { font-size:0.75rem; padding:3px 8px; border-radius:20px; border:1px solid; }
.ac-count-badge.pinned { border-color:#c8a84a; color:#c8a84a; background:rgba(200,168,74,0.08); }
.ac-count-badge.learned { border-color:#4c8ab5; color:#4c8ab5; background:rgba(76,138,181,0.08); }
.ac-section { margin-bottom:24px; }
.ac-section-title { font-size:0.85rem; font-weight:600; margin-bottom:4px; }
.ac-section-sub { font-size:0.75rem; color:#748090; margin-bottom:12px; line-height:1.5; }
.ac-item { background:#0f1525; border:1px solid #1b2640; border-radius:10px;
padding:13px 15px; margin-bottom:10px; transition:border-color 0.15s; }
.ac-item-pinned { border-color:#c8a84a; background:rgba(200,168,74,0.04); }
.ac-item-head { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:8px; }
.ac-type-badge { font-size:0.72rem; font-weight:600; padding:2px 7px; border-radius:12px; }
.ac-type-badge.pinned { background:rgba(200,168,74,0.15); color:#c8a84a; }
.ac-type-badge.learned { background:rgba(76,138,181,0.15); color:#4c8ab5; }
.ac-cat-chip { font-size:0.7rem; background:#172038; border:1px solid #1b2640;
border-radius:8px; padding:2px 7px; color:#748090; }
.ac-conf-bar { flex:1; max-width:80px; height:4px; background:#1b2640; border-radius:2px; overflow:hidden; }
.ac-conf-fill { height:100%; border-radius:2px; transition:width 0.3s; }
.ac-item-content { font-size:0.85rem; line-height:1.55; color:#e8e6df; }
.ac-lineage { font-size:0.72rem; color:#748090; margin-top:7px;
background:#172038; border-radius:6px; padding:4px 8px; display:inline-block; }
.ac-item-actions { display:flex; gap:8px; margin-top:10px; }
.ac-action-btn { font-size:0.75rem; padding:3px 10px; }
.ac-dismiss-btn { color:#e08080; border-color:#e08080; }
.ac-dismiss-btn:hover { background:rgba(224,128,128,0.08); }
.ac-empty-state { text-align:center; padding:48px 24px; }
.ac-empty-icon { font-size:2.5rem; margin-bottom:12px; }
.ac-empty-title { font-weight:600; font-size:0.95rem; margin-bottom:6px; }
.ac-empty-sub { font-size:0.82rem; color:#748090; line-height:1.6; max-width:340px; margin:0 auto; }
.ac-pin-form { background:#0f1525; border:1px solid #1b2640; border-radius:10px; padding:14px 16px; }
.ac-pin-textarea { width:100%; background:#172038; border:1px solid #1b2640; border-radius:8px;
color:#e8e6df; font-size:0.84rem; padding:9px 11px; resize:vertical; outline:none; font-family:inherit; }
.ac-pin-textarea:focus { border-color:#4a5568; }
.ac-pin-row { display:flex; gap:10px; margin-top:8px; align-items:center; }
.ac-pin-cat { flex:1; background:#172038; border:1px solid #1b2640; border-radius:8px;
color:#e8e6df; font-size:0.82rem; padding:6px 10px; outline:none; font-family:inherit; }
.ac-pin-cat:focus { border-color:#4a5568; }
.ac-toast { position:fixed; bottom:28px; right:28px; padding:10px 18px; border-radius:10px;
font-size:0.85rem; font-weight:500; z-index:9999; animation:ac-fadein 0.2s ease; }
.ac-toast.ok { background:#1a3d1a; border:1px solid #4ade80; color:#4ade80; }
.ac-toast.err { background:#3d1a1a; border:1px solid #e08080; color:#e08080; }
@keyframes ac-fadein { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:none; } }
@media (max-width:900px) {
.ac-root { padding:16px; }
.ac-layout { flex-direction:column; }
.ac-rail { width:100%; min-width:0; }
.ac-main { width:100%; }
}
`;
document.head.appendChild(el);
}
window.AgentContextPage = AgentContextPage;
})();