nexus-prime 7.9.29 → 7.9.31

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.
@@ -132,7 +132,21 @@
132
132
  .kcard.dragging { opacity: 0.35; transform: scale(0.96); cursor: grabbing; pointer-events: none; }
133
133
 
134
134
  /* ── Agents Live Strip ── */
135
- #agents-live-strip { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; }
135
+ #agents-live-strip { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 10px; }
136
+ .agent-live-summary {
137
+ display: inline-flex;
138
+ align-items: center;
139
+ gap: 5px;
140
+ padding: 3px 8px;
141
+ border: 1px solid rgba(0,255,136,0.18);
142
+ border-radius: 20px;
143
+ background: rgba(0,255,136,0.05);
144
+ color: var(--text-dim);
145
+ font-family: var(--font-mono);
146
+ font-size: 0.7rem;
147
+ }
148
+ .agent-live-summary strong { color: var(--accent); }
149
+ .agent-live-summary .agent-live-warn { color: var(--warning); }
136
150
  .agent-live-pill {
137
151
  display: inline-flex; align-items: center; gap: 5px;
138
152
  font-family: var(--font-mono); font-size: 0.78rem;
@@ -143,6 +157,8 @@
143
157
  .agent-live-pill .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--text-dim); flex-shrink: 0; }
144
158
  .agent-live-pill.active .dot { background: var(--accent); box-shadow: 0 0 4px rgba(0,255,136,0.6); }
145
159
  .agent-live-pill.active { border-color: rgba(0,255,136,0.25); }
160
+ .agent-live-pill.idle .dot { background: var(--text-dim); opacity: 0.8; }
161
+ .agent-live-pill.idle { border-color: rgba(255,255,255,0.08); opacity: 0.78; }
146
162
  .agent-live-pill.blocked .dot { background: var(--warning); }
147
163
  .agent-live-pill.blocked { border-color: rgba(255,95,87,0.25); }
148
164
  .agent-inline-badge {
@@ -2,6 +2,74 @@
2
2
 
3
3
  .governance-container { padding: 4px 0; }
4
4
 
5
+ .governance-status-grid {
6
+ display: grid;
7
+ grid-template-columns: repeat(2, minmax(0, 1fr));
8
+ gap: 12px;
9
+ max-width: 1120px;
10
+ }
11
+
12
+ .governance-status-card {
13
+ padding: 18px 20px;
14
+ min-height: 150px;
15
+ }
16
+
17
+ .governance-card-head {
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: space-between;
21
+ gap: 10px;
22
+ margin-bottom: 16px;
23
+ }
24
+
25
+ .governance-card-title {
26
+ color: var(--text);
27
+ font-weight: var(--weight-semibold);
28
+ }
29
+
30
+ .governance-status-body {
31
+ display: grid;
32
+ gap: 9px;
33
+ }
34
+
35
+ .governance-status-body.compact {
36
+ margin-top: 14px;
37
+ }
38
+
39
+ .governance-status-row {
40
+ display: grid;
41
+ grid-template-columns: minmax(120px, 0.42fr) minmax(0, 1fr);
42
+ gap: 12px;
43
+ align-items: baseline;
44
+ padding-bottom: 8px;
45
+ border-bottom: 1px solid var(--border);
46
+ }
47
+
48
+ .governance-status-row:last-child {
49
+ border-bottom: none;
50
+ }
51
+
52
+ .governance-status-row span {
53
+ color: var(--text-muted);
54
+ font-size: var(--text-sm);
55
+ }
56
+
57
+ .governance-status-row strong {
58
+ min-width: 0;
59
+ color: var(--text);
60
+ font-family: var(--font-mono);
61
+ font-size: var(--text-sm);
62
+ overflow-wrap: anywhere;
63
+ text-align: right;
64
+ }
65
+
66
+ .governance-status-copy {
67
+ color: var(--text-muted);
68
+ font-size: var(--text-sm);
69
+ line-height: 1.5;
70
+ max-width: 420px;
71
+ }
72
+
5
73
  /* Darwin proposal cards */
6
74
  .darwin-card { margin-bottom: 14px; padding: 16px 18px; }
7
75
 
@@ -37,12 +105,21 @@
37
105
  }
