@theihtisham/agent-shadow-brain 7.22.0 → 7.22.1

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 (66) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +7 -3
  3. package/dist/brain/auto-update.js +1 -1
  4. package/dist/brain/brain-portability.js +1 -1
  5. package/dist/brain/client-integrations.js +1 -1
  6. package/dist/brain/context-efficiency.d.ts.map +1 -1
  7. package/dist/brain/mcp-presence.js +2 -2
  8. package/dist/brain/mcp-server.js +5 -5
  9. package/dist/brain/native-codex.js +1 -1
  10. package/dist/brain/orchestrator.js +1 -1
  11. package/dist/brain/project-hub-pdf-worker.js +57 -57
  12. package/dist/brain/project-hub-store.js +11 -11
  13. package/dist/cli.js +1 -1
  14. package/dist/dashboard/brain-hub-agent-dock.js +252 -252
  15. package/dist/dashboard/brain-hub-controls.js +61 -61
  16. package/dist/dashboard/brain-hub-exchanges.js +80 -80
  17. package/dist/dashboard/brain-hub-graph.js +336 -336
  18. package/dist/dashboard/brain-hub-html.js +78 -78
  19. package/dist/dashboard/brain-hub-live.js +149 -149
  20. package/dist/dashboard/brain-hub-managed-skills.js +18 -18
  21. package/dist/dashboard/brain-hub-native.js +42 -42
  22. package/dist/dashboard/live-workspace-html.js +1 -1
  23. package/dist/dashboard/project-feedback-html.js +211 -211
  24. package/dist/dashboard/project-kit-html.js +353 -353
  25. package/dist/dashboard/server.js +1 -1
  26. package/dist/dashboard/v6-dashboard-html.js +4261 -4261
  27. package/dist/dashboard/workspace-api.js +1 -1
  28. package/dist/dashboard/workspace-html.js +2534 -2534
  29. package/dist/dashboard/workspace-status.js +1 -1
  30. package/dist/project-cli.js +1 -1
  31. package/docs/BRAND_ASSETS.md +36 -36
  32. package/docs/COMPARISON.md +22 -22
  33. package/docs/COMPONENTS.md +66 -66
  34. package/docs/CONTEXT_LAB.md +79 -79
  35. package/docs/INTEGRATIONS.md +69 -69
  36. package/docs/LEARNING.md +45 -45
  37. package/docs/PROJECT_KIT.md +97 -97
  38. package/docs/SHARING.md +13 -13
  39. package/docs/SHOWCASE.md +87 -87
  40. package/docs/STATS.md +21 -21
  41. package/docs/TROUBLESHOOTING.md +42 -42
  42. package/docs/UPGRADE_PLAN_v7.0.0.md +203 -203
  43. package/docs/WORKFLOW_INTEGRATION.md +125 -125
  44. package/docs/evaluation/CONTEXT_EVALUATION_v7.1.4.json +1818 -1818
  45. package/docs/evaluation/CONTEXT_EVALUATION_v7.1.4.md +36 -36
  46. package/docs/launch/COMPONENT_MAP_v7.1.4.md +122 -122
  47. package/docs/launch/PROJECT_KIT_PLAN_v7.1.4.md +110 -110
  48. package/docs/launch/PROJECT_LEARNING_PLAN_v7.1.4.md +63 -63
  49. package/docs/launch/RELEASE_ACCEPTANCE_v7.22.0.md +48 -48
  50. package/docs/launch/RELEASE_CHECKLIST_v7.0.0.md +24 -24
  51. package/docs/launch/RELEASE_NOTES_v7.0.0.md +44 -44
  52. package/docs/launch/RELEASE_NOTES_v7.1.0.md +32 -32
  53. package/docs/launch/RELEASE_NOTES_v7.1.3.md +40 -40
  54. package/docs/launch/RELEASE_NOTES_v7.1.4.md +30 -30
  55. package/docs/launch/RELEASE_NOTES_v7.2.0.md +24 -24
  56. package/docs/launch/RELEASE_NOTES_v7.22.0.md +53 -53
  57. package/docs/launch/RELEASE_NOTES_v7.22.1.md +11 -0
  58. package/docs/launch/RESEARCH_AND_PLAN_v7.1.4.md +255 -255
  59. package/docs/launch/RESEARCH_AND_PLAN_v7.22.0.md +672 -672
  60. package/package.json +7 -3
  61. package/skills/shadow-debug/SKILL.md +16 -16
  62. package/skills/shadow-implement/SKILL.md +16 -16
  63. package/skills/shadow-release/SKILL.md +16 -16
  64. package/skills/shadow-research/SKILL.md +14 -14
  65. package/skills/shadow-review/SKILL.md +14 -14
  66. package/skills/shadow-workflow/SKILL.md +39 -39
