peon-mem 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +301 -0
  3. package/bin/peon-mem.mjs +273 -0
  4. package/dist/brain.d.ts +72 -0
  5. package/dist/brain.js +224 -0
  6. package/dist/compression.d.ts +9 -0
  7. package/dist/compression.js +37 -0
  8. package/dist/config.d.ts +22 -0
  9. package/dist/config.js +99 -0
  10. package/dist/daemon-cli.d.ts +2 -0
  11. package/dist/daemon-cli.js +54 -0
  12. package/dist/daemon.d.ts +23 -0
  13. package/dist/daemon.js +1078 -0
  14. package/dist/embedding-store.d.ts +43 -0
  15. package/dist/embedding-store.js +169 -0
  16. package/dist/embeddings.d.ts +93 -0
  17. package/dist/embeddings.js +345 -0
  18. package/dist/entities.d.ts +61 -0
  19. package/dist/entities.js +191 -0
  20. package/dist/entity-extraction.d.ts +33 -0
  21. package/dist/entity-extraction.js +75 -0
  22. package/dist/eval-metrics.d.ts +27 -0
  23. package/dist/eval-metrics.js +50 -0
  24. package/dist/evaluation.d.ts +58 -0
  25. package/dist/evaluation.js +244 -0
  26. package/dist/global-extraction.d.ts +15 -0
  27. package/dist/global-extraction.js +61 -0
  28. package/dist/global-memory.d.ts +43 -0
  29. package/dist/global-memory.js +306 -0
  30. package/dist/global-promotion.d.ts +25 -0
  31. package/dist/global-promotion.js +29 -0
  32. package/dist/hyde.d.ts +31 -0
  33. package/dist/hyde.js +46 -0
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.js +246 -0
  36. package/dist/injection.d.ts +38 -0
  37. package/dist/injection.js +133 -0
  38. package/dist/logger.d.ts +17 -0
  39. package/dist/logger.js +63 -0
  40. package/dist/memory-mutations.d.ts +24 -0
  41. package/dist/memory-mutations.js +57 -0
  42. package/dist/memory-store.d.ts +194 -0
  43. package/dist/memory-store.js +1205 -0
  44. package/dist/monitor.d.ts +13 -0
  45. package/dist/monitor.js +977 -0
  46. package/dist/overview.d.ts +73 -0
  47. package/dist/overview.js +104 -0
  48. package/dist/processor.d.ts +90 -0
  49. package/dist/processor.js +450 -0
  50. package/dist/quality.d.ts +86 -0
  51. package/dist/quality.js +338 -0
  52. package/dist/recuration.d.ts +13 -0
  53. package/dist/recuration.js +65 -0
  54. package/dist/reranker.d.ts +34 -0
  55. package/dist/reranker.js +89 -0
  56. package/dist/retrieval.d.ts +106 -0
  57. package/dist/retrieval.js +392 -0
  58. package/dist/session-index.d.ts +34 -0
  59. package/dist/session-index.js +87 -0
  60. package/dist/temporal.d.ts +20 -0
  61. package/dist/temporal.js +62 -0
  62. package/dist/token-ab-monitor.d.ts +1 -0
  63. package/dist/token-ab-monitor.js +7 -0
  64. package/dist/tools.d.ts +232 -0
  65. package/dist/tools.js +546 -0
  66. package/dist/types.d.ts +169 -0
  67. package/dist/types.js +1 -0
  68. package/docs/assets/neural-universe.png +0 -0
  69. package/package.json +57 -0
  70. package/scripts/claude-peon-hook.mjs +522 -0
  71. package/scripts/codex-peon-hook.mjs +4 -0
  72. package/scripts/eval-retrieval-labeled.mjs +135 -0
  73. package/scripts/eval-retrieval.mjs +96 -0
  74. package/scripts/evaluate-peon.mjs +47 -0
  75. package/scripts/install-peon-stl.mjs +82 -0
  76. package/scripts/install-peon.mjs +318 -0
  77. package/scripts/lib/eval-ledger.mjs +104 -0
  78. package/scripts/lib/stl-classify.mjs +44 -0
  79. package/scripts/longmemeval-eval.mjs +144 -0
  80. package/scripts/peon-report.mjs +155 -0
  81. package/scripts/peon-stl.mjs +506 -0
  82. package/scripts/token-ab-monitor.html +235 -0