38
106
 
39
107
  .darwin-metric {
108
+ display: inline-grid;
109
+ grid-template-columns: auto auto auto;
110
+ align-items: center;
111
+ gap: 6px;
40
112
  font-family: var(--font-mono);
41
113
  font-size: var(--caption);
42
114
  padding: 2px 8px;
43
115
  border-radius: var(--radius-sm);
44
116
  background: var(--surface-2);
45
117
  }
118
+ .darwin-metric strong,
119
+ .darwin-metric em {
120
+ font-style: normal;
121
+ font-weight: 600;
122
+ }
46
123
  .darwin-metric.metric-up { color: var(--ok); }
47
124
  .darwin-metric.metric-down { color: var(--bad); }
48
125
  .darwin-metric.metric-flat { color: var(--text-muted); }
@@ -119,3 +196,19 @@
119
196
 
120
197
  .btn-ok { color: var(--ok); border-color: var(--ok); }
121
198
  .btn-bad { color: var(--bad); border-color: var(--bad); }
199
+
200
+ @media (max-width: 900px) {
201
+ .governance-status-grid {
202
+ grid-template-columns: 1fr;
203
+ }
204
+ .governance-status-row {
205
+ grid-template-columns: 1fr;
206
+ gap: 3px;
207
+ }
208
+ .governance-status-row strong {
209
+ text-align: left;
210
+ }
211
+ .darwin-metric {
212
+ grid-template-columns: 1fr;
213
+ }
214
+ }
@@ -49,7 +49,7 @@
49
49
  .memory-graph-hud {
50
50
  position: absolute; top: 10px; left: 10px;
51
51
  display: flex; flex-wrap: wrap; gap: 6px;
52
- max-width: min(560px, calc(100% - 170px));
52
+ max-width: min(560px, calc(100% - 230px));
53
53
  pointer-events: none;
54
54
  z-index: 9;
55
55
  }
@@ -63,6 +63,84 @@
63
63
  font-size: 0.68rem;
64
64
  white-space: nowrap;
65
65
  }
66
+ .memory-graph-controls {
67
+ position: absolute;
68
+ right: 12px;
69
+ bottom: 12px;
70
+ z-index: 12;
71
+ display: flex;
72
+ flex-wrap: wrap;
73
+ justify-content: flex-end;
74
+ gap: 6px;
75
+ max-width: min(360px, calc(100% - 24px));
76
+ padding: 6px;
77
+ border: 1px solid rgba(255,255,255,0.08);
78
+ border-radius: var(--radius);
79
+ background: rgba(0,0,0,0.58);
80
+ backdrop-filter: blur(10px);
81
+ }
82
+ .memory-graph-btn {
83
+ min-width: 32px;
84
+ height: 28px;
85
+ padding: 0 9px;
86
+ border: 1px solid var(--border);
87
+ border-radius: var(--radius);
88
+ background: rgba(12,12,14,0.86);
89
+ color: var(--text-muted);
90
+ font-family: var(--font-mono);
91
+ font-size: 0.7rem;
92
+ cursor: pointer;
93
+ }
94
+ .memory-graph-btn:hover,
95
+ .memory-graph-btn:focus-visible {
96
+ border-color: rgba(0,255,136,0.35);
97
+ color: var(--accent);
98
+ outline: none;
99
+ }
100
+ .memory-graph-node-browser {
101
+ display: flex;
102
+ flex-direction: column;
103
+ gap: 6px;
104
+ }
105
+ .memory-graph-node-summary {
106
+ display: flex;
107
+ gap: 6px;
108
+ flex-wrap: wrap;
109
+ margin-bottom: 6px;
110
+ }
111
+ .memory-graph-node-row {
112
+ width: 100%;
113
+ display: grid;
114
+ grid-template-columns: 82px minmax(0, 1fr) auto;
115
+ gap: 8px;
116
+ align-items: center;
117
+ padding: 8px 10px;
118
+ border: 1px solid var(--border);
119
+ border-radius: var(--radius);
120
+ background: var(--bg-panel);
121
+ color: var(--text-main);
122
+ text-align: left;
123
+ cursor: pointer;
124
+ }
125
+ .memory-graph-node-row:hover,
126
+ .memory-graph-node-row:focus-visible {
127
+ border-color: rgba(0,255,136,0.28);
128
+ outline: none;
129
+ }
130
+ .memory-graph-node-type,
131
+ .memory-graph-node-links {
132
+ color: var(--text-dim);
133
+ font-family: var(--font-mono);
134
+ font-size: 0.66rem;
135
+ white-space: nowrap;
136
+ }
137
+ .memory-graph-node-title {
138
+ min-width: 0;
139
+ overflow: hidden;
140
+ text-overflow: ellipsis;
141
+ white-space: nowrap;
142
+ font-size: 0.76rem;
143
+ }
66
144
  .leg-item {
67
145
  display: flex; align-items: center; gap: 6px;
68
146
  font-family: var(--font-mono); font-size: 0.78rem; color: var(--text-dim);
@@ -183,6 +261,8 @@
183
261
  #repo-graph-container.graph-expanded { left: 16px; right: 16px; top: 56px; bottom: 16px; }
184
262
  .repo-graph-actions { justify-content: flex-start; margin-left: 0; width: 100%; }
185
263
  .repo-graph-controls { grid-template-columns: repeat(5, auto); }
264
+ .memory-graph-hud { max-width: calc(100% - 22px); top: 52px; }
265
+ .memory-graph-controls { left: 10px; right: 10px; justify-content: flex-start; }
186
266
  .memory-bottom { grid-template-columns: 1fr; }
187
267
  }
