nexus-prime 7.9.21 → 7.9.23

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 (59) hide show
  1. package/dist/agents/adapters/ide-compat.d.ts +3 -3
  2. package/dist/agents/adapters/ide-compat.js +51 -1
  3. package/dist/agents/adapters/mcp/definitions.js +4 -1
  4. package/dist/agents/adapters/mcp/dispatch.js +66 -4
  5. package/dist/agents/adapters/mcp/handlers/memory.js +44 -5
  6. package/dist/agents/adapters/mcp/handlers/orchestration.js +91 -0
  7. package/dist/agents/adapters/mcp/runHandler.js +3 -0
  8. package/dist/agents/adapters/mcp/util/detect-caller.js +21 -0
  9. package/dist/agents/adapters/mcp.js +1 -1
  10. package/dist/agents/adapters.d.ts +10 -1
  11. package/dist/agents/adapters.js +21 -0
  12. package/dist/agents/core/types.d.ts +1 -1
  13. package/dist/cli/hook.d.ts +4 -6
  14. package/dist/cli/hook.js +6 -8
  15. package/dist/cli/install-wizard.js +5 -1
  16. package/dist/cli.js +181 -15
  17. package/dist/core/types.d.ts +1 -1
  18. package/dist/dashboard/app/styles/board.css +85 -1
  19. package/dist/dashboard/app/styles/runtime.css +148 -0
  20. package/dist/dashboard/app/styles/workforce.css +28 -0
  21. package/dist/dashboard/app/views/board.js +56 -0
  22. package/dist/dashboard/app/views/memory.js +71 -10
  23. package/dist/dashboard/app/views/runtime.js +138 -4
  24. package/dist/dashboard/app/views/workforce.js +11 -4
  25. package/dist/dashboard/routes/events.js +3 -0
  26. package/dist/dashboard/selectors/operate-selector.js +5 -0
  27. package/dist/dashboard/selectors/runs-selector.js +5 -0
  28. package/dist/dashboard/server.js +6 -0
  29. package/dist/dashboard/types.d.ts +4 -0
  30. package/dist/engines/client-bootstrap.d.ts +5 -1
  31. package/dist/engines/client-bootstrap.js +105 -10
  32. package/dist/engines/client-registry.js +51 -0
  33. package/dist/engines/event-bus.d.ts +20 -2
  34. package/dist/engines/feature-registry.js +1 -0
  35. package/dist/engines/instruction-gateway.d.ts +9 -0
  36. package/dist/engines/instruction-gateway.js +113 -4
  37. package/dist/engines/memory/types.d.ts +28 -0
  38. package/dist/engines/memory-bridge.d.ts +1 -1
  39. package/dist/engines/memory-bridge.js +1 -1
  40. package/dist/engines/memory.d.ts +5 -0
  41. package/dist/engines/memory.js +144 -12
  42. package/dist/engines/orchestrator/decision-spine.d.ts +26 -0
  43. package/dist/engines/orchestrator/decision-spine.js +145 -6
  44. package/dist/engines/orchestrator/funnel.js +8 -1
  45. package/dist/engines/orchestrator/scoring.d.ts +1 -1
  46. package/dist/engines/orchestrator/scoring.js +24 -2
  47. package/dist/engines/orchestrator.d.ts +3 -0
  48. package/dist/engines/orchestrator.js +73 -13
  49. package/dist/engines/peer-connectors.d.ts +1 -1
  50. package/dist/engines/peer-connectors.js +9 -2
  51. package/dist/engines/runtime-registry.d.ts +9 -0
  52. package/dist/index.js +9 -0
  53. package/dist/install/state-locator.d.ts +1 -1
  54. package/dist/install/state-locator.js +3 -0
  55. package/dist/synapse/bootstrap.js +3 -0
  56. package/dist/synapse/mandate/pipeline.js +52 -5
  57. package/dist/synapse/sorties/runner.js +32 -0
  58. package/dist/synapse/types.d.ts +27 -0
  59. package/package.json +1 -1
@@ -133,6 +133,9 @@ const _CLIENT_LABEL = {
133
133
  continue: 'Continue',
134
134
  cline: 'Cline',
135
135
  openclaw: 'OpenClaw',
136
+ hermes: 'Hermes',
137
+ nanoclaw: 'NanoClaw',
138
+ picoclaw: 'PicoClaw',
136
139
  mcp: 'MCP',
137
140
  };