@@ -0,0 +1,977 @@
1
+ /**
2
+ * Peon Memory Cockpit — a dark, multi-page console for trusting, steering, and
3
+ * proving the value of your AI's memory.
4
+ *
5
+ * Served at GET /monitor as one self-contained HTML document (no build step, no
6
+ * CDN — local-first). Hash-routed pages (#/overview, #/memory, #/network, #/ops):
7
+ * - Overview: what the AI knows, what it injected last, tokens saved, what needs review
8
+ * - Memory: the belief list — now editable (pin / edit / delete / merge)
9
+ * - Network: global memory + the cross-project map
10
+ * - Ops: consolidation cost, token A/B, live traffic and logs
11
+ * Fed by /monitor/state (poll), /overview, /network, and the /memory/* mutations.
12
+ */
13
+ export function renderMonitorHtml() {
14
+ return DOCUMENT;
15
+ }
16
+ const CLIENT_SCRIPT = String.raw `
17
+ var EL = function (id) { return document.getElementById(id); };
18
+ var STATUSES = ["active", "archived", "superseded", "stale", "conflicted"];
19
+ var STATUS_HELP = {
20
+ active: "a current belief Peon recalls",
21
+ archived: "compressed or resolved by the brain — recoverable, searchable, not injected",
22
+ superseded: "an old belief replaced by a newer one — kept as history",
23
+ stale: "aged out — not recalled by default",
24
+ conflicted: "contradicts another belief"
25
+ };
26
+ var ROUTES = ["brain","projects","overview","memory","ops"];
27
+ var ui = { project:"", filter:"all", search:null, focus:null, editing:null };
28
+ var latest = null, overview = null, network = null, dash = null, busy = false;
29
+ var ovLoadedAt = 0, netLoadedAt = 0, dashLoadedAt = 0, HEAVY_TTL = 12000;
30
+ var seenActivity = {}, activityFirstLoad = true, recentActivity = []; // for live popups
31
+ function freshNow(){ try{ return Date.now(); }catch(e){ return 0; } }
32
+ var KIND_ICON = { resolve_conflict:"⚖", merge_duplicate:"⛓", compress_cluster:"🗜", reinforce:"✦" };
33
+ var KIND_VERB = { resolve_conflict:"Resolved a conflict", merge_duplicate:"Merged duplicates", compress_cluster:"Compressed a topic", reinforce:"Reinforced a belief" };
34
+
35
+ async function pollActivity(){
36
+ var items;
37
+ try{ items=await fetch("/brain/activity?limit=40",{cache:"no-store"}).then(function(r){return r.json();}); }catch(e){ return; }
38
+ recentActivity=items||[];
39
+ var fresh=[];
40
+ recentActivity.forEach(function(a){ var key=a.at+"|"+a.type+"|"+a.detail; if(!seenActivity[key]){ seenActivity[key]=1; fresh.push(a); } });
41
+ if(!activityFirstLoad){
42
+ // Only the compression/resolve/merge actions deserve a popup — reinforce is too frequent/noisy.
43
+ fresh.filter(function(a){return a.type!=="reinforce";}).slice(0,3).forEach(showToast);
44
+ if(fresh.some(function(a){return a.type!=="reinforce";})) flashBrain();
45
+ }
46
+ activityFirstLoad=false;
47
+ if(currentRoute()==="brain"){ renderActivityFeed(); uniTicker(); }
48
+ }
49
+ function flashBrain(){ UNI.pulses.push({x:0,y:0,r0:24,r1:480,t:Date.now()}); }
50
+ function showToast(a){
51
+ var wrap=EL("toasts"); if(!wrap) return;
52
+ var t=document.createElement("div"); t.className="toast";
53
+ t.innerHTML='<span class="ti">'+(KIND_ICON[a.type]||"🧠")+'</span><div class="tb"><div class="tt">'+esc(KIND_VERB[a.type]||"Brain acted")+' · <span class="tp">'+esc(a.projectName)+'</span></div><div class="td">'+esc(clip(a.detail,90))+'</div></div>';
54
+ wrap.appendChild(t);
55
+ requestAnimationFrame(function(){ t.classList.add("in"); });
56
+ setTimeout(function(){ t.classList.remove("in"); setTimeout(function(){ if(t.parentNode) t.parentNode.removeChild(t); },400); },6000);
57
+ }
58
+
59
+ function esc(v){ return String(v==null?"":v).replace(/[&<>"']/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c];}); }
60
+ function clip(v,n){ var s=String(v==null?"":v).trim(); return s.length>n?s.slice(0,n)+"…":s; }
61
+ function fmt(n){ return (Number(n)||0).toLocaleString("en-US"); }
62
+ function tm(iso){ try{ return new Date(iso).toLocaleTimeString([], {hour:"2-digit",minute:"2-digit",second:"2-digit"}); }catch(e){ return ""; } }
63
+ function name(p){ return String(p||"").split("/").filter(Boolean).pop()||"project"; }
64
+ function pct(x){ return Math.max(0,Math.min(100,Math.round((Number(x)||0)*100))); }
65
+ function recordsOf(p){ return (p&&p.brain&&p.brain.records)||[]; }
66
+ function count(recs,st){ var n=0; for(var i=0;i<recs.length;i++) if(recs[i].status===st) n++; return n; }
67
+ function projects(){ return (latest&&latest.projects)||[]; }
68
+ function selected(){ var ps=projects(); if(!ps.length) return null; for(var i=0;i<ps.length;i++) if(ps[i].projectPath===ui.project) return ps[i]; return ps[0]; }
69
+ function recent(iso,sec){ try{ return (Date.now()-new Date(iso).getTime())<sec*1000; }catch(e){ return false; } }
70
+ function ago(iso){ try{ var s=Math.round((Date.now()-new Date(iso).getTime())/1000); if(s<60) return s+"s ago"; if(s<3600) return Math.round(s/60)+"m ago"; if(s<86400) return Math.round(s/3600)+"h ago"; return Math.round(s/86400)+"d ago"; }catch(e){ return ""; } }
71
+ function currentRoute(){ var h=(location.hash||"#/overview").replace("#/",""); return ROUTES.indexOf(h)>=0?h:"overview"; }
72
+ function scopeBadge(s){ var cls=s==="global"?"sb-global":"sb-project"; return '<span class="sb '+cls+'">'+esc(s)+'</span>'; }
73
+
74
+ async function refresh(){
75
+ var state;
76
+ try{ state=await fetch("/monitor/state",{cache:"no-store"}).then(function(r){return r.json();}); }
77
+ catch(e){ EL("status").textContent="offline"; EL("dot").classList.add("off"); return; }
78
+ EL("dot").classList.remove("off"); EL("status").textContent="live · "+tm(state.generatedAt);
79
+ latest=state;
80
+ var ps=projects();
81
+ if(!ui.project && ps.length){ var best=ps[0]; ps.forEach(function(p){ if(recordsOf(p).length>recordsOf(best).length) best=p; }); ui.project=best.projectPath; }
82
+ syncSwitcher(); renderRoute();
83
+ }
84
+
85
+ function syncSwitcher(){
86
+ var sel=EL("switcher"); if(!sel) return;
87
+ var ps=projects().slice().sort(function(a,b){return recordsOf(b).length-recordsOf(a).length;});
88
+ var cur=ui.project;
89
+ sel.innerHTML=ps.map(function(p){ return '<option value="'+esc(p.projectPath)+'"'+(p.projectPath===cur?" selected":"")+'>'+esc(name(p.projectPath))+' · '+count(recordsOf(p),"active")+'</option>'; }).join("");
90
+ }
91
+
92
+ function renderRoute(force){
93
+ var r=currentRoute();
94
+ ROUTES.forEach(function(x){ var p=EL("page-"+x); if(p) p.hidden=(x!==r); });
95
+ // Nav highlight: overview/memory belong under "projects".
96
+ ["brain","projects","ops"].forEach(function(x){ var t=EL("nav-"+x); if(t) t.classList.toggle("on", x===r || ((r==="overview"||r==="memory")&&x==="projects")); });
97
+ if(r==="brain"){ loadDashboard(force); renderBrainHome(); }
98
+ else if(r==="projects"){ loadNetwork(force); renderProjectsGrid(); }
99
+ else if(r==="overview"){ loadOverview(force); renderOverviewPage(); }
100
+ else if(r==="memory") renderMemoryPage();
101
+ else if(r==="ops") renderOpsPage();
102
+ }
103
+
104
+ // ===== BRAIN HOME (global) =====
105
+ async function loadDashboard(force){
106
+ if(!force && (freshNow()-dashLoadedAt)<HEAVY_TTL) return;
107
+ dashLoadedAt=freshNow();
108
+ try{ dash=await fetch("/global/dashboard",{cache:"no-store"}).then(function(r){return r.json();}); renderBrainHome(); }catch(e){}
109
+ }
110
+ // ===== NEURAL UNIVERSE — every real belief is a star; projects are galaxies =====
111
+ var UNI = { nodes:[], clusters:[], grid:{}, cell:46, cam:{x:0,y:0,z:1}, tx:{x:0,y:0,z:1},
112
+ hover:null, selected:null, hits:null, sig:"", raf:0, flash:0, pulses:[], drag:null, moved:false, t0:Date.now() };
113
+ var TYPE_COLOR = { decision:"#59e3ff", preference:"#5dffb0", fact:"#eaf6ff", artifact:"#ffc957",
114
+ open_question:"#ff9d70", timeline:"#7db4ff", summary:"#9db4ff" };
115
+ function uniCanvas(){ return EL("uni"); }
116
+ function hashN(str){ var h=2166136261>>>0; for(var i=0;i<str.length;i++){ h^=str.charCodeAt(i); h=Math.imul(h,16777619); } return (h>>>0)/4294967295; }
117
+
118
+ function buildUniverse(){
119
+ var ps=projects(); if(!ps.length) return;
120
+ var sig=ps.map(function(p){ return name(p.projectPath)+":"+recordsOf(p).length; }).join("|")+"|g"+((dash&&dash.records&&dash.records.length)||0);
121
+ if(sig===UNI.sig) return; UNI.sig=sig;
122
+ var nodes=[], clusters=[];
123
+ var sorted=ps.slice().sort(function(a,b){ return recordsOf(b).length-recordsOf(a).length; });
124
+ var golden=2.39996; var maxCount=Math.max(1,recordsOf(sorted[0]).length);
125
+ // galaxies on a ring; radius of each galaxy grows with sqrt(node count)
126
+ var ringR=430;
127
+ sorted.forEach(function(p,pi){
128
+ var recs=recordsOf(p); if(!recs.length) return;
129
+ var ang=(pi/sorted.length)*Math.PI*2 - Math.PI/2;
130
+ var gR=Math.sqrt(recs.length)*7+40;
131
+ var cx=Math.cos(ang)*(ringR+gR*0.35), cy=Math.sin(ang)*(ringR+gR*0.35)*0.72;
132
+ var cluster={ x:cx, y:cy, r:gR, name:name(p.projectPath), path:p.projectPath, count:recs.length,
133
+ active:count(recs,"active"), spin:(pi%2?1:-1)*(0.00003+0.00002*hashN(p.projectPath)) };
134
+ clusters.push(cluster);
135
+ recs.forEach(function(rec,i){
136
+ var rr=Math.sqrt(i+1)/Math.sqrt(recs.length)*gR;
137
+ var th=i*golden + hashN(rec.id)*0.35;
138
+ var imp=(rec.score&&rec.score.importance)||0.5;
139
+ var st=rec.status;
140
+ var col=TYPE_COLOR[rec.type]||"#9fd0e0";
141
+ var alpha=st==="active"?0.95:(st==="conflicted"?0.95:0.22);
142
+ if(st==="conflicted") col="#ff7a70";
143
+ nodes.push({ id:rec.id, rec:rec, cl:cluster, rad:rr, th:th,
144
+ x:0, y:0, r:0.9+imp*2.6+(rec.pinned?1.2:0), c:col, a:alpha, tw:hashN(rec.id)*6.28,
145
+ hot:recent(rec.updatedAt, 3600*6) });
146
+ });
147
+ });
148
+ // global memory = the center core's own small galaxy
149
+ var g=(dash&&dash.records)||[];
150
+ var gc={ x:0, y:0, r:Math.sqrt(g.length||1)*6+30, name:"GLOBAL", path:null, count:g.length, active:g.length, spin:0.00005 };
151
+ clusters.push(gc);
152
+ g.forEach(function(rec,i){
153
+ var rr=26+Math.sqrt(i+1)/Math.sqrt(g.length||1)*gc.r;
154
+ nodes.push({ id:"g-"+i, rec:rec, cl:gc, rad:rr, th:i*golden, x:0,y:0,
155
+ r:1.2+((rec.score&&rec.score.importance)||0.6)*2.2, c:"#eaf6ff", a:0.9, tw:hashN(String(i))*6.28, hot:false });
156
+ });
157
+ UNI.nodes=nodes; UNI.clusters=clusters;
158
+ uniFit(); uniLegend(); uniStats();
159
+ }
160
+
161
+ function uniFit(){
162
+ var c=uniCanvas(); if(!c) return;
163
+ var maxR=0; UNI.clusters.forEach(function(cl){ maxR=Math.max(maxR, Math.hypot(cl.x,cl.y)+cl.r+60); });
164
+ var z=Math.min(c.clientWidth,c.clientHeight)/(maxR*2)||1;
165
+ UNI.tx={x:0,y:0,z:z}; UNI.cam={x:0,y:0,z:z};
166
+ }
167
+ function uniLegend(){
168
+ var el=EL("uni-legend"); if(!el) return;
169
+ var t=Object.keys(TYPE_COLOR).map(function(k){ return '<span class="ul"><i style="background:'+TYPE_COLOR[k]+'"></i>'+k.replace("_"," ")+'</span>'; }).join("");
170
+ el.innerHTML=t+'<span class="ul"><i style="background:#ff7a70"></i>conflict</span><span class="ul dim"><i></i>dim = archived / superseded</span>';
171
+ }
172
+ function uniStats(){
173
+ var el=EL("uni-stats"); if(!el) return;
174
+ var tot=UNI.nodes.length, act=0, conf=0;
175
+ UNI.nodes.forEach(function(n){ if(n.rec.status==="active") act++; if(n.rec.status==="conflicted") conf++; });
176
+ el.innerHTML='<b>'+fmt(tot)+'</b> BELIEFS · <b>'+fmt(act)+'</b> ACTIVE · <b class="'+(conf?"warn":"")+'">'+fmt(conf)+'</b> CONFLICTED · <b>'+fmt(UNI.clusters.length-1)+'</b> GALAXIES';
177
+ }
178
+ function uniHealth(){
179
+ var el=EL("uni-health"); if(!el) return;
180
+ var h=(latest&&latest.health)||{}; var sv=h.serve;
181
+ var stl=h.stl?h.stl.headline:"";
182
+ var dotc=stl.indexOf("🔴")>=0?"#ff7a70":(stl.indexOf("🟡")>=0?"#ffc957":"#5dffb0");
183
+ el.innerHTML=(sv?('UPLINK <b>'+fmt(sv.count)+'</b>/24H · <b>'+(sv.avgMs/1000).toFixed(1)+'S</b> AVG · <b>~'+fmt(sv.avgTokens)+'</b> TOK'):'UPLINK —')+
184
+ ' <span class="stl-dot" style="background:'+dotc+'" title="'+esc(stl)+'"></span>';
185
+ }
186
+
187
+ function uniProject(n){ /* world position with slow galaxy rotation */
188
+ var t=(Date.now()-UNI.t0);
189
+ var th=n.th + t*n.cl.spin;
190
+ n.x=n.cl.x+Math.cos(th)*n.rad; n.y=n.cl.y+Math.sin(th)*n.rad*0.9;
191
+ }
192
+ function uniGridBuild(){
193
+ UNI.grid={};
194
+ UNI.nodes.forEach(function(n,i){ var k=Math.floor(n.x/UNI.cell)+","+Math.floor(n.y/UNI.cell); (UNI.grid[k]=UNI.grid[k]||[]).push(i); });
195
+ }
196
+ function uniPick(wx,wy){
197
+ var best=null,bd=1e9, cx=Math.floor(wx/UNI.cell), cy=Math.floor(wy/UNI.cell);
198
+ for(var dx=-1;dx<=1;dx++)for(var dy=-1;dy<=1;dy++){
199
+ var b=UNI.grid[(cx+dx)+","+(cy+dy)]; if(!b) continue;
200
+ for(var i=0;i<b.length;i++){ var n=UNI.nodes[b[i]]; var d=Math.hypot(n.x-wx,n.y-wy); if(d<bd){bd=d;best=n;} }
201
+ }
202
+ return bd < 14/UNI.cam.z ? best : null;
203
+ }
204
+
205
+ function uniDraw(){
206
+ var c=uniCanvas(); if(!c||currentRoute()!=="brain"){ UNI.raf=0; return; }
207
+ UNI.lastDraw=Date.now();
208
+ var dpr=window.devicePixelRatio||1;
209
+ if(c.width!==c.clientWidth*dpr||c.height!==c.clientHeight*dpr){ c.width=c.clientWidth*dpr; c.height=c.clientHeight*dpr; }
210
+ var ctx=c.getContext("2d");
211
+ var W=c.clientWidth,H=c.clientHeight,t=Date.now();
212
+ // ease camera
213
+ UNI.cam.x+=(UNI.tx.x-UNI.cam.x)*0.12; UNI.cam.y+=(UNI.tx.y-UNI.cam.y)*0.12; UNI.cam.z+=(UNI.tx.z-UNI.cam.z)*0.12;
214
+ ctx.setTransform(dpr,0,0,dpr,0,0);
215
+ ctx.clearRect(0,0,W,H);
216
+ ctx.save();
217
+ ctx.translate(W/2,H/2); ctx.scale(UNI.cam.z,UNI.cam.z); ctx.translate(-UNI.cam.x,-UNI.cam.y);
218
+ // galaxy rings + labels
219
+ UNI.clusters.forEach(function(cl){
220
+ ctx.beginPath(); ctx.arc(cl.x,cl.y,cl.r+14,0,6.29);
221
+ ctx.strokeStyle="rgba(89,227,255,.10)"; ctx.lineWidth=1/UNI.cam.z; ctx.setLineDash([4/UNI.cam.z,7/UNI.cam.z]); ctx.stroke(); ctx.setLineDash([]);
222
+ ctx.font=(11/UNI.cam.z)+"px ui-monospace,Menlo,monospace";
223
+ ctx.fillStyle="rgba(127,169,192,.85)"; ctx.textAlign="center";
224
+ ctx.fillText(cl.name.toUpperCase()+" · "+cl.count, cl.x, cl.y-cl.r-22/UNI.cam.z);
225
+ });
226
+ // core reactor
227
+ var pul=0.5+0.5*Math.sin(t/600);
228
+ var gr=ctx.createRadialGradient(0,0,2,0,0,26);
229
+ gr.addColorStop(0,"rgba(234,255,255,.95)"); gr.addColorStop(0.4,"rgba(158,240,255,.8)"); gr.addColorStop(1,"rgba(43,168,201,0)");
230
+ ctx.beginPath(); ctx.arc(0,0,26+pul*6,0,6.29); ctx.fillStyle=gr; ctx.fill();
231
+ // injection / activity pulses
232
+ UNI.pulses=UNI.pulses.filter(function(pp){ return t-pp.t<1200; });
233
+ UNI.pulses.forEach(function(pp){
234
+ var k=(t-pp.t)/1200;
235
+ ctx.beginPath(); ctx.arc(pp.x,pp.y,pp.r0+k*pp.r1,0,6.29);
236
+ ctx.strokeStyle="rgba(89,227,255,"+(0.5*(1-k))+")"; ctx.lineWidth=1.6/UNI.cam.z; ctx.stroke();
237
+ });
238
+ // nodes
239
+ var hits=UNI.hits, dimOthers=!!(hits&&hits.size);
240
+ UNI.nodes.forEach(function(n){
241
+ uniProject(n);
242
+ var tw=0.78+0.22*Math.sin(t/900+n.tw);
243
+ var a=n.a*tw, r=n.r;
244
+ if(dimOthers){ if(hits.has(n.id)){ a=1; r=n.r*1.7; } else a*=0.08; }
245
+ if(n.hot) r*=1.15;
246
+ ctx.beginPath(); ctx.arc(n.x,n.y,r/Math.sqrt(UNI.cam.z),0,6.29);
247
+ ctx.globalAlpha=Math.min(1,a);
248
+ ctx.fillStyle=n.c;
249
+ if((dimOthers&&hits.has(n.id))||n===UNI.hover||n===UNI.selected||n.rec.status==="conflicted"){ ctx.shadowColor=n.c; ctx.shadowBlur=12; } else ctx.shadowBlur=0;
250
+ ctx.fill(); ctx.shadowBlur=0; ctx.globalAlpha=1;
251
+ });
252
+ // hover crosshair
253
+ if(UNI.hover){ var hN=UNI.hover; var hr=10/UNI.cam.z;
254
+ ctx.strokeStyle="rgba(234,255,255,.85)"; ctx.lineWidth=1/UNI.cam.z;
255
+ ctx.beginPath(); ctx.arc(hN.x,hN.y,hr,0,6.29); ctx.stroke();
256
+ ctx.beginPath(); ctx.moveTo(hN.x-hr*1.8,hN.y); ctx.lineTo(hN.x-hr,hN.y); ctx.moveTo(hN.x+hr,hN.y); ctx.lineTo(hN.x+hr*1.8,hN.y);
257
+ ctx.moveTo(hN.x,hN.y-hr*1.8); ctx.lineTo(hN.x,hN.y-hr); ctx.moveTo(hN.x,hN.y+hr); ctx.lineTo(hN.x,hN.y+hr*1.8); ctx.stroke();
258
+ }
259
+ ctx.restore();
260
+ uniGridBuild();
261
+ // tooltip
262
+ var tip=EL("uni-tip");
263
+ if(tip){ if(UNI.hover){ var sp=uniToScreen(UNI.hover,W,H);
264
+ tip.hidden=false; tip.style.left=Math.min(W-330,Math.max(8,sp.x+16))+"px"; tip.style.top=Math.max(8,sp.y-14)+"px";
265
+ tip.innerHTML='<span class="tt-type" style="color:'+UNI.hover.c+'">'+esc(UNI.hover.rec.type)+'</span> '+esc(clip(UNI.hover.rec.content,140));
266
+ } else tip.hidden=true; }
267
+ UNI.raf=requestAnimationFrame(uniDraw);
268
+ }
269
+ function uniToScreen(n,W,H){ return { x:(n.x-UNI.cam.x)*UNI.cam.z+W/2, y:(n.y-UNI.cam.y)*UNI.cam.z+H/2 }; }
270
+ function uniToWorld(sx,sy){ var c=uniCanvas(); var W=c.clientWidth,H=c.clientHeight;
271
+ return { x:(sx-W/2)/UNI.cam.z+UNI.cam.x, y:(sy-H/2)/UNI.cam.z+UNI.cam.y }; }
272
+ function uniStart(){
273
+ if(!UNI.raf&&uniCanvas()) UNI.raf=requestAnimationFrame(uniDraw);
274
+ // Some embedded/webview panes throttle rAF to zero — a watchdog keeps the field alive.
275
+ if(!UNI.watchdog) UNI.watchdog=setInterval(function(){
276
+ if(currentRoute()!=="brain") return;
277
+ if(!UNI.lastDraw||Date.now()-UNI.lastDraw>400){ UNI.raf=0; uniDraw(); }
278
+ },300);
279
+ uniHealth();
280
+ }
281
+
282
+ function uniSearch(q){
283
+ q=String(q||"").trim().toLowerCase();
284
+ var hitsEl=EL("uq-hits");
285
+ if(!q){ UNI.hits=null; if(hitsEl) hitsEl.textContent=""; return; }
286
+ var toks=q.split(/\s+/).filter(Boolean);
287
+ var set=new Set(), sx=0, sy=0, m=0;
288
+ UNI.nodes.forEach(function(n){
289
+ var hay=(n.rec.content+" "+(n.rec.entities||[]).join(" ")).toLowerCase();
290
+ var ok=true; for(var i=0;i<toks.length;i++) if(hay.indexOf(toks[i])<0){ ok=false; break; }
291
+ if(ok){ set.add(n.id); sx+=n.x; sy+=n.y; m++; }
292
+ });
293
+ UNI.hits=set;
294
+ if(hitsEl) hitsEl.textContent=m?(m+" MATCH"+(m>1?"ES":"")):"NO MATCH";
295
+ if(m){ UNI.tx.x=sx/m; UNI.tx.y=sy/m; UNI.tx.z=Math.max(UNI.cam.z, m<20?1.1:0.55); }
296
+ }
297
+ function uniInspect(n){
298
+ var el=EL("uni-inspect"); if(!el) return;
299
+ if(!n){ el.hidden=true; UNI.selected=null; return; }
300
+ UNI.selected=n; var r=n.rec;
301
+ el.hidden=false;
302
+ el.innerHTML='<button class="ui-x" id="ui-x">✕</button>'+
303
+ '<div class="ui-type" style="color:'+n.c+'">'+esc(r.type)+' · '+esc(r.status||"global")+'</div>'+
304
+ '<div class="ui-content">'+esc(r.content)+'</div>'+
305
+ '<div class="ui-meta">'+
306
+ '<span class="g">IMP <b>'+pct(r.score&&r.score.importance)+'</b><i class="bar"><i style="width:'+pct(r.score&&r.score.importance)+'%"></i></i></span>'+
307
+ '<span class="g">CONF <b>'+pct(r.score&&r.score.confidence)+'</b><i class="bar"><i class="b2" style="width:'+pct(r.score&&r.score.confidence)+'%"></i></i></span></div>'+
308
+ ((r.entities&&r.entities.length)?'<div class="ui-ents">'+r.entities.slice(0,8).map(function(e){return '<span class="ent mono">'+esc(e)+'</span>';}).join("")+'</div>':"")+
309
+ '<div class="ui-proj mono">'+esc(n.cl.name)+(r.updatedAt?' · '+esc(ago(r.updatedAt)):'')+'</div>'+
310
+ (n.cl.path?'<button class="btn sm" id="ui-open">OPEN IN MEMORY BANKS →</button>':"");
311
+ var x=EL("ui-x"); if(x) x.addEventListener("click",function(){ uniInspect(null); });
312
+ var op=EL("ui-open"); if(op) op.addEventListener("click",function(){
313
+ ui.project=n.cl.path; ui.focus=n.id; ui.search=null; syncSwitcher(); location.hash="#/memory"; });
314
+ }
315
+ function uniTicker(){
316
+ var el=EL("uni-ticker"); if(!el) return;
317
+ var items=recentActivity.slice(0,3);
318
+ el.innerHTML=items.length?items.map(function(a,i){
319
+ return '<div class="utk" style="opacity:'+(1-i*0.3)+'"><span class="utk-k">'+esc(a.type.replace("_"," "))+'</span> '+esc(a.projectName)+' — '+esc(clip(a.detail,110))+' <span class="utk-t">'+esc(ago(a.at))+'</span></div>';
320
+ }).join(""):'<div class="utk">memory field stable — no autonomous actions yet</div>';
321
+ }
322
+ function uniWire(){
323
+ var c=uniCanvas(); if(!c||c.getAttribute("data-wired")) return; c.setAttribute("data-wired","1");
324
+ c.addEventListener("wheel",function(e){ e.preventDefault();
325
+ var w=uniToWorld(e.offsetX,e.offsetY);
326
+ var z=UNI.tx.z*Math.pow(1.0015,-e.deltaY); z=Math.max(0.12,Math.min(9,z));
327
+ UNI.tx.z=z; UNI.tx.x=w.x-(e.offsetX-c.clientWidth/2)/z; UNI.tx.y=w.y-(e.offsetY-c.clientHeight/2)/z;
328
+ UNI.cam.z=z; UNI.cam.x=UNI.tx.x; UNI.cam.y=UNI.tx.y;
329
+ },{passive:false});
330
+ c.addEventListener("mousedown",function(e){ UNI.drag={x:e.clientX,y:e.clientY,cx:UNI.tx.x,cy:UNI.tx.y}; UNI.moved=false; });
331
+ window.addEventListener("mousemove",function(e){
332
+ if(UNI.drag){ var dx=(e.clientX-UNI.drag.x)/UNI.cam.z, dy=(e.clientY-UNI.drag.y)/UNI.cam.z;
333
+ if(Math.hypot(e.clientX-UNI.drag.x,e.clientY-UNI.drag.y)>4) UNI.moved=true;
334
+ UNI.tx.x=UNI.drag.cx-dx; UNI.tx.y=UNI.drag.cy-dy; UNI.cam.x=UNI.tx.x; UNI.cam.y=UNI.tx.y; }
335
+ else if(e.target===c){ var r=c.getBoundingClientRect(); var w=uniToWorld(e.clientX-r.left,e.clientY-r.top); UNI.hover=uniPick(w.x,w.y); }
336
+ });
337
+ window.addEventListener("mouseup",function(e){
338
+ if(UNI.drag&&!UNI.moved&&e.target===c){ if(UNI.hover) uniInspect(UNI.hover); else uniInspect(null); }
339
+ UNI.drag=null;
340
+ });
341
+ c.addEventListener("dblclick",function(){ uniFit(); });
342
+ var uq=EL("uq"); if(uq){ uq.addEventListener("input",function(){ uniSearch(uq.value); }); }
343
+ var fit=EL("uni-fit"); if(fit) fit.addEventListener("click",function(){ uniFit(); });
344
+ }
345
+
346
+ function renderBrainHome(){
347
+ var d=dash||{totalBeliefs:0,byType:{},topEntities:[],recentActions:[],records:[]};
348
+ buildUniverse(); uniWire(); uniStart(); uniTicker(); uniHealth();
349
+ var types=Object.keys(d.byType||{}).map(function(t){return [t,d.byType[t]];}).sort(function(a,b){return b[1]-a[1];});
350
+ var html='<section class="panel"><div class="phead"><div class="ht"><h2>Global memory</h2><span class="note">cross-cutting facts every project recalls · '+fmt(d.totalBeliefs)+' beliefs</span></div></div>'+
351
+ '<div class="bhtypes" style="margin-bottom:10px">'+(types.length?types.map(function(t){return '<span class="tchip">'+esc(t[0])+' <b>'+t[1]+'</b></span>';}).join(""):'<span class="muted"></span>')+'</div>'+
352
+ '<div class="scroll" style="max-height:260px">'+((d.records||[]).length?d.records.map(function(g){return '<div class="rec active"><div class="recline"><span class="rtype">'+esc(g.type)+'</span><span class="rcontent">'+esc(g.content)+'</span></div></div>';}).join(""):'<div class="empty">Global memory is empty — cross-cutting facts auto-promote here.</div>')+'</div></section>'+
353
+ '<section class="panel"><div class="phead"><div class="ht"><h2>what the brain is doing</h2><span class="note">live autonomous activity</span></div></div><div id="bhactivity"></div></section>';
354
+ EL("bh-body").innerHTML=html;
355
+ renderActivityFeed();
356
+ }
357
+ function renderActivityFeed(){
358
+ var el=EL("bhactivity"); if(!el) return;
359
+ var items=recentActivity.slice(0,12);
360
+ el.innerHTML=items.length?items.map(function(a){
361
+ return '<div class="didrow"><span class="didkind k-'+esc(a.type)+'">'+esc(a.type.replace("_"," "))+'</span>'+
362
+ '<span class="sb '+(a.scope==="global"?"sb-global":"sb-project")+'">'+esc(a.projectName)+'</span>'+
363
+ '<span class="didtxt">'+esc(clip(a.detail,80))+'</span><span class="didtime">'+esc(ago(a.at))+'</span></div>';
364
+ }).join(""):'<div class="empty">The brain hasn\'t needed to act yet. It curates automatically as memory grows.</div>';
365
+ }
366
+
367
+ // ===== PROJECTS GRID → drill into insights =====
368
+ function renderProjectsGrid(){
369
+ var n=network||{projects:[]};
370
+ // Disambiguate same-named projects by showing their parent folder.
371
+ var nameCount={}; n.projects.forEach(function(p){ nameCount[p.projectName]=(nameCount[p.projectName]||0)+1; });
372
+ function parentOf(path){ var parts=String(path).split("/").filter(Boolean); return parts.length>1?parts[parts.length-2]:""; }
373
+ EL("projgrid").innerHTML=n.projects.length?n.projects.map(function(p){
374
+ var dis=nameCount[p.projectName]>1?'<span class="pcdis"> · in '+esc(parentOf(p.projectPath))+'</span>':'';
375
+ return '<button class="pcard" data-p="'+esc(p.projectPath)+'" title="'+esc(p.projectPath)+'">'+
376
+ '<div class="pcname">'+esc(p.projectName)+dis+'</div>'+
377
+ '<div class="pcstats"><span class="pcg">'+p.active+'</span> active beliefs'+(p.pinned?' · '+p.pinned+' pinned':'')+'</div>'+
378
+ '<div class="pcgo">open insights →</div></button>';
379
+ }).join(""):'<div class="empty">No projects yet</div>';
380
+ Array.prototype.forEach.call(document.querySelectorAll(".pcard"),function(b){ b.addEventListener("click",function(){ ui.project=b.getAttribute("data-p"); ui.search=null; ui.focus=null; overview=null; syncSwitcher(); location.hash="#/overview"; }); });
381
+ }
382
+
383
+ // ===== OVERVIEW =====
384
+ async function loadOverview(force){
385
+ if(!ui.project) return;
386
+ if(!force && (freshNow()-ovLoadedAt)<HEAVY_TTL) return; // throttle the heavy endpoint
387
+ ovLoadedAt=freshNow();
388
+ try{ overview=await fetch("/overview?projectPath="+encodeURIComponent(ui.project),{cache:"no-store"}).then(function(r){return r.json();}); renderOverviewPage(); }catch(e){}
389
+ }
390
+ function renderOverviewPage(){
391
+ var sel=selected();
392
+ if(!sel){ EL("ov-body").innerHTML='<div class="empty">No project selected.</div>'; return; }
393
+ var o=overview && overview.projectPath===ui.project ? overview : null;
394
+ var recs=recordsOf(sel);
395
+ var c=o?o.counts:{active:count(recs,"active"),superseded:count(recs,"superseded"),conflicts:count(recs,"conflicted"),stale:count(recs,"stale"),total:recs.length,project:count(recs,"active"),global:0};
396
+ var conflicts=o?o.needsReview.conflicts:[];
397
+ var dups=o?o.needsReview.duplicates:[];
398
+ var reviewN=conflicts.length+dups.length;
399
+ var healthy=reviewN===0;
400
+ var saved=o&&o.tokensSaved;
401
+
402
+ var vitals=o&&o.vitals;
403
+ var pulse=vitals&&vitals.alive?'<span class="pulse-dot" title="the brain is awake"></span> awake':'<span class="pulse-dot off"></span> asleep';
404
+ var dream=vitals&&vitals.lastDreamAt?' · last curated '+esc(ago(vitals.lastDreamAt)):(vitals&&vitals.lastHeartbeatAt?' · last pulse '+esc(ago(vitals.lastHeartbeatAt)):'');
405
+ var hero='<div class="hero"><div class="hero-l"><span class="hero-name">'+esc(name(sel.projectPath))+'</span>'+
406
+ '<span class="health '+(healthy?"ok":"warn")+'">'+(healthy?"memory healthy":reviewN+" need review")+'</span>'+
407
+ '<span class="vital">'+pulse+dream+'</span></div>'+
408
+ '<span class="hero-r">'+(o&&o.lastConsolidatedAt?"last consolidated "+esc(ago(o.lastConsolidatedAt)):"")+'</span></div>';
409
+
410
+ var savedCard = saved
411
+ ? '<div class="cardnum g">~'+fmt(saved.savedPerSession)+'</div><div class="cardlbl">tokens saved / session</div><div class="cardsub">'+saved.onSessions+' on · '+saved.offSessions+' off baseline</div>'
412
+ : '<div class="cardnum muted">—</div><div class="cardlbl">tokens saved / session</div><div class="cardsub">run a Peon-off session to compare</div>';
413
+ var cards='<div class="cards">'+
414
+ '<div class="card"><div class="cardnum">'+fmt(c.active)+'</div><div class="cardlbl">beliefs known</div><div class="cardsub">'+fmt(c.project)+' project · '+fmt(c.global)+' global</div></div>'+
415
+ '<div class="card">'+savedCard+'</div>'+
416
+ '<div class="card"><div class="cardnum '+(reviewN?"a":"")+'">'+reviewN+'</div><div class="cardlbl">needs review</div><div class="cardsub">'+dups.length+' duplicate · '+conflicts.length+' conflict</div></div>'+
417
+ '</div>';
418
+
419
+ var injItems=o&&o.lastInjection?o.lastInjection.items:[];
420
+ var injBody=injItems.length?injItems.slice(0,8).map(function(it){
421
+ return '<div class="injrow">'+scopeBadge(it.scope)+'<span class="injc">'+esc(clip(it.content,150))+'</span><span class="injs">'+pct(it.score)+'%</span></div>';
422
+ }).join(""):'<div class="empty">No injection captured yet — Peon fills this on your next prompt.</div>';
423
+ var inj='<section class="panel"><div class="phead"><div class="ht"><h2>what Peon injected into your last prompt</h2><span class="note">'+(o&&o.lastInjection&&o.lastInjection.query?'"'+esc(clip(o.lastInjection.query,60))+'"':"")+'</span></div></div>'+injBody+'</section>';
424
+
425
+ var review="";
426
+ if(reviewN){
427
+ var rows=dups.map(function(d){
428
+ return '<div class="revrow"><span class="revtxt">Near-duplicate: <b>'+esc(clip(d.aContent,60))+'</b> / '+esc(clip(d.bContent,60))+'</span>'+
429
+ '<span class="revacts"><button class="btn sm" data-merge-keep="'+esc(d.aId)+'" data-merge-drop="'+esc(d.bId)+'">Merge</button></span></div>';
430
+ }).join("");
431
+ rows+=conflicts.map(function(cf){
432
+ return '<div class="revrow"><span class="revtxt">Conflict: '+esc(clip(cf.content,90))+'</span>'+
433
+ '<span class="revacts"><button class="btn sm ghost" data-goto="'+esc(cf.id)+'">Resolve</button></span></div>';
434
+ }).join("");
435
+ review='<section class="panel review"><div class="phead"><div class="ht"><h2>needs your attention</h2></div></div>'+rows+'</section>';
436
+ }
437
+
438
+ // What the brain did on its own — the autonomous action feed, with undo.
439
+ var acts=(o&&o.brainActions)||[];
440
+ var did="";
441
+ if(acts.length){
442
+ var rows=acts.slice(0,8).map(function(e){
443
+ return e.actions.map(function(a){
444
+ return '<div class="didrow"><span class="didkind k-'+esc(a.type)+'">'+esc(a.type.replace("_"," "))+'</span><span class="didtxt">'+esc(clip(a.detail,90))+'</span><span class="didtime">'+esc(ago(e.at))+'</span></div>';
445
+ }).join("");
446
+ }).join("");
447
+ did='<section class="panel"><div class="phead"><div class="ht"><h2>what the brain did on its own</h2><button class="btn sm ghost" id="undo-brain" title="restore the most recent backup snapshot">Undo last</button></div><div class="cap">autonomous curation — every change is recoverable</div></div>'+rows+'</section>';
448
+ }
449
+
450
+ EL("ov-body").innerHTML=hero+cards+inj+review+did;
451
+ Array.prototype.forEach.call(document.querySelectorAll("[data-merge-keep]"),function(b){ b.addEventListener("click",function(){ mutate("/memory/merge",{projectPath:ui.project,keepId:b.getAttribute("data-merge-keep"),dropId:b.getAttribute("data-merge-drop")}); }); });
452
+ Array.prototype.forEach.call(document.querySelectorAll("[data-goto]"),function(b){ b.addEventListener("click",function(){ ui.focus=b.getAttribute("data-goto"); location.hash="#/memory"; }); });
453
+ var undo=EL("undo-brain"); if(undo) undo.addEventListener("click",function(){ if(confirm("Restore the most recent backup, undoing the brain's last changes?")) mutate("/brain/restore",{projectPath:ui.project}); });
454
+ }
455
+
456
+ // ===== MEMORY (editable) =====
457
+ function renderMemoryPage(){
458
+ var sel=selected();
459
+ EL("memScope").textContent=sel?name(sel.projectPath):"";
460
+ if(!sel){ EL("records").innerHTML='<div class="empty">No project</div>'; EL("filters").innerHTML=""; return; }
461
+ var recs=recordsOf(sel);
462
+ var chips=[["all",recs.length,"every record"]]; STATUSES.forEach(function(s){ chips.push([s,count(recs,s),STATUS_HELP[s]]); });
463
+ EL("filters").innerHTML=chips.map(function(c){ return '<button class="chip'+(ui.filter===c[0]?" on":"")+'" data-f="'+c[0]+'" title="'+esc(c[2])+'">'+(c[0]==="all"?"":'<span class="sd '+c[0]+'"></span>')+esc(c[0])+'<span class="cc">'+c[1]+'</span></button>'; }).join("");
464
+ Array.prototype.forEach.call(document.querySelectorAll(".chip"),function(b){ b.addEventListener("click",function(){ ui.filter=b.getAttribute("data-f"); renderMemoryPage(); }); });
465
+ var isSearch=!!ui.search,rows;
466
+ if(isSearch) rows=ui.search.map(function(r){return {record:r.record,why:r.explanation||""};});
467
+ else rows=recs.filter(function(r){return ui.filter==="all"||r.status===ui.filter;}).sort(function(a,b){ var ap=a.pinned?1:0,bp=b.pinned?1:0; if(ap!==bp) return bp-ap; var aw=a.status==="active"?1:0,bw=b.status==="active"?1:0; if(aw!==bw) return bw-aw; return (b.score.importance+b.score.confidence)-(a.score.importance+a.score.confidence); }).map(function(r){return {record:r,why:""};});
468
+ EL("memCount").textContent=rows.length+(isSearch?" matches":"");
469
+ EL("records").innerHTML=rows.length?rows.map(function(r){return recCard(r.record,r.why);}).join(""):'<div class="empty">'+(isSearch?"No matches":"No records")+'</div>';
470
+ wireRecordActions();
471
+ EL("inject").textContent=(sel.brain&&sel.brain.injectionPreview&&sel.brain.injectionPreview.trim())?clip(sel.brain.injectionPreview,2400):"—";
472
+ }
473
+ function recCard(r,why){ var hl=ui.focus===r.id?" hl":""; var editing=ui.editing===r.id;
474
+ var content=editing
475
+ ? '<input class="editin" id="edit-'+esc(r.id)+'" value="'+esc(r.content)+'"><button class="btn sm" data-save="'+esc(r.id)+'">Save</button><button class="btn sm ghost" data-canceledit="1">Cancel</button>'
476
+ : '<span class="rcontent">'+esc(r.content)+'</span>';
477
+ return '<div class="rec '+esc(r.status)+hl+(r.pinned?" pinned":"")+'" id="rec-'+esc(r.id)+'"><div class="recline"><span class="sd '+esc(r.status)+'" title="'+esc(STATUS_HELP[r.status]||r.status)+'"></span><span class="rtype">'+esc(r.type)+'</span>'+content+
478
+ '<span class="racts">'+
479
+ '<button class="ract'+(r.pinned?" on":"")+'" data-pin="'+esc(r.id)+'" data-pinned="'+(r.pinned?"1":"0")+'" title="'+(r.pinned?"unpin":"pin")+'">'+(r.pinned?"★":"☆")+'</button>'+
480
+ '<button class="ract" data-edit="'+esc(r.id)+'" title="edit">✎</button>'+
481
+ '<button class="ract del" data-del="'+esc(r.id)+'" title="delete">✕</button>'+
482
+ '</span></div>'+
483
+ '<div class="recmeta"><span class="g">imp <b>'+pct(r.score.importance)+'</b><i class="bar"><i style="width:'+pct(r.score.importance)+'%"></i></i></span><span class="g">conf <b>'+pct(r.score.confidence)+'</b><i class="bar"><i class="b2" style="width:'+pct(r.score.confidence)+'%"></i></i></span>'+scopeBadge(r.scope)+
484
+ (r.entities&&r.entities.length?r.entities.slice(0,6).map(function(e){return '<span class="ent mono">'+esc(e)+'</span>';}).join(""):"")+'</div>'+(why?'<div class="why">'+esc(clip(why,130))+'</div>':"")+'</div>';
485
+ }
486
+ function wireRecordActions(){
487
+ Array.prototype.forEach.call(document.querySelectorAll("[data-pin]"),function(b){ b.addEventListener("click",function(){ mutate("/memory/update",{projectPath:ui.project,id:b.getAttribute("data-pin"),pinned:b.getAttribute("data-pinned")!=="1"}); }); });
488
+ Array.prototype.forEach.call(document.querySelectorAll("[data-del]"),function(b){ b.addEventListener("click",function(){ if(confirm("Delete this belief permanently?")) mutate("/memory/delete",{projectPath:ui.project,id:b.getAttribute("data-del")}); }); });
489
+ Array.prototype.forEach.call(document.querySelectorAll("[data-edit]"),function(b){ b.addEventListener("click",function(){ ui.editing=b.getAttribute("data-edit"); renderMemoryPage(); var inp=EL("edit-"+ui.editing); if(inp){ inp.focus(); inp.select(); } }); });
490
+ Array.prototype.forEach.call(document.querySelectorAll("[data-canceledit]"),function(b){ b.addEventListener("click",function(){ ui.editing=null; renderMemoryPage(); }); });
491
+ Array.prototype.forEach.call(document.querySelectorAll("[data-save]"),function(b){ b.addEventListener("click",function(){ var id=b.getAttribute("data-save"); var inp=EL("edit-"+id); var v=inp?inp.value.trim():""; if(v){ ui.editing=null; mutate("/memory/update",{projectPath:ui.project,id:id,content:v}); } }); });
492
+ Array.prototype.forEach.call(document.querySelectorAll(".editin"),function(inp){ inp.addEventListener("keydown",function(e){ if(e.key==="Enter"){ var id=inp.id.replace("edit-",""); var v=inp.value.trim(); if(v){ ui.editing=null; mutate("/memory/update",{projectPath:ui.project,id:id,content:v}); } } if(e.key==="Escape"){ ui.editing=null; renderMemoryPage(); } }); });
493
+ }
494
+ async function mutate(path,body){
495
+ if(busy) return; busy=true;
496
+ try{ await fetch(path,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(body)}); }catch(e){}
497
+ busy=false; ui.search=null; var cl=EL("clear"); if(cl) cl.hidden=true; var q=EL("q"); if(q) q.value="";
498
+ await refresh(); if(currentRoute()==="overview") loadOverview(true);
499
+ }
500
+ async function runSearch(){ var q=EL("q").value.trim(); if(!q||!ui.project) return; EL("go").disabled=true;
501
+ try{ var res=await fetch("/search?projectPath="+encodeURIComponent(ui.project)+"&query="+encodeURIComponent(q),{cache:"no-store"}).then(function(r){return r.json();}); ui.search=res.records||[]; EL("clear").hidden=false; }catch(e){ ui.search=[]; }
502
+ EL("go").disabled=false; renderMemoryPage(); }
503
+ async function processNow(){ if(!ui.project) return; var b=EL("process"); b.disabled=true; b.textContent="Processing…";
504
+ try{ await fetch("/process",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({projectPath:ui.project,reason:"monitor-manual"})}); }catch(e){} b.disabled=false; b.textContent="Process now"; refresh(); }
505
+
506
+ // ===== NETWORK =====
507
+ async function loadNetwork(force){
508
+ if(!force && (freshNow()-netLoadedAt)<HEAVY_TTL) return; // throttle the heavy endpoint
509
+ netLoadedAt=freshNow();
510
+ try{ network=await fetch("/network",{cache:"no-store"}).then(function(r){return r.json();}); renderProjectsGrid(); }catch(e){}
511
+ }
512
+
513
+ // ===== OPS (tokens + activity) =====
514
+ function renderOpsPage(){
515
+ /* health strip: serve latency/cost (24h telemetry) + STL daily self-check verdict */
516
+ var h=(latest&&latest.health)||{};
517
+ var sv=h.serve;
518
+ EL("hl-lat").textContent = sv ? (sv.avgMs/1000).toFixed(1)+"s / "+(sv.p95Ms/1000).toFixed(1)+"s" : "–";
519
+ EL("hl-count").textContent = sv ? fmt(sv.count) : "–";
520
+ EL("hl-tok").textContent = sv ? fmt(sv.avgTokens) : "–";
521
+ EL("hl-stl").textContent = h.stl ? "Daily self-check ("+(h.stl.generatedAt||"").slice(0,10)+"): "+h.stl.headline : "Daily self-check: no report yet.";
522
+ var t=(latest&&latest.tokens)||{total:0,runs:0,byModel:{},byProject:{},recent:[]};
523
+ EL("tok-total").textContent=fmt(t.total);
524
+ EL("tok-runs").textContent=fmt(t.runs);
525
+ EL("tok-avg").textContent=t.runs?fmt(Math.round(t.total/t.runs)):"0";
526
+ EL("tok-cost").textContent="≈ $"+(t.total/1000000*0.10).toFixed(4);
527
+ var byp=Object.keys(t.byProject||{}).map(function(p){return [p,t.byProject[p]];}).filter(function(x){return x[1]>0;}).sort(function(a,b){return b[1]-a[1];}).slice(0,8);
528
+ var pmax=byp.reduce(function(a,p){return Math.max(a,p[1]);},1);
529
+ EL("tok-projects").innerHTML=byp.length?byp.map(function(p){ var on=p[0]===ui.project; return '<div class="trow"><div class="tlabel'+(on?" cur":"")+'">'+esc(name(p[0]))+'</div><div class="tbar"><i class="g" style="width:'+(p[1]/pmax*100)+'%"></i></div><div class="tval">'+fmt(p[1])+'</div></div>'; }).join(""):'<div class="empty">No runs yet</div>';
530
+ EL("tok-recent").innerHTML=(t.recent||[]).length?t.recent.slice(0,14).map(function(r){
531
+ var bits=[]; if(r.superseded) bits.push(r.superseded+" changed"); if(r.merged) bits.push(r.merged+" merged"); if(r.recordsAdded) bits.push(r.recordsAdded+" learned");
532
+ return '<div class="row"><div class="rmain"><span class="rt">'+esc(name(r.projectPath))+'</span><span class="rs">'+esc(tm(r.createdAt))+'</span></div><div class="rsub mono">'+fmt(r.tokens)+' tok · '+esc(r.model)+(bits.length?' · '+bits.join(", "):"")+'</div></div>';
533
+ }).join(""):'<div class="empty">No consolidations recorded yet</div>';
534
+ var s=latest||{}; var sess=s.activeSessions||[], jobs=s.processingJobs||[], logs=s.recentLogs||[];
535
+ EL("sessions").innerHTML=sess.length?sess.slice(0,10).map(function(x){ return '<div class="row"><div class="rmain"><span class="rt">'+esc(x.client)+'</span><span class="rs">'+esc(tm(x.startedAt))+'</span></div><div class="rsub mono">'+esc(name(x.projectPath))+'</div></div>'; }).join(""):'<div class="empty">No active sessions</div>';
536
+ EL("jobs").innerHTML=jobs.length?jobs.slice(0,10).map(function(j){ var st=j.stats; var line=st?((st.superseded||0)+" superseded · "+(st.merged||0)+" merged · "+(st.recordsAdded||0)+" added"):"";
537
+ return '<div class="row"><div class="rmain"><span class="rt"><span class="sd '+esc(j.status)+'"></span>'+esc(j.status)+'</span><span class="rs">'+esc(tm(j.createdAt))+'</span></div><div class="rsub">'+esc(j.model||"no model")+' · '+esc(j.estimatedTokens||0)+' tok</div>'+(line?'<div class="rsub strong">'+esc(line)+'</div>':'')+(j.error?'<div class="rsub err">'+esc(clip(j.error,70))+'</div>':'')+'</div>'; }).join(""):'<div class="empty">No processing yet</div>';
538
+ EL("logs").innerHTML=logs.length?logs.slice(0,14).map(function(l){ return '<div class="row tight"><div class="rmain"><span class="rt">'+esc(l.type)+'</span><span class="rs">'+esc(tm(l.createdAt))+'</span></div>'+(l.path?'<div class="rsub mono">'+esc(l.method||"")+' '+esc(l.path)+' '+esc(l.status||"")+'</div>':'')+(l.error?'<div class="rsub err">'+esc(clip(l.error,80))+'</div>':'')+'</div>'; }).join(""):'<div class="empty">No logs</div>';
539
+ }
540
+
541
+ // wire-up
542
+ window.addEventListener("hashchange",function(){ renderRoute(true); });
543
+ EL("switcher").addEventListener("change",function(e){ ui.project=e.target.value; ui.search=null; ui.focus=null; ui.editing=null; overview=null; renderRoute(true); });
544
+ EL("go").addEventListener("click",runSearch);
545
+ EL("clear").addEventListener("click",function(){ ui.search=null; EL("q").value=""; EL("clear").hidden=true; renderMemoryPage(); });
546
+ EL("q").addEventListener("keydown",function(e){ if(e.key==="Enter") runSearch(); });
547
+ EL("process").addEventListener("click",processNow);
548
+ if(!location.hash) location.hash="#/brain";
549
+ refresh(); setInterval(refresh,2500);
550
+ pollActivity(); setInterval(pollActivity,4000); // live brain popups
551
+ `;
552
+ const DOCUMENT = String.raw `<!doctype html>
553
+ <html lang="en">
554
+ <head>
555
+ <meta charset="utf-8">
556
+ <meta name="viewport" content="width=device-width, initial-scale=1">
557
+ <title>PEON // NEURAL HUD</title>
558
+ <style>
559
+ :root{
560
+ --bg:#020710; --bg2:#041020; --panel:rgba(7,22,38,.66); --panel2:rgba(5,16,30,.66);
561
+ --line:rgba(89,227,255,.16); --line2:rgba(89,227,255,.36);
562
+ --ink:#d9f6ff; --muted:#7fa9c0; --faint:#48708a;
563
+ --cyan:#59e3ff; --cyan-soft:rgba(89,227,255,.12); --cyan-ink:#b9f2ff;
564
+ --green:#5dffb0; --green-soft:rgba(93,255,176,.12); --green-ink:#b9ffdd;
565
+ --blue:#7db4ff; --blue-soft:rgba(125,180,255,.14); --violet:#9db4ff;
566
+ --amber:#ffc957; --amber-soft:rgba(255,201,87,.13); --red:#ff7a70;
567
+ --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,monospace; --r:4px;
568
+ --cham:polygon(10px 0,100% 0,100% calc(100% - 10px),calc(100% - 10px) 100%,0 100%,0 10px);
569
+ }
570
+ *{box-sizing:border-box;}
571
+ html{scrollbar-color:#12374d var(--bg);}
572
+ body{margin:0; color:var(--ink); font-family:ui-sans-serif,system-ui,-apple-system,"SF Pro Text","Segoe UI",Roboto,sans-serif;
573
+ font-size:13.5px; line-height:1.55; letter-spacing:.01em; -webkit-font-smoothing:antialiased;
574
+ background:
575
+ radial-gradient(1100px 700px at 50% -220px, rgba(43,168,201,.16) 0%, transparent 60%),
576
+ radial-gradient(900px 500px at 110% 110%, rgba(43,120,201,.10) 0%, transparent 55%),
577
+ linear-gradient(rgba(89,227,255,.028) 1px, transparent 1px),
578
+ linear-gradient(90deg, rgba(89,227,255,.028) 1px, transparent 1px),
579
+ var(--bg);
580
+ background-size:auto,auto,44px 44px,44px 44px,auto;}
581
+ /* CRT scanlines + roaming scan bar + vignette */
582
+ body::before{content:""; position:fixed; inset:0; z-index:99; pointer-events:none;
583
+ background:repeating-linear-gradient(0deg, rgba(200,240,255,.022) 0 1px, transparent 1px 3px);}
584
+ body::after{content:""; position:fixed; left:0; right:0; top:-15%; height:26vh; z-index:98; pointer-events:none;
585
+ background:linear-gradient(180deg, transparent, rgba(89,227,255,.045) 55%, rgba(89,227,255,.09) 60%, transparent);
586
+ animation:scanroam 9s linear infinite;}
587
+ @keyframes scanroam{0%{transform:translateY(-30vh);} 100%{transform:translateY(160vh);}}
588
+ h1,h2,h3,p{margin:0;}
589
+ .mono{font-family:var(--mono); font-size:11px; letter-spacing:.02em;}
590
+ button,select,input{font:inherit; cursor:pointer; color:inherit;}
591
+ :focus-visible{outline:1.5px solid var(--cyan); outline-offset:2px;}
592
+ [title]{cursor:help;}
593
+ ::selection{background:rgba(89,227,255,.3);}
594
+
595
+ /* ── HUD top bar ── */
596
+ header{position:sticky; top:0; z-index:40; display:flex; align-items:center; gap:18px;
597
+ padding:10px 22px; background:linear-gradient(180deg, rgba(4,14,26,.94), rgba(3,10,20,.86));
598
+ backdrop-filter:saturate(1.3) blur(14px); border-bottom:1px solid var(--line);
599
+ box-shadow:0 1px 0 rgba(89,227,255,.06), 0 12px 40px -18px rgba(43,168,201,.35);}
600
+ header::after{content:""; position:absolute; left:0; right:0; bottom:-1px; height:1px;
601
+ background:linear-gradient(90deg, transparent, var(--cyan) 30%, transparent 60%); opacity:.5; animation:hdrsweep 7s linear infinite;}
602
+ @keyframes hdrsweep{0%{transform:translateX(-40%);} 100%{transform:translateX(60%);}}
603
+ .brand{display:flex; align-items:center; gap:11px; flex:none;}
604
+ .logo{position:relative; width:34px; height:34px; border-radius:50%; flex:none;
605
+ background:radial-gradient(circle at 50% 45%, #eaffff 0%, #9ef0ff 30%, #1f7e9c 68%, #06202e 100%);
606
+ display:grid; place-items:center; color:#03202c; font-weight:900; font-size:13px;
607
+ box-shadow:0 0 22px -2px rgba(89,227,255,.8), inset 0 0 8px rgba(255,255,255,.5);}
608
+ .logo::before{content:""; position:absolute; inset:-5px; border-radius:50%;
609
+ border:1px dashed rgba(89,227,255,.65); animation:spin 14s linear infinite;}
610
+ .logo::after{content:""; position:absolute; inset:-10px; border-radius:50%;
611
+ border:1px solid rgba(89,227,255,.22); border-top-color:rgba(89,227,255,.8); animation:spin 4s linear infinite reverse;}
612
+ .brand h1{font-size:14px; font-weight:760; letter-spacing:.34em; text-transform:uppercase; color:var(--cyan-ink);
613
+ text-shadow:0 0 14px rgba(89,227,255,.55);}
614
+ nav{display:flex; gap:6px; flex:1; flex-wrap:wrap;}
615
+ nav a{padding:6px 15px; clip-path:var(--cham); font-size:11px; font-weight:700; letter-spacing:.18em; text-transform:uppercase;
616
+ color:var(--muted); text-decoration:none; background:rgba(89,227,255,.04); border:1px solid transparent; transition:all .18s;}
617
+ nav a:hover{background:rgba(89,227,255,.1); color:var(--cyan-ink);}
618
+ nav a.on{background:linear-gradient(180deg, rgba(89,227,255,.22), rgba(89,227,255,.08)); color:#eaffff;
619
+ box-shadow:0 0 18px -4px rgba(89,227,255,.7), inset 0 0 12px rgba(89,227,255,.12); text-shadow:0 0 10px rgba(89,227,255,.8);}
620
+ .switcher{background:rgba(5,18,32,.9); border:1px solid var(--line2); color:var(--cyan-ink); clip-path:var(--cham);
621
+ padding:7px 12px; max-width:230px; font-size:12px; font-family:var(--mono);}
622
+ .live{display:flex; align-items:center; gap:8px; font-size:10.5px; color:var(--muted); flex:none;
623
+ font-family:var(--mono); text-transform:uppercase; letter-spacing:.14em;}
624
+ .dot{width:8px; height:8px; border-radius:50%; background:var(--cyan); box-shadow:0 0 12px var(--cyan); animation:beat 2s infinite;}
625
+ .dot.off{background:var(--red); box-shadow:0 0 12px var(--red); animation:none;}
626
+ @keyframes beat{0%,100%{opacity:1;} 50%{opacity:.35;}}
627
+ @keyframes spin{to{transform:rotate(360deg);}}
628
+ @media (prefers-reduced-motion:reduce){*{animation:none!important; transition:none!important;}}
629
+
630
+ .wrap{max-width:1200px; margin:0 auto; padding:24px 24px 80px;}
631
+
632
+ /* boot reveal */
633
+ .wrap > section > *{animation:boot .5s cubic-bezier(.2,.8,.3,1) both;}
634
+ .wrap > section > *:nth-child(2){animation-delay:.07s;} .wrap > section > *:nth-child(3){animation-delay:.14s;}
635
+ .wrap > section > *:nth-child(4){animation-delay:.21s;} .wrap > section > *:nth-child(5){animation-delay:.28s;}
636
+ @keyframes boot{from{opacity:0; transform:translateY(10px);} to{opacity:1; transform:none;}}
637
+
638
+ /* ── holo panels: chamfer + corner brackets ── */
639
+ .panel,.card,.bignum,.pcard,.netnode{position:relative;
640
+ background:linear-gradient(165deg, var(--panel), var(--panel2));
641
+ border:1px solid var(--line); clip-path:var(--cham);}
642
+ .panel{padding:16px 18px; margin-bottom:16px; backdrop-filter:blur(6px);}
643
+ .panel::before,.card::before,.bignum::before,.pcard::before{content:""; position:absolute; top:0; left:0; width:14px; height:14px;
644
+ border-top:1.5px solid var(--line2); border-left:1.5px solid var(--line2); pointer-events:none;}
645
+ .panel::after,.card::after,.bignum::after,.pcard::after{content:""; position:absolute; bottom:0; right:0; width:14px; height:14px;
646
+ border-bottom:1.5px solid var(--line2); border-right:1.5px solid var(--line2); pointer-events:none;}
647
+ .phead{margin-bottom:12px;}
648
+ .phead .ht{display:flex; align-items:baseline; justify-content:space-between; gap:10px;}
649
+ .phead h2{font-size:10.5px; font-weight:760; text-transform:uppercase; letter-spacing:.22em; color:var(--cyan-ink);
650
+ text-shadow:0 0 12px rgba(89,227,255,.35);}
651
+ .phead h2::before{content:"◢ "; color:var(--cyan); font-size:8px; vertical-align:2px;}
652
+ .phead .cap{font-size:11px; color:var(--faint); margin-top:4px;}
653
+ .phead .note{font-size:11px; color:var(--faint); font-family:var(--mono);}
654
+ .empty{color:var(--faint); font-size:12.5px; padding:12px 0; font-family:var(--mono);}
655
+ .pagehead{font-size:19px; font-weight:740; letter-spacing:.12em; text-transform:uppercase; margin-bottom:4px; color:#eaffff;
656
+ text-shadow:0 0 18px rgba(89,227,255,.4);}
657
+ .pagesub{color:var(--muted); font-size:12.5px; margin-bottom:18px;}
658
+
659
+ /* hero + cards */
660
+ .hero{display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:16px; flex-wrap:wrap;}
661
+ .hero-l{display:flex; align-items:center; gap:12px; flex-wrap:wrap;}
662
+ .hero-name{font-size:20px; font-weight:740; letter-spacing:.06em; color:#eaffff; text-shadow:0 0 16px rgba(89,227,255,.45);}
663
+ .health{font-size:10px; font-weight:760; letter-spacing:.14em; text-transform:uppercase; padding:3px 12px; clip-path:var(--cham);}
664
+ .health.ok{background:var(--green-soft); color:var(--green-ink); box-shadow:0 0 14px -4px rgba(93,255,176,.6);}
665
+ .health.warn{background:var(--amber-soft); color:var(--amber); box-shadow:0 0 14px -4px rgba(255,201,87,.6);}
666
+ .hero-r{font-size:11px; color:var(--faint); font-family:var(--mono);}
667
+ .vital{display:inline-flex; align-items:center; gap:6px; font-size:11px; color:var(--muted); font-family:var(--mono);}
668
+ .pulse-dot{width:8px; height:8px; border-radius:50%; background:var(--cyan); box-shadow:0 0 10px var(--cyan); animation:beat 1.8s infinite; display:inline-block;}
669
+ .pulse-dot.off{background:var(--faint); box-shadow:none; animation:none;}
670
+ .cards{display:grid; grid-template-columns:repeat(3,1fr); gap:14px; margin-bottom:16px;}
671
+ .card{padding:16px 18px;}
672
+ .cardnum{font-size:30px; font-weight:800; letter-spacing:.01em; font-variant-numeric:tabular-nums; font-family:var(--mono);
673
+ color:#eaffff; text-shadow:0 0 16px rgba(89,227,255,.5);}
674
+ .cardnum.g{color:var(--cyan); text-shadow:0 0 18px rgba(89,227,255,.8);}
675
+ .cardnum.a{color:var(--amber); text-shadow:0 0 16px rgba(255,201,87,.6);}
676
+ .cardnum.muted{color:var(--faint); text-shadow:none;}
677
+ .cardlbl{font-size:9.5px; color:var(--cyan-ink); opacity:.75; text-transform:uppercase; letter-spacing:.2em; margin-top:7px;}
678
+ .cardsub{font-size:11px; color:var(--muted); margin-top:4px;}
679
+
680
+ .didrow{display:flex; align-items:center; gap:10px; padding:7px 0; border-bottom:1px dashed rgba(89,227,255,.1);}
681
+ .didrow:last-child{border-bottom:0;}
682
+ .didkind{flex:none; font-size:9px; font-weight:760; text-transform:uppercase; letter-spacing:.1em; padding:2px 9px; clip-path:var(--cham); background:rgba(89,227,255,.08); color:var(--muted);}
683
+ .didkind.k-resolve_conflict{background:rgba(255,122,112,.14); color:#ffb0a8;}
684
+ .didkind.k-merge_duplicate{background:var(--blue-soft); color:#b3d2ff;}
685
+ .didkind.k-compress_cluster{background:rgba(157,180,255,.14); color:#c8d6ff;}
686
+ .didkind.k-reinforce{background:var(--cyan-soft); color:var(--cyan-ink);}
687
+ .didtxt{flex:1; font-size:12.5px;}
688
+ .didtime{flex:none; font-size:10.5px; color:var(--faint); font-family:var(--mono);}
689
+ .injrow{display:flex; align-items:center; gap:10px; padding:8px 0; border-bottom:1px dashed rgba(89,227,255,.1);}
690
+ .injrow:last-child{border-bottom:0;}
691
+ .injc{flex:1; font-size:12.5px;}
692
+ .injs{font-size:10.5px; color:var(--cyan); font-family:var(--mono); flex:none; text-shadow:0 0 8px rgba(89,227,255,.6);}
693
+ .sb{font-size:9px; font-weight:760; text-transform:uppercase; letter-spacing:.1em; padding:2px 8px; clip-path:var(--cham); flex:none;}
694
+ .sb-global{background:var(--blue-soft); color:#b3d2ff;} .sb-project{background:rgba(89,227,255,.07); color:var(--muted);}
695
+ .review{border-color:rgba(255,201,87,.4);}
696
+ .revrow{display:flex; align-items:center; justify-content:space-between; gap:12px; padding:9px 0; border-bottom:1px dashed rgba(89,227,255,.1);}
697
+ .revrow:last-child{border-bottom:0;}
698
+ .revtxt{font-size:12.5px;} .revtxt b{color:var(--ink); font-weight:640;}
699
+ .revacts{flex:none; display:flex; gap:6px;}
700
+
701
+ /* buttons / inputs */
702
+ .btn{padding:8px 16px; clip-path:var(--cham); background:linear-gradient(180deg, rgba(89,227,255,.9), rgba(43,168,201,.9));
703
+ color:#03202c; font-weight:760; font-size:11.5px; letter-spacing:.1em; text-transform:uppercase; border:0; transition:all .15s;
704
+ box-shadow:0 0 18px -6px rgba(89,227,255,.8);}
705
+ .btn:hover{filter:brightness(1.12);} .btn:disabled{opacity:.45; cursor:not-allowed;}
706
+ .btn.ghost{background:rgba(89,227,255,.06); color:var(--cyan-ink); border:1px solid var(--line2); box-shadow:none;}
707
+ .btn.ghost:hover{background:rgba(89,227,255,.13);}
708
+ .btn.sm{padding:4px 11px; font-size:10px;}
709
+ .toolbar{display:flex; gap:10px; align-items:center; margin-bottom:12px; flex-wrap:wrap;}
710
+ .field{flex:1; min-width:170px; display:flex; gap:8px;}
711
+ .field input{flex:1; padding:8px 13px; border:1px solid var(--line2); clip-path:var(--cham); background:rgba(4,14,26,.85);
712
+ font-family:var(--mono); font-size:12px; color:var(--cyan-ink);}
713
+ .field input:focus{border-color:var(--cyan); outline:none; box-shadow:0 0 16px -6px rgba(89,227,255,.8);}
714
+ .field input::placeholder{color:var(--faint);}
715
+ .chips{display:flex; gap:7px; flex-wrap:wrap; margin-bottom:12px;}
716
+ .chip{display:inline-flex; align-items:center; padding:4px 12px; clip-path:var(--cham); border:1px solid var(--line);
717
+ background:rgba(4,14,26,.7); color:var(--muted); font-size:10.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; transition:all .15s;}
718
+ .chip:hover{background:rgba(89,227,255,.09);}
719
+ .chip.on{background:linear-gradient(180deg, rgba(89,227,255,.85), rgba(43,168,201,.85)); border-color:var(--cyan); color:#03202c;}
720
+ .chip .cc{margin-left:7px; color:var(--faint); font-family:var(--mono);} .chip.on .cc{color:rgba(3,32,44,.7);}
721
+
722
+ /* records */
723
+ .scroll{max-height:600px; overflow:auto;}
724
+ .scroll::-webkit-scrollbar{width:7px;} .scroll::-webkit-scrollbar-thumb{background:#12374d; border-radius:4px;}
725
+ .rec{padding:10px 4px; border-bottom:1px dashed rgba(89,227,255,.1);} .rec:last-child{border-bottom:0;}
726
+ .rec.hl{background:var(--cyan-soft);}
727
+ .rec.pinned{background:rgba(89,227,255,.05); border-left:2px solid var(--cyan); padding-left:8px;}
728
+ .recline{display:flex; align-items:center;}
729
+ .sd{display:inline-block; width:7px; height:7px; border-radius:50%; flex:none; margin-right:8px;}
730
+ .sd.active{background:var(--green); box-shadow:0 0 8px rgba(93,255,176,.8);}
731
+ .sd.superseded{background:var(--violet);} .sd.stale{background:var(--amber);} .sd.conflicted{background:var(--red); box-shadow:0 0 8px rgba(255,122,112,.7);}
732
+ .sd.archived{background:var(--faint);}
733
+ .sd.processed{background:var(--green);} .sd.skipped{background:var(--amber);} .sd.failed{background:var(--red);}
734
+ .rtype{font-size:9px; text-transform:uppercase; letter-spacing:.14em; color:var(--cyan); font-weight:760; flex:none; margin-right:10px; min-width:76px; text-shadow:0 0 8px rgba(89,227,255,.4);}
735
+ .rcontent{font-size:13px; flex:1;}
736
+ .rec.superseded .rcontent,.rec.stale .rcontent{color:var(--muted); text-decoration:line-through; text-decoration-color:rgba(157,180,255,.5);}
737
+ .editin{flex:1; padding:6px 10px; border:1px solid var(--cyan); background:rgba(4,14,26,.9); color:var(--ink); margin-right:8px; font-family:var(--mono); font-size:12px;}
738
+ .racts{flex:none; display:flex; gap:3px; margin-left:8px; opacity:0; transition:opacity .12s;}
739
+ .rec:hover .racts{opacity:1;}
740
+ .ract{width:25px; height:25px; background:transparent; border:1px solid transparent; color:var(--muted); font-size:12px; display:grid; place-items:center; padding:0;}
741
+ .ract:hover{background:rgba(89,227,255,.1); border-color:var(--line2); color:var(--cyan-ink);}
742
+ .ract.on{color:var(--cyan);} .ract.del:hover{color:var(--red); border-color:var(--red);}
743
+ .recmeta{display:flex; align-items:center; gap:11px; margin-top:7px; padding-left:86px; flex-wrap:wrap;}
744
+ .g{display:inline-flex; align-items:center; gap:6px; font-size:9.5px; color:var(--faint); text-transform:uppercase; letter-spacing:.08em; font-family:var(--mono);} .g b{color:var(--muted); font-weight:700;}
745
+ .bar{display:inline-block; width:44px; height:3px; background:rgba(89,227,255,.1); overflow:hidden;}
746
+ .bar i{display:block; height:100%; background:linear-gradient(90deg, #2ba8c9, var(--cyan)); box-shadow:0 0 6px rgba(89,227,255,.8);}
747
+ .bar i.b2{background:linear-gradient(90deg, #4a72c4, var(--blue));}
748
+ .ent{padding:1.5px 8px; clip-path:var(--cham); background:rgba(89,227,255,.06); border:1px solid var(--line); color:var(--muted);}
749
+ .pcg{color:var(--cyan); font-weight:760; text-shadow:0 0 8px rgba(89,227,255,.5);}
750
+ .why{margin-top:7px; margin-left:86px; font-size:11px; color:var(--cyan-ink); background:var(--cyan-soft); clip-path:var(--cham); padding:6px 11px; font-family:var(--mono);}
751
+ pre.inject{margin:0; font-family:var(--mono); font-size:10.5px; line-height:1.55; color:var(--muted); white-space:pre-wrap; overflow-wrap:anywhere; max-height:300px; overflow:auto; background:rgba(3,10,20,.8); border:1px solid var(--line); padding:12px;}
752
+
753
+ .cols{display:grid; grid-template-columns:minmax(0,1fr) 340px; gap:18px; align-items:start;}
754
+ .cols .panel{margin-bottom:0;}
755
+ .col{display:flex; flex-direction:column; gap:16px;}
756
+
757
+ .netgrid{display:grid; grid-template-columns:repeat(auto-fill,minmax(200px,1fr)); gap:14px;}
758
+ .pcard{text-align:left; padding:15px 16px; transition:all .16s;}
759
+ .pcard:hover{border-color:var(--cyan); box-shadow:0 0 26px -8px rgba(89,227,255,.6); transform:translateY(-2px);}
760
+ .pcard .pcname{font-weight:700; font-size:14px; margin-bottom:6px; letter-spacing:.03em; color:#eaffff;}
761
+ .pcdis{font-weight:400; font-size:11px; color:var(--faint);}
762
+ .pcard .pcstats{font-size:11.5px; color:var(--muted); font-family:var(--mono);}
763
+ .pcgo{font-size:10px; letter-spacing:.14em; text-transform:uppercase; color:var(--cyan); margin-top:10px; opacity:0; transition:opacity .15s; text-shadow:0 0 8px rgba(89,227,255,.6);}
764
+ .pcard:hover .pcgo{opacity:1;}
765
+
766
+ /* ops */
767
+ .bignums{display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-bottom:16px;}
768
+ .bignum{padding:15px 17px;}
769
+ .bignum .n{font-size:26px; font-weight:800; font-variant-numeric:tabular-nums; font-family:var(--mono); color:#eaffff; text-shadow:0 0 14px rgba(89,227,255,.45);}
770
+ .bignum.accent .n{color:var(--cyan); text-shadow:0 0 18px rgba(89,227,255,.8);}
771
+ .bignum .l{font-size:9px; color:var(--cyan-ink); opacity:.7; text-transform:uppercase; letter-spacing:.18em; margin-top:6px;}
772
+ .trow{display:grid; grid-template-columns:150px 1fr 110px; gap:12px; align-items:center; padding:8px 0; border-bottom:1px dashed rgba(89,227,255,.1);}
773
+ .trow:last-child{border-bottom:0;}
774
+ .tlabel{font-size:12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;} .tlabel.cur{color:var(--cyan); font-weight:700;}
775
+ .tbar{height:6px; background:rgba(89,227,255,.08); overflow:hidden;}
776
+ .tbar i{display:block; height:100%; background:linear-gradient(90deg,#2b78c9,var(--blue));}
777
+ .tbar i.g{background:linear-gradient(90deg,#2ba8c9,var(--cyan)); box-shadow:0 0 10px rgba(89,227,255,.7);}
778
+ .tval{font-size:11px; color:var(--muted); text-align:right; font-family:var(--mono);}
779
+ .row{padding:8px 0; border-bottom:1px dashed rgba(89,227,255,.1);} .row:last-child{border-bottom:0;} .row.tight{padding:6px 0;}
780
+ .rmain{display:flex; align-items:baseline; justify-content:space-between; gap:8px;}
781
+ .rt{font-size:12px; font-weight:620; display:inline-flex; align-items:center; gap:6px;}
782
+ .rs{font-size:10px; color:var(--faint); font-family:var(--mono); flex:none;}
783
+ .rsub{font-size:11px; color:var(--muted); margin-top:2px; overflow-wrap:anywhere;} .rsub.err{color:var(--red);} .rsub.strong{color:var(--cyan-ink);}
784
+ .abnote{font-size:11.5px; color:var(--muted); margin-bottom:16px; font-family:var(--mono);} .abnote a{color:var(--cyan-ink);}
785
+ .backlink{color:var(--muted); text-decoration:none; font-size:13px; font-weight:500; margin-right:6px;} .backlink:hover{color:var(--cyan-ink);}
786
+
787
+ /* ── reactor core ── */
788
+ .bhwrap{display:grid; grid-template-columns:360px minmax(0,1fr); gap:22px; align-items:center; margin-bottom:18px;}
789
+ .bhviz{position:relative; display:flex; flex-direction:column; align-items:center; gap:8px;}
790
+ .bhpulse{display:inline-flex; align-items:center; gap:7px; font-size:10px; color:var(--muted); font-family:var(--mono); letter-spacing:.16em; text-transform:uppercase;}
791
+ .brainviz{width:320px; height:320px; filter:drop-shadow(0 0 34px rgba(89,227,255,.3));}
792
+ .brainviz .btick{stroke:rgba(89,227,255,.25); stroke-width:1;}
793
+ .brainviz .btick.maj{stroke:rgba(89,227,255,.55); stroke-width:1.4;}
794
+ .brainviz .bring{fill:none; stroke:rgba(89,227,255,.4); stroke-width:1.1; transform-origin:150px 150px;}
795
+ .brainviz .bring.rA{stroke-dasharray:60 22 8 22; animation:spin 26s linear infinite;}
796
+ .brainviz .bring.rB{stroke-dasharray:34 14; stroke:rgba(89,227,255,.28); animation:spin 38s linear infinite reverse;}
797
+ .brainviz .bring.rC{stroke-dasharray:10 7; stroke:rgba(89,227,255,.5); stroke-width:1.6; animation:spin 17s linear infinite;}
798
+ .brainviz .bsweep{transform-origin:150px 150px; animation:spin 6s linear infinite; opacity:.8;}
799
+ .brainviz .bsegs{transform-origin:150px 150px; animation:spin 48s linear infinite reverse;}
800
+ .brainviz .bseg{fill:rgba(89,227,255,.5);}
801
+ .brainviz .bcore-halo{fill:rgba(89,227,255,.14); animation:halo2 3.2s ease-in-out infinite;}
802
+ .brainviz .bcore{filter:drop-shadow(0 0 16px rgba(89,227,255,.95));}
803
+ .brainviz .bcore-rim{fill:none; stroke:rgba(200,245,255,.7); stroke-width:1.2; stroke-dasharray:4 9;
804
+ transform-origin:150px 150px; animation:spin 9s linear infinite reverse;}
805
+ .brainviz .bcore-spark{fill:none; stroke:#eaffff; stroke-width:1.5; opacity:0; transform-origin:150px 150px;}
806
+ .brainviz .bcore-label{font-family:var(--mono); font-size:8.5px; font-weight:700; letter-spacing:.3em; fill:rgba(185,242,255,.85);}
807
+ .brainviz .orbit{transform-origin:150px 150px; animation:spin linear infinite;}
808
+ .brainviz .bnode{fill:var(--cyan); filter:drop-shadow(0 0 5px rgba(89,227,255,.9));}
809
+ .brainviz .bnode.r1{fill:#eaffff;} .brainviz .bnode.r2{fill:#7db4ff; filter:drop-shadow(0 0 5px rgba(125,180,255,.9));} .brainviz .bnode.r3{fill:#9db4ff; filter:drop-shadow(0 0 5px rgba(157,180,255,.9));}
810
+ .brainviz.flash .bcore-halo{fill:rgba(234,255,255,.4);}
811
+ .brainviz.flash .bcore-spark{opacity:1; animation:spark .9s ease-out;}
812
+ @keyframes halo2{0%,100%{opacity:.6;} 50%{opacity:.15;}}
813
+ @keyframes spark{0%{stroke-width:1.5; opacity:.95; r:30;} 100%{stroke-width:0; opacity:0; r:110;}}
814
+ .cards3{display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-bottom:12px;}
815
+ .bhtypes{display:flex; flex-wrap:wrap; gap:8px;}
816
+ .tchip{font-size:10px; letter-spacing:.1em; text-transform:uppercase; padding:3px 11px; clip-path:var(--cham); background:rgba(89,227,255,.06); border:1px solid var(--line); color:var(--muted);}
817
+ .tchip b{color:var(--cyan-ink); margin-left:4px; font-family:var(--mono);}
818
+
819
+ /* ── neural universe ── */
820
+ .uniwrap{position:relative; height:76vh; min-height:520px; margin-bottom:18px; border:1px solid var(--line);
821
+ background:radial-gradient(900px 500px at 50% 40%, rgba(43,168,201,.08), transparent 70%), rgba(2,8,16,.6); clip-path:var(--cham); overflow:hidden;}
822
+ .uniwrap::before{content:""; position:absolute; top:0; left:0; width:16px; height:16px; border-top:1.5px solid var(--line2); border-left:1.5px solid var(--line2); z-index:5; pointer-events:none;}
823
+ .uniwrap::after{content:""; position:absolute; bottom:0; right:0; width:16px; height:16px; border-bottom:1.5px solid var(--line2); border-right:1.5px solid var(--line2); z-index:5; pointer-events:none;}
824
+ #uni{position:absolute; inset:0; width:100%; height:100%; cursor:crosshair;}
825
+ .uni-search{position:absolute; top:14px; left:50%; transform:translateX(-50%); z-index:6; display:flex; gap:8px; align-items:center; width:min(560px,80%);}
826
+ .uni-search input{flex:1; padding:9px 16px; border:1px solid rgba(89,227,255,.5); clip-path:var(--cham); background:rgba(3,12,22,.88);
827
+ font-family:var(--mono); font-size:12px; color:var(--cyan-ink); backdrop-filter:blur(8px);}
828
+ .uni-search input:focus{border-color:var(--cyan); outline:none; box-shadow:0 0 22px -6px rgba(89,227,255,.9);}
829
+ .uni-search input::placeholder{color:var(--faint);}
830
+ #uq-hits{color:var(--cyan); font-size:10px; letter-spacing:.12em; text-shadow:0 0 8px rgba(89,227,255,.6); min-width:80px;}
831
+ .uni-hud{position:absolute; z-index:6; font-family:var(--mono); font-size:10px; letter-spacing:.12em; color:var(--muted);
832
+ background:rgba(3,12,22,.72); border:1px solid var(--line); padding:7px 12px; clip-path:var(--cham); backdrop-filter:blur(6px);}
833
+ .uni-hud b{color:var(--cyan-ink); font-weight:700;} .uni-hud b.warn{color:var(--amber);}
834
+ .uni-hud.tl{top:14px; left:14px;} .uni-hud.tr{top:60px; right:14px;}
835
+ .stl-dot{display:inline-block; width:8px; height:8px; border-radius:50%; margin-left:6px; vertical-align:-1px; box-shadow:0 0 8px currentColor;}
836
+ .uni-legend{position:absolute; left:14px; bottom:76px; z-index:6; display:flex; flex-wrap:wrap; gap:9px; max-width:70%;}
837
+ .ul{display:inline-flex; align-items:center; gap:5px; font-family:var(--mono); font-size:9px; letter-spacing:.1em; text-transform:uppercase; color:var(--muted);}
838
+ .ul i{width:7px; height:7px; border-radius:50%; display:inline-block; box-shadow:0 0 6px rgba(89,227,255,.4);}
839
+ .ul.dim i{background:#48708a; opacity:.4;}
840
+ .uni-tip{position:absolute; z-index:7; max-width:320px; pointer-events:none; background:rgba(3,12,22,.92); border:1px solid var(--line2);
841
+ padding:8px 11px; font-size:10.5px; line-height:1.5; color:var(--ink); clip-path:var(--cham);}
842
+ .uni-tip .tt-type{text-transform:uppercase; letter-spacing:.12em; font-weight:700; margin-right:6px;}
843
+ .uni-inspect{position:absolute; top:60px; left:14px; z-index:8; width:330px; max-height:calc(100% - 130px); overflow:auto;
844
+ background:linear-gradient(165deg, rgba(8,26,42,.96), rgba(4,14,26,.96)); border:1px solid var(--cyan); clip-path:var(--cham);
845
+ padding:16px; box-shadow:0 0 34px -8px rgba(89,227,255,.5);}
846
+ .ui-x{position:absolute; top:8px; right:10px; background:none; border:0; color:var(--muted); font-size:13px;}
847
+ .ui-x:hover{color:var(--cyan-ink);}
848
+ .ui-type{font-family:var(--mono); font-size:10px; letter-spacing:.18em; text-transform:uppercase; margin-bottom:9px; text-shadow:0 0 10px currentColor;}
849
+ .ui-content{font-size:13px; line-height:1.55; margin-bottom:11px;}
850
+ .ui-meta{display:flex; gap:14px; margin-bottom:9px;}
851
+ .ui-ents{display:flex; flex-wrap:wrap; gap:5px; margin-bottom:10px;}
852
+ .ui-proj{color:var(--faint); font-size:10px; margin-bottom:11px;}
853
+ .uni-ticker{position:absolute; left:0; right:0; bottom:0; z-index:6; padding:8px 16px 10px;
854
+ background:linear-gradient(180deg, transparent, rgba(2,8,16,.9) 40%); font-family:var(--mono); font-size:10px; color:var(--muted);}
855
+ .utk{padding:2px 0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
856
+ .utk-k{color:var(--cyan); text-transform:uppercase; letter-spacing:.1em;}
857
+ .utk-t{color:var(--faint);}
858
+
859
+ /* toasts */
860
+ .toasts{position:fixed; top:64px; right:20px; z-index:90; display:flex; flex-direction:column; gap:10px; pointer-events:none;}
861
+ .toast{display:flex; gap:11px; align-items:flex-start; width:330px; background:linear-gradient(165deg, rgba(8,26,42,.96), rgba(4,14,26,.96));
862
+ border:1px solid var(--cyan); clip-path:var(--cham); padding:12px 15px;
863
+ box-shadow:0 14px 44px -10px rgba(0,0,0,.7), 0 0 24px -6px rgba(89,227,255,.6);
864
+ transform:translateX(370px); opacity:0; transition:transform .4s cubic-bezier(.2,.9,.3,1.15), opacity .4s;}
865
+ .toast.in{transform:translateX(0); opacity:1;}
866
+ .toast .ti{font-size:17px; line-height:1.2; flex:none;}
867
+ .toast .tt{font-size:11px; font-weight:760; letter-spacing:.1em; text-transform:uppercase; color:var(--cyan-ink);} .toast .tp{color:#eaffff;}
868
+ .toast .td{font-size:11.5px; color:var(--muted); margin-top:3px; line-height:1.45;}
869
+
870
+ @media (max-width:980px){ .cols{grid-template-columns:1fr;} .cards{grid-template-columns:1fr;} .bignums{grid-template-columns:repeat(2,1fr);} .trow{grid-template-columns:110px 1fr 90px;} .bhwrap{grid-template-columns:1fr;} }
871
+ </style>
872
+ </head>
873
+ <body>
874
+ <header>
875
+ <div class="brand"><div class="logo">P</div><h1>Peon</h1></div>
876
+ <nav>
877
+ <a id="nav-brain" href="#/brain">Neural Core</a>
878
+ <a id="nav-projects" href="#/projects">Sectors</a>
879
+ <a id="nav-ops" href="#/ops">Systems</a>
880
+ </nav>
881
+ <select class="switcher" id="switcher" aria-label="Active project"></select>
882
+ <div class="live"><span class="dot" id="dot"></span><span id="status">linking…</span></div>
883
+ </header>
884
+
885
+ <div class="toasts" id="toasts"></div>
886
+
887
+ <div class="wrap">
888
+ <!-- NEURAL CORE — the living memory universe -->
889
+ <section id="page-brain" hidden>
890
+ <div class="uniwrap">
891
+ <canvas id="uni"></canvas>
892
+ <div class="uni-search"><input id="uq" placeholder="ask the memory field… (type to light up matching beliefs)" autocomplete="off"><span id="uq-hits" class="mono"></span><button class="btn ghost sm" id="uni-fit" title="reset view">⤢</button></div>
893
+ <div class="uni-hud tl" id="uni-stats"></div>
894
+ <div class="uni-hud tr" id="uni-health"></div>
895
+ <div class="uni-legend" id="uni-legend"></div>
896
+ <div class="uni-tip mono" id="uni-tip" hidden></div>
897
+ <div class="uni-inspect" id="uni-inspect" hidden></div>
898
+ <div class="uni-ticker" id="uni-ticker"></div>
899
+ </div>
900
+ <div id="bh-body"></div>
901
+ </section>
902
+
903
+ <!-- SECTORS (projects) -->
904
+ <section id="page-projects" hidden>
905
+ <div class="pagehead">Sectors</div>
906
+ <div class="pagesub">Each project runs its own memory brain. Open one for insights, beliefs, and autonomous activity.</div>
907
+ <div class="netgrid" id="projgrid"></div>
908
+ </section>
909
+
910
+ <!-- OVERVIEW -->
911
+ <section id="page-overview" hidden>
912
+ <div class="pagehead"><a href="#/projects" class="backlink">← Sectors</a> Insights <a href="#/memory" class="backlink" style="float:right;font-size:12px">all beliefs →</a></div>
913
+ <div class="pagesub">What your AI knows here, what Peon injected last, and what needs attention.</div>
914
+ <div id="ov-body"></div>
915
+ </section>
916
+
917
+ <!-- MEMORY -->
918
+ <section id="page-memory" hidden>
919
+ <div class="pagehead"><a href="#/overview" class="backlink">← Insights</a> Memory Banks <span class="mono" style="color:var(--faint);font-weight:400" id="memScope"></span></div>
920
+ <div class="pagesub">Every belief in this sector's brain. Hover a belief to pin, edit, or delete.</div>
921
+ <div class="cols">
922
+ <div class="col">
923
+ <section class="panel">
924
+ <div class="phead"><div class="ht"><h2>Beliefs</h2><span class="note" id="memCount"></span></div></div>
925
+ <div class="toolbar">
926
+ <div class="field"><input id="q" placeholder="query the memory banks…" autocomplete="off"><button class="btn" id="go">Scan</button><button class="btn ghost" id="clear" hidden>Clear</button></div>
927
+ <button class="btn ghost" id="process" title="Run consolidation now (uses the LLM)">Consolidate</button>
928
+ </div>
929
+ <div class="chips" id="filters"></div>
930
+ <div class="scroll" id="records"><div class="empty">No sector selected</div></div>
931
+ </section>
932
+ </div>
933
+ <div class="col">
934
+ <section class="panel">
935
+ <div class="phead"><div class="ht"><h2>Uplink Preview</h2></div><div class="cap">the exact context Peon hands your AI next prompt</div></div>
936
+ <pre class="inject" id="inject">—</pre>
937
+ </section>
938
+ </div>
939
+ </div>
940
+ </section>
941
+
942
+ <!-- SYSTEMS (ops) -->
943
+ <section id="page-ops" hidden>
944
+ <div class="pagehead">Systems</div>
945
+ <div class="pagesub">Peon's own LLM cost, consolidation activity, and telemetry. (Your main AI's tokens live in its own usage view.)</div>
946
+ <div class="abnote">Comparing Peon-on vs Peon-off token usage? See the <a href="/token-ab-monitor">token A/B monitor</a>.</div>
947
+ <div class="bignums">
948
+ <div class="bignum accent"><div class="n" id="tok-total">0</div><div class="l">Peon tokens (total)</div></div>
949
+ <div class="bignum"><div class="n" id="tok-runs">0</div><div class="l">consolidation runs</div></div>
950
+ <div class="bignum"><div class="n" id="tok-avg">0</div><div class="l">avg tokens / run</div></div>
951
+ <div class="bignum"><div class="n" id="tok-cost">≈ $0</div><div class="l">est. cost (approx)</div></div>
952
+ </div>
953
+ <div class="bignums">
954
+ <div class="bignum"><div class="n" id="hl-lat">–</div><div class="l">injection latency avg / p95 (24h)</div></div>
955
+ <div class="bignum"><div class="n" id="hl-count">–</div><div class="l">injections served (24h)</div></div>
956
+ <div class="bignum"><div class="n" id="hl-tok">–</div><div class="l">avg tokens / injection</div></div>
957
+ </div>
958
+ <div class="abnote" id="hl-stl">Daily self-check: no report yet.</div>
959
+ <div class="cols">
960
+ <div class="col">
961
+ <section class="panel"><div class="phead"><div class="ht"><h2>Tokens by sector</h2></div></div><div id="tok-projects"></div></section>
962
+ <section class="panel"><div class="phead"><div class="ht"><h2>Cost gate · processing</h2></div></div><div class="scroll" style="max-height:300px" id="jobs"></div></section>
963
+ </div>
964
+ <div class="col">
965
+ <section class="panel"><div class="phead"><div class="ht"><h2>Recent consolidations</h2></div></div><div class="scroll" style="max-height:240px" id="tok-recent"></div></section>
966
+ <section class="panel"><div class="phead"><div class="ht"><h2>Active uplinks</h2></div></div><div class="scroll" style="max-height:160px" id="sessions"></div></section>
967
+ <section class="panel"><div class="phead"><div class="ht"><h2>System log</h2></div></div><div class="scroll" style="max-height:220px" id="logs"></div></section>
968
+ </div>
969
+ </div>
970
+ </section>
971
+ </div>
972
+
973
+ <script>
974
+ ${CLIENT_SCRIPT}
975
+ </script>
976
+ </body>
977
+ </html>`;