188
268
 
@@ -9,12 +9,12 @@
9
9
 
10
10
  /* KPI row */
11
11
  .runtime-kpis {
12
- display: flex;
12
+ display: grid;
13
+ grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
13
14
  gap: 12px;
14
15
  }
15
16
 
16
17
  .rt-kpi {
17
- flex: 1;
18
18
  background: var(--surface);
19
19
  border: 1px solid var(--border);
20
20
  border-radius: 8px;
@@ -10,7 +10,10 @@
10
10
  display: flex; align-items: center; justify-content: center;
11
11
  }
12
12
  .org-box {
13
- padding: 7px 13px; background: var(--bg-panel); border: 1px solid var(--border);
13
+ display: inline-flex;
14
+ align-items: center;
15
+ gap: 7px;
16
+ padding: 8px 12px; background: var(--bg-panel); border: 1px solid var(--border);
14
17
  border-radius: var(--radius); font-family: var(--font-mono); font-size: 0.78rem;
15
18
  color: var(--text-main); white-space: nowrap; cursor: pointer; transition: border-color 0.15s;
16
19
  }
@@ -24,10 +27,32 @@
24
27
  .org-branch[data-team] > div > .org-box { border-color: rgba(0,212,255,0.25); }
25
28
  .op-status-dot {
26
29
  display: inline-block; width: 5px; height: 5px; border-radius: 50%;
27
- margin-right: 5px; background: var(--text-dim); vertical-align: middle;
30
+ background: var(--text-dim); vertical-align: middle; flex-shrink: 0;
28
31
  }
29
32
  .op-status-dot.active { background: var(--accent); }
30
33
  .op-status-dot.dead { background: var(--warning); }
34
+ .org-op-text {
35
+ display: flex;
36
+ flex-direction: column;
37
+ align-items: flex-start;
38
+ gap: 1px;
39
+ min-width: 0;
40
+ }
41
+ .org-op-name {
42
+ max-width: 180px;
43
+ overflow: hidden;
44
+ text-overflow: ellipsis;
45
+ white-space: nowrap;
46
+ }
47
+ .org-op-meta {
48
+ max-width: 180px;
49
+ overflow: hidden;
50
+ text-overflow: ellipsis;
51
+ white-space: nowrap;
52
+ color: var(--text-dim);
53
+ font-size: 0.62rem;
54
+ text-transform: uppercase;
55
+ }
31
56
 
32
57
  /* ── Missions / Dispatch ── */
33
58
  .workforce-bottom { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@@ -49,6 +74,12 @@
49
74
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
50
75
  gap: 10px; margin-bottom: 14px;
51
76
  }
77
+ .spec-cost {
78
+ margin-top: 5px;
79
+ color: var(--text-dim);
80
+ font-family: var(--font-mono);
81
+ font-size: 0.68rem;
82
+ }
52
83
 