@@ -1,338 +1,338 @@
1
- export const BRAIN_HUB_GRAPH = String.raw `
2
- const tones={project:'124,233,232',memory:'172,199,255',agent:'234,172,249',measured:'124,233,232',reported:'250,208,140'};
3
- const relationColors={supports:'91,209,221',related:'119,165,221',contradicts:'227,175,119',supersedes:'150,154,222'};
4
- const graphMemoryLimit='500';
5
- let graphSurface=null;
6
- const defaultCamera=()=>({scale:1,x:0,y:0});
7
- let graphCamera=defaultCamera();
8
- function graphFinderResults(){
9
- const search=state.search;
10
- if(!search)return '<div class="graph-results" id="graph-results">'+list(arr(state.graph?.nodes).map(node=>
11
- '<li><button class="entry" type="button" data-node="'+esc(node.id)+'" aria-pressed="'+(state.focus&&state.picked===node.id)+'"><strong>'+
12
- esc(node.kind==='agent'?hostName(node.label):node.label)+'</strong><small class="muted">'+esc(node.kind+(node.category?' · '+node.category:''))+'</small></button></li>').join(''),
13
- 'aria-label="Displayed graph records"')+'</div>';
14
- const items=arr(search.items);
15
- return '<div class="graph-results" id="graph-results" aria-busy="'+!!search.loading+'"><p class="small muted graph-search-note" role="status">'+
16
- esc(search.error??(search.loading?'Searching stored memories…':items.length?fmt(items.length)+' of '+fmt(search.total)+' matches'+(search.truncated?' · bounded inventory':''):'No matching memories in this scope.'))+'</p>'+
17
- (items.length?list(items.map(item=>'<li><button class="entry" type="button" data-graph-memory="'+esc(item.id)+'" data-graph-project="'+esc(item.project)+
18
- '"><strong>'+esc(item.title||item.id)+'</strong><small class="muted">'+esc(item.category+' · '+hostName(item.agent)+(item.projectName?' · '+item.projectName:''))+'</small></button></li>').join(''),'aria-label="Stored memory search results"'):'')+'</div>';
19
- }
20
- function graphFinderMarkup(hidden){
21
- return '<div id="node-list" class="node-list graph-finder" '+(hidden?'hidden':'')+'><form id="graph-find-form" class="graph-find" role="search" aria-label="Find stored memories">'+
22
- '<label for="graph-search" class="sr-only">Find stored memories in this scope</label><input id="graph-search" name="query" type="search" value="'+esc(state.search?.query)+
23
- '" placeholder="Search all stored memories" maxlength="180"><button class="btn" type="submit">Search</button>'+action('clear-graph-search','Clear','quiet')+
24
- '</form>'+graphFinderResults()+'</div>';
25
- }
26
- async function findGraphMemories(query){
27
- const epoch=state.dataEpoch;
28
- const search=query?{query,loading:true}:null;
29
- state.search=search;
30
- const field=$('graph-search'),results=$('graph-results');
31
- if(field)field.value=query;
32
- if(results)results.outerHTML=graphFinderResults();
33
- if(!search)return;
34
- try{Object.assign(search,await api('memories'+projectQuery({q:query,limit:'30'})));}
35
- catch(failure){search.error=failure.message;}
36
- if(epoch!==state.dataEpoch||state.search!==search)return;
37
- search.loading=false;
38
- const result=$('graph-results');
39
- if(result)result.outerHTML=graphFinderResults();
40
- }
41
- async function focusGraphMemory(id,project){
42
- const dataEpoch=state.dataEpoch,graphEpoch=++state.graphEpoch;
43
- const query=projectQuery({limit:graphMemoryLimit,memory:id});
44
- try{
45
- const graph=await api('graph'+query);
46
- if(dataEpoch!==state.dataEpoch||graphEpoch!==state.graphEpoch||state.view!=='overview')return;
47
- if(!arr(graph.nodes).some(node=>node.memoryId===id&&node.project===project))throw new Error('The memory is no longer available in this scope.');
48
- state.graphEpoch++;state.graph=graph;state.listing=false;render();
49
- const details=chooseMemory('memory:'+id);
50
- $('main').querySelector('[data-inspect=record]')?.focus({preventScroll:true});
51
- await details;
52
- }catch(failure){if(dataEpoch===state.dataEpoch&&graphEpoch===state.graphEpoch&&state.view==='overview')status(failure.message,true);}
53
- }
54
- function filterGraph(value){
55
- if(!['all','project','author','typed'].includes(value))return;
56
- state.graphFilter=value;
57
- drawGraph();syncGraphControls();
58
- }
59
- const graphCoordinates=(surface,x,y)=>({x:(x-surface.width/2-graphCamera.x)/graphCamera.scale+surface.width/2,
60
- y:(y-surface.height/2-graphCamera.y)/graphCamera.scale+surface.height/2});
61
- function boundCamera(surface){
62
- const dx=(graphCamera.scale-1)*surface.width/2,dy=(graphCamera.scale-1)*surface.height/2;
63
- graphCamera.x=Math.max(-dx,Math.min(dx,graphCamera.x));
64
- graphCamera.y=Math.max(-dy,Math.min(dy,graphCamera.y));
65
- }
66
- function zoomGraph(direction){
67
- if(!graphSurface)return;
68
- const before=graphCamera.scale;
69
- const scale=direction==='fit'?1:Math.round(Math.max(1,Math.min(3,before+direction*.5))*10)/10;
70
- const point=state.focus&&graphSurface.points.get(state.picked);
71
- if(point){
72
- const x=point.x-graphSurface.width/2,y=point.y-graphSurface.height/2;
73
- graphCamera={scale,x:graphCamera.x+x*(before-scale),y:graphCamera.y+y*(before-scale)};
74
- }else graphCamera.scale=scale;
75
- boundCamera(graphSurface);drawGraph();syncGraphControls();
76
- }
77
- function rand(seed){return ()=>{seed=(Math.imul(seed,1664525)+1013904223)>>>0;return seed/4294967296;};}
78
- function hash(id){let value=2166136261;for(const char of id)value=Math.imul(value^char.charCodeAt(0),16777619);return value>>>0;}
79
- function mountGraph(){
80
- const canvas=$('brain-canvas'),stage=$('brain-stage');
81
- if(!canvas||canvas.hidden||!stage)return;
82
- const ctx=canvas.getContext('2d'),layer=document.createElement('canvas'),layerContext=layer.getContext('2d');
83
- if(!ctx||!layerContext)return;
84
- const surface={canvas,stage,ctx,layer,layerContext,width:0,height:0,raf:0,observer:null,
85
- points:new Map(),edges:[],snapshot:undefined,counts:{},drag:null,layerKey:''};
86
- graphSurface=surface;
87
- surface.observer=new ResizeObserver(()=>resizeGraph(surface));
88
- surface.observer.observe(stage);
89
- resizeGraph(surface);
90
- function hit(event){
91
- const box=canvas.getBoundingClientRect();
92
- const {x,y}=graphCoordinates(surface,event.clientX-box.left,event.clientY-box.top);
93
- let best=null,distance=15/graphCamera.scale;
94
- for(const point of surface.points.values()){
95
- const d=Math.hypot(x-point.x,y-point.y);
96
- if(d<distance){best=point;distance=d;}
97
- }
98
- return best;
99
- }
100
- canvas.addEventListener('pointerdown',event=>{
101
- if(event.button!==0||event.pointerType==='touch'||graphCamera.scale===1)return;
102
- surface.drag={id:event.pointerId,x:event.clientX,y:event.clientY,
103
- fromX:graphCamera.x,fromY:graphCamera.y,moved:false};
104
- });
105
- canvas.addEventListener('pointermove',event=>{
106
- const drag=surface.drag;
107
- if(drag?.id===event.pointerId){
108
- const dx=event.clientX-drag.x,dy=event.clientY-drag.y;
109
- if(drag.moved||Math.hypot(dx,dy)>4){
110
- if(!drag.moved)canvas.setPointerCapture(event.pointerId);
111
- drag.moved=true;graphCamera.x=drag.fromX+dx;graphCamera.y=drag.fromY+dy;
112
- boundCamera(surface);canvas.style.cursor='grabbing';drawGraph();return;
113
- }
114
- }
115
- const point=hit(event);
116
- canvas.style.cursor=point?'pointer':graphCamera.scale>1?'grab':'default';
117
- const caption=$('node-caption');
118
- if(caption&&point){caption.hidden=false;caption.textContent=point.kind==='agent'?hostName(point.label):point.label;}
119
- else syncGraphControls();
120
- });
121
- canvas.addEventListener('pointerleave',()=>{if(!surface.drag?.moved)surface.drag=null;syncGraphControls();});
122
- canvas.addEventListener('pointercancel',()=>{surface.drag=null;});
123
- canvas.addEventListener('pointerup',event=>{
124
- const moved=surface.drag?.moved;surface.drag=null;
125
- canvas.style.cursor=graphCamera.scale>1?'grab':'default';
126
- if(moved||event.button!==0)return;
127
- const point=hit(event);
128
- if(point)void chooseMemory(point.id);
129
- });
130
- syncGraphControls();
131
- }
132
- function unmountGraph(){
133
- if(!graphSurface)return;
134
- graphSurface.observer.disconnect();cancelAnimationFrame(graphSurface.raf);graphSurface=null;
135
- }
136
- function stroke(ctx,rgb,alpha,width){ctx.strokeStyle='rgba('+rgb+','+alpha+')';ctx.lineWidth=width;ctx.stroke();}
137
- function dot(ctx,x,y,r,rgb,alpha){ctx.beginPath();ctx.arc(x,y,r,0,Math.PI*2);ctx.fillStyle='rgba('+rgb+','+alpha+')';ctx.fill();}
138
- function resizeGraph(surface){
139
- if(graphSurface!==surface)return;
140
- const {stage,canvas,ctx}=surface;
141
- const size=stage.getBoundingClientRect(),dpr=Math.min(devicePixelRatio,1.75);
142
- if(!size.width||!size.height)return;
143
- surface.width=size.width;surface.height=size.height;surface.snapshot=undefined;boundCamera(surface);
144
- canvas.width=Math.round(size.width*dpr);
145
- canvas.height=Math.round(size.height*dpr);
146
- surface.layer.width=canvas.width;surface.layer.height=canvas.height;
147
- surface.layerContext.setTransform(dpr,0,0,dpr,0,0);
148
- surface.layerKey='';
149
- ctx.setTransform(dpr,0,0,dpr,0,0);
150
- drawGraph();
151
- }
152
- function brainFrame(width,height){return {x:width*.50,y:height*.49,rx:Math.min(width*.34,height*.70),ry:Math.min(height*.42,width*.255)};}
153
- function brainShell(angle){
154
- const x=Math.cos(angle),y=Math.sin(angle);
155
- const contour=1+.044*Math.cos(3*angle+.7)+.036*Math.cos(5*angle-.7)+.018*Math.sin(9*angle);
156
- return {x:x*contour,y:y*contour*(y>0?.91+.12*x:1.02)-.07*x};
157
- }
158
- function brainProjection(frame,x,y,z){
159
- const tilt=-.16,scale=1+.067*z;
160
- const across=frame.rx*(x*scale+.10*z),down=frame.ry*(y*scale-.12*z);
161
- return {x:frame.x+across*Math.cos(tilt)-down*Math.sin(tilt),y:frame.y+across*Math.sin(tilt)+down*Math.cos(tilt)};
162
- }
163
- function shellPath(ctx,frame,depth){
164
- for(let step=0;step<=112;step++){
165
- const rim=brainShell(step/112*Math.PI*2);
166
- const point=brainProjection(frame,rim.x,rim.y,depth);
167
- if(step===0)ctx.moveTo(point.x,point.y);
168
- else ctx.lineTo(point.x,point.y);
169
- }
170
- ctx.closePath();
171
- }
172
- function drawOutline(ctx,frame){
173
- const {x,y,rx,ry}=frame;
174
- const field=ctx.createRadialGradient(x+rx*.22,y-ry*.42,0,x,y,rx*1.1);
175
- field.addColorStop(0,'rgba(47,123,151,.22)');
176
- field.addColorStop(.64,'rgba(52,74,132,.13)');
177
- field.addColorStop(1,'rgba(15,24,47,.06)');
178
- ctx.beginPath();shellPath(ctx,frame,0);ctx.fillStyle=field;ctx.fill();
179
- ctx.beginPath();shellPath(ctx,frame,-.74);stroke(ctx,'108,162,215',.19,.7);
180
- ctx.beginPath();shellPath(ctx,frame,.66);stroke(ctx,'140,193,225',.30,.9);
181
- // Subtle contour guides give the point volume spatial context.
182
- ctx.beginPath();
183
- for(const [angle,depth] of [[-.76,-.22],[.38,.35],[1.24,.57]]){
184
- const start=brainShell(angle),end=brainShell(angle+Math.PI);
185
- const a=brainProjection(frame,start.x*.97,start.y*.97,depth);
186
- const b=brainProjection(frame,-.28,.04,depth);
187
- const c=brainProjection(frame,.45,-.22,depth);
188
- const d=brainProjection(frame,end.x*.98,end.y*.98,depth);
189
- ctx.moveTo(a.x,a.y);ctx.bezierCurveTo(c.x,c.y,b.x,b.y,d.x,d.y);
190
- }
191
- stroke(ctx,'135,181,233',.12,.75);
192
- }
193
- function graphPoints(width,height){
194
- const nodes=arr(state.graph?.nodes),anchors=nodes.filter(node=>node.kind!=='memory');
195
- const memories=nodes.filter(node=>node.kind==='memory');
196
- const frame=brainFrame(width,height);
197
- const points=new Map();
198
- function point(node,x,y,depth=1){points.set(node.id,{...node,x,y,depth});}
199
- for(const kind of ['project','agent']){
200
- const subset=anchors.filter(node=>node.kind===kind);
201
- subset.forEach((node,i)=>{
202
- const t=(i+1)/(subset.length+1),sign=kind==='project'?-1:1;
203
- point(node,frame.x+sign*(frame.rx+Math.max(7,Math.min(32,width*.043))),height*.21+height*.58*t);
204
- });
205
- }
206
- const projected=memories.map(node=>{
207
- const random=rand(hash(node.id));
208
- const angle=random()*Math.PI*2,polar=random()*2-1;
209
- const shell=random()<.40;
210
- const radius=shell?.87+.13*random():Math.cbrt(random());
211
- const rim=brainShell(angle),disk=Math.sqrt(1-polar**2)*radius,depth=polar*radius;
212
- return {node,depth,...brainProjection(frame,rim.x*disk,rim.y*disk,depth)};
213
- }).sort((left,right)=>left.depth-right.depth);
214
- for(const node of projected)point(node.node,node.x,node.y,(node.depth+1)/2);
215
- return points;
216
- }
217
- function displayEvent(){return state.replayId?state.events.find(event=>event.id===state.replayId):state.events.at(-1);}
218
- function graphEventPath(event,surface){
219
- if(!exchangeKey(event))return null;
220
- const {width,height}=surface;
221
- const workspace={x:width*.47,y:height*.67},client={x:width*.92,y:height*.37};
222
- const controls=[{x:width*.31,y:height*.27},{x:width*.68,y:height*.16}];
223
- return event.kind==='tool-start'?[client,...controls.reverse(),workspace]:[workspace,...controls,client];
224
- }
225
- function labelPoint(ctx,point,label,side,color,width,alpha){
226
- ctx.font='600 10.5px system-ui,sans-serif';
227
- const limit=Math.min(126,width*.20);
228
- while(ctx.measureText(label).width>limit&&label.length>1)label=label.slice(0,label.endsWith('…')?-2:-1)+'…';
229
- const pad=6,labelWidth=ctx.measureText(label).width+2*pad;
230
- const x=Math.max(3,Math.min(width-labelWidth-3,side<0?point.x-12-labelWidth:point.x+12)),y=point.y-9;
231
- ctx.globalAlpha=alpha;ctx.fillStyle='#111e31';ctx.beginPath();ctx.roundRect(x,y,labelWidth,18,3);ctx.fill();stroke(ctx,color,.43,.7);
232
- ctx.fillStyle='#ddefff';ctx.textAlign='left';ctx.textBaseline='middle';ctx.fillText(label,x+pad,y+9);ctx.globalAlpha=1;
233
- }
234
- function drawMemoryLayer(surface,current){
235
- const {layerContext:ctx,width,height,points,edges}=surface;
236
- const connected=new Set(current?[current]:[]);
237
- if(current)for(const edge of edges){
238
- if(edge.source===current)connected.add(edge.target);
239
- if(edge.target===current)connected.add(edge.source);
240
- }
241
- ctx.clearRect(0,0,width,height);
242
- ctx.save();ctx.translate(width/2+graphCamera.x,height/2+graphCamera.y);ctx.scale(graphCamera.scale,graphCamera.scale);ctx.translate(-width/2,-height/2);
243
- if(points.size)drawOutline(ctx,brainFrame(width,height));
244
- for(const edge of edges){
245
- const relation=edge.kind==='belongs-to'?'project':edge.kind==='recorded-by'?'author':'typed';
246
- if(state.graphFilter!=='all'&&relation!==state.graphFilter)continue;
247
- const a=points.get(edge.source),b=points.get(edge.target);
248
- const active=current&&(edge.source===current||edge.target===current);
249
- const isTyped=relation==='typed',depth=(a.depth+b.depth)/2;
250
- ctx.beginPath();ctx.moveTo(a.x,a.y);
251
- if(isTyped)ctx.lineTo(b.x,b.y);
252
- else ctx.quadraticCurveTo((a.x+b.x)/2,(a.y+b.y)/2+(a.x-b.x)*.08,b.x,b.y);
253
- const color=relation==='project'?tones.project:relation==='author'?tones.agent:relationColors[edge.kind]??tones.memory;
254
- if(active){stroke(ctx,color,.18,4);stroke(ctx,color,.88,1.25);}
255
- else{
256
- const alpha=isTyped?.10+depth*.38:state.graphFilter===relation?.16:.022;
257
- stroke(ctx,color,current?alpha*.12:alpha,isTyped?.38+depth*.46:.57);
258
- }
259
- }
260
- for(const point of points.values()){
261
- const active=point.id===current,memory=point.kind==='memory';
262
- const color=memory?(point.depth>.65?'145,230,247':tones.memory):tones[point.kind];
263
- const alpha=(current&&!connected.has(point.id)?.23:1)*(.35+.65*point.depth);
264
- const r=memory?(points.size>70?.68+1.12*point.depth:1.7+1.6*point.depth):3.4;
265
- if(active||point.depth>.68)dot(ctx,point.x,point.y,active?12:r+2.4,color,.15*alpha);
266
- dot(ctx,point.x,point.y,active?3.5:r,color,.88*alpha);
267
- if(active||!memory){
268
- ctx.beginPath();ctx.arc(point.x,point.y,active?8:5.4,0,Math.PI*2);stroke(ctx,color,.62*alpha,.75);
269
- }
270
- if(!memory&&width>510&&(active||surface.counts[point.kind]<=11))labelPoint(ctx,point,point.kind==='agent'?hostName(point.label):point.label,point.kind==='project'?-1:1,color,width,alpha);
271
- }
272
- ctx.restore();
273
- }
274
- function drawGraph(){
275
- const surface=graphSurface;
276
- if(!surface||!surface.width)return;
277
- if(surface.raf){cancelAnimationFrame(surface.raf);surface.raf=0;}
278
- const {ctx,width,height,canvas}=surface;
279
- if(surface.snapshot!==state.graph){
280
- surface.snapshot=state.graph;
281
- surface.points=graphPoints(width,height);
282
- surface.edges=arr(state.graph?.edges).filter(edge=>surface.points.has(edge.source)&&surface.points.has(edge.target));
283
- surface.counts={};
284
- for(const point of surface.points.values())surface.counts[point.kind]=(surface.counts[point.kind]??0)+1;
285
- surface.layerKey='';
286
- }
287
- const {points,edges}=surface;
288
- const current=state.focus?state.picked:null;
289
- const layerKey=JSON.stringify([current,state.graphFilter,graphCamera]);
290
- if(surface.layerKey!==layerKey){drawMemoryLayer(surface,current);surface.layerKey=layerKey;}
291
- ctx.clearRect(0,0,width,height);
292
- ctx.drawImage(surface.layer,0,0,width,height);
293
- const age=performance.now()-state.motionStarted;
294
- const animate=!document.hidden&&!reduced.matches&&!state.motionPaused&&!state.replayId&&state.live&&state.connection==='reachable'&&state.pulseIds.length&&age<1350&&!state.listing;
295
- const traceEvents=animate?state.events.filter(event=>state.pulseIds.includes(event.id)&&exchangeKey(event)):
296
- state.replayId?[displayEvent()].filter(Boolean):[];
297
- // The lower lane represents MCP boundary activity independently of the stored graph.
298
- const step=animate?age/1350*traceEvents.length:0;
299
- const currentEvent=traceEvents[Math.floor(step)];
300
- const path=currentEvent&&graphEventPath(currentEvent,surface);
301
- if(path){
302
- const event=currentEvent;
303
- const [a,b,c,d]=path,color=event.kind==='tool-start'?'104,205,238':event.outcome==='success'?tones.measured:'255,172,182';
304
- const direction=Math.atan2(d.y-c.y,d.x-c.x);
305
- ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.bezierCurveTo(b.x,b.y,c.x,c.y,d.x,d.y);
306
- stroke(ctx,color,.10,7);stroke(ctx,color,.24,3.2);stroke(ctx,color,.88,1.35);
307
- ctx.save();ctx.translate(d.x,d.y);ctx.rotate(direction);
308
- ctx.beginPath();ctx.moveTo(-6,-3.1);ctx.lineTo(0,0);ctx.lineTo(-6,3.1);stroke(ctx,color,.92,1.3);ctx.restore();
309
- const t=animate?step%1:.58,u=1-t;
310
- const x=u**3*a.x+3*u*u*t*b.x+3*u*t*t*c.x+t**3*d.x;
311
- const y=u**3*a.y+3*u*u*t*b.y+3*u*t*t*c.y+t**3*d.y;
312
- dot(ctx,x,y,8,color,.19);dot(ctx,x,y,4,color,.52);dot(ctx,x,y,1.9,'237,255,255',1);
313
- }
314
- const graphEvent=$('graph-event');
315
- if(graphEvent){
316
- const text=path?exchangeDirection(currentEvent)+' · '+exchangeLabel(currentEvent):'';
317
- if(graphEvent.textContent!==text)graphEvent.textContent=text;
318
- graphEvent.hidden=!path||!!current||state.listing;
319
- }
320
- canvas.dataset.nodes=String(points.size);canvas.dataset.edges=String(edges.length);
321
- canvas.dataset.pulses=path?'1':'0';canvas.dataset.frame=animate&&path?String(Math.round(age)):'static';
322
- if(animate&&path)surface.raf=requestAnimationFrame(drawGraph);
323
- }
324
- function syncGraphControls(){
325
- const label=$('node-caption'),point=arr(state.graph?.nodes).find(node=>node.id===state.picked);
326
- if(label){label.hidden=!state.focus||!point||state.listing;label.textContent=point?.label??'';}
327
- const clear=$('clear-focus');if(clear)clear.hidden=!state.focus;
328
- const zoom=$('graph-zoom');
329
- if(zoom)zoom.textContent=Math.round(graphCamera.scale*100)+'%';
330
- const plus=$('graph-zoom-in'),minus=$('graph-zoom-out');
331
- // Keep zoom controls focusable at bounds for keyboard continuity.
332
- plus?.setAttribute('aria-disabled',String(graphCamera.scale>=3));
333
- minus?.setAttribute('aria-disabled',String(graphCamera.scale<=1));
334
- $('main').querySelectorAll('[data-graph-filter]').forEach(button=>button.setAttribute('aria-pressed',String(state.graphFilter===button.dataset.graphFilter)));
335
- $('brain-stage')?.querySelectorAll('[data-node]').forEach(button=>button.setAttribute('aria-pressed',String(button.dataset.node===state.picked&&state.focus)));
336
- }
1
+ export const BRAIN_HUB_GRAPH = String.raw `
2
+ const tones={project:'124,233,232',memory:'172,199,255',agent:'234,172,249',measured:'124,233,232',reported:'250,208,140'};
3
+ const relationColors={supports:'91,209,221',related:'119,165,221',contradicts:'227,175,119',supersedes:'150,154,222'};
4
+ const graphMemoryLimit='500';
5
+ let graphSurface=null;
6
+ const defaultCamera=()=>({scale:1,x:0,y:0});
7
+ let graphCamera=defaultCamera();
8
+ function graphFinderResults(){
9
+ const search=state.search;
10
+ if(!search)return '<div class="graph-results" id="graph-results">'+list(arr(state.graph?.nodes).map(node=>
11
+ '<li><button class="entry" type="button" data-node="'+esc(node.id)+'" aria-pressed="'+(state.focus&&state.picked===node.id)+'"><strong>'+
12
+ esc(node.kind==='agent'?hostName(node.label):node.label)+'</strong><small class="muted">'+esc(node.kind+(node.category?' · '+node.category:''))+'</small></button></li>').join(''),
13
+ 'aria-label="Displayed graph records"')+'</div>';
14
+ const items=arr(search.items);
15
+ return '<div class="graph-results" id="graph-results" aria-busy="'+!!search.loading+'"><p class="small muted graph-search-note" role="status">'+
16
+ esc(search.error??(search.loading?'Searching stored memories…':items.length?fmt(items.length)+' of '+fmt(search.total)+' matches'+(search.truncated?' · bounded inventory':''):'No matching memories in this scope.'))+'</p>'+
17
+ (items.length?list(items.map(item=>'<li><button class="entry" type="button" data-graph-memory="'+esc(item.id)+'" data-graph-project="'+esc(item.project)+
18
+ '"><strong>'+esc(item.title||item.id)+'</strong><small class="muted">'+esc(item.category+' · '+hostName(item.agent)+(item.projectName?' · '+item.projectName:''))+'</small></button></li>').join(''),'aria-label="Stored memory search results"'):'')+'</div>';
19
+ }
20
+ function graphFinderMarkup(hidden){
21
+ return '<div id="node-list" class="node-list graph-finder" '+(hidden?'hidden':'')+'><form id="graph-find-form" class="graph-find" role="search" aria-label="Find stored memories">'+
22
+ '<label for="graph-search" class="sr-only">Find stored memories in this scope</label><input id="graph-search" name="query" type="search" value="'+esc(state.search?.query)+
23
+ '" placeholder="Search all stored memories" maxlength="180"><button class="btn" type="submit">Search</button>'+action('clear-graph-search','Clear','quiet')+
24
+ '</form>'+graphFinderResults()+'</div>';
25
+ }
26
+ async function findGraphMemories(query){
27
+ const epoch=state.dataEpoch;
28
+ const search=query?{query,loading:true}:null;
29
+ state.search=search;
30
+ const field=$('graph-search'),results=$('graph-results');
31
+ if(field)field.value=query;
32
+ if(results)results.outerHTML=graphFinderResults();
33
+ if(!search)return;
34
+ try{Object.assign(search,await api('memories'+projectQuery({q:query,limit:'30'})));}
35
+ catch(failure){search.error=failure.message;}
36
+ if(epoch!==state.dataEpoch||state.search!==search)return;
37
+ search.loading=false;
38
+ const result=$('graph-results');
39
+ if(result)result.outerHTML=graphFinderResults();
40
+ }
41
+ async function focusGraphMemory(id,project){
42
+ const dataEpoch=state.dataEpoch,graphEpoch=++state.graphEpoch;
43
+ const query=projectQuery({limit:graphMemoryLimit,memory:id});
44
+ try{
45
+ const graph=await api('graph'+query);
46
+ if(dataEpoch!==state.dataEpoch||graphEpoch!==state.graphEpoch||state.view!=='overview')return;
47
+ if(!arr(graph.nodes).some(node=>node.memoryId===id&&node.project===project))throw new Error('The memory is no longer available in this scope.');
48
+ state.graphEpoch++;state.graph=graph;state.listing=false;render();
49
+ const details=chooseMemory('memory:'+id);
50
+ $('main').querySelector('[data-inspect=record]')?.focus({preventScroll:true});
51
+ await details;
52
+ }catch(failure){if(dataEpoch===state.dataEpoch&&graphEpoch===state.graphEpoch&&state.view==='overview')status(failure.message,true);}
53
+ }
54
+ function filterGraph(value){
55
+ if(!['all','project','author','typed'].includes(value))return;
56
+ state.graphFilter=value;
57
+ drawGraph();syncGraphControls();
58
+ }
59
+ const graphCoordinates=(surface,x,y)=>({x:(x-surface.width/2-graphCamera.x)/graphCamera.scale+surface.width/2,
60
+ y:(y-surface.height/2-graphCamera.y)/graphCamera.scale+surface.height/2});
61
+ function boundCamera(surface){
62
+ const dx=(graphCamera.scale-1)*surface.width/2,dy=(graphCamera.scale-1)*surface.height/2;
63
+ graphCamera.x=Math.max(-dx,Math.min(dx,graphCamera.x));
64
+ graphCamera.y=Math.max(-dy,Math.min(dy,graphCamera.y));
65
+ }
66
+ function zoomGraph(direction){
67
+ if(!graphSurface)return;
68
+ const before=graphCamera.scale;
69
+ const scale=direction==='fit'?1:Math.round(Math.max(1,Math.min(3,before+direction*.5))*10)/10;
70
+ const point=state.focus&&graphSurface.points.get(state.picked);
71
+ if(point){
72
+ const x=point.x-graphSurface.width/2,y=point.y-graphSurface.height/2;
73
+ graphCamera={scale,x:graphCamera.x+x*(before-scale),y:graphCamera.y+y*(before-scale)};
74
+ }else graphCamera.scale=scale;
75
+ boundCamera(graphSurface);drawGraph();syncGraphControls();
76
+ }
77
+ function rand(seed){return ()=>{seed=(Math.imul(seed,1664525)+1013904223)>>>0;return seed/4294967296;};}
78
+ function hash(id){let value=2166136261;for(const char of id)value=Math.imul(value^char.charCodeAt(0),16777619);return value>>>0;}
79
+ function mountGraph(){
80
+ const canvas=$('brain-canvas'),stage=$('brain-stage');
81
+ if(!canvas||canvas.hidden||!stage)return;
82
+ const ctx=canvas.getContext('2d'),layer=document.createElement('canvas'),layerContext=layer.getContext('2d');
83
+ if(!ctx||!layerContext)return;
84
+ const surface={canvas,stage,ctx,layer,layerContext,width:0,height:0,raf:0,observer:null,
85
+ points:new Map(),edges:[],snapshot:undefined,counts:{},drag:null,layerKey:''};
86
+ graphSurface=surface;
87
+ surface.observer=new ResizeObserver(()=>resizeGraph(surface));
88
+ surface.observer.observe(stage);
89
+ resizeGraph(surface);
90
+ function hit(event){
91
+ const box=canvas.getBoundingClientRect();
92
+ const {x,y}=graphCoordinates(surface,event.clientX-box.left,event.clientY-box.top);
93
+ let best=null,distance=15/graphCamera.scale;
94
+ for(const point of surface.points.values()){
95
+ const d=Math.hypot(x-point.x,y-point.y);
96
+ if(d<distance){best=point;distance=d;}
97
+ }
98
+ return best;
99
+ }
100
+ canvas.addEventListener('pointerdown',event=>{
101
+ if(event.button!==0||event.pointerType==='touch'||graphCamera.scale===1)return;
102
+ surface.drag={id:event.pointerId,x:event.clientX,y:event.clientY,
103
+ fromX:graphCamera.x,fromY:graphCamera.y,moved:false};
104
+ });
105
+ canvas.addEventListener('pointermove',event=>{
106
+ const drag=surface.drag;
107
+ if(drag?.id===event.pointerId){
108
+ const dx=event.clientX-drag.x,dy=event.clientY-drag.y;
109
+ if(drag.moved||Math.hypot(dx,dy)>4){
110
+ if(!drag.moved)canvas.setPointerCapture(event.pointerId);
111
+ drag.moved=true;graphCamera.x=drag.fromX+dx;graphCamera.y=drag.fromY+dy;
112
+ boundCamera(surface);canvas.style.cursor='grabbing';drawGraph();return;
113
+ }
114
+ }
115
+ const point=hit(event);
116
+ canvas.style.cursor=point?'pointer':graphCamera.scale>1?'grab':'default';
117
+ const caption=$('node-caption');
118
+ if(caption&&point){caption.hidden=false;caption.textContent=point.kind==='agent'?hostName(point.label):point.label;}
119
+ else syncGraphControls();
120
+ });
121
+ canvas.addEventListener('pointerleave',()=>{if(!surface.drag?.moved)surface.drag=null;syncGraphControls();});
122
+ canvas.addEventListener('pointercancel',()=>{surface.drag=null;});
123
+ canvas.addEventListener('pointerup',event=>{
124
+ const moved=surface.drag?.moved;surface.drag=null;
125
+ canvas.style.cursor=graphCamera.scale>1?'grab':'default';
126
+ if(moved||event.button!==0)return;
127
+ const point=hit(event);
128
+ if(point)void chooseMemory(point.id);
129
+ });
130
+ syncGraphControls();
131
+ }
132
+ function unmountGraph(){
133
+ if(!graphSurface)return;
134
+ graphSurface.observer.disconnect();cancelAnimationFrame(graphSurface.raf);graphSurface=null;
135
+ }
136
+ function stroke(ctx,rgb,alpha,width){ctx.strokeStyle='rgba('+rgb+','+alpha+')';ctx.lineWidth=width;ctx.stroke();}
137
+ function dot(ctx,x,y,r,rgb,alpha){ctx.beginPath();ctx.arc(x,y,r,0,Math.PI*2);ctx.fillStyle='rgba('+rgb+','+alpha+')';ctx.fill();}
138
+ function resizeGraph(surface){
139
+ if(graphSurface!==surface)return;
140
+ const {stage,canvas,ctx}=surface;
141
+ const size=stage.getBoundingClientRect(),dpr=Math.min(devicePixelRatio,1.75);
142
+ if(!size.width||!size.height)return;
143
+ surface.width=size.width;surface.height=size.height;surface.snapshot=undefined;boundCamera(surface);
144
+ canvas.width=Math.round(size.width*dpr);
145
+ canvas.height=Math.round(size.height*dpr);
146
+ surface.layer.width=canvas.width;surface.layer.height=canvas.height;
147
+ surface.layerContext.setTransform(dpr,0,0,dpr,0,0);
148
+ surface.layerKey='';
149
+ ctx.setTransform(dpr,0,0,dpr,0,0);
150
+ drawGraph();
151
+ }
152
+ function brainFrame(width,height){return {x:width*.50,y:height*.49,rx:Math.min(width*.34,height*.70),ry:Math.min(height*.42,width*.255)};}
153
+ function brainShell(angle){
154
+ const x=Math.cos(angle),y=Math.sin(angle);
155
+ const contour=1+.044*Math.cos(3*angle+.7)+.036*Math.cos(5*angle-.7)+.018*Math.sin(9*angle);
156
+ return {x:x*contour,y:y*contour*(y>0?.91+.12*x:1.02)-.07*x};
157
+ }
158
+ function brainProjection(frame,x,y,z){
159
+ const tilt=-.16,scale=1+.067*z;
160
+ const across=frame.rx*(x*scale+.10*z),down=frame.ry*(y*scale-.12*z);
161
+ return {x:frame.x+across*Math.cos(tilt)-down*Math.sin(tilt),y:frame.y+across*Math.sin(tilt)+down*Math.cos(tilt)};
162
+ }
163
+ function shellPath(ctx,frame,depth){
164
+ for(let step=0;step<=112;step++){
165
+ const rim=brainShell(step/112*Math.PI*2);
166
+ const point=brainProjection(frame,rim.x,rim.y,depth);
167
+ if(step===0)ctx.moveTo(point.x,point.y);
168
+ else ctx.lineTo(point.x,point.y);
169
+ }
170
+ ctx.closePath();
171
+ }
172
+ function drawOutline(ctx,frame){
173
+ const {x,y,rx,ry}=frame;
174
+ const field=ctx.createRadialGradient(x+rx*.22,y-ry*.42,0,x,y,rx*1.1);
175
+ field.addColorStop(0,'rgba(47,123,151,.22)');
176
+ field.addColorStop(.64,'rgba(52,74,132,.13)');
177
+ field.addColorStop(1,'rgba(15,24,47,.06)');
178
+ ctx.beginPath();shellPath(ctx,frame,0);ctx.fillStyle=field;ctx.fill();
179
+ ctx.beginPath();shellPath(ctx,frame,-.74);stroke(ctx,'108,162,215',.19,.7);
180
+ ctx.beginPath();shellPath(ctx,frame,.66);stroke(ctx,'140,193,225',.30,.9);
181
+ // Subtle contour guides give the point volume spatial context.
182
+ ctx.beginPath();
183
+ for(const [angle,depth] of [[-.76,-.22],[.38,.35],[1.24,.57]]){
184
+ const start=brainShell(angle),end=brainShell(angle+Math.PI);
185
+ const a=brainProjection(frame,start.x*.97,start.y*.97,depth);
186
+ const b=brainProjection(frame,-.28,.04,depth);
187
+ const c=brainProjection(frame,.45,-.22,depth);
188
+ const d=brainProjection(frame,end.x*.98,end.y*.98,depth);
189
+ ctx.moveTo(a.x,a.y);ctx.bezierCurveTo(c.x,c.y,b.x,b.y,d.x,d.y);
190
+ }
191
+ stroke(ctx,'135,181,233',.12,.75);
192
+ }
193
+ function graphPoints(width,height){
194
+ const nodes=arr(state.graph?.nodes),anchors=nodes.filter(node=>node.kind!=='memory');
195
+ const memories=nodes.filter(node=>node.kind==='memory');
196
+ const frame=brainFrame(width,height);
197
+ const points=new Map();
198
+ function point(node,x,y,depth=1){points.set(node.id,{...node,x,y,depth});}
199
+ for(const kind of ['project','agent']){
200
+ const subset=anchors.filter(node=>node.kind===kind);
201
+ subset.forEach((node,i)=>{
202
+ const t=(i+1)/(subset.length+1),sign=kind==='project'?-1:1;
203
+ point(node,frame.x+sign*(frame.rx+Math.max(7,Math.min(32,width*.043))),height*.21+height*.58*t);
204
+ });
205
+ }
206
+ const projected=memories.map(node=>{
207
+ const random=rand(hash(node.id));
208
+ const angle=random()*Math.PI*2,polar=random()*2-1;
209
+ const shell=random()<.40;
210
+ const radius=shell?.87+.13*random():Math.cbrt(random());
211
+ const rim=brainShell(angle),disk=Math.sqrt(1-polar**2)*radius,depth=polar*radius;
212
+ return {node,depth,...brainProjection(frame,rim.x*disk,rim.y*disk,depth)};
213
+ }).sort((left,right)=>left.depth-right.depth);
214
+ for(const node of projected)point(node.node,node.x,node.y,(node.depth+1)/2);
215
+ return points;
216
+ }
217
+ function displayEvent(){return state.replayId?state.events.find(event=>event.id===state.replayId):state.events.at(-1);}
218
+ function graphEventPath(event,surface){
219
+ if(!exchangeKey(event))return null;
220
+ const {width,height}=surface;
221
+ const workspace={x:width*.47,y:height*.67},client={x:width*.92,y:height*.37};
222
+ const controls=[{x:width*.31,y:height*.27},{x:width*.68,y:height*.16}];
223
+ return event.kind==='tool-start'?[client,...controls.reverse(),workspace]:[workspace,...controls,client];
224
+ }
225
+ function labelPoint(ctx,point,label,side,color,width,alpha){
226
+ ctx.font='600 10.5px system-ui,sans-serif';
227
+ const limit=Math.min(126,width*.20);
228
+ while(ctx.measureText(label).width>limit&&label.length>1)label=label.slice(0,label.endsWith('…')?-2:-1)+'…';
229
+ const pad=6,labelWidth=ctx.measureText(label).width+2*pad;
230
+ const x=Math.max(3,Math.min(width-labelWidth-3,side<0?point.x-12-labelWidth:point.x+12)),y=point.y-9;
231
+ ctx.globalAlpha=alpha;ctx.fillStyle='#111e31';ctx.beginPath();ctx.roundRect(x,y,labelWidth,18,3);ctx.fill();stroke(ctx,color,.43,.7);
232
+ ctx.fillStyle='#ddefff';ctx.textAlign='left';ctx.textBaseline='middle';ctx.fillText(label,x+pad,y+9);ctx.globalAlpha=1;
233
+ }
234
+ function drawMemoryLayer(surface,current){
235
+ const {layerContext:ctx,width,height,points,edges}=surface;
236
+ const connected=new Set(current?[current]:[]);
237
+ if(current)for(const edge of edges){
238
+ if(edge.source===current)connected.add(edge.target);
239
+ if(edge.target===current)connected.add(edge.source);
240
+ }
241
+ ctx.clearRect(0,0,width,height);
242
+ ctx.save();ctx.translate(width/2+graphCamera.x,height/2+graphCamera.y);ctx.scale(graphCamera.scale,graphCamera.scale);ctx.translate(-width/2,-height/2);
243
+ if(points.size)drawOutline(ctx,brainFrame(width,height));
244
+ for(const edge of edges){
245
+ const relation=edge.kind==='belongs-to'?'project':edge.kind==='recorded-by'?'author':'typed';
246
+ if(state.graphFilter!=='all'&&relation!==state.graphFilter)continue;
247
+ const a=points.get(edge.source),b=points.get(edge.target);
248
+ const active=current&&(edge.source===current||edge.target===current);
249
+ const isTyped=relation==='typed',depth=(a.depth+b.depth)/2;
250
+ ctx.beginPath();ctx.moveTo(a.x,a.y);
251
+ if(isTyped)ctx.lineTo(b.x,b.y);
252
+ else ctx.quadraticCurveTo((a.x+b.x)/2,(a.y+b.y)/2+(a.x-b.x)*.08,b.x,b.y);
253
+ const color=relation==='project'?tones.project:relation==='author'?tones.agent:relationColors[edge.kind]??tones.memory;
254
+ if(active){stroke(ctx,color,.18,4);stroke(ctx,color,.88,1.25);}
255
+ else{
256
+ const alpha=isTyped?.10+depth*.38:state.graphFilter===relation?.16:.022;
257
+ stroke(ctx,color,current?alpha*.12:alpha,isTyped?.38+depth*.46:.57);
258
+ }
259
+ }
260
+ for(const point of points.values()){
261
+ const active=point.id===current,memory=point.kind==='memory';
262
+ const color=memory?(point.depth>.65?'145,230,247':tones.memory):tones[point.kind];
263
+ const alpha=(current&&!connected.has(point.id)?.23:1)*(.35+.65*point.depth);
264
+ const r=memory?(points.size>70?.68+1.12*point.depth:1.7+1.6*point.depth):3.4;
265
+ if(active||point.depth>.68)dot(ctx,point.x,point.y,active?12:r+2.4,color,.15*alpha);
266
+ dot(ctx,point.x,point.y,active?3.5:r,color,.88*alpha);
267
+ if(active||!memory){
268
+ ctx.beginPath();ctx.arc(point.x,point.y,active?8:5.4,0,Math.PI*2);stroke(ctx,color,.62*alpha,.75);
269
+ }
270
+ if(!memory&&width>510&&(active||surface.counts[point.kind]<=11))labelPoint(ctx,point,point.kind==='agent'?hostName(point.label):point.label,point.kind==='project'?-1:1,color,width,alpha);
271
+ }
272
+ ctx.restore();
273
+ }
274
+ function drawGraph(){
275
+ const surface=graphSurface;
276
+ if(!surface||!surface.width)return;
277
+ if(surface.raf){cancelAnimationFrame(surface.raf);surface.raf=0;}
278
+ const {ctx,width,height,canvas}=surface;
279
+ if(surface.snapshot!==state.graph){
280
+ surface.snapshot=state.graph;
281
+ surface.points=graphPoints(width,height);
282
+ surface.edges=arr(state.graph?.edges).filter(edge=>surface.points.has(edge.source)&&surface.points.has(edge.target));
283
+ surface.counts={};
284
+ for(const point of surface.points.values())surface.counts[point.kind]=(surface.counts[point.kind]??0)+1;
285
+ surface.layerKey='';
286
+ }
287
+ const {points,edges}=surface;
288
+ const current=state.focus?state.picked:null;
289
+ const layerKey=JSON.stringify([current,state.graphFilter,graphCamera]);
290
+ if(surface.layerKey!==layerKey){drawMemoryLayer(surface,current);surface.layerKey=layerKey;}
291
+ ctx.clearRect(0,0,width,height);
292
+ ctx.drawImage(surface.layer,0,0,width,height);
293
+ const age=performance.now()-state.motionStarted;
294
+ const animate=!document.hidden&&!reduced.matches&&!state.motionPaused&&!state.replayId&&state.live&&state.connection==='reachable'&&state.pulseIds.length&&age<1350&&!state.listing;
295
+ const traceEvents=animate?state.events.filter(event=>state.pulseIds.includes(event.id)&&exchangeKey(event)):
296
+ state.replayId?[displayEvent()].filter(Boolean):[];
297
+ // The lower lane represents MCP boundary activity independently of the stored graph.
298
+ const step=animate?age/1350*traceEvents.length:0;
299
+ const currentEvent=traceEvents[Math.floor(step)];
300
+ const path=currentEvent&&graphEventPath(currentEvent,surface);
301
+ if(path){
302
+ const event=currentEvent;
303
+ const [a,b,c,d]=path,color=event.kind==='tool-start'?'104,205,238':event.outcome==='success'?tones.measured:'255,172,182';
304
+ const direction=Math.atan2(d.y-c.y,d.x-c.x);
305
+ ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.bezierCurveTo(b.x,b.y,c.x,c.y,d.x,d.y);
306
+ stroke(ctx,color,.10,7);stroke(ctx,color,.24,3.2);stroke(ctx,color,.88,1.35);
307
+ ctx.save();ctx.translate(d.x,d.y);ctx.rotate(direction);
308
+ ctx.beginPath();ctx.moveTo(-6,-3.1);ctx.lineTo(0,0);ctx.lineTo(-6,3.1);stroke(ctx,color,.92,1.3);ctx.restore();
309
+ const t=animate?step%1:.58,u=1-t;
310
+ const x=u**3*a.x+3*u*u*t*b.x+3*u*t*t*c.x+t**3*d.x;
311
+ const y=u**3*a.y+3*u*u*t*b.y+3*u*t*t*c.y+t**3*d.y;
312
+ dot(ctx,x,y,8,color,.19);dot(ctx,x,y,4,color,.52);dot(ctx,x,y,1.9,'237,255,255',1);
313
+ }
314
+ const graphEvent=$('graph-event');
315
+ if(graphEvent){
316
+ const text=path?exchangeDirection(currentEvent)+' · '+exchangeLabel(currentEvent):'';
317
+ if(graphEvent.textContent!==text)graphEvent.textContent=text;
318
+ graphEvent.hidden=!path||!!current||state.listing;
319
+ }
320
+ canvas.dataset.nodes=String(points.size);canvas.dataset.edges=String(edges.length);
321
+ canvas.dataset.pulses=path?'1':'0';canvas.dataset.frame=animate&&path?String(Math.round(age)):'static';
322
+ if(animate&&path)surface.raf=requestAnimationFrame(drawGraph);
323
+ }
324
+ function syncGraphControls(){
325
+ const label=$('node-caption'),point=arr(state.graph?.nodes).find(node=>node.id===state.picked);
326
+ if(label){label.hidden=!state.focus||!point||state.listing;label.textContent=point?.label??'';}
327
+ const clear=$('clear-focus');if(clear)clear.hidden=!state.focus;
328
+ const zoom=$('graph-zoom');
329
+ if(zoom)zoom.textContent=Math.round(graphCamera.scale*100)+'%';
330
+ const plus=$('graph-zoom-in'),minus=$('graph-zoom-out');
331
+ // Keep zoom controls focusable at bounds for keyboard continuity.
332
+ plus?.setAttribute('aria-disabled',String(graphCamera.scale>=3));
333
+ minus?.setAttribute('aria-disabled',String(graphCamera.scale<=1));
334
+ $('main').querySelectorAll('[data-graph-filter]').forEach(button=>button.setAttribute('aria-pressed',String(state.graphFilter===button.dataset.graphFilter)));
335
+ $('brain-stage')?.querySelectorAll('[data-node]').forEach(button=>button.setAttribute('aria-pressed',String(button.dataset.node===state.picked&&state.focus)));
336
+ }
337
337
  `;
338
338
  //# sourceMappingURL=brain-hub-graph.js.map