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.
- package/dist/dashboard/app/styles/board.css +17 -1
- package/dist/dashboard/app/styles/governance.css +93 -0
- package/dist/dashboard/app/styles/memory.css +81 -1
- package/dist/dashboard/app/styles/runtime.css +2 -2
- package/dist/dashboard/app/styles/workforce.css +108 -4
- package/dist/dashboard/app/views/board.js +69 -13
- package/dist/dashboard/app/views/governance.js +19 -11
- package/dist/dashboard/app/views/memory.js +167 -9
- package/dist/dashboard/app/views/runtime.js +93 -3
- package/dist/dashboard/app/views/workforce.js +204 -23
- package/dist/dashboard/routes/governance.js +16 -3
- package/dist/dashboard/routes/workforce.js +47 -1
- package/dist/workforce/db/schema.d.ts +1 -1
- package/dist/workforce/db/schema.js +4 -2
- package/dist/workforce/index.d.ts +3 -2
- package/dist/workforce/index.js +3 -2
- package/dist/workforce/jobs.d.ts +1 -0
- package/dist/workforce/jobs.js +7 -0
- package/dist/workforce/kanban-view.js +15 -0
- package/dist/workforce/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -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:
|
|
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% -
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
536
|
-
: 'Hired
|
|
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
|
-
|
|
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 = (
|
|
568
|
-
const label = esc((op.
|
|
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
|
|
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',
|
|
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="
|
|
69
|
-
<div class="card
|
|
70
|
-
<div class="
|
|
71
|
-
<div class="
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
78
|
-
<div class="
|
|
79
|
-
<div class="
|
|
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)}
|
|
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
|
|