53
84
  /* ── Trust / Diff Viewer ── */
54
85
  .trust-layout { display: grid; grid-template-columns: 1fr 300px; gap: 12px; }
@@ -131,10 +162,33 @@
131
162
 
132
163
  /* ── Unified workforce kanban ── */
133
164
  .workforce-kanban { padding: 0; background: transparent; border: none !important; }
134
- .kanban-meta { font-size: 0.75rem; color: var(--text-dim); margin-bottom: 8px; padding: 0 4px; }
165
+ .kanban-meta {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 8px;
169
+ flex-wrap: wrap;
170
+ font-size: 0.75rem;
171
+ color: var(--text-dim);
172
+ margin-bottom: 8px;
173
+ padding: 0 4px;
174
+ }
175
+ .kanban-summary {
176
+ display: flex;
177
+ gap: 5px;
178
+ flex-wrap: wrap;
179
+ }
180
+ .kanban-summary-chip {
181
+ border: 1px solid var(--border);
182
+ border-radius: 999px;
183
+ padding: 1px 7px;
184
+ background: var(--bg-panel);
185
+ color: var(--text-muted);
186
+ font-family: var(--font-mono);
187
+ font-size: 0.66rem;
188
+ }
135
189
  .kanban-scroll { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 8px; }
136
190
  .kanban-lane {
137
- min-width: 160px; max-width: 200px; flex-shrink: 0;
191
+ min-width: 220px; max-width: 280px; flex-shrink: 0;
138
192
  background: var(--bg-panel); border: 1px solid var(--border);
139
193
  border-radius: var(--radius); display: flex; flex-direction: column;
140
194
  }
@@ -170,6 +224,56 @@
170
224
  }
171
225
  .kc-id { font-size: 0.65rem; color: var(--text-dim); font-family: var(--font-mono); }
172
226
  .kc-title { font-size: 0.74rem; color: var(--text-main); line-height: 1.35; word-break: break-word; }
227
+ .kc-goal {
228
+ margin-top: 5px;
229
+ color: var(--text-main);
230
+ font-size: 0.72rem;
231
+ line-height: 1.35;
232
+ word-break: break-word;
233
+ }
234
+ .kc-flow {
235
+ margin-top: 7px;
236
+ display: grid;
237
+ gap: 5px;
238
+ }
239
+ .kc-flow div {
240
+ border-left: 2px solid rgba(255,255,255,0.08);
241
+ padding-left: 7px;
242
+ }
243
+ .kc-flow span {
244
+ display: block;
245
+ margin-bottom: 1px;
246
+ color: var(--text-dim);
247
+ font-size: 0.58rem;
248
+ font-family: var(--font-mono);
249
+ text-transform: uppercase;
250
+ }
251
+ .kc-flow p {
252
+ margin: 0;
253
+ color: var(--text-muted);
254
+ font-size: 0.66rem;
255
+ line-height: 1.3;
256
+ word-break: break-word;
257
+ }
258
+ .kc-meta {
259
+ display: flex;
260
+ flex-wrap: wrap;
261
+ gap: 4px;
262
+ margin-top: 7px;
263
+ }
264
+ .kc-meta span {
265
+ max-width: 100%;
266
+ overflow: hidden;
267
+ text-overflow: ellipsis;
268
+ white-space: nowrap;
269
+ color: var(--text-dim);
270
+ background: var(--bg-panel);
271
+ border: 1px solid rgba(255,255,255,0.06);
272
+ border-radius: 3px;
273
+ padding: 1px 5px;
274
+ font-size: 0.6rem;
275
+ font-family: var(--font-mono);
276
+ }
173
277
  .kc-worker { font-size: 0.65rem; color: var(--text-dim); font-family: var(--font-mono); margin-top: 3px; display: block; }
174
278
  .kanban-overflow { font-size: 0.7rem; color: var(--text-dim); text-align: center; padding: 4px; }
175
279
  .kanban-empty { color: var(--text-dim); font-size: 0.8rem; padding: 20px; text-align: center; }
@@ -67,13 +67,21 @@ function catColor(cat) {
67
67
  }