138
141
  function _clientStateColor(state) {
@@ -683,6 +686,9 @@ function _buildDecisionSpineHtml(spine) {
683
686
  const decisionLog = Array.isArray(artifacts.decisionLog) ? artifacts.decisionLog : [];
684
687
  const selected = spine?.summary?.selected || plan.selected || {};
685
688
  const model = spine?.summary?.modelRoute || plan.modelRoute || brief.modelPolicy || {};
689
+ const budgets = spine?.summary?.budgets || plan.budgets || {};
690
+ const executionPolicy = spine?.summary?.executionPolicy || plan.executionPolicy || {};
691
+ const agentFlow = spine?.summary?.agentFlow || executionPolicy.agentFlow || {};
686
692
  const latestDecision = spine?.summary?.latestDecision || decisionLog[decisionLog.length - 1] || null;
687
693
  const prompt = brief.rewrittenPrompt || brief.rawPrompt || '';
688
694
  return `<div class="dsec decision-spine">
@@ -699,6 +705,11 @@ function _buildDecisionSpineHtml(spine) {
699
705
  <div class="decision-spine-v">${esc(model.workerTier || '—')}</div>
700
706
  <div class="decision-spine-sub">${esc(model.reviewerTier ? 'review ' + model.reviewerTier : model.reason || '—')}</div>
701
707
  </div>
708
+ <div class="decision-spine-card">
709
+ <div class="decision-spine-k">Budget</div>
710
+ <div class="decision-spine-v">${fmtNum(budgets.totalTokens || 0)} t</div>
711
+ <div class="decision-spine-sub">${fmtNum(budgets.codeBlocks || budgets.codeBlockPolicy?.reservedTokens || 0)} t code blocks</div>
712
+ </div>
702
713
  <div class="decision-spine-card">
703
714
  <div class="decision-spine-k">Context</div>
704
715
  <div class="decision-spine-v">${fmtNum(contextLog.length)} events</div>
@@ -707,12 +718,53 @@ function _buildDecisionSpineHtml(spine) {
707
718
  </div>
708
719
  <div class="decision-spine-block"><span>Files</span>${miniListHtml(selected.files || plan.candidates?.files || [])}</div>
709
720
  <div class="decision-spine-block"><span>Skills</span>${miniListHtml(selected.skills || plan.candidates?.skills || [])}</div>
721
+ <div class="decision-spine-block"><span>Workflows</span>${miniListHtml(selected.workflows || plan.candidates?.workflows || [])}</div>
710
722
  <div class="decision-spine-block"><span>Crew</span>${miniListHtml([...(selected.crews || []), ...(selected.specialists || [])], 6)}</div>
723
+ <div class="decision-spine-block"><span>Workers</span>${miniListHtml(selected.workers || executionPolicy.subagents?.map(agent => `${agent.role}:${agent.modelTier}`) || [], 8)}</div>
724
+ ${_buildDecisionSpineBudgetHtml(budgets)}
725
+ ${_buildAgentFlowHtml(agentFlow)}
711
726
  ${latestDecision ? `<div class="decision-spine-latest">${esc(latestDecision.verb || 'decision')} · ${esc(latestDecision.decision || '')}</div>` : ''}
712
727
  ${_buildDecisionSpineLogBrowser(contextLog, decisionLog)}
713
728
  </div>`;
714
729
  }
715
730
 
731
+ function _buildDecisionSpineBudgetHtml(budgets) {
732
+ if (!budgets || typeof budgets !== 'object' || !Object.keys(budgets).length) return '';
733
+ const rows = [
734
+ ['Prompt', budgets.prompt],
735
+ ['Memory', budgets.memory],
736
+ ['Repo', budgets.repo],
737
+ ['Skills', budgets.skills],
738
+ ['Reasoning', budgets.reasoning],
739
+ ['Code blocks', budgets.codeBlocks || budgets.codeBlockPolicy?.reservedTokens],
740
+ ['Verification', budgets.verification],
741
+ ['Reserve', budgets.reserve],
742
+ ].filter(([, value]) => Number(value) > 0);
743
+ const templates = budgets.codeBlockPolicy?.templates || [];
744
+ return `<div class="decision-spine-budget-grid" aria-label="Token budget breakdown">
745
+ ${rows.map(([label, value]) => `<div><span>${esc(label)}</span><strong>${fmtNum(value)}t</strong></div>`).join('')}
746
+ ${templates.length ? `<div class="decision-spine-budget-wide"><span>Code block templates</span>${miniListHtml(templates, 6)}</div>` : ''}
747
+ </div>`;
748
+ }
749
+
750
+ function _buildAgentFlowHtml(agentFlow) {
751
+ const stages = Array.isArray(agentFlow?.stages) ? agentFlow.stages : [];
752
+ if (!stages.length) return '';
753
+ const gates = Array.isArray(agentFlow.deterministicGates) ? agentFlow.deterministicGates : [];
754
+ return `<div class="decision-spine-agentflow" aria-label="AgentFlow pipeline">
755
+ <div class="decision-spine-browser-title">AgentFlow pipeline</div>
756
+ <div class="decision-spine-agentflow-rail">
757
+ ${stages.map(stage => `<div class="decision-spine-agentflow-stage">
758
+ <span>${esc(stage.stage)}</span>
759
+ <strong>${esc(stage.ownerRole || 'worker')}</strong>
760
+ <small>${esc(stage.modelTier || '')}</small>
761
+ </div>`).join('')}
762
+ </div>
763
+ <div class="decision-spine-log-ref">${esc(agentFlow.dispatchPolicy || '')}</div>
764
+ ${gates.length ? `<div class="decision-spine-block"><span>Gates</span>${miniListHtml(gates, 8)}</div>` : ''}
765
+ </div>`;
766
+ }
767
+
716
768
  function _buildDecisionSpineLogBrowser(contextLog, decisionLog) {
717
769
  const contextRows = contextLog.slice(0, 6).map(entry => `
718
770
  <div class="decision-spine-log-row">
@@ -751,11 +803,15 @@ function _buildDecisionSpineMiniHtml(spine) {
751
803
  const plan = artifacts.selectionPlan || {};
752
804
  const selected = spine?.summary?.selected || plan.selected || {};
753
805
  const model = spine?.summary?.modelRoute || plan.modelRoute || brief.modelPolicy || {};
806
+ const budgets = spine?.summary?.budgets || plan.budgets || {};
807
+ const agentFlow = spine?.summary?.agentFlow || plan.executionPolicy?.agentFlow || {};
754
808
  return `<div class="decision-spine-mini">
755
809
  <div class="decision-spine-mini-head">Decision Spine · run ${esc((spine.runId || '').slice(-8))}</div>
756
810
  <div class="decision-spine-mini-row">
757
811
  <span>intent ${esc(brief.intent || plan.intent || spine?.summary?.intent || '—')}</span>
758
812
  <span>model ${esc(model.workerTier || '—')}</span>
813
+ <span>budget ${fmtNum(budgets.totalTokens || 0)}t</span>
814
+ <span>flow ${fmtNum(agentFlow.stages?.length || 0)}</span>
759
815
  <span>ctx ${fmtNum(spine?.summary?.contextEvents || artifacts.contextLog?.length || 0)}</span>
760
816
  <span>decisions ${fmtNum(spine?.summary?.decisions || artifacts.decisionLog?.length || 0)}</span>
761
817
  </div>
@@ -34,6 +34,42 @@ function memText(m) {
34
34
  return m.title || m.excerpt || m.content || m.summary || m.id || '';
35
35
  }
36
36
 
37
+ function pct(value) {
38
+ const n = Number(value);
39
+ return Number.isFinite(n) ? `${Math.round(n * 100)}%` : '—';
40
+ }
41
+
42
+ function decayProfile(m) {
43
+ const rawScore = Number(m.decay?.score ?? m.entropyScore ?? m.entropy ?? 0);
44
+ const score = Number.isFinite(rawScore) ? Math.max(0, Math.min(1, rawScore)) : 0;
45
+ const state = m.decay?.state || (score >= 0.75 ? 'retiring' : score >= 0.5 ? 'stale' : score >= 0.25 ? 'fading' : 'fresh');
46
+ return {
47
+ ...m.decay,
48
+ score,
49
+ state,
50
+ nextAction: m.decay?.nextAction || (state === 'fresh' ? 'keep' : state === 'fading' ? 'reinforce' : state === 'stale' ? 'review' : 'archive'),
51
+ };
52
+ }
53
+
54
+ function reinforcementProfile(m) {
55
+ const rawScore = Number(m.reinforcement?.score ?? m.trustScore ?? m.trust ?? 0);
56
+ const score = Number.isFinite(rawScore) ? Math.max(0, Math.min(1, rawScore)) : 0;
57
+ return { ...m.reinforcement, score };
58
+ }
59
+
60
+ function decayStroke(state) {
61
+ if (state === 'retiring') return '#ff5f57';
62
+ if (state === 'stale') return '#ffd14d';
63
+ if (state === 'fading') return '#00d4ff';
64
+ return '#00ff88';
65
+ }
66
+
67
+ function lifecycleLabel(m) {
68
+ const decay = decayProfile(m);
69
+ const reinforcement = reinforcementProfile(m);
70
+ return `${decay.state} · ${decay.nextAction} · reinforced ${pct(reinforcement.score)}`;
71
+ }
72
+
37
73
  function selectedMemories() {
38
74
  const selected = new Set(S.selectedMemoryIds || []);
39
75
  return S.memories.filter(m => selected.has(m.id));
@@ -282,7 +318,9 @@ function buildGraph() {
282
318
  S.gNodes = sortedMemories.map(m=>({
283
319
  id:m.id, label:m.title||(m.content||'').substring(0,50)||m.id,
284
320
  priority:m.priority||0.5, tier:m.tier||'semantic', nodeType:'memory',
285
- createdAt:m.createdAt||m.timestamp||Date.now(), tags:m.tags||[], data:m,
321
+ createdAt:m.createdAt||m.timestamp||Date.now(), tags:m.tags||[],
322
+ decayState:decayProfile(m).state, decayScore:decayProfile(m).score,
323
+ reinforcementScore:reinforcementProfile(m).score, data:m,
286
324
  }));
287
325
 
288
326
  const links = [];
@@ -383,11 +421,11 @@ function renderGraph() {
383
421
  const isPromoted=d=>(d.tier==='hippocampus'||d.tier==='cortex'||d.tier==='episodic'||d.tier==='semantic');
384
422
  const nodeEl=d3s.append('g').selectAll('circle').data(memNodes).enter().append('circle')
385
423
  .attr('r',d=>4+d.priority*9).attr('fill',nc).attr('fill-opacity',0.85)
386
- .attr('stroke',d=>isPromoted(d)?'#a78bfa':'#000').attr('stroke-width',d=>isPromoted(d)?1.5:1)
424
+ .attr('stroke',d=>decayStroke(d.decayState)).attr('stroke-width',d=>isPromoted(d)||d.decayState==='stale'||d.decayState==='retiring'?1.8:1)
387
425
  .style('cursor','pointer')
388
- .classed('node-pulse', d=>isPromoted(d))
426
+ .classed('node-pulse', d=>isPromoted(d) || d.reinforcementScore >= 0.7)
389
427
  .on('click',(_,d)=>_openMemDrawer(d))
390
- .on('mouseover',function(ev,d){ _memHover(d, nodeEl, fileEl, linkEl); _showTip(ev,d.label); })
428
+ .on('mouseover',function(ev,d){ _memHover(d, nodeEl, fileEl, linkEl); _showTip(ev,`${d.label} · ${lifecycleLabel(d.data||d)}`); })
391
429
  .on('mouseout', function(){ _memHoverOut(nodeEl, fileEl, linkEl); _hideTip(); });
392
430
  const drag=d3.drag()
393
431
  .on('start',(ev,d)=>{ if(!ev.active) sim.alphaTarget(0.3).restart(); d.fx=d.x; d.fy=d.y; })
@@ -469,14 +507,17 @@ export function renderMemList() {
469
507
  const tier=m.tier||'semantic';
470
508
  const pri=m.priority!=null?`p:${Math.round(m.priority*100)}%`:'';
471
509
  const state=m.state && m.state !== 'active' ? ` · ${m.state}` : '';
472
- const decay=m.entropy>0.6?'opacity:0.6':'';
510
+ const decay=decayProfile(m);
511
+ const reinforcement=reinforcementProfile(m);
512
+ const decayStyle=decay.state==='stale'||decay.state==='retiring'?'opacity:0.72':'';
513
+ const createdAt=m.createdAt||m.timestamp;
473
514
  const selected = (S.selectedMemoryIds || []).includes(m.id);
474
- return `<div class="mem-item ${selected ? 'selected' : ''}" style="${decay}" data-memid="${esc(m.id)}">
515
+ return `<div class="mem-item ${selected ? 'selected' : ''}" style="${decayStyle}" data-memid="${esc(m.id)}">
475
516
  <button class="mem-select-btn ${selected ? 'active' : ''}" data-mem-toggle="${esc(m.id)}" aria-pressed="${selected ? 'true' : 'false'}">${selected ? 'Selected' : 'Select'}</button>
476
517
  <div class="tier-dot t-${esc(tier)}"></div>
477
518
  <div style="flex:1;min-width:0">
478
519
  <div class="mem-title">${esc(memText(m).substring(0,90))}</div>
479
- <div class="mem-meta">${esc(tier)}${esc(state)}${pri?' · '+pri:''}${m.createdAt?' · '+timeAgo(m.createdAt):''}</div>
520
+ <div class="mem-meta">${esc(tier)}${esc(state)}${pri?' · '+pri:''}${createdAt?' · '+timeAgo(createdAt):''} · ${esc(decay.state)}:${esc(decay.nextAction)} · r:${esc(pct(reinforcement.score))}</div>
480
521
  </div>
481
522
  </div>`;
482
523
  }).join('');
@@ -501,25 +542,45 @@ function _browseMemories() {
501
542
  ${items.map(m => `<div class="trust-event-row" data-drawer-mem="${esc(m.id)}">
502
543
  <div class="trust-event-main">
503
544
  <span class="chip chip-muted">${esc(m.tier || 'semantic')}</span>
545
+ <span class="chip chip-muted">${esc(lifecycleLabel(m))}</span>
504
546
  <span class="trust-event-type">${esc(memText(m).slice(0, 120))}</span>
505
547
  </div>
506
- <div class="trust-event-meta">${esc(m.state || 'active')}${m.createdAt ? ' · '+esc(timeAgo(m.createdAt)) : ''}</div>
548
+ <div class="trust-event-meta">${esc(m.state || 'active')}${(m.createdAt||m.timestamp) ? ' · '+esc(timeAgo(m.createdAt||m.timestamp)) : ''}</div>
507
549
  </div>`).join('')}
508
550
  </div>` : '<div class="empty-sub">No memories available for the selected runtime.</div>',
509
551
  });
552
+ document.querySelectorAll('[data-drawer-mem]').forEach(row => {
553
+ row.addEventListener('click', () => {
554
+ const memory = S.memories.find(item => item.id === row.dataset.drawerMem);
555
+ if (memory) _openMemDrawer({ id: memory.id, label: memText(memory), data: memory });
556
+ });
557
+ });
510
558
  }
511
559
 
512
560
  function _openMemDrawer(node) {
513
561
  const m=node.data||node;
514
562
  const tags=(m.tags||[]).map(t=>`<span class="chip">${esc(t)}</span>`).join(' ');
515
563
  const drows=rows=>rows.map(([k,v])=>`<div class="drow"><span class="drow-k">${esc(k)}</span><span class="drow-v">${esc(String(v??'—'))}</span></div>`).join('');
564
+ const decay=decayProfile(m);
565
+ const reinforcement=reinforcementProfile(m);
566
+ const createdAt=m.createdAt||m.timestamp;
567
+ const decayReasons=(decay.reasons||[]).slice(0,4).map(reason=>`<span class="chip chip-muted">${esc(reason)}</span>`).join(' ');
568
+ const reinforcementReasons=(reinforcement.reasons||[]).slice(0,4).map(reason=>`<span class="chip chip-muted">${esc(reason)}</span>`).join(' ');
516
569
  openDrawer({ title: node.label||'Memory', body:
517
570
  `<div class="dsec"><div class="dsec-title">Memory</div>${drows([
518
571
  ['ID',m.id],['Tier',m.tier||'semantic'],
519
572
  ['Priority',m.priority!=null?Math.round(m.priority*100)+'%':'—'],
520
- ['Created',m.createdAt?new Date(m.createdAt).toLocaleString():'—'],
521
- ['Entropy',m.entropy!=null?m.entropy.toFixed(3):'—']
573
+ ['Created',createdAt?new Date(createdAt).toLocaleString():'—'],
574
+ ['Entropy',m.entropyScore!=null?m.entropyScore.toFixed(3):(m.entropy!=null?m.entropy.toFixed(3):'—')]
522
575
  ])}</div>
576
+ <div class="dsec"><div class="dsec-title">Lifecycle</div>${drows([
577
+ ['Decay',`${decay.state} (${pct(decay.score)})`],
578
+ ['Next action',decay.nextAction],
579
+ ['Half-life',decay.halfLifeDays!=null?`${Math.round(decay.halfLifeDays)}d`:'—'],
580
+ ['Reinforcement',pct(reinforcement.score)],
581
+ ['Feedback',reinforcement.feedbackCount ?? 0],
582
+ ['Recalls',reinforcement.accessCount ?? m.accessCount ?? 0]
583
+ ])}${decayReasons?`<div class="dtags">${decayReasons}</div>`:''}${reinforcementReasons?`<div class="dtags">${reinforcementReasons}</div>`:''}</div>
523
584
  ${tags?`<div class="dsec"><div class="dsec-title">Tags</div><div class="dtags">${tags}</div></div>`:''}
524
585
  <div class="dsec"><div class="dsec-title">Content</div><div class="dcontent">${esc(m.content||m.excerpt||m.title||'(no content)')}</div></div>
525
586
  <div class="dsec"><div class="dsec-title">Code block</div><pre class="memory-code-block"><code>${esc(memoryBlock(m))}</code></pre></div>` });
@@ -30,6 +30,10 @@ let _settledToolTimes = new Map();
30
30
  let _pulseTimer = null;
31
31
  // toast queue
32
32
  let _toastTimer = null;
33
+ let _tokenFlyoutOpen = false;
34
+ let _tokenFlyoutLoading = false;
35
+ let _tokenFlyoutError = '';
36
+ let _tokenTelemetry = null;
33
37
 
34
38
  /* ── Category metadata ──────────────────────────────────────────────────────── */
35
39
  const CATEGORY_META = {
@@ -65,6 +69,27 @@ function humanMs(ms) {
65
69
  return `${(ms / 1000).toFixed(1)}s`;
66
70
  }
67
71
 
72
+ function fmtTokens(n) {
73
+ const v = Number(n ?? 0);
74
+ if (!Number.isFinite(v) || v <= 0) return '0';
75
+ if (v >= 1_000_000) return `${(v / 1_000_000).toFixed(1)}M`;
76
+ if (v >= 10_000) return `${Math.round(v / 1000)}k`;
77
+ if (v >= 1000) return `${(v / 1000).toFixed(1)}k`;
78
+ return Math.round(v).toLocaleString();
79
+ }
80
+
81
+ function fmtPct(n) {
82
+ const v = Number(n ?? 0);
83
+ if (!Number.isFinite(v)) return '0%';
84
+ return `${Math.round(v)}%`;
85
+ }
86
+
87
+ function fmtTime(ts) {
88
+ const n = Number(ts ?? 0);
89
+ if (!Number.isFinite(n) || n <= 0) return 'recent';
90
+ return new Date(n).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
91
+ }
92
+
68
93
  function toolNameFromPayload(payload) {
69
94
  return String(payload.toolName ?? payload.tool ?? payload.name ?? '').trim();
70
95
  }
@@ -159,10 +184,10 @@ function mount() {
159
184
  <div class="rt-kpi-val" id="rt-toolcalls">0</div>
160
185
  <div class="rt-kpi-lbl">Tool Calls</div>
161
186
  </div>
162
- <div class="rt-kpi" id="rt-kpi-saved">
187
+ <button class="rt-kpi rt-kpi-action" id="rt-kpi-saved" type="button" aria-expanded="false" aria-controls="rt-token-flyout" title="Open token telemetry">
163
188
  <div class="rt-kpi-val" id="rt-tokens-saved">0</div>
164
189
  <div class="rt-kpi-lbl">Tokens Saved</div>
165
- </div>
190
+ </button>
166
191
  <div class="rt-kpi">
167
192
  <div class="rt-kpi-val" id="rt-active-count">0</div>
168
193
  <div class="rt-kpi-lbl">Active Now</div>
@@ -170,6 +195,7 @@ function mount() {
170
195
  </div>
171
196
 
172
197
  <div class="rt-mcp-strip" id="rt-mcp-strip"></div>
198
+ <div class="rt-token-flyout-slot" id="rt-token-flyout-slot"></div>
173
199
 
174
200
  <div class="rt-filter-bar">
175
201
  <button class="rt-filter-btn active" data-filter="all">All</button>
@@ -212,6 +238,14 @@ function mount() {
212
238
  renderAll();
213
239
  });
214
240
 
241
+ $('rt-kpi-saved')?.addEventListener('click', () => {
242
+ _tokenFlyoutOpen = !_tokenFlyoutOpen;
243
+ renderTokenFlyout();
244
+ if (_tokenFlyoutOpen && !_tokenTelemetry && !_tokenFlyoutLoading) {
245
+ void loadTokenTelemetry();
246
+ }
247
+ });
248
+
215
249
  _mounted = true;
216
250
  renderAll();
217
251
  }
@@ -220,6 +254,7 @@ function mount() {
220
254
  function renderAll() {
221
255
  expireStaleActiveTools();
222
256
  renderKPIs();
257
+ renderTokenFlyout();
223
258
  renderMcpStrip();
224
259
  renderFeed();
225
260
  }
@@ -233,6 +268,96 @@ function renderKPIs() {
233
268
  ? (_totalTokensSaved >= 1000 ? `${(_totalTokensSaved / 1000).toFixed(1)}k` : String(_totalTokensSaved))
234
269
  : '0';
235
270
  if (activeEl) activeEl.textContent = String(_activeTools.size);
271
+ const tokenBtn = $('rt-kpi-saved');
272
+ if (tokenBtn) tokenBtn.setAttribute('aria-expanded', _tokenFlyoutOpen ? 'true' : 'false');
273
+ }
274
+
275
+ async function loadTokenTelemetry() {
276
+ _tokenFlyoutLoading = true;
277
+ _tokenFlyoutError = '';
278
+ renderTokenFlyout();
279
+ try {
280
+ const [summary, lifetimeRaw, bySource, timeline] = await Promise.all([
281
+ api('/api/tokens/summary', 0),
282
+ api('/api/tokens/lifetime', 0),
283
+ api('/api/tokens/by-source', 0),
284
+ api('/api/tokens/timeline?limit=8', 0),
285
+ ]);
286
+ _tokenTelemetry = {
287
+ summary: summary ?? {},
288
+ lifetime: lifetimeRaw?.data ?? lifetimeRaw ?? {},
289
+ bySource: bySource ?? {},
290
+ timeline: Array.isArray(timeline) ? timeline : [],
291
+ };
292
+ } catch (err) {
293
+ _tokenFlyoutError = err?.message || String(err);
294
+ } finally {
295
+ _tokenFlyoutLoading = false;
296
+ renderTokenFlyout();
297
+ }
298
+ }
299
+
300
+ function renderTokenFlyout() {
301
+ const slot = $('rt-token-flyout-slot');
302
+ if (!slot) return;
303
+ const btn = $('rt-kpi-saved');
304
+ if (btn) btn.setAttribute('aria-expanded', _tokenFlyoutOpen ? 'true' : 'false');
305
+ if (!_tokenFlyoutOpen) {
306
+ slot.innerHTML = '';
307
+ return;
308
+ }
309
+
310
+ if (_tokenFlyoutLoading && !_tokenTelemetry) {
311
+ slot.innerHTML = `<div class="rt-token-flyout" id="rt-token-flyout">
312
+ <div class="rt-token-head"><div><div class="rt-token-title">Token telemetry</div><div class="rt-token-sub">Loading current runtime ledger...</div></div><button class="rt-token-close" id="rt-token-close" type="button">Close</button></div>
313
+ </div>`;
314
+ } else if (_tokenFlyoutError) {
315
+ slot.innerHTML = `<div class="rt-token-flyout" id="rt-token-flyout">
316
+ <div class="rt-token-head"><div><div class="rt-token-title">Token telemetry</div><div class="rt-token-sub rt-token-error">${esc(_tokenFlyoutError)}</div></div><button class="rt-token-close" id="rt-token-close" type="button">Close</button></div>
317
+ </div>`;
318
+ } else {
319
+ const data = _tokenTelemetry ?? {};
320
+ const summary = data.summary ?? {};
321
+ const lifetime = data.lifetime ?? {};
322
+ const bySource = data.bySource ?? {};
323
+ const timeline = Array.isArray(data.timeline) ? data.timeline : [];
324
+ const sourceRows = Object.entries(bySource).slice(0, 5).map(([source, value]) => {
325
+ const item = typeof value === 'object' && value ? value : { savedTokens: Number(value ?? 0) };
326
+ const saved = item.savedTokens ?? item.tokensSaved ?? item.saved ?? 0;
327
+ const gross = item.grossInputTokens ?? item.tokensOptimized ?? item.gross ?? 0;
328
+ return `<div class="rt-token-row"><span>${esc(source)}</span><strong>${fmtTokens(saved)}</strong><span>${fmtTokens(gross)} gross</span></div>`;
329
+ }).join('') || '<div class="rt-token-muted">No source ledger yet.</div>';
330
+ const timelineRows = timeline.slice(0, 6).map((item) => {
331
+ const saved = item.savedTokens ?? item.tokensSaved ?? item.saved ?? 0;
332
+ const run = item.runId ?? item.sessionId ?? item.source ?? 'runtime';
333
+ return `<div class="rt-token-row"><span>${esc(String(run).slice(0, 18))}</span><strong>${fmtTokens(saved)}</strong><span>${fmtTime(item.timestamp ?? item.ts ?? item.time)}</span></div>`;
334
+ }).join('') || '<div class="rt-token-muted">No recent token events yet.</div>';
335
+ slot.innerHTML = `<div class="rt-token-flyout" id="rt-token-flyout">
336
+ <div class="rt-token-head">
337
+ <div>
338
+ <div class="rt-token-title">Token telemetry</div>
339
+ <div class="rt-token-sub">Runtime ledger, lifetime savings, and live SSE savings for this tab.</div>
340
+ </div>
341
+ <button class="rt-token-close" id="rt-token-close" type="button">Close</button>
342
+ </div>
343
+ <div class="rt-token-grid">
344
+ <div class="rt-token-stat"><span>Recent saved</span><strong>${fmtTokens(summary.savedTokens ?? summary.saved)}</strong></div>
345
+ <div class="rt-token-stat"><span>Recent compression</span><strong>${fmtPct(summary.compressionPct)}</strong></div>
346
+ <div class="rt-token-stat"><span>Lifetime saved</span><strong>${fmtTokens(lifetime.savedTokens ?? lifetime.totalSaved)}</strong></div>
347
+ <div class="rt-token-stat"><span>Live tab saved</span><strong>${fmtTokens(_totalTokensSaved)}</strong></div>
348
+ </div>
349
+ <div class="rt-token-columns">
350
+ <div><div class="rt-token-section-title">By source</div>${sourceRows}</div>
351
+ <div><div class="rt-token-section-title">Recent runs</div>${timelineRows}</div>
352
+ </div>
353
+ </div>`;
354
+ }
355
+
356
+ $('rt-token-close')?.addEventListener('click', () => {
357
+ _tokenFlyoutOpen = false;
358
+ renderTokenFlyout();
359
+ renderKPIs();
360
+ });
236
361
  }
237
362
 
238
363
  function renderMcpStrip() {
@@ -421,11 +546,20 @@ export function ingestEvent(evt) {
421
546
  break;
422
547
  case 'memory.store':
423
548
  label = 'memory · store';
424
- detail = String(payload.key ?? payload.content ?? '').slice(0, 80);
549
+ detail = [
550
+ payload.id ? `id ${String(payload.id).slice(0, 12)}` : null,
551
+ payload.tier ? `tier ${payload.tier}` : null,
552
+ payload.priority != null ? `priority ${Number(payload.priority).toFixed(2)}` : null,
553
+ Array.isArray(payload.tags) && payload.tags.length ? `tags ${payload.tags.slice(0, 3).join(', ')}` : null,
554
+ ].filter(Boolean).join(' · ') || String(payload.key ?? payload.content ?? '').slice(0, 80);
425
555
  break;
426
556
  case 'memory.recall':
427
557
  label = 'memory · recall';
428
- detail = String(payload.query ?? '').slice(0, 80);
558
+ detail = [
559
+ `${Number(payload.count ?? 0)} recalled`,
560
+ payload.crossClient ? 'cross-client' : null,
561
+ String(payload.query ?? '').slice(0, 60),
562
+ ].filter(Boolean).join(' · ');
429
563
  break;
430
564
  case 'tokens.optimized': {
431
565
  const src = payload.source ? ` · ${payload.source}` : '';
@@ -45,6 +45,7 @@ export function handleDispatchEvent(evt) {
45
45
  const type = evt.type ?? '';
46
46
 
47
47
  if (type === 'dispatch.started') {
48
+ if (oid) _dispatches.delete(`__warmup__:${oid}`);
48
49
  run.status = 'spawning';
49
50
  run.invoker = p.invokerId ?? '';
50
51
  } else if (type === 'dispatch.event') {
@@ -117,18 +118,20 @@ function _buildDispatchStrip(run) {
117
118
  }).join('<span class="ds-sep">→</span>');
118
119
 
119
120
  const isFinal = ['complete','failed','cancelled'].includes(run.status);
121
+ const isPendingAdapter = run.pendingAdapter === true;
120
122
 
121
123
  return `<div class="dispatch-strip" data-run-id="${esc(run.runId)}">
122
124
  <div class="ds-stages">${stageHtml}</div>
123
125
  ${run.messages.length ? `<div class="ds-stdout">${esc(run.messages[run.messages.length - 1])}</div>` : ''}
124
126
  <div class="ds-meta">
127
+ ${run.invoker ? `<span>${esc(run.invoker)}</span>` : ''}
125
128
  ${run.tokens ? `<span>${run.tokens.toLocaleString()} tokens</span>` : ''}
126
129
  ${run.costUsd ? `<span>$${run.costUsd.toFixed(4)}</span>` : ''}
127
130
  ${run.filesChanged.length ? `<span>${run.filesChanged.length} file(s)</span>` : ''}
128
131
  </div>
129
132
  ${run.summary ? `<div class="ds-summary">${esc(String(run.summary).slice(0, 160))}</div>` : ''}
130
133
  ${run.error ? `<div class="ds-error">${esc(run.error)}</div>` : ''}
131
- ${!isFinal ? `<button class="btn btn-sm ds-stop-btn" data-stop-run="${esc(run.runId)}">Stop</button>` : ''}
134
+ ${!isFinal && !isPendingAdapter ? `<button class="btn btn-sm ds-stop-btn" data-stop-run="${esc(run.runId)}">Stop</button>` : ''}
132
135
  </div>`;
133
136
  }
134
137
 
@@ -560,14 +563,18 @@ function _showHireSheet(specialistId, name) {
560
563
  stripDiv.setAttribute('data-dispatch-strip', '');
561
564
  drawerBody.appendChild(stripDiv);
562
565
  }
563
- const warmupRun = { runId: '__warmup__', operativeId, status: 'queued', tokens: 0, costUsd: 0, messages: ['Warm-up dispatching…'], filesChanged: [] };
564
- _dispatches.set('__warmup__', warmupRun);
566
+ const warmupKey = `__warmup__:${operativeId}`;
567
+ const warmupRun = { runId: warmupKey, operativeId, status: 'queued', tokens: 0, costUsd: 0, messages: ['Adapter pending; waiting for dispatch.started…'], filesChanged: [], pendingAdapter: true };
568
+ _dispatches.set(warmupKey, warmupRun);
565
569
  stripDiv.innerHTML = _buildDispatchStrip(warmupRun);
566
570
  }
567
571
  const fd = real.data?.firstDispatch;
568
- _dispatches.delete('__warmup__');
569
572
  if (fd?.runId) {
573
+ _dispatches.delete(`__warmup__:${operativeId}`);
570
574
  _dispatches.set(fd.runId, { runId: fd.runId, operativeId, status: 'queued', tokens: 0, costUsd: 0, messages: [], filesChanged: [] });
575
+ } else if (fd?.queued) {
576
+ const pending = _dispatches.get(`__warmup__:${operativeId}`);
577
+ if (pending) pending.messages = ['First sortie queued; adapter start will stream here.'];
571
578
  }
572
579
  _refreshDrawerForOp(operativeId);
573
580
  }
@@ -174,6 +174,9 @@ export const handleEventRoutes = async (ctx, req, res, url) => {
174
174
  intent: requestBrief?.intent ?? selectionPlan?.intent ?? run.intent ?? null,
175
175
  risk: requestBrief?.risk ?? null,
176
176
  modelRoute: selectionPlan?.modelRoute ?? requestBrief?.modelPolicy ?? null,
177
+ budgets: selectionPlan?.budgets ?? null,
178
+ executionPolicy: selectionPlan?.executionPolicy ?? null,
179
+ agentFlow: selectionPlan?.executionPolicy?.agentFlow ?? null,
177
180
  selected: selectionPlan?.selected ?? null,
178
181
  contextEvents: Array.isArray(contextLog) ? contextLog.length : 0,
179
182
  decisions: Array.isArray(decisionLog) ? decisionLog.length : 0,
@@ -8,6 +8,11 @@ export async function buildOperateSurface(ctx, url) {
8
8
  runs: serializeRuns(state.runtime?.listRuns?.(12) ?? []),
9
9
  usage: state.usage,
10
10
  tokenOptimization: state.tokenOptimization,
11
+ sourceAwareTokenBudget: state.snapshot?.sourceAwareTokenBudget ?? state.runtime?.getUsageSnapshot?.()?.sourceAwareTokenBudget ?? {},
12
+ agentFlow: state.snapshot?.instructionPacket?.selectionPlan?.executionPolicy?.agentFlow
13
+ ?? state.snapshot?.executionLedger?.agentFlow
14
+ ?? state.runtime?.getUsageSnapshot?.()?.selectionPlan?.executionPolicy?.agentFlow
15
+ ?? {},
11
16
  gateSummary: state.gateSummary,
12
17
  memoryContainers: state.memoryContainers,
13
18
  interpretationIssues: state.interpretationIssues,
@@ -11,6 +11,11 @@ export async function buildRunsSurface(ctx, url) {
11
11
  gateTimeline: serializeGateTimeline(state.latestRun),
12
12
  parallelism: serializeParallelism(state.snapshot, state.runtime),
13
13
  tokenOptimization: state.tokenOptimization,
14
+ sourceAwareTokenBudget: state.snapshot?.sourceAwareTokenBudget ?? state.runtime?.getUsageSnapshot?.()?.sourceAwareTokenBudget ?? {},
15
+ agentFlow: state.snapshot?.instructionPacket?.selectionPlan?.executionPolicy?.agentFlow
16
+ ?? state.snapshot?.executionLedger?.agentFlow
17
+ ?? state.runtime?.getUsageSnapshot?.()?.selectionPlan?.executionPolicy?.agentFlow
18
+ ?? {},
14
19
  orchestrationSession: state.snapshot?.orchestration ?? state.orchestrator?.getSessionState?.() ?? {},
15
20
  orchestrationLedger: state.snapshot?.executionLedger ?? state.runtime?.getExecutionLedger?.() ?? {},
16
21
  workerPlan: state.snapshot?.workerPlan ?? state.runtime?.getUsageSnapshot?.()?.workerPlan ?? {},
@@ -992,6 +992,12 @@ export class DashboardServer {
992
992
  selectedFiles: Array.isArray(budget?.selectedFiles) ? budget.selectedFiles : [],
993
993
  reason: budget?.reason || 'Token optimization has not reported a source-aware budget yet.',
994
994
  dominantSource: budget?.dominantSource || null,
995
+ bySource: budget?.bySource ?? {},
996
+ byStage: budget?.byStage ?? {},
997
+ totalBudget: Number(budget?.totalBudget ?? 0),
998
+ codeBlocks: budget?.codeBlocks ?? null,
999
+ qualityGates: Array.isArray(budget?.qualityGates) ? budget.qualityGates : [],
1000
+ recommendations: Array.isArray(budget?.recommendations) ? budget.recommendations : [],
995
1001
  dropped: Array.isArray(budget?.dropped) ? budget.dropped : [],
996
1002
  };
997
1003
  }
@@ -175,6 +175,8 @@ export interface OperateSurfaceDTO {
175
175
  runs: unknown[];
176
176
  usage: unknown;
177
177
  tokenOptimization: unknown;
178
+ sourceAwareTokenBudget: unknown;
179
+ agentFlow: unknown;
178
180
  gateSummary: unknown;
179
181
  memoryContainers: unknown;
180
182
  interpretationIssues: unknown[];
@@ -220,6 +222,8 @@ export interface RunsSurfaceDTO {
220
222
  gateTimeline: unknown[];
221
223
  parallelism: unknown;
222
224
  tokenOptimization: unknown;
225
+ sourceAwareTokenBudget: unknown;
226
+ agentFlow: unknown;
223
227
  orchestrationSession: unknown;
224
228
  orchestrationLedger: unknown;
225
229
  workerPlan: unknown;
@@ -1,4 +1,4 @@
1
- export type SetupClientId = 'cursor' | 'claude' | 'claude-code' | 'claude-desktop' | 'opencode' | 'windsurf' | 'antigravity' | 'openclaw' | 'codex' | 'aider' | 'continue' | 'cline';
1
+ export type SetupClientId = 'cursor' | 'claude' | 'claude-code' | 'claude-desktop' | 'opencode' | 'windsurf' | 'antigravity' | 'openclaw' | 'hermes' | 'nanoclaw' | 'picoclaw' | 'codex' | 'aider' | 'continue' | 'cline';
2
2
  export type SetupInstructionMode = 'replace' | 'codex-managed-agents';
3
3
  export type SetupInstructionScope = 'home' | 'workspace';
4
4
  export type SetupState = 'missing' | 'drifted' | 'installed';
@@ -23,6 +23,8 @@ export interface BootstrapManifestClientStatus {
23
23
  instructionFiles: string[];
24
24
  homeReady: boolean;
25
25
  workspaceReady: boolean;
26
+ memoryHookReady: boolean;
27
+ memoryHookSummary: string;
26
28
  summary: string;
27
29
  updatedAt: number;
28
30
  }
@@ -62,6 +64,8 @@ export declare function statusForDefinition(definition: SetupDefinition): {
62
64
  summary: string;
63
65
  homeReady: boolean;
64
66
  workspaceReady: boolean;
67
+ memoryHookReady: boolean;
68
+ memoryHookSummary: string;
65
69
  };
66
70
  export declare function supportedSetupClients(): SetupClientId[];
67
71
  export declare function readBootstrapManifest(stateRoot?: string, workspaceRoot?: string): BootstrapManifestStatus | undefined;