68
68
  function normalizeOperative(op) {
69
69
  if (!op || typeof op !== 'object') return op;
70
- return { ...op, role: op.roleTitle||op.role||op.name||null,
71
- status: op.state||op.healthState||op.status||'IDLE',
70
+ const displayName = op.name || op.displayName || op.roleTitle || op.role || op.specialistId || op.id || op.operativeId;
71
+ const rawStatus = op.state || op.healthState || op.status || 'IDLE';
72
+ return { ...op,
73
+ displayName,
74
+ role: op.role || op.roleTitle || op.specialistId || displayName || null,
75
+ status: String(rawStatus).toLowerCase(),
72
76
  budget: op.budgetCapUsd??op.budget??null,
73
77
  budgetUsed: op.spentUsd??op.budgetUsed??0,
74
78
  team: op.strikeTeamId||op.team||op.strikeName||null };
75
79
  }
76
80
 
81
+ function isOpActive(op) {
82
+ return ['active','running','wip','claimed'].includes(String(op?.status || '').toLowerCase());
83
+ }
84
+
77
85
  /* ── Data loader ── */
78
86
  export async function load() {
79
87
  const settle = async (jobs) => (await Promise.allSettled(jobs)).map(r => r.status === 'fulfilled' ? r.value : null);
@@ -92,12 +100,14 @@ export async function load() {
92
100
  api('/api/health', 15_000, { timeoutMs: 3_500 }),
93
101
  api('/api/memory/health', 15_000, { timeoutMs: 4_000 }),
94
102
  api('/api/runs?limit=12', 5_000, { timeoutMs: 3_500 }),
95
- ]).then(([opR, shR, healthR, memR, runsR]) => {
103
+ api('/api/workforce/kanban', 5_000, { timeoutMs: 3_500 }),
104
+ ]).then(([opR, shR, healthR, memR, runsR, kanbanR]) => {
96
105
  const op = opR.status === 'fulfilled' ? opR.value : null;
97
106
  const sh = shR.status === 'fulfilled' ? shR.value : null;
98
107
  const health = healthR.status === 'fulfilled' ? healthR.value : null;
99
108
  const memHealth = memR.status === 'fulfilled' ? memR.value : null;
100
109
  const runs = runsR.status === 'fulfilled' ? runsR.value : null;
110
+ const kanban = kanbanR.status === 'fulfilled' ? kanbanR.value : null;
101
111
  if (op) S.operateSurface = op;
102
112
  if (sh) {
103
113
  S.synapseHealthRaw = sh;
@@ -107,6 +117,7 @@ export async function load() {
107
117
  if (health) S.healthData = health;
108
118
  if (memHealth) S.memHealth = memHealth;
109
119
  if (Array.isArray(runs)) S.runs = runs;
120
+ if (kanban) S.wfKanban = kanban;
110
121
  render();
111
122
  });
112
123
  // Prefetch curated specialists for first-run hero (non-blocking)
@@ -376,7 +387,7 @@ function renderHero() {
376
387
  const pEl = $('m-pct');
377
388
  if (pEl) { pEl.innerHTML=`${pct}<sup>%</sup>`; pEl.dataset.raw=pct; }
378
389
  const memCount = op?.memorySummary?.total ?? op?.memory?.total ?? S.memHealth?.total ?? S.memories.length;
379
- const opsCount = S.synapseHealth.filter(o=>o.status==='ACTIVE'||o.status==='active').length;
390
+ const opsCount = S.synapseHealth.length;
380
391
  animCounter('m-memories', memCount);
381
392
  animCounter('m-ops', opsCount);
382
393
  if (!S.spark) S.spark = { tokens:[], pct:[], memories:[], ops:[] };
@@ -524,16 +535,19 @@ function renderFirstRunHero() {
524
535
  }
525
536
  btn.disabled = true; btn.textContent = 'Hiring…';
526
537
  setFirstRunStatus('Submitting hire request…');
538
+ const input = card.querySelector('#frh-goal-input');
539
+ const goal = (input?.value || `Use ${btn.dataset.specname || 'this specialist'} to inspect ${S.workspace?.repoName || 'this repo'} and report the next useful task`).trim();
527
540
  const result = await post('/api/synapse/hire', {
528
541
  specialistId: btn.dataset.specid,
529
542
  name: btn.dataset.specname,
530
543
  budgetCapUsd: 2,
531
544
  fireFirstSortie: true,
545
+ goal,
532
546
  });
533
547
  if (result.ok) {
534
548
  setFirstRunStatus(readiness.notes.some(note => note.tone === 'warn')
535
- ? 'Hired successfully. Fallback storage warning is still active.'
536
- : 'Hired successfully.');
549
+ ? 'Hired and first goal queued. Fallback storage warning is still active.'
550
+ : 'Hired and first goal queued.');
537
551
  try { localStorage.setItem(FIRST_RUN_KEY, '1'); } catch { /* ignore */ }
538
552
  bustCache('/api/synapse/health');
539
553
  setTimeout(load, 800);
@@ -562,11 +576,18 @@ function renderAgentsLiveStrip() {
562
576
  const strip = $('agents-live-strip'); if (!strip) return;
563
577
  const ops = S.synapseHealth;
564
578
  if (!ops.length) { strip.innerHTML=''; return; }
565
- strip.innerHTML = ops.slice(0,12).map(op => {
579
+ const active = ops.filter(isOpActive).length;
580
+ const failedJobs = (S.wfKanban?.lanes?.failed || []).length;
581
+ const summary = `<div class="agent-live-summary">
582
+ <strong>${fmtNum(ops.length)}</strong><span>hired</span>
583
+ <strong>${fmtNum(active)}</strong><span>active</span>
584
+ ${failedJobs ? `<strong class="agent-live-warn">${fmtNum(failedJobs)}</strong><span>failed</span>` : ''}
585
+ </div>`;
586
+ strip.innerHTML = summary + ops.slice(0,12).map(op => {
566
587
  const st = (op.status||'idle').toLowerCase();
567
- const cls = (st==='active'||st==='running') ? 'active' : (st==='blocked'||st==='zombie'||st==='dead') ? 'blocked' : '';
568
- const label = esc((op.role||op.name||op.id||'agent').slice(0,20));
569
- return `<div class="agent-live-pill ${cls}" data-opid="${esc(op.id)}" title="${label}">
588
+ const cls = isOpActive(op) ? 'active' : (st==='blocked'||st==='zombie'||st==='dead'||st==='failed') ? 'blocked' : 'idle';
589
+ const label = esc((op.displayName||op.name||op.role||op.id||'agent').slice(0,24));
590
+ return `<div class="agent-live-pill ${cls}" data-opid="${esc(op.id)}" title="${label} · ${esc(st)}">
570
591
  <span class="dot"></span><span>${label}</span></div>`;
571
592
  }).join('');
572
593
  }
@@ -574,6 +595,7 @@ function renderAgentsLiveStrip() {
574
595
  /* ── Kanban ── */
575
596
  function buildKanbanCols() {
576
597
  const cols={planning:[],hiring:[],running:[],ghostpass:[],done:[]};
598
+ const seen = new Set();
577
599
  const op=S.operateSurface;
578
600
  if (op) {
579
601
  const pc=op.orchestration?.planningContext||op.planningContext;
@@ -585,6 +607,36 @@ function buildKanbanCols() {
585
607
  if (sg) cols[sg].push({id:w.id||w.workerId||w.goal,goal:w.goal||w.task||w.approach||'(worker)',status:st,tokens:w.tokensUsed||w.budget,time:w.startedAt||w.createdAt,role:w.role});
586
608
  }
587
609
  }
610
+ const wfLanes = S.wfKanban?.lanes || {};
611
+ const wfLaneMap = {
612
+ backlog: 'planning',
613
+ ready: 'hiring',
614
+ claimed: 'hiring',
615
+ wip: 'running',
616
+ review: 'ghostpass',
617
+ blocked: 'ghostpass',
618
+ done: 'done',
619
+ failed: 'done',
620
+ cancelled: 'done',
621
+ };
622
+ for (const [lane, cards] of Object.entries(wfLanes)) {
623
+ const target = wfLaneMap[lane] || 'planning';
624
+ for (const card of (Array.isArray(cards) ? cards : [])) {
625
+ const id = card.id || card.jobId;
626
+ if (!id || seen.has(id)) continue;
627
+ seen.add(id);
628
+ const payload = card.payload && typeof card.payload === 'object' ? card.payload : {};
629
+ cols[target].push({
630
+ id,
631
+ workerId: card.workerId || payload.operativeId,
632
+ goal: payload.goal || card.title || '(workforce job)',
633
+ status: lane === 'failed' ? 'failed' : lane === 'cancelled' ? 'cancelled' : (card.status || lane),
634
+ tokens: card.tokensUsed || payload.tokensUsed,
635
+ time: card.updatedAt || card.completedAt || card.claimedAt || card.createdAt,
636
+ role: payload.specialistId || card.client || 'workforce',
637
+ });
638
+ }
639
+ }
588
640
  const ghost = S.lastDecomposition?.autoGhostPass || S.lastCompletion?.autoGhostPass || op?.orchestration?.autoGhostPass || op?.autoGhostPass;
589
641
  if (ghost && (ghost.applied || ghost.policy?.reason)) {
590
642
  const risks = Array.isArray(ghost.riskAreas) ? ghost.riskAreas.length : 0;
@@ -603,6 +655,8 @@ function buildKanbanCols() {
603
655
  for (const r of (S.runs||[]).slice(0,8)) {
604
656
  const runId = r.runId || r.id;
605
657
  if (!runId) continue;
658
+ if (seen.has(runId)) continue;
659
+ seen.add(runId);
606
660
  const status = String(r.status || r.state || '').toLowerCase();
607
661
  const stage = String(r.stage || '').toLowerCase();
608
662
  const advisory = status === 'inspected' || /advisory|no.diff|no-diff|no mutation/i.test(String(r.result || r.summary || r.outcome || ''));
@@ -635,9 +689,10 @@ function kcardHtml(c, stage) {
635
689
  const tm = c.time ? `<div class="kcard-time">${timeAgo(c.time)}</div>` : '';
636
690
  const op = S.synapseHealth.find(o =>
637
691
  (c.role && (o.role===c.role||o.name===c.role)) ||
692
+ (c.workerId && (o.id===c.workerId||o.operativeId===c.workerId)) ||
638
693
  (c.id && (o.id===c.id||o.operativeId===c.id)));
639
694
  const opBadge = op
640
- ? `<span class="agent-inline-badge st-${esc((op.status||'idle').toLowerCase())}" title="${esc(op.role||op.name||'')}">◎ ${esc((op.role||op.name||'').slice(0,18)||'agent')}</span>`
695
+ ? `<span class="agent-inline-badge st-${esc((op.status||'idle').toLowerCase())}" title="${esc(op.displayName||op.role||op.name||'')}">◎ ${esc((op.displayName||op.role||op.name||'').slice(0,18)||'agent')}</span>`
641
696
  : '';
642
697
  return `<div class="kcard s-${esc(stage)}" data-runid="${esc(String(c.id))}">
643
698
  <div class="kcard-goal">${esc(c.goal)}</div>
@@ -686,13 +741,14 @@ function renderEvents() {
686
741
  function renderPulse() {
687
742
  const el=$('system-pulse'); if (!el) return;
688
743
  const op=S.operateSurface;
689
- const ops=S.synapseHealth.filter(o=>o.status==='ACTIVE'||o.status==='active').length;
744
+ const activeOps=S.synapseHealth.filter(isOpActive).length;
745
+ const totalOps=S.synapseHealth.length;
690
746
  const mem=op?.memorySummary?.total??op?.memory?.total??S.memories.length;
691
747
  const gt=S.tokensSummary?.gross||0, nt=S.tokensSummary?.net||0;
692
748
  const eff=gt>0?Math.round((1-nt/gt)*100)+'%':'—';
693
749
  const gh=S.worktreeHealth?.pending??0;
694
750
  const up=timeAgo(S.startTime).replace(' ago','');
695
- const rows=[['Synapse',ops?`${ops} active`:'idle'],['Memory',mem?`${fmtNum(mem)} entries`:'—'],['Efficiency',eff],['Ghost-pass',gh?`${gh} pending`:'clear'],['Uptime',up||'—']];
751
+ const rows=[['Synapse',totalOps?`${activeOps}/${totalOps} active`:'idle'],['Memory',mem?`${fmtNum(mem)} entries`:'—'],['Efficiency',eff],['Ghost-pass',gh?`${gh} pending`:'clear'],['Uptime',up||'—']];
696
752
  el.innerHTML=rows.map(([k,v])=>`<div class="row"><span class="row-k">${esc(k)}</span><span class="row-v">${esc(v)}</span></div>`).join('');
697
753
  }
698
754
 
@@ -26,6 +26,10 @@ function outcomeChip(outcome) {
26
26
  return `<span class="chip ${cls[outcome] || 'chip-muted'}">${esc(outcome)}</span>`;
27
27
  }
28
28
 
29
+ function statusRow(label, value) {
30
+ return `<div class="governance-status-row"><span>${esc(label)}</span><strong>${esc(value)}</strong></div>`;
31
+ }
32
+
29
33
  /** Render a compact unified diff with line highlighting. */
30
34
  function renderDiff(diff) {
31
35
  if (!diff) return `<div class="diff-empty">No diff recorded</div>`;
@@ -65,18 +69,22 @@ export function render(cycles, health = null) {
65
69
 
66
70
  if (!cycles.length) {
67
71
  container.innerHTML = `
68
- <div class="trust-grid">
69
- <div class="card trust-card">
70
- <div class="trust-card-hd"><span class="trust-card-title">Darwin auto-propose</span><span class="chip chip-muted">idle</span></div>
71
- <div class="trust-posture-body">
72
- <div class="trust-posture-row"><span>Proposals</span><strong>0</strong></div>
73
- <div class="trust-posture-row"><span>Trigger</span><strong>nexus_orchestrate</strong></div>
74
- <div class="trust-posture-row"><span>Self-improve</span><strong>${esc(health?.runtime?.selfImprove ? 'on' : 'off')}</strong></div>
72
+ <div class="governance-status-grid">
73
+ <div class="card governance-status-card">
74
+ <div class="governance-card-head"><span class="governance-card-title">Darwin auto-propose</span><span class="chip chip-muted">idle</span></div>
75
+ <div class="governance-status-body">
76
+ ${statusRow('Proposals', '0')}
77
+ ${statusRow('Trigger', 'nexus_orchestrate')}
78
+ ${statusRow('Self-improve', health?.runtime?.selfImprove ? 'on' : 'off')}
75
79
  </div>
76
80
  </div>
77
- <div class="card trust-card">
78
- <div class="trust-card-hd"><span class="trust-card-title">Consensus guard</span><span class="chip chip-ok">ready</span></div>
79
- <div class="empty-sub">Live Byzantine votes and review decisions appear here when proposals are created.</div>
81
+ <div class="card governance-status-card">
82
+ <div class="governance-card-head"><span class="governance-card-title">Consensus guard</span><span class="chip chip-ok">ready</span></div>
83
+ <div class="governance-status-copy">Live Byzantine votes and review decisions appear here when proposals are created.</div>
84
+ <div class="governance-status-body compact">
85
+ ${statusRow('Review mode', 'human gated')}
86
+ ${statusRow('Live votes', String(S.byzantineVotes?.size || 0))}
87
+ </div>
80
88
  </div>
81
89
  </div>`;
82
90
  _renderByzantineSection();
@@ -168,7 +176,7 @@ function _fmtMetrics(before, after) {
168
176
  const b = Number(before[k] ?? 0), a = Number(after[k] ?? 0);
169
177
  const delta = a - b;
170
178
  const cls = delta > 0 ? 'metric-up' : delta < 0 ? 'metric-down' : 'metric-flat';
171
- return `<span class="darwin-metric ${cls}">${esc(k)}: ${b.toFixed(2)} ${a.toFixed(2)} (${delta >= 0 ? '+' : ''}${delta.toFixed(2)})</span>`;
179
+ return `<span class="darwin-metric ${cls}"><span>${esc(k)}</span><strong>${b.toFixed(2)} -> ${a.toFixed(2)}</strong><em>${delta >= 0 ? '+' : ''}${delta.toFixed(2)}</em></span>`;
172
180
  }).join('');
173
181
  }
174
182