@ai-cortex/daemon 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +64 -0
- package/README.md +217 -0
- package/brain/cortex-brain.yaml +3 -0
- package/brain/entries/bash-permission-denied.md +100 -0
- package/brain/entries/cors-preflight.md +98 -0
- package/brain/entries/docker-port-conflict.md +99 -0
- package/brain/entries/env-vars-missing.md +107 -0
- package/brain/entries/git-merge-conflict.md +88 -0
- package/brain/entries/node-esm-import.md +71 -0
- package/brain/entries/npm-peer-deps.md +93 -0
- package/brain/entries/python-import-circular.md +112 -0
- package/brain/entries/python-import-resolution.md +83 -0
- package/brain/entries/python-venv-activate.md +98 -0
- package/brain/entries/react-stale-closure.md +76 -0
- package/brain/entries/sqlite-locked.md +110 -0
- package/brain/entries/typescript-strict-null.md +93 -0
- package/dist/bin/cortex-statusline.js +3 -0
- package/dist/dashboard/index.html +1616 -0
- package/dist/index.js +3 -0
- package/package.json +44 -0
|
@@ -0,0 +1,1616 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Cortex Dashboard</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #141414;
|
|
10
|
+
--surface: #1e1e1e;
|
|
11
|
+
--surface2: #252525;
|
|
12
|
+
--border: #333;
|
|
13
|
+
--text: #e0e0e0;
|
|
14
|
+
--text-muted: #888;
|
|
15
|
+
--green: #22c55e;
|
|
16
|
+
--yellow: #eab308;
|
|
17
|
+
--gray: #6b7280;
|
|
18
|
+
--red: #ef4444;
|
|
19
|
+
--blue: #3b82f6;
|
|
20
|
+
--header-h: 60px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
24
|
+
|
|
25
|
+
html, body {
|
|
26
|
+
background: var(--bg);
|
|
27
|
+
color: var(--text);
|
|
28
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
29
|
+
height: 100%;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Header */
|
|
33
|
+
.header {
|
|
34
|
+
position: fixed;
|
|
35
|
+
top: 0;
|
|
36
|
+
left: 0;
|
|
37
|
+
right: 0;
|
|
38
|
+
height: var(--header-h);
|
|
39
|
+
background: var(--surface);
|
|
40
|
+
border-bottom: 1px solid var(--border);
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: space-between;
|
|
44
|
+
padding: 0 24px;
|
|
45
|
+
z-index: 100;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.header-title {
|
|
49
|
+
font-size: 18px;
|
|
50
|
+
font-weight: 700;
|
|
51
|
+
letter-spacing: -0.3px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.header-stats {
|
|
55
|
+
display: flex;
|
|
56
|
+
gap: 24px;
|
|
57
|
+
font-size: 13px;
|
|
58
|
+
font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.stat-item {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 6px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.stat-label {
|
|
68
|
+
color: var(--text-muted);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.stat-value {
|
|
72
|
+
color: var(--text);
|
|
73
|
+
font-weight: 600;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Main layout */
|
|
77
|
+
.main {
|
|
78
|
+
margin-top: var(--header-h);
|
|
79
|
+
display: flex;
|
|
80
|
+
height: calc(100vh - var(--header-h));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Stream panel */
|
|
84
|
+
.stream-panel {
|
|
85
|
+
flex: 7;
|
|
86
|
+
overflow-y: auto;
|
|
87
|
+
padding: 16px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.stream-panel::-webkit-scrollbar { width: 6px; }
|
|
91
|
+
.stream-panel::-webkit-scrollbar-track { background: transparent; }
|
|
92
|
+
.stream-panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
93
|
+
|
|
94
|
+
.empty-state {
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
justify-content: center;
|
|
98
|
+
height: 100%;
|
|
99
|
+
color: var(--text-muted);
|
|
100
|
+
font-size: 14px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Request card */
|
|
104
|
+
.card {
|
|
105
|
+
background: var(--surface);
|
|
106
|
+
border: 1px solid var(--border);
|
|
107
|
+
border-left: 4px solid var(--gray);
|
|
108
|
+
border-radius: 8px;
|
|
109
|
+
padding: 14px 16px;
|
|
110
|
+
margin-bottom: 10px;
|
|
111
|
+
animation: fadeIn 0.2s ease-out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@keyframes fadeIn {
|
|
115
|
+
from { opacity: 0; transform: translateY(-6px); }
|
|
116
|
+
to { opacity: 1; transform: translateY(0); }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.card.status-injected { border-left-color: var(--green); }
|
|
120
|
+
.card.status-pruned { border-left-color: var(--yellow); }
|
|
121
|
+
.card.status-passthrough { border-left-color: var(--gray); }
|
|
122
|
+
.card.status-error { border-left-color: var(--red); }
|
|
123
|
+
|
|
124
|
+
.card-header {
|
|
125
|
+
display: flex;
|
|
126
|
+
justify-content: space-between;
|
|
127
|
+
align-items: center;
|
|
128
|
+
margin-bottom: 10px;
|
|
129
|
+
font-size: 13px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.card-id {
|
|
133
|
+
font-weight: 700;
|
|
134
|
+
font-size: 14px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.card-meta {
|
|
138
|
+
color: var(--text-muted);
|
|
139
|
+
font-family: monospace;
|
|
140
|
+
font-size: 12px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.card-line {
|
|
144
|
+
font-size: 12px;
|
|
145
|
+
font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;
|
|
146
|
+
padding: 3px 0;
|
|
147
|
+
color: var(--text-muted);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.card-line span.label {
|
|
151
|
+
display: inline-block;
|
|
152
|
+
width: 90px;
|
|
153
|
+
color: var(--text-muted);
|
|
154
|
+
text-transform: uppercase;
|
|
155
|
+
font-size: 10px;
|
|
156
|
+
font-weight: 600;
|
|
157
|
+
letter-spacing: 0.5px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.card-line .value { color: var(--text); }
|
|
161
|
+
.card-line .green { color: var(--green); }
|
|
162
|
+
.card-line .yellow { color: var(--yellow); }
|
|
163
|
+
.card-line .red { color: var(--red); }
|
|
164
|
+
|
|
165
|
+
/* Sidebar */
|
|
166
|
+
.sidebar {
|
|
167
|
+
flex: 3;
|
|
168
|
+
border-left: 1px solid var(--border);
|
|
169
|
+
overflow-y: auto;
|
|
170
|
+
background: var(--surface);
|
|
171
|
+
display: flex;
|
|
172
|
+
flex-direction: column;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.sidebar::-webkit-scrollbar { width: 6px; }
|
|
176
|
+
.sidebar::-webkit-scrollbar-track { background: transparent; }
|
|
177
|
+
.sidebar::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
178
|
+
|
|
179
|
+
.sidebar-section {
|
|
180
|
+
padding: 16px;
|
|
181
|
+
border-bottom: 1px solid var(--border);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.sidebar-section:last-child {
|
|
185
|
+
border-bottom: none;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.section-title {
|
|
189
|
+
font-size: 11px;
|
|
190
|
+
font-weight: 700;
|
|
191
|
+
margin-bottom: 12px;
|
|
192
|
+
color: var(--text-muted);
|
|
193
|
+
text-transform: uppercase;
|
|
194
|
+
letter-spacing: 0.5px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Brain section */
|
|
198
|
+
.brain-entry {
|
|
199
|
+
background: var(--surface2);
|
|
200
|
+
border: 1px solid var(--border);
|
|
201
|
+
border-radius: 6px;
|
|
202
|
+
padding: 10px 12px;
|
|
203
|
+
margin-bottom: 8px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.brain-entry-title {
|
|
207
|
+
font-size: 13px;
|
|
208
|
+
font-weight: 600;
|
|
209
|
+
margin-bottom: 6px;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.brain-summary-row {
|
|
213
|
+
display: flex;
|
|
214
|
+
justify-content: space-between;
|
|
215
|
+
font-size: 12px;
|
|
216
|
+
margin-bottom: 8px;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.brain-summary-label {
|
|
220
|
+
color: var(--text-muted);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.brain-summary-value {
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.badge {
|
|
229
|
+
display: inline-block;
|
|
230
|
+
font-size: 10px;
|
|
231
|
+
font-weight: 600;
|
|
232
|
+
padding: 2px 7px;
|
|
233
|
+
border-radius: 4px;
|
|
234
|
+
margin-right: 4px;
|
|
235
|
+
margin-bottom: 3px;
|
|
236
|
+
text-transform: uppercase;
|
|
237
|
+
letter-spacing: 0.3px;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.badge-lang {
|
|
241
|
+
background: rgba(59, 130, 246, 0.15);
|
|
242
|
+
color: var(--blue);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.badge-tag {
|
|
246
|
+
background: rgba(107, 114, 128, 0.2);
|
|
247
|
+
color: var(--text-muted);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.sidebar-empty {
|
|
251
|
+
color: var(--text-muted);
|
|
252
|
+
font-size: 13px;
|
|
253
|
+
text-align: center;
|
|
254
|
+
padding: 16px 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Features section */
|
|
258
|
+
.feature-row {
|
|
259
|
+
display: flex;
|
|
260
|
+
align-items: center;
|
|
261
|
+
justify-content: space-between;
|
|
262
|
+
padding: 8px 0;
|
|
263
|
+
border-bottom: 1px solid var(--border);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.feature-row:last-child {
|
|
267
|
+
border-bottom: none;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.feature-info {
|
|
271
|
+
display: flex;
|
|
272
|
+
flex-direction: column;
|
|
273
|
+
gap: 2px;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.feature-name {
|
|
277
|
+
font-size: 13px;
|
|
278
|
+
font-weight: 600;
|
|
279
|
+
text-transform: capitalize;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.feature-reason {
|
|
283
|
+
font-size: 11px;
|
|
284
|
+
color: var(--text-muted);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* Toggle switch */
|
|
288
|
+
.toggle {
|
|
289
|
+
position: relative;
|
|
290
|
+
width: 36px;
|
|
291
|
+
height: 20px;
|
|
292
|
+
flex-shrink: 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.toggle input {
|
|
296
|
+
opacity: 0;
|
|
297
|
+
width: 0;
|
|
298
|
+
height: 0;
|
|
299
|
+
position: absolute;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.toggle-track {
|
|
303
|
+
position: absolute;
|
|
304
|
+
inset: 0;
|
|
305
|
+
background: var(--border);
|
|
306
|
+
border-radius: 10px;
|
|
307
|
+
cursor: pointer;
|
|
308
|
+
transition: background 0.2s;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.toggle input:checked + .toggle-track {
|
|
312
|
+
background: var(--green);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.toggle input:disabled + .toggle-track {
|
|
316
|
+
opacity: 0.4;
|
|
317
|
+
cursor: not-allowed;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.toggle-thumb {
|
|
321
|
+
position: absolute;
|
|
322
|
+
top: 3px;
|
|
323
|
+
left: 3px;
|
|
324
|
+
width: 14px;
|
|
325
|
+
height: 14px;
|
|
326
|
+
background: white;
|
|
327
|
+
border-radius: 50%;
|
|
328
|
+
transition: transform 0.2s;
|
|
329
|
+
pointer-events: none;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.toggle input:checked ~ .toggle-thumb {
|
|
333
|
+
transform: translateX(16px);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/* Agent type badges */
|
|
337
|
+
.badge-main { background: var(--green); color: #000; }
|
|
338
|
+
.badge-subagent { background: var(--blue); color: #fff; }
|
|
339
|
+
.badge-teammate { background: #a855f7; color: #fff; }
|
|
340
|
+
.badge-unknown { background: var(--gray); color: #fff; }
|
|
341
|
+
|
|
342
|
+
.agent-badge {
|
|
343
|
+
display: inline-block;
|
|
344
|
+
font-size: 10px;
|
|
345
|
+
font-weight: 600;
|
|
346
|
+
padding: 1px 6px;
|
|
347
|
+
border-radius: 3px;
|
|
348
|
+
text-transform: uppercase;
|
|
349
|
+
letter-spacing: 0.5px;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* Agent sidebar section */
|
|
353
|
+
.agent-section { margin-top: 16px; }
|
|
354
|
+
.agent-section h3 { font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: var(--text-muted); margin-bottom: 8px; }
|
|
355
|
+
.agent-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 4px; cursor: pointer; font-size: 13px; }
|
|
356
|
+
.agent-row:hover { background: var(--surface2); }
|
|
357
|
+
.agent-row.active { background: var(--surface2); border-left: 2px solid var(--blue); }
|
|
358
|
+
.agent-model { color: var(--text-muted); font-size: 11px; }
|
|
359
|
+
.agent-requests { color: var(--text-muted); font-size: 11px; margin-left: auto; }
|
|
360
|
+
|
|
361
|
+
/* History section */
|
|
362
|
+
.history-section { margin-top: 12px; }
|
|
363
|
+
.history-toggle { cursor: pointer; font-size: 12px; color: var(--text-muted); display: flex; align-items: center; gap: 4px; }
|
|
364
|
+
.history-toggle:hover { color: var(--text); }
|
|
365
|
+
.history-list { display: none; }
|
|
366
|
+
.history-list.open { display: block; }
|
|
367
|
+
.history-row { padding: 4px 8px; font-size: 12px; color: var(--text-muted); }
|
|
368
|
+
|
|
369
|
+
/* Hierarchical session tree */
|
|
370
|
+
.session-group { margin-bottom: 4px; }
|
|
371
|
+
|
|
372
|
+
.session-header {
|
|
373
|
+
display: flex;
|
|
374
|
+
align-items: center;
|
|
375
|
+
gap: 6px;
|
|
376
|
+
padding: 6px 8px;
|
|
377
|
+
border-radius: 4px;
|
|
378
|
+
cursor: pointer;
|
|
379
|
+
font-size: 13px;
|
|
380
|
+
}
|
|
381
|
+
.session-header:hover { background: var(--surface2); }
|
|
382
|
+
.session-header.active { background: var(--surface2); border-left: 2px solid var(--blue); padding-left: 6px; }
|
|
383
|
+
|
|
384
|
+
.session-toggle { font-size: 10px; color: var(--text-muted); width: 12px; flex-shrink: 0; }
|
|
385
|
+
|
|
386
|
+
.session-children { padding-left: 16px; display: block; }
|
|
387
|
+
.session-children.collapsed { display: none; }
|
|
388
|
+
|
|
389
|
+
.child-group-label {
|
|
390
|
+
font-size: 10px;
|
|
391
|
+
font-weight: 700;
|
|
392
|
+
text-transform: uppercase;
|
|
393
|
+
letter-spacing: 0.5px;
|
|
394
|
+
color: var(--text-muted);
|
|
395
|
+
padding: 6px 8px 3px;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.child-row {
|
|
399
|
+
display: flex;
|
|
400
|
+
align-items: center;
|
|
401
|
+
gap: 6px;
|
|
402
|
+
padding: 4px 8px;
|
|
403
|
+
border-radius: 4px;
|
|
404
|
+
cursor: pointer;
|
|
405
|
+
font-size: 12px;
|
|
406
|
+
}
|
|
407
|
+
.child-row:hover { background: var(--surface2); }
|
|
408
|
+
.child-row.active { background: var(--surface2); border-left: 2px solid var(--blue); padding-left: 6px; }
|
|
409
|
+
|
|
410
|
+
.connector {
|
|
411
|
+
font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;
|
|
412
|
+
font-size: 11px;
|
|
413
|
+
color: var(--text-muted);
|
|
414
|
+
flex-shrink: 0;
|
|
415
|
+
}
|
|
416
|
+
.connector.solid { color: var(--green); }
|
|
417
|
+
.connector.dashed { color: var(--yellow); }
|
|
418
|
+
|
|
419
|
+
.unlinked-section { margin-top: 8px; border-top: 1px solid var(--border); padding-top: 8px; }
|
|
420
|
+
|
|
421
|
+
/* Responsive */
|
|
422
|
+
@media (max-width: 768px) {
|
|
423
|
+
.main { flex-direction: column; }
|
|
424
|
+
.sidebar { border-left: none; border-top: 1px solid var(--border); max-height: 40vh; }
|
|
425
|
+
.header-stats { gap: 12px; font-size: 11px; }
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* Slide-over panel */
|
|
429
|
+
.context-overlay { position:fixed; top:var(--header-h); left:0; right:0; bottom:0; background:rgba(0,0,0,0.5); z-index:50; display:none; }
|
|
430
|
+
.context-overlay.open { display:block; }
|
|
431
|
+
.context-panel { position:fixed; top:var(--header-h); right:0; bottom:0; width:60%; background:var(--surface); border-left:2px solid var(--blue); z-index:51; display:none; overflow-y:auto; flex-direction:column; }
|
|
432
|
+
.context-panel.open { display:flex; }
|
|
433
|
+
.context-header { padding:12px 16px; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items:center; flex-shrink:0; }
|
|
434
|
+
.context-header h3 { font-size:14px; font-weight:600; }
|
|
435
|
+
.context-close { cursor:pointer; color:var(--text-muted); font-size:18px; background:none; border:none; }
|
|
436
|
+
.context-close:hover { color:var(--text); }
|
|
437
|
+
.context-tabs { display:flex; gap:0; border-bottom:1px solid var(--border); flex-shrink:0; }
|
|
438
|
+
.context-tab { padding:8px 16px; font-size:12px; cursor:pointer; border-bottom:2px solid transparent; color:var(--text-muted); background:none; border-top:none; border-left:none; border-right:none; }
|
|
439
|
+
.context-tab.active { color:var(--blue); border-bottom-color:var(--blue); }
|
|
440
|
+
.context-body { display:flex; flex:1; overflow:hidden; }
|
|
441
|
+
.context-timeline { width:100px; border-right:1px solid var(--border); overflow-y:auto; padding:8px; flex-shrink:0; }
|
|
442
|
+
.context-timeline-item { padding:4px 8px; font-size:11px; cursor:pointer; border-radius:3px; margin-bottom:4px; color:var(--text-muted); }
|
|
443
|
+
.context-timeline-item:hover { background:var(--surface2); }
|
|
444
|
+
.context-timeline-item.active { background:var(--surface2); color:var(--text); border-left:2px solid var(--blue); padding-left:6px; }
|
|
445
|
+
.context-content { flex:1; overflow-y:auto; padding:16px; }
|
|
446
|
+
.context-stats { padding:8px 16px; border-top:1px solid var(--border); font-size:12px; color:var(--text-muted); display:flex; gap:16px; flex-shrink:0; flex-wrap:wrap; }
|
|
447
|
+
|
|
448
|
+
/* Message rows (Overview) */
|
|
449
|
+
.msg-row { display:flex; align-items:center; gap:8px; padding:6px 8px; border-radius:4px; cursor:pointer; font-size:13px; border-bottom:1px solid var(--border); }
|
|
450
|
+
.msg-row:hover { background:var(--surface2); }
|
|
451
|
+
.msg-role { font-weight:600; width:50px; font-size:11px; text-transform:uppercase; flex-shrink:0; }
|
|
452
|
+
.msg-role.user { color:var(--blue); }
|
|
453
|
+
.msg-role.assistant { color:var(--green); }
|
|
454
|
+
.msg-type { font-size:10px; padding:1px 5px; border-radius:3px; background:var(--surface2); color:var(--text-muted); flex-shrink:0; }
|
|
455
|
+
.msg-tokens { font-size:11px; color:var(--text-muted); margin-left:auto; flex-shrink:0; }
|
|
456
|
+
.msg-preview { font-size:12px; color:var(--text-muted); flex:1; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:300px; }
|
|
457
|
+
.msg-markers { display:flex; gap:3px; flex-shrink:0; }
|
|
458
|
+
.msg-marker { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
|
|
459
|
+
.msg-marker.pruned { background:var(--red); }
|
|
460
|
+
.msg-marker.injected { background:var(--green); }
|
|
461
|
+
|
|
462
|
+
/* Diff highlights (Expanded/Cortex Only) */
|
|
463
|
+
.diff-pruned { background:rgba(239,68,68,0.1); border-left:3px solid var(--red); padding:8px; margin:4px 0; font-family:monospace; font-size:12px; white-space:pre-wrap; word-break:break-word; }
|
|
464
|
+
.diff-pruned-header { color:var(--red); font-size:11px; font-weight:600; margin-bottom:4px; }
|
|
465
|
+
.diff-pruned .strikethrough { text-decoration:line-through; opacity:0.6; }
|
|
466
|
+
.diff-injected { background:rgba(34,197,94,0.1); border-left:3px solid var(--green); padding:8px; margin:4px 0; font-family:monospace; font-size:12px; white-space:pre-wrap; word-break:break-word; }
|
|
467
|
+
.diff-injected-header { color:var(--green); font-size:11px; font-weight:600; margin-bottom:4px; }
|
|
468
|
+
.diff-expand { cursor:pointer; color:var(--blue); font-size:11px; padding:4px 0; display:block; }
|
|
469
|
+
.diff-expand:hover { text-decoration:underline; }
|
|
470
|
+
.msg-expanded { padding:8px; font-family:monospace; font-size:12px; white-space:pre-wrap; word-break:break-word; border-bottom:1px solid var(--border); }
|
|
471
|
+
.msg-expanded-header { font-size:11px; font-weight:600; color:var(--text-muted); margin-bottom:4px; padding:4px 8px; background:var(--surface2); border-radius:3px; }
|
|
472
|
+
.context-section-title { font-size:12px; font-weight:700; color:var(--text-muted); text-transform:uppercase; letter-spacing:0.5px; margin:12px 0 6px; }
|
|
473
|
+
|
|
474
|
+
/* Agent row context button */
|
|
475
|
+
.agent-context-btn { font-size:10px; color:var(--blue); cursor:pointer; padding:1px 5px; border-radius:3px; border:1px solid var(--blue); background:none; flex-shrink:0; }
|
|
476
|
+
.agent-context-btn:hover { background:rgba(59,130,246,0.1); }
|
|
477
|
+
</style>
|
|
478
|
+
</head>
|
|
479
|
+
<body>
|
|
480
|
+
|
|
481
|
+
<div class="header">
|
|
482
|
+
<div class="header-title">Cortex Dashboard</div>
|
|
483
|
+
<div class="header-stats">
|
|
484
|
+
<div class="stat-item">
|
|
485
|
+
<span class="stat-label">Uptime</span>
|
|
486
|
+
<span class="stat-value" id="stat-uptime">0s</span>
|
|
487
|
+
</div>
|
|
488
|
+
<div class="stat-item">
|
|
489
|
+
<span class="stat-label">Requests</span>
|
|
490
|
+
<span class="stat-value" id="stat-requests">0</span>
|
|
491
|
+
</div>
|
|
492
|
+
<div class="stat-item">
|
|
493
|
+
<span class="stat-label">Tokens saved</span>
|
|
494
|
+
<span class="stat-value" id="stat-tokens">0</span>
|
|
495
|
+
</div>
|
|
496
|
+
<div class="stat-item">
|
|
497
|
+
<span class="stat-label">Injection rate</span>
|
|
498
|
+
<span class="stat-value" id="stat-injection">0%</span>
|
|
499
|
+
</div>
|
|
500
|
+
<div class="stat-item" id="agentsStat" style="display:none">
|
|
501
|
+
<span class="stat-label">Agents</span>
|
|
502
|
+
<span class="stat-value" id="agentsCount">0</span>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
</div>
|
|
506
|
+
|
|
507
|
+
<div class="main">
|
|
508
|
+
<div class="stream-panel" id="stream">
|
|
509
|
+
<div class="empty-state" id="empty-state">Waiting for requests...</div>
|
|
510
|
+
</div>
|
|
511
|
+
<div class="sidebar">
|
|
512
|
+
<div class="sidebar-section">
|
|
513
|
+
<div class="section-title">Brain Directory</div>
|
|
514
|
+
<div id="brain-content"><div class="sidebar-empty">Loading...</div></div>
|
|
515
|
+
<div class="agent-section" id="agentSection" style="display:none">
|
|
516
|
+
<h3>Active Agents</h3>
|
|
517
|
+
<div id="agentList"></div>
|
|
518
|
+
<div class="history-section">
|
|
519
|
+
<div class="history-toggle" onclick="toggleHistory()">
|
|
520
|
+
<span id="historyArrow">►</span> History (<span id="historyCount">0</span>)
|
|
521
|
+
</div>
|
|
522
|
+
<div class="history-list" id="historyList"></div>
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
<div class="sidebar-section">
|
|
527
|
+
<div class="section-title">Features</div>
|
|
528
|
+
<div id="features-list"><div class="sidebar-empty">Loading...</div></div>
|
|
529
|
+
</div>
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
|
|
533
|
+
<div class="context-overlay" id="contextOverlay" onclick="closeContextPanel()"></div>
|
|
534
|
+
<div class="context-panel" id="contextPanel">
|
|
535
|
+
<div class="context-header">
|
|
536
|
+
<h3 id="contextTitle">Context Window</h3>
|
|
537
|
+
<button class="context-close" onclick="closeContextPanel()">×</button>
|
|
538
|
+
</div>
|
|
539
|
+
<div class="context-tabs">
|
|
540
|
+
<button class="context-tab active" id="ctxTabOverview" onclick="setViewLevel('overview', this)">Overview</button>
|
|
541
|
+
<button class="context-tab" id="ctxTabExpanded" onclick="setViewLevel('expanded', this)">Expanded</button>
|
|
542
|
+
<button class="context-tab" id="ctxTabCortex" onclick="setViewLevel('cortex', this)">Cortex Only</button>
|
|
543
|
+
</div>
|
|
544
|
+
<div class="context-body">
|
|
545
|
+
<div class="context-timeline" id="contextTimeline"></div>
|
|
546
|
+
<div class="context-content" id="contextContent"></div>
|
|
547
|
+
</div>
|
|
548
|
+
<div class="context-stats" id="contextStats"></div>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
<script>
|
|
552
|
+
(function() {
|
|
553
|
+
// Authenticated fetch helper for API calls
|
|
554
|
+
function apiFetch(url, options) {
|
|
555
|
+
options = options || {};
|
|
556
|
+
var headers = Object.assign({}, options.headers || {});
|
|
557
|
+
if (window.CORTEX_API_TOKEN) {
|
|
558
|
+
headers['Authorization'] = 'Bearer ' + window.CORTEX_API_TOKEN;
|
|
559
|
+
}
|
|
560
|
+
return fetch(url, Object.assign({}, options, { headers: headers }));
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// State
|
|
564
|
+
const requests = {};
|
|
565
|
+
const MAX_CARDS = 100;
|
|
566
|
+
const startTime = Date.now();
|
|
567
|
+
|
|
568
|
+
// Agent tracking
|
|
569
|
+
const agents = new Map(); // agentId → agent info
|
|
570
|
+
const requestAgentMap = new Map(); // requestId → { agentId, agentType }
|
|
571
|
+
let agentFilter = null; // null = show all, string = filter by agentId
|
|
572
|
+
let historyAgents = [];
|
|
573
|
+
|
|
574
|
+
// DOM refs
|
|
575
|
+
const streamEl = document.getElementById('stream');
|
|
576
|
+
const emptyEl = document.getElementById('empty-state');
|
|
577
|
+
const brainContentEl = document.getElementById('brain-content');
|
|
578
|
+
const featuresListEl = document.getElementById('features-list');
|
|
579
|
+
const statUptime = document.getElementById('stat-uptime');
|
|
580
|
+
const statRequests = document.getElementById('stat-requests');
|
|
581
|
+
const statTokens = document.getElementById('stat-tokens');
|
|
582
|
+
const statInjection = document.getElementById('stat-injection');
|
|
583
|
+
|
|
584
|
+
// Uptime timer (client-side, tracks time since page load)
|
|
585
|
+
setInterval(function() {
|
|
586
|
+
var s = Math.floor((Date.now() - startTime) / 1000);
|
|
587
|
+
var h = Math.floor(s / 3600);
|
|
588
|
+
var m = Math.floor((s % 3600) / 60);
|
|
589
|
+
var sec = s % 60;
|
|
590
|
+
statUptime.textContent = h > 0 ? h + 'h ' + m + 'm' : m > 0 ? m + 'm ' + sec + 's' : sec + 's';
|
|
591
|
+
}, 1000);
|
|
592
|
+
|
|
593
|
+
// Load initial session stats from /api/session/current
|
|
594
|
+
// Shape: { requestCount, tokensSaved, injectionsCount, matchRate, avgLatencyMs }
|
|
595
|
+
function loadSessionStats() {
|
|
596
|
+
apiFetch('/api/session/current')
|
|
597
|
+
.then(function(r) { return r.json(); })
|
|
598
|
+
.then(function(data) {
|
|
599
|
+
statRequests.textContent = data.requestCount;
|
|
600
|
+
statTokens.textContent = formatNum(data.tokensSaved);
|
|
601
|
+
statInjection.textContent = Math.round(data.matchRate * 100) + '%';
|
|
602
|
+
})
|
|
603
|
+
.catch(function() {});
|
|
604
|
+
}
|
|
605
|
+
loadSessionStats();
|
|
606
|
+
|
|
607
|
+
// Load brain summary from /api/brain/summary
|
|
608
|
+
// Shape: { totalEntries, bySource: Record<string, number>, recentMatches }
|
|
609
|
+
function loadBrainSummary() {
|
|
610
|
+
apiFetch('/api/brain/summary')
|
|
611
|
+
.then(function(r) { return r.json(); })
|
|
612
|
+
.then(function(data) {
|
|
613
|
+
var html = '';
|
|
614
|
+
html += '<div class="brain-summary-row">';
|
|
615
|
+
html += '<span class="brain-summary-label">Total entries</span>';
|
|
616
|
+
html += '<span class="brain-summary-value">' + data.totalEntries + '</span>';
|
|
617
|
+
html += '</div>';
|
|
618
|
+
|
|
619
|
+
var sources = Object.keys(data.bySource || {});
|
|
620
|
+
if (sources.length > 0) {
|
|
621
|
+
sources.forEach(function(src) {
|
|
622
|
+
html += '<div class="brain-entry">';
|
|
623
|
+
html += '<div class="brain-entry-title">' + esc(src) + '</div>';
|
|
624
|
+
html += '<span class="badge badge-tag">' + data.bySource[src] + ' entries</span>';
|
|
625
|
+
html += '</div>';
|
|
626
|
+
});
|
|
627
|
+
} else if (data.totalEntries === 0) {
|
|
628
|
+
html += '<div class="sidebar-empty">No entries loaded</div>';
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
brainContentEl.innerHTML = html;
|
|
632
|
+
})
|
|
633
|
+
.catch(function() {
|
|
634
|
+
brainContentEl.innerHTML = '<div class="sidebar-empty">Failed to load</div>';
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
loadBrainSummary();
|
|
638
|
+
|
|
639
|
+
// Load features from /api/features
|
|
640
|
+
// Shape: { features: Array<{ name, enabled, reason? }> }
|
|
641
|
+
function loadFeatures() {
|
|
642
|
+
apiFetch('/api/features')
|
|
643
|
+
.then(function(r) { return r.json(); })
|
|
644
|
+
.then(function(data) {
|
|
645
|
+
renderFeatures(data.features || []);
|
|
646
|
+
})
|
|
647
|
+
.catch(function() {
|
|
648
|
+
featuresListEl.innerHTML = '<div class="sidebar-empty">Failed to load</div>';
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function renderFeatures(features) {
|
|
653
|
+
if (!features.length) {
|
|
654
|
+
featuresListEl.innerHTML = '<div class="sidebar-empty">No features</div>';
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
featuresListEl.innerHTML = '';
|
|
659
|
+
features.forEach(function(feature) {
|
|
660
|
+
var row = document.createElement('div');
|
|
661
|
+
row.className = 'feature-row';
|
|
662
|
+
row.setAttribute('data-feature', feature.name);
|
|
663
|
+
|
|
664
|
+
var info = document.createElement('div');
|
|
665
|
+
info.className = 'feature-info';
|
|
666
|
+
|
|
667
|
+
var nameEl = document.createElement('div');
|
|
668
|
+
nameEl.className = 'feature-name';
|
|
669
|
+
nameEl.textContent = feature.name;
|
|
670
|
+
info.appendChild(nameEl);
|
|
671
|
+
|
|
672
|
+
if (feature.reason) {
|
|
673
|
+
var reasonEl = document.createElement('div');
|
|
674
|
+
reasonEl.className = 'feature-reason';
|
|
675
|
+
reasonEl.textContent = feature.reason;
|
|
676
|
+
info.appendChild(reasonEl);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
var label = document.createElement('label');
|
|
680
|
+
label.className = 'toggle';
|
|
681
|
+
|
|
682
|
+
var input = document.createElement('input');
|
|
683
|
+
input.type = 'checkbox';
|
|
684
|
+
input.checked = feature.enabled;
|
|
685
|
+
input.disabled = !!feature.reason;
|
|
686
|
+
|
|
687
|
+
// Capture current input/row in closure for the event listener
|
|
688
|
+
(function(inp, rw) {
|
|
689
|
+
inp.addEventListener('change', function() {
|
|
690
|
+
toggleFeature(feature.name, inp, rw);
|
|
691
|
+
});
|
|
692
|
+
})(input, row);
|
|
693
|
+
|
|
694
|
+
var track = document.createElement('div');
|
|
695
|
+
track.className = 'toggle-track';
|
|
696
|
+
|
|
697
|
+
var thumb = document.createElement('div');
|
|
698
|
+
thumb.className = 'toggle-thumb';
|
|
699
|
+
|
|
700
|
+
label.appendChild(input);
|
|
701
|
+
label.appendChild(track);
|
|
702
|
+
label.appendChild(thumb);
|
|
703
|
+
|
|
704
|
+
row.appendChild(info);
|
|
705
|
+
row.appendChild(label);
|
|
706
|
+
featuresListEl.appendChild(row);
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// POST /api/features/:name/toggle
|
|
711
|
+
// Response shape: { name, enabled, reason? }
|
|
712
|
+
function toggleFeature(name, inputEl, rowEl) {
|
|
713
|
+
inputEl.disabled = true;
|
|
714
|
+
apiFetch('/api/features/' + encodeURIComponent(name) + '/toggle', { method: 'POST' })
|
|
715
|
+
.then(function(r) { return r.json(); })
|
|
716
|
+
.then(function(updated) {
|
|
717
|
+
inputEl.checked = updated.enabled;
|
|
718
|
+
var infoEl = rowEl.querySelector('.feature-info');
|
|
719
|
+
var existingReason = rowEl.querySelector('.feature-reason');
|
|
720
|
+
if (updated.reason) {
|
|
721
|
+
if (existingReason) {
|
|
722
|
+
existingReason.textContent = updated.reason;
|
|
723
|
+
} else {
|
|
724
|
+
var reasonEl = document.createElement('div');
|
|
725
|
+
reasonEl.className = 'feature-reason';
|
|
726
|
+
reasonEl.textContent = updated.reason;
|
|
727
|
+
infoEl.appendChild(reasonEl);
|
|
728
|
+
}
|
|
729
|
+
inputEl.disabled = true;
|
|
730
|
+
} else {
|
|
731
|
+
if (existingReason) existingReason.remove();
|
|
732
|
+
inputEl.disabled = false;
|
|
733
|
+
}
|
|
734
|
+
})
|
|
735
|
+
.catch(function() {
|
|
736
|
+
// Revert optimistic toggle on error
|
|
737
|
+
inputEl.checked = !inputEl.checked;
|
|
738
|
+
inputEl.disabled = false;
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
loadFeatures();
|
|
742
|
+
|
|
743
|
+
// Agent functions — topology-based session tree
|
|
744
|
+
async function renderSessionTree() {
|
|
745
|
+
try {
|
|
746
|
+
var topoRes = await apiFetch('/api/agents/topology');
|
|
747
|
+
if (!topoRes.ok) return; // graceful 404 — tracking may be disabled
|
|
748
|
+
var topo = await topoRes.json();
|
|
749
|
+
if (!topo.enabled) return;
|
|
750
|
+
|
|
751
|
+
document.getElementById('agentSection').style.display = '';
|
|
752
|
+
document.getElementById('agentsStat').style.display = '';
|
|
753
|
+
|
|
754
|
+
// Sync agents map from topology so card tagging and context panel still work
|
|
755
|
+
if (topo.version === 2 && Array.isArray(topo.sessions)) {
|
|
756
|
+
topo.sessions.forEach(function(session) {
|
|
757
|
+
agents.set(session.agent.conversationId, session.agent);
|
|
758
|
+
(session.teams || []).forEach(function(team) {
|
|
759
|
+
(team.members || []).forEach(function(m) { agents.set(m.agent.conversationId, m.agent); });
|
|
760
|
+
});
|
|
761
|
+
(session.subagents || []).forEach(function(s) { agents.set(s.agent.conversationId, s.agent); });
|
|
762
|
+
});
|
|
763
|
+
(topo.unlinked || []).forEach(function(a) { agents.set(a.conversationId, a); });
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
var list = document.getElementById('agentList');
|
|
767
|
+
list.innerHTML = '';
|
|
768
|
+
|
|
769
|
+
if (topo.version === 2 && Array.isArray(topo.sessions)) {
|
|
770
|
+
// Hierarchical tree rendering
|
|
771
|
+
topo.sessions.forEach(function(session) {
|
|
772
|
+
list.appendChild(buildSessionGroup(session));
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// Unlinked section
|
|
776
|
+
if (topo.unlinked && topo.unlinked.length > 0) {
|
|
777
|
+
var unlinkedSection = document.createElement('div');
|
|
778
|
+
unlinkedSection.className = 'unlinked-section';
|
|
779
|
+
var label = document.createElement('div');
|
|
780
|
+
label.className = 'child-group-label';
|
|
781
|
+
label.textContent = 'Unlinked';
|
|
782
|
+
unlinkedSection.appendChild(label);
|
|
783
|
+
topo.unlinked.forEach(function(agent) {
|
|
784
|
+
unlinkedSection.appendChild(buildChildRow(agent, null));
|
|
785
|
+
});
|
|
786
|
+
list.appendChild(unlinkedSection);
|
|
787
|
+
}
|
|
788
|
+
} else {
|
|
789
|
+
// Fallback: flat list from legacy /api/agents
|
|
790
|
+
agents.forEach(function(agent, id) {
|
|
791
|
+
list.appendChild(buildFlatAgentRow(agent, id));
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// "Show all" button when filtered
|
|
796
|
+
if (agentFilter) {
|
|
797
|
+
var allBtn = document.createElement('div');
|
|
798
|
+
allBtn.className = 'agent-row';
|
|
799
|
+
allBtn.style.cssText = 'color:var(--blue);margin-top:4px';
|
|
800
|
+
allBtn.textContent = '\u2190 Show all';
|
|
801
|
+
allBtn.onclick = function() { filterByAgent(null); };
|
|
802
|
+
list.insertBefore(allBtn, list.firstChild);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
var total = agents.size + historyAgents.length;
|
|
806
|
+
document.getElementById('agentsCount').textContent = agents.size + '/' + total;
|
|
807
|
+
} catch(_) {}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function buildSessionGroup(session) {
|
|
811
|
+
var agent = session.agent;
|
|
812
|
+
var id = agent.conversationId;
|
|
813
|
+
var hasChildren = (session.teams && session.teams.length > 0) ||
|
|
814
|
+
(session.subagents && session.subagents.length > 0);
|
|
815
|
+
|
|
816
|
+
var group = document.createElement('div');
|
|
817
|
+
group.className = 'session-group';
|
|
818
|
+
group.dataset.sessionId = id;
|
|
819
|
+
|
|
820
|
+
var header = document.createElement('div');
|
|
821
|
+
header.className = 'session-header' + (agentFilter === id ? ' active' : '');
|
|
822
|
+
|
|
823
|
+
var toggleSpan = document.createElement('span');
|
|
824
|
+
toggleSpan.className = 'session-toggle';
|
|
825
|
+
toggleSpan.textContent = hasChildren ? '\u25bc' : '';
|
|
826
|
+
header.appendChild(toggleSpan);
|
|
827
|
+
|
|
828
|
+
var badge = document.createElement('span');
|
|
829
|
+
badge.className = 'agent-badge badge-main';
|
|
830
|
+
badge.textContent = 'main';
|
|
831
|
+
header.appendChild(badge);
|
|
832
|
+
|
|
833
|
+
var modelShort = agent.model ? agent.model.split('-').pop() : '';
|
|
834
|
+
if (modelShort) {
|
|
835
|
+
var modelSpan = document.createElement('span');
|
|
836
|
+
modelSpan.className = 'agent-model';
|
|
837
|
+
modelSpan.textContent = modelShort;
|
|
838
|
+
header.appendChild(modelSpan);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
var reqSpan = document.createElement('span');
|
|
842
|
+
reqSpan.className = 'agent-requests';
|
|
843
|
+
reqSpan.textContent = (agent.requestCount || 0) + ' req';
|
|
844
|
+
header.appendChild(reqSpan);
|
|
845
|
+
|
|
846
|
+
var ctxBtn = document.createElement('button');
|
|
847
|
+
ctxBtn.className = 'agent-context-btn';
|
|
848
|
+
ctxBtn.textContent = 'view';
|
|
849
|
+
ctxBtn.onclick = function(e) { e.stopPropagation(); openContextPanel(id); };
|
|
850
|
+
header.appendChild(ctxBtn);
|
|
851
|
+
|
|
852
|
+
header.onclick = function(e) {
|
|
853
|
+
if (e.target === ctxBtn) return;
|
|
854
|
+
if (hasChildren) {
|
|
855
|
+
var childrenEl = group.querySelector('.session-children');
|
|
856
|
+
var isCollapsed = childrenEl && childrenEl.classList.contains('collapsed');
|
|
857
|
+
if (childrenEl) childrenEl.classList.toggle('collapsed');
|
|
858
|
+
toggleSpan.textContent = isCollapsed ? '\u25bc' : '\u25b6';
|
|
859
|
+
}
|
|
860
|
+
filterBySession(id, session);
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
group.appendChild(header);
|
|
864
|
+
|
|
865
|
+
if (hasChildren) {
|
|
866
|
+
var children = document.createElement('div');
|
|
867
|
+
children.className = 'session-children';
|
|
868
|
+
|
|
869
|
+
// Team children
|
|
870
|
+
(session.teams || []).forEach(function(team) {
|
|
871
|
+
if (!team.members || team.members.length === 0) return;
|
|
872
|
+
var teamLabel = document.createElement('div');
|
|
873
|
+
teamLabel.className = 'child-group-label';
|
|
874
|
+
teamLabel.textContent = 'Team: ' + team.name;
|
|
875
|
+
children.appendChild(teamLabel);
|
|
876
|
+
team.members.forEach(function(m) {
|
|
877
|
+
children.appendChild(buildChildRow(m.agent, m.parentLink));
|
|
878
|
+
});
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
// Subagent children
|
|
882
|
+
if (session.subagents && session.subagents.length > 0) {
|
|
883
|
+
var subLabel = document.createElement('div');
|
|
884
|
+
subLabel.className = 'child-group-label';
|
|
885
|
+
subLabel.textContent = 'Subagents';
|
|
886
|
+
children.appendChild(subLabel);
|
|
887
|
+
session.subagents.forEach(function(s) {
|
|
888
|
+
children.appendChild(buildChildRow(s.agent, s.parentLink));
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
group.appendChild(children);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return group;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function buildChildRow(agent, parentLink) {
|
|
899
|
+
var id = agent.conversationId;
|
|
900
|
+
var row = document.createElement('div');
|
|
901
|
+
row.className = 'child-row' + (agentFilter === id ? ' active' : '');
|
|
902
|
+
row.dataset.agentId = id;
|
|
903
|
+
|
|
904
|
+
var connText = '\u2500\u2500'; // solid ──
|
|
905
|
+
var connClass = 'solid';
|
|
906
|
+
var connTitle = '';
|
|
907
|
+
if (parentLink) {
|
|
908
|
+
if (parentLink.confidence === 'inferred') {
|
|
909
|
+
connText = '\u254c\u254c'; // dashed ╌╌
|
|
910
|
+
connClass = 'dashed';
|
|
911
|
+
}
|
|
912
|
+
if (parentLink.method === 'team-config') connTitle = 'Linked via team config (confirmed)';
|
|
913
|
+
else if (parentLink.method === 'temporal' && parentLink.confidence === 'high') connTitle = 'Linked via temporal correlation (high confidence)';
|
|
914
|
+
else if (parentLink.method === 'content-match') connTitle = 'Linked via content matching (confirmed)';
|
|
915
|
+
else if (parentLink.method === 'temporal' && parentLink.confidence === 'inferred') connTitle = 'Linked via temporal fallback (inferred)';
|
|
916
|
+
else if (parentLink.method === 'direct') connTitle = 'Linked via direct assignment (confirmed)';
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
var connSpan = document.createElement('span');
|
|
920
|
+
connSpan.className = 'connector ' + connClass;
|
|
921
|
+
connSpan.textContent = connText;
|
|
922
|
+
if (connTitle) connSpan.title = connTitle;
|
|
923
|
+
row.appendChild(connSpan);
|
|
924
|
+
|
|
925
|
+
var badge = document.createElement('span');
|
|
926
|
+
badge.className = 'agent-badge badge-' + escClass(agent.type);
|
|
927
|
+
badge.textContent = agent.teamRole || agent.type || 'unknown';
|
|
928
|
+
row.appendChild(badge);
|
|
929
|
+
|
|
930
|
+
var modelShort = agent.model ? agent.model.split('-').pop() : '';
|
|
931
|
+
if (modelShort) {
|
|
932
|
+
var modelSpan = document.createElement('span');
|
|
933
|
+
modelSpan.className = 'agent-model';
|
|
934
|
+
modelSpan.textContent = modelShort;
|
|
935
|
+
row.appendChild(modelSpan);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
var reqSpan = document.createElement('span');
|
|
939
|
+
reqSpan.className = 'agent-requests';
|
|
940
|
+
reqSpan.textContent = (agent.requestCount || 0) + ' req';
|
|
941
|
+
row.appendChild(reqSpan);
|
|
942
|
+
|
|
943
|
+
var ctxBtn = document.createElement('button');
|
|
944
|
+
ctxBtn.className = 'agent-context-btn';
|
|
945
|
+
ctxBtn.textContent = 'view';
|
|
946
|
+
ctxBtn.onclick = function(e) { e.stopPropagation(); openContextPanel(id); };
|
|
947
|
+
row.appendChild(ctxBtn);
|
|
948
|
+
|
|
949
|
+
row.onclick = function(e) { if (e.target !== ctxBtn) filterByAgent(id); };
|
|
950
|
+
|
|
951
|
+
return row;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function buildFlatAgentRow(agent, id) {
|
|
955
|
+
var row = document.createElement('div');
|
|
956
|
+
row.className = 'agent-row' + (agentFilter === id ? ' active' : '');
|
|
957
|
+
var modelShort = agent.model ? agent.model.split('-').pop() : '';
|
|
958
|
+
|
|
959
|
+
var badge = document.createElement('span');
|
|
960
|
+
badge.className = 'agent-badge badge-' + escClass(agent.type);
|
|
961
|
+
badge.textContent = agent.type || 'unknown';
|
|
962
|
+
row.appendChild(badge);
|
|
963
|
+
|
|
964
|
+
var modelSpan = document.createElement('span');
|
|
965
|
+
modelSpan.className = 'agent-model';
|
|
966
|
+
modelSpan.textContent = modelShort;
|
|
967
|
+
row.appendChild(modelSpan);
|
|
968
|
+
|
|
969
|
+
var reqSpan = document.createElement('span');
|
|
970
|
+
reqSpan.className = 'agent-requests';
|
|
971
|
+
reqSpan.textContent = (agent.requestCount || 0) + ' req';
|
|
972
|
+
row.appendChild(reqSpan);
|
|
973
|
+
|
|
974
|
+
var ctxBtn = document.createElement('button');
|
|
975
|
+
ctxBtn.className = 'agent-context-btn';
|
|
976
|
+
ctxBtn.textContent = 'view';
|
|
977
|
+
ctxBtn.onclick = function(e) { e.stopPropagation(); openContextPanel(id); };
|
|
978
|
+
row.appendChild(ctxBtn);
|
|
979
|
+
|
|
980
|
+
row.onclick = function(e) { if (e.target === row) filterByAgent(id); };
|
|
981
|
+
return row;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// Filter cards to a session (main + all its children) or a specific child
|
|
985
|
+
function filterBySession(mainId, session) {
|
|
986
|
+
var ids = new Set([mainId]);
|
|
987
|
+
(session.teams || []).forEach(function(t) {
|
|
988
|
+
(t.members || []).forEach(function(m) { ids.add(m.agent.conversationId); });
|
|
989
|
+
});
|
|
990
|
+
(session.subagents || []).forEach(function(s) { ids.add(s.agent.conversationId); });
|
|
991
|
+
|
|
992
|
+
agentFilter = mainId;
|
|
993
|
+
document.querySelectorAll('.card').forEach(function(card) {
|
|
994
|
+
card.style.display = ids.has(card.dataset.agentId) ? '' : 'none';
|
|
995
|
+
});
|
|
996
|
+
renderSessionTree();
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function renderHistory() {
|
|
1000
|
+
var list = document.getElementById('historyList');
|
|
1001
|
+
var count = document.getElementById('historyCount');
|
|
1002
|
+
count.textContent = historyAgents.length;
|
|
1003
|
+
list.innerHTML = '';
|
|
1004
|
+
historyAgents.forEach(function(h) {
|
|
1005
|
+
var row = document.createElement('div');
|
|
1006
|
+
row.className = 'history-row';
|
|
1007
|
+
var dur = Math.round((h.duration || 0) / 1000);
|
|
1008
|
+
var type = (h.info && h.info.type) ? h.info.type : 'unknown';
|
|
1009
|
+
row.innerHTML =
|
|
1010
|
+
'<span class="agent-badge badge-' + escClass(type) + '">' + esc(type) + '</span> ' +
|
|
1011
|
+
dur + 's \u00b7 ' + (h.totalRequests || 0) + ' req \u00b7 ' + esc(h.completionReason || '');
|
|
1012
|
+
list.appendChild(row);
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function toggleHistory() {
|
|
1017
|
+
var list = document.getElementById('historyList');
|
|
1018
|
+
var arrow = document.getElementById('historyArrow');
|
|
1019
|
+
list.classList.toggle('open');
|
|
1020
|
+
arrow.textContent = list.classList.contains('open') ? '\u25bc' : '\u25ba';
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function filterByAgent(id) {
|
|
1024
|
+
agentFilter = id;
|
|
1025
|
+
renderSessionTree();
|
|
1026
|
+
document.querySelectorAll('.card').forEach(function(card) {
|
|
1027
|
+
if (!id) { card.style.display = ''; return; }
|
|
1028
|
+
card.style.display = card.dataset.agentId === id ? '' : 'none';
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
async function loadAgents() {
|
|
1033
|
+
try {
|
|
1034
|
+
var histRes = await apiFetch('/api/agents/history');
|
|
1035
|
+
var histData = await histRes.json();
|
|
1036
|
+
historyAgents = histData.history || [];
|
|
1037
|
+
renderHistory();
|
|
1038
|
+
} catch(_) {}
|
|
1039
|
+
renderSessionTree();
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
loadAgents();
|
|
1043
|
+
|
|
1044
|
+
// SSE — connect to /api/pipeline/live with token auth via query param
|
|
1045
|
+
var sseUrl = '/api/pipeline/live?token=' + encodeURIComponent(window.CORTEX_API_TOKEN || '');
|
|
1046
|
+
var eventSource = new EventSource(sseUrl);
|
|
1047
|
+
|
|
1048
|
+
eventSource.onmessage = function(e) {
|
|
1049
|
+
var event;
|
|
1050
|
+
try { event = JSON.parse(e.data); } catch(_) { return; }
|
|
1051
|
+
if (event.type === 'ping') return;
|
|
1052
|
+
|
|
1053
|
+
if (event.type === 'agent:detected') {
|
|
1054
|
+
// Update local agents map for request-to-agent mapping
|
|
1055
|
+
var existing = agents.get(event.agentId);
|
|
1056
|
+
agents.set(event.agentId, {
|
|
1057
|
+
conversationId: event.agentId,
|
|
1058
|
+
type: event.agentType,
|
|
1059
|
+
model: event.model,
|
|
1060
|
+
toolCount: event.toolCount,
|
|
1061
|
+
teamName: event.teamName,
|
|
1062
|
+
teamRole: event.teamRole,
|
|
1063
|
+
requestCount: existing ? (existing.requestCount || 0) + 1 : 1,
|
|
1064
|
+
});
|
|
1065
|
+
// Map this request to the agent for card tagging
|
|
1066
|
+
if (event.requestId !== undefined) {
|
|
1067
|
+
requestAgentMap.set(event.requestId, { agentId: event.agentId, agentType: event.agentType });
|
|
1068
|
+
}
|
|
1069
|
+
document.getElementById('agentSection').style.display = '';
|
|
1070
|
+
document.getElementById('agentsStat').style.display = '';
|
|
1071
|
+
renderSessionTree();
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
if (event.type === 'agent:completed') {
|
|
1076
|
+
agents.delete(event.agentId);
|
|
1077
|
+
historyAgents.unshift({
|
|
1078
|
+
info: { type: event.agentType, conversationId: event.agentId },
|
|
1079
|
+
totalRequests: event.totalRequests,
|
|
1080
|
+
duration: event.duration,
|
|
1081
|
+
completionReason: event.completionReason,
|
|
1082
|
+
});
|
|
1083
|
+
renderSessionTree();
|
|
1084
|
+
renderHistory();
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
var rid = event.requestId;
|
|
1089
|
+
if (rid === undefined) return;
|
|
1090
|
+
|
|
1091
|
+
if (!requests[rid]) {
|
|
1092
|
+
requests[rid] = { events: [] };
|
|
1093
|
+
}
|
|
1094
|
+
requests[rid].events.push(event);
|
|
1095
|
+
|
|
1096
|
+
// Map request to agent from request:start if available
|
|
1097
|
+
if (event.type === 'request:start' && event.agentId && !requestAgentMap.has(rid)) {
|
|
1098
|
+
requestAgentMap.set(rid, { agentId: event.agentId, agentType: event.agentType || 'unknown' });
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
if (event.type === 'request:end') {
|
|
1102
|
+
// Refresh header stats from server after each completed request
|
|
1103
|
+
loadSessionStats();
|
|
1104
|
+
renderCard(rid);
|
|
1105
|
+
}
|
|
1106
|
+
if (event.type === 'pipeline:error') {
|
|
1107
|
+
renderCard(rid);
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
function renderCard(rid) {
|
|
1112
|
+
var data = requests[rid];
|
|
1113
|
+
if (!data) return;
|
|
1114
|
+
|
|
1115
|
+
var evts = data.events;
|
|
1116
|
+
var startEvt = find(evts, 'request:start');
|
|
1117
|
+
var pruneEvt = find(evts, 'prune:complete');
|
|
1118
|
+
var fpEvt = find(evts, 'fingerprint:complete');
|
|
1119
|
+
var matchEvt = find(evts, 'match:complete');
|
|
1120
|
+
var injectEvt = find(evts, 'inject:complete');
|
|
1121
|
+
var endEvt = find(evts, 'request:end');
|
|
1122
|
+
var errEvt = find(evts, 'pipeline:error');
|
|
1123
|
+
|
|
1124
|
+
// Determine status
|
|
1125
|
+
var status = 'passthrough';
|
|
1126
|
+
if (errEvt) status = 'error';
|
|
1127
|
+
else if (injectEvt && injectEvt.injected) status = 'injected';
|
|
1128
|
+
else if (pruneEvt && pruneEvt.tokensSaved > 0) status = 'pruned';
|
|
1129
|
+
|
|
1130
|
+
// Look up agent info for this request
|
|
1131
|
+
var agentInfo = requestAgentMap.get(rid);
|
|
1132
|
+
var agentId = agentInfo ? agentInfo.agentId : '';
|
|
1133
|
+
var agentType = agentInfo ? agentInfo.agentType : '';
|
|
1134
|
+
|
|
1135
|
+
// Build card HTML
|
|
1136
|
+
var html = '<div class="card-header">';
|
|
1137
|
+
html += '<span class="card-id">Request #' + rid + '</span>';
|
|
1138
|
+
if (agentId) {
|
|
1139
|
+
html += ' <span class="agent-badge badge-' + escClass(agentType) + '">' + esc(agentType) + '</span>';
|
|
1140
|
+
}
|
|
1141
|
+
html += '<span class="card-meta">';
|
|
1142
|
+
if (startEvt) html += formatTime(startEvt.timestamp);
|
|
1143
|
+
if (endEvt) html += ' · ' + endEvt.totalTimeMs + 'ms';
|
|
1144
|
+
html += '</span></div>';
|
|
1145
|
+
|
|
1146
|
+
if (pruneEvt) {
|
|
1147
|
+
html += line('Prune',
|
|
1148
|
+
pruneEvt.tokensBefore + ' → ' + pruneEvt.tokensAfter +
|
|
1149
|
+
' <span class="' + (pruneEvt.tokensSaved > 0 ? 'green' : '') + '">(saved ' + pruneEvt.tokensSaved + ')</span>');
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
if (fpEvt) {
|
|
1153
|
+
html += line('Fingerprint',
|
|
1154
|
+
'<span class="value">' + esc(fpEvt.languages.join(', ')) + '</span>' +
|
|
1155
|
+
(fpEvt.errorTypes.length ? ' · ' + esc(fpEvt.errorTypes.join(', ')) : '') +
|
|
1156
|
+
' · ' + fpEvt.phase);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (matchEvt && matchEvt.topMatches.length) {
|
|
1160
|
+
var matches = matchEvt.topMatches.slice(0, 3).map(function(m) {
|
|
1161
|
+
return esc(m.title) + ' (' + m.score.toFixed(2) + ')';
|
|
1162
|
+
}).join(', ');
|
|
1163
|
+
html += line('Match', '<span class="value">' + matches + '</span>');
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
if (injectEvt) {
|
|
1167
|
+
if (injectEvt.injected) {
|
|
1168
|
+
html += line('Inject',
|
|
1169
|
+
'<span class="green">✓ ' + esc(injectEvt.entryTitle || '') +
|
|
1170
|
+
' (' + (injectEvt.confidence ? injectEvt.confidence.toFixed(2) : '?') + ')</span>');
|
|
1171
|
+
} else {
|
|
1172
|
+
html += line('Inject', '<span class="red">✗ no injection</span>');
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
if (errEvt) {
|
|
1177
|
+
html += line('Error', '<span class="red">[' + esc(errEvt.stage) + '] ' + esc(errEvt.error) + '</span>');
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
if (endEvt && endEvt.reason) {
|
|
1181
|
+
html += line('Reason', '<span class="value">' + esc(endEvt.reason) + '</span>');
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
var card = document.createElement('div');
|
|
1185
|
+
card.className = 'card status-' + status;
|
|
1186
|
+
card.setAttribute('data-rid', rid);
|
|
1187
|
+
card.dataset.agentId = agentId;
|
|
1188
|
+
card.innerHTML = html;
|
|
1189
|
+
|
|
1190
|
+
if (emptyEl) emptyEl.style.display = 'none';
|
|
1191
|
+
|
|
1192
|
+
var existing = streamEl.querySelector('[data-rid="' + rid + '"]');
|
|
1193
|
+
if (existing) existing.remove();
|
|
1194
|
+
|
|
1195
|
+
streamEl.insertBefore(card, streamEl.firstChild);
|
|
1196
|
+
|
|
1197
|
+
while (streamEl.children.length > MAX_CARDS + 1) {
|
|
1198
|
+
streamEl.removeChild(streamEl.lastChild);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// Helpers
|
|
1203
|
+
function find(arr, type) {
|
|
1204
|
+
for (var i = arr.length - 1; i >= 0; i--) {
|
|
1205
|
+
if (arr[i].type === type) return arr[i];
|
|
1206
|
+
}
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function line(label, content) {
|
|
1211
|
+
return '<div class="card-line"><span class="label">' + label + '</span>' + content + '</div>';
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function esc(s) {
|
|
1215
|
+
if (!s) return '';
|
|
1216
|
+
var d = document.createElement('div');
|
|
1217
|
+
d.textContent = s;
|
|
1218
|
+
return d.innerHTML;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function escClass(s) {
|
|
1222
|
+
if (!s) return 'unknown';
|
|
1223
|
+
return s.replace(/[^a-zA-Z0-9-_]/g, '');
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function formatTime(ts) {
|
|
1227
|
+
var d = new Date(ts);
|
|
1228
|
+
return d.toLocaleTimeString();
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function formatNum(n) {
|
|
1232
|
+
if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
|
|
1233
|
+
if (n >= 1000) return (n / 1000).toFixed(1) + 'k';
|
|
1234
|
+
return '' + n;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// ── Context Window Panel ──────────────────────────────────────────────────
|
|
1238
|
+
|
|
1239
|
+
var contextAgentId = null;
|
|
1240
|
+
var contextSnapshot = null;
|
|
1241
|
+
var contextViewLevel = 'overview';
|
|
1242
|
+
|
|
1243
|
+
function openContextPanel(agentId) {
|
|
1244
|
+
contextAgentId = agentId;
|
|
1245
|
+
var agent = agents.get(agentId);
|
|
1246
|
+
var label = agent
|
|
1247
|
+
? (agent.type || 'agent') + (agent.teamRole ? ' (' + agent.teamRole + ')' : '')
|
|
1248
|
+
: agentId.slice(0, 8);
|
|
1249
|
+
document.getElementById('contextTitle').textContent = 'Context: ' + label;
|
|
1250
|
+
document.getElementById('contextOverlay').classList.add('open');
|
|
1251
|
+
document.getElementById('contextPanel').classList.add('open');
|
|
1252
|
+
document.getElementById('contextContent').innerHTML = '<p style="color:var(--text-muted);font-size:13px">Loading...</p>';
|
|
1253
|
+
document.getElementById('contextStats').innerHTML = '';
|
|
1254
|
+
document.getElementById('contextTimeline').innerHTML = '';
|
|
1255
|
+
// Reset to overview tab
|
|
1256
|
+
setViewLevel('overview', document.getElementById('ctxTabOverview'));
|
|
1257
|
+
loadRequestTimeline(agentId);
|
|
1258
|
+
document.addEventListener('keydown', contextEscHandler);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
function closeContextPanel() {
|
|
1262
|
+
document.getElementById('contextOverlay').classList.remove('open');
|
|
1263
|
+
document.getElementById('contextPanel').classList.remove('open');
|
|
1264
|
+
document.removeEventListener('keydown', contextEscHandler);
|
|
1265
|
+
contextAgentId = null;
|
|
1266
|
+
contextSnapshot = null;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function contextEscHandler(e) {
|
|
1270
|
+
if (e.key === 'Escape') closeContextPanel();
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
async function loadRequestTimeline(agentId) {
|
|
1274
|
+
try {
|
|
1275
|
+
var res = await apiFetch('/api/agents/' + encodeURIComponent(agentId) + '/requests');
|
|
1276
|
+
if (!res.ok) throw new Error('status ' + res.status);
|
|
1277
|
+
var data = await res.json();
|
|
1278
|
+
renderTimeline(data.requests || [], data.capped);
|
|
1279
|
+
if (data.requests && data.requests.length > 0) {
|
|
1280
|
+
// Load the most recent request
|
|
1281
|
+
loadSnapshot(agentId, data.requests[data.requests.length - 1].requestId);
|
|
1282
|
+
} else {
|
|
1283
|
+
document.getElementById('contextContent').innerHTML = '<p style="color:var(--text-muted);font-size:13px">No requests captured yet.</p>';
|
|
1284
|
+
}
|
|
1285
|
+
} catch(e) {
|
|
1286
|
+
document.getElementById('contextContent').innerHTML = '<p style="color:var(--red);font-size:13px">Failed to load request history.</p>';
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
async function loadSnapshot(agentId, requestId) {
|
|
1291
|
+
document.getElementById('contextContent').innerHTML = '<p style="color:var(--text-muted);font-size:13px">Loading...</p>';
|
|
1292
|
+
try {
|
|
1293
|
+
var res = await apiFetch('/api/agents/' + encodeURIComponent(agentId) + '/requests/' + requestId);
|
|
1294
|
+
if (!res.ok) throw new Error('status ' + res.status);
|
|
1295
|
+
var data = await res.json();
|
|
1296
|
+
contextSnapshot = data.snapshot;
|
|
1297
|
+
renderViewLevel();
|
|
1298
|
+
renderContextStats();
|
|
1299
|
+
// Highlight active timeline item
|
|
1300
|
+
document.querySelectorAll('.context-timeline-item').forEach(function(el) {
|
|
1301
|
+
el.classList.toggle('active', parseInt(el.dataset.requestId, 10) === requestId);
|
|
1302
|
+
});
|
|
1303
|
+
} catch(e) {
|
|
1304
|
+
document.getElementById('contextContent').innerHTML = '<p style="color:var(--red);font-size:13px">Failed to load snapshot.</p>';
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
function renderTimeline(requests, capped) {
|
|
1309
|
+
var el = document.getElementById('contextTimeline');
|
|
1310
|
+
el.innerHTML = '';
|
|
1311
|
+
// Newest first
|
|
1312
|
+
requests.slice().reverse().forEach(function(r) {
|
|
1313
|
+
var item = document.createElement('div');
|
|
1314
|
+
item.className = 'context-timeline-item';
|
|
1315
|
+
item.dataset.requestId = r.requestId;
|
|
1316
|
+
var ago = Math.round((Date.now() - r.timestamp) / 1000);
|
|
1317
|
+
var agoStr = ago < 60 ? ago + 's' : Math.round(ago / 60) + 'm';
|
|
1318
|
+
item.textContent = '#' + r.requestId + ' ' + agoStr;
|
|
1319
|
+
item.title = r.messageCount + ' msgs, ' + r.tokensBefore + ' tok';
|
|
1320
|
+
(function(rid) {
|
|
1321
|
+
item.onclick = function() { loadSnapshot(contextAgentId, rid); };
|
|
1322
|
+
})(r.requestId);
|
|
1323
|
+
el.appendChild(item);
|
|
1324
|
+
});
|
|
1325
|
+
if (capped) {
|
|
1326
|
+
var btn = document.createElement('div');
|
|
1327
|
+
btn.className = 'context-timeline-item';
|
|
1328
|
+
btn.style.color = 'var(--blue)';
|
|
1329
|
+
btn.style.marginTop = '8px';
|
|
1330
|
+
btn.style.borderTop = '1px solid var(--border)';
|
|
1331
|
+
btn.style.paddingTop = '8px';
|
|
1332
|
+
btn.textContent = 'Keep all future';
|
|
1333
|
+
btn.onclick = function() {
|
|
1334
|
+
if (!contextAgentId) return;
|
|
1335
|
+
apiFetch('/api/agents/' + encodeURIComponent(contextAgentId) + '/uncap-history', { method: 'POST' })
|
|
1336
|
+
.then(function() {
|
|
1337
|
+
btn.textContent = 'Uncapped';
|
|
1338
|
+
btn.style.color = 'var(--green)';
|
|
1339
|
+
btn.onclick = null;
|
|
1340
|
+
})
|
|
1341
|
+
.catch(function() {});
|
|
1342
|
+
};
|
|
1343
|
+
el.appendChild(btn);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
function setViewLevel(level, tabEl) {
|
|
1348
|
+
contextViewLevel = level;
|
|
1349
|
+
document.querySelectorAll('.context-tab').forEach(function(t) { t.classList.remove('active'); });
|
|
1350
|
+
if (tabEl) tabEl.classList.add('active');
|
|
1351
|
+
renderViewLevel();
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
function renderViewLevel() {
|
|
1355
|
+
if (!contextSnapshot) return;
|
|
1356
|
+
var el = document.getElementById('contextContent');
|
|
1357
|
+
if (contextViewLevel === 'overview') {
|
|
1358
|
+
el.innerHTML = renderOverview(contextSnapshot);
|
|
1359
|
+
} else if (contextViewLevel === 'expanded') {
|
|
1360
|
+
el.innerHTML = renderExpanded(contextSnapshot);
|
|
1361
|
+
} else {
|
|
1362
|
+
el.innerHTML = renderCortexOnly(contextSnapshot);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// Level A — compact message table
|
|
1367
|
+
function renderOverview(snap) {
|
|
1368
|
+
var ops = snap.operations || [];
|
|
1369
|
+
var prunedIndexes = new Set();
|
|
1370
|
+
var injectedIndexes = new Set();
|
|
1371
|
+
ops.forEach(function(op) {
|
|
1372
|
+
if (op.type === 'prune') prunedIndexes.add(op.messageIndex);
|
|
1373
|
+
if (op.type === 'inject') injectedIndexes.add(op.insertedAtIndex);
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
var html = '';
|
|
1377
|
+
if (snap.systemPromptPreview) {
|
|
1378
|
+
html += '<div class="msg-expanded-header">System: ' + esc(snap.systemPromptPreview.slice(0, 80)) + (snap.systemPromptPreview.length > 80 ? '...' : '') + '</div>';
|
|
1379
|
+
}
|
|
1380
|
+
(snap.messagesSummary || []).forEach(function(msg) {
|
|
1381
|
+
var markers = '';
|
|
1382
|
+
if (prunedIndexes.has(msg.index)) markers += '<span class="msg-marker pruned" title="Pruned"></span>';
|
|
1383
|
+
if (injectedIndexes.has(msg.index)) markers += '<span class="msg-marker injected" title="Injected before this"></span>';
|
|
1384
|
+
|
|
1385
|
+
html += '<div class="msg-row" onclick="setViewLevel(\'expanded\', document.getElementById(\'ctxTabExpanded\'))">';
|
|
1386
|
+
html += '<span class="msg-role ' + escClass(msg.role) + '">' + esc(msg.role) + '</span>';
|
|
1387
|
+
html += '<span class="msg-type">' + esc(msg.contentType) + '</span>';
|
|
1388
|
+
html += '<span class="msg-preview">' + esc(msg.preview || '') + '</span>';
|
|
1389
|
+
html += '<span class="msg-tokens">' + msg.tokenCount + ' tok</span>';
|
|
1390
|
+
if (markers) html += '<span class="msg-markers">' + markers + '</span>';
|
|
1391
|
+
html += '</div>';
|
|
1392
|
+
});
|
|
1393
|
+
if (!snap.messagesSummary || snap.messagesSummary.length === 0) {
|
|
1394
|
+
html += '<p style="color:var(--text-muted);font-size:13px">No messages.</p>';
|
|
1395
|
+
}
|
|
1396
|
+
return html;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// Level B — full expanded messages with diff highlights
|
|
1400
|
+
function renderExpanded(snap) {
|
|
1401
|
+
var ops = snap.operations || [];
|
|
1402
|
+
var prunesByMsg = {};
|
|
1403
|
+
var injectOp = null;
|
|
1404
|
+
ops.forEach(function(op) {
|
|
1405
|
+
if (op.type === 'prune') {
|
|
1406
|
+
if (!prunesByMsg[op.messageIndex]) prunesByMsg[op.messageIndex] = [];
|
|
1407
|
+
prunesByMsg[op.messageIndex].push(op);
|
|
1408
|
+
}
|
|
1409
|
+
if (op.type === 'inject') injectOp = op;
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
var html = '';
|
|
1413
|
+
if (snap.systemPromptPreview) {
|
|
1414
|
+
html += '<div class="msg-expanded-header">System: ' + esc(snap.systemPromptPreview) + '</div>';
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
var msgs = snap.originalMessages || [];
|
|
1418
|
+
msgs.forEach(function(msg, i) {
|
|
1419
|
+
html += '<div class="msg-expanded-header">[' + esc(msg.role) + '] message ' + i + '</div>';
|
|
1420
|
+
|
|
1421
|
+
// Show injected content if it was inserted before this message
|
|
1422
|
+
if (injectOp && injectOp.insertedAtIndex === i) {
|
|
1423
|
+
html += '<div class="diff-injected">';
|
|
1424
|
+
html += '<div class="diff-injected-header">+ INJECTED: ' + esc(injectOp.brainEntryTitle) + ' (confidence: ' + (injectOp.confidence || 0).toFixed(2) + ')</div>';
|
|
1425
|
+
html += esc(injectOp.content.slice(0, 500)) + (injectOp.content.length > 500 ? '\n...' : '');
|
|
1426
|
+
html += '</div>';
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
var msgPruneOps = prunesByMsg[i] || [];
|
|
1430
|
+
var content = msg.content;
|
|
1431
|
+
|
|
1432
|
+
if (typeof content === 'string') {
|
|
1433
|
+
var pruned = msgPruneOps[0];
|
|
1434
|
+
if (pruned) {
|
|
1435
|
+
html += renderPrunedBlock(pruned);
|
|
1436
|
+
} else {
|
|
1437
|
+
html += '<div class="msg-expanded">' + esc(content.slice(0, 2000)) + (content.length > 2000 ? '\n...[truncated]' : '') + '</div>';
|
|
1438
|
+
}
|
|
1439
|
+
} else if (Array.isArray(content)) {
|
|
1440
|
+
content.forEach(function(block, bi) {
|
|
1441
|
+
var blockPrune = msgPruneOps.find(function(p) { return p.blockIndex === bi; });
|
|
1442
|
+
html += renderContentBlock(block, blockPrune);
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
// Show injected content at end if insertedAtIndex >= msgs.length
|
|
1448
|
+
if (injectOp && injectOp.insertedAtIndex >= msgs.length) {
|
|
1449
|
+
html += '<div class="diff-injected">';
|
|
1450
|
+
html += '<div class="diff-injected-header">+ INJECTED (appended): ' + esc(injectOp.brainEntryTitle) + ' (confidence: ' + (injectOp.confidence || 0).toFixed(2) + ')</div>';
|
|
1451
|
+
html += esc(injectOp.content.slice(0, 500)) + (injectOp.content.length > 500 ? '\n...' : '');
|
|
1452
|
+
html += '</div>';
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
if (msgs.length === 0) html += '<p style="color:var(--text-muted);font-size:13px">No messages stored.</p>';
|
|
1456
|
+
return html;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
function renderContentBlock(block, pruneOp) {
|
|
1460
|
+
var html = '';
|
|
1461
|
+
if (block.type === 'text') {
|
|
1462
|
+
if (pruneOp) {
|
|
1463
|
+
html += renderPrunedBlock(pruneOp);
|
|
1464
|
+
} else {
|
|
1465
|
+
var txt = block.text || '';
|
|
1466
|
+
html += '<div class="msg-expanded">' + esc(txt.slice(0, 2000)) + (txt.length > 2000 ? '\n...[truncated]' : '') + '</div>';
|
|
1467
|
+
}
|
|
1468
|
+
} else if (block.type === 'tool_use') {
|
|
1469
|
+
html += '<div class="msg-expanded"><span style="color:var(--yellow)">[tool_use] ' + esc(block.name || '') + '</span>';
|
|
1470
|
+
if (block.input) {
|
|
1471
|
+
var inp = JSON.stringify(block.input, null, 2);
|
|
1472
|
+
html += '\n' + esc(inp.slice(0, 500)) + (inp.length > 500 ? '\n...' : '');
|
|
1473
|
+
}
|
|
1474
|
+
html += '</div>';
|
|
1475
|
+
} else if (block.type === 'tool_result') {
|
|
1476
|
+
html += '<div class="msg-expanded"><span style="color:var(--text-muted)">[tool_result]</span>\n';
|
|
1477
|
+
if (typeof block.content === 'string') {
|
|
1478
|
+
if (pruneOp) {
|
|
1479
|
+
html += renderPrunedBlock(pruneOp);
|
|
1480
|
+
} else {
|
|
1481
|
+
html += esc(block.content.slice(0, 1000)) + (block.content.length > 1000 ? '\n...' : '');
|
|
1482
|
+
}
|
|
1483
|
+
} else if (Array.isArray(block.content)) {
|
|
1484
|
+
(block.content || []).forEach(function(sub, si) {
|
|
1485
|
+
var subPrune = pruneOp && pruneOp.subBlockIndex === si ? pruneOp : null;
|
|
1486
|
+
if (sub.type === 'text') {
|
|
1487
|
+
if (subPrune) {
|
|
1488
|
+
html += renderPrunedBlock(subPrune);
|
|
1489
|
+
} else {
|
|
1490
|
+
html += esc((sub.text || '').slice(0, 500)) + ((sub.text || '').length > 500 ? '\n...' : '');
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
html += '</div>';
|
|
1496
|
+
} else if (block.type === 'image') {
|
|
1497
|
+
html += '<div class="msg-expanded" style="color:var(--text-muted)">[image: ' + esc((block.source && block.source.media_type) || 'unknown') + ']</div>';
|
|
1498
|
+
} else if (block.type === 'thinking') {
|
|
1499
|
+
html += '<div class="msg-expanded" style="color:var(--text-muted)">[thinking block — ' + ((block.thinking || '').length) + ' chars]</div>';
|
|
1500
|
+
} else {
|
|
1501
|
+
html += '<div class="msg-expanded" style="color:var(--text-muted)">[' + esc(block.type || 'unknown') + ']</div>';
|
|
1502
|
+
}
|
|
1503
|
+
return html;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
function renderPrunedBlock(op) {
|
|
1507
|
+
var lines = (op.originalContent || '').split('\n');
|
|
1508
|
+
var preview = lines.slice(0, 3).join('\n');
|
|
1509
|
+
var remaining = lines.length - 3;
|
|
1510
|
+
var id = 'prune-' + op.messageIndex + '-' + op.blockIndex + (op.subBlockIndex !== undefined ? '-' + op.subBlockIndex : '');
|
|
1511
|
+
var html = '<div class="diff-pruned">';
|
|
1512
|
+
html += '<div class="diff-pruned-header">- PRUNED: ' + op.tokensBefore + ' tok → ' + op.tokensAfter + ' tok (saved ' + (op.tokensBefore - op.tokensAfter) + ')</div>';
|
|
1513
|
+
html += '<span class="strikethrough">' + esc(preview) + '</span>';
|
|
1514
|
+
if (remaining > 0) {
|
|
1515
|
+
html += '\n<span class="diff-expand" onclick="togglePruneExpand(\'' + id + '\')">Show ' + remaining + ' more pruned lines</span>';
|
|
1516
|
+
html += '<span id="' + id + '" style="display:none"><span class="strikethrough">' + esc(lines.slice(3).join('\n')) + '</span></span>';
|
|
1517
|
+
}
|
|
1518
|
+
html += '\n<span style="color:var(--green);font-size:11px">+ ' + esc(op.prunedContent || '[removed]') + '</span>';
|
|
1519
|
+
html += '</div>';
|
|
1520
|
+
return html;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// Level C — only Cortex operations + surrounding context
|
|
1524
|
+
function renderCortexOnly(snap) {
|
|
1525
|
+
var ops = snap.operations || [];
|
|
1526
|
+
if (ops.length === 0) {
|
|
1527
|
+
return '<p style="color:var(--text-muted);font-size:13px">No Cortex operations in this request.</p>';
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
var msgs = snap.originalMessages || [];
|
|
1531
|
+
var html = '';
|
|
1532
|
+
|
|
1533
|
+
ops.forEach(function(op) {
|
|
1534
|
+
if (op.type === 'prune') {
|
|
1535
|
+
html += '<div class="context-section-title">Prune — message ' + op.messageIndex + ', block ' + op.blockIndex + (op.subBlockIndex !== undefined ? '.' + op.subBlockIndex : '') + '</div>';
|
|
1536
|
+
html += renderPrunedBlock(op);
|
|
1537
|
+
// Show 2 messages of surrounding context
|
|
1538
|
+
var ctxStart = Math.max(0, op.messageIndex - 1);
|
|
1539
|
+
var ctxEnd = Math.min(msgs.length - 1, op.messageIndex + 1);
|
|
1540
|
+
if (ctxStart < op.messageIndex) {
|
|
1541
|
+
var prev = msgs[ctxStart];
|
|
1542
|
+
html += '<div style="opacity:0.5;font-size:11px;padding:4px 8px;color:var(--text-muted)">[context] [' + esc(prev.role) + '] ' + esc(getPreview(prev)) + '</div>';
|
|
1543
|
+
}
|
|
1544
|
+
if (ctxEnd > op.messageIndex && msgs[ctxEnd]) {
|
|
1545
|
+
var next = msgs[ctxEnd];
|
|
1546
|
+
html += '<div style="opacity:0.5;font-size:11px;padding:4px 8px;color:var(--text-muted)">[context] [' + esc(next.role) + '] ' + esc(getPreview(next)) + '</div>';
|
|
1547
|
+
}
|
|
1548
|
+
} else if (op.type === 'inject') {
|
|
1549
|
+
html += '<div class="context-section-title">Inject — at position ' + op.insertedAtIndex + '</div>';
|
|
1550
|
+
// 2 messages of context before injection point
|
|
1551
|
+
var before = msgs.slice(Math.max(0, op.insertedAtIndex - 2), op.insertedAtIndex);
|
|
1552
|
+
before.forEach(function(m) {
|
|
1553
|
+
html += '<div style="opacity:0.5;font-size:11px;padding:4px 8px;color:var(--text-muted)">[context] [' + esc(m.role) + '] ' + esc(getPreview(m)) + '</div>';
|
|
1554
|
+
});
|
|
1555
|
+
html += '<div class="diff-injected">';
|
|
1556
|
+
html += '<div class="diff-injected-header">+ INJECTED: ' + esc(op.brainEntryTitle) + ' (confidence: ' + (op.confidence || 0).toFixed(2) + ')</div>';
|
|
1557
|
+
html += esc(op.content.slice(0, 1000)) + (op.content.length > 1000 ? '\n...' : '');
|
|
1558
|
+
html += '</div>';
|
|
1559
|
+
// 2 messages after
|
|
1560
|
+
var after = msgs.slice(op.insertedAtIndex, op.insertedAtIndex + 2);
|
|
1561
|
+
after.forEach(function(m) {
|
|
1562
|
+
html += '<div style="opacity:0.5;font-size:11px;padding:4px 8px;color:var(--text-muted)">[context] [' + esc(m.role) + '] ' + esc(getPreview(m)) + '</div>';
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
return html;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
function getPreview(msg) {
|
|
1570
|
+
if (typeof msg.content === 'string') return msg.content.slice(0, 80);
|
|
1571
|
+
if (Array.isArray(msg.content)) {
|
|
1572
|
+
var first = msg.content.find(function(b) { return b.type === 'text' && b.text; });
|
|
1573
|
+
if (first) return first.text.slice(0, 80);
|
|
1574
|
+
return '[' + (msg.content[0] ? msg.content[0].type : 'empty') + ']';
|
|
1575
|
+
}
|
|
1576
|
+
return '';
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
function renderContextStats() {
|
|
1580
|
+
if (!contextSnapshot) return;
|
|
1581
|
+
var snap = contextSnapshot;
|
|
1582
|
+
var pruneCount = (snap.operations || []).filter(function(o) { return o.type === 'prune'; }).length;
|
|
1583
|
+
var injectOp = (snap.operations || []).find(function(o) { return o.type === 'inject'; });
|
|
1584
|
+
var saved = snap.tokensBefore - snap.tokensAfter;
|
|
1585
|
+
var el = document.getElementById('contextStats');
|
|
1586
|
+
el.innerHTML =
|
|
1587
|
+
'<span>Tokens: ' + snap.tokensBefore + ' → ' + snap.tokensAfter + ' <span style="color:var(--green)">(-' + saved + ')</span></span>' +
|
|
1588
|
+
(pruneCount ? '<span>Pruned: ' + pruneCount + ' block' + (pruneCount > 1 ? 's' : '') + '</span>' : '') +
|
|
1589
|
+
(injectOp ? '<span style="color:var(--green)">Injected: ' + esc(injectOp.brainEntryTitle) + ' (' + (injectOp.confidence || 0).toFixed(2) + ')</span>' : '<span>No injection</span>') +
|
|
1590
|
+
'<span style="color:var(--text-muted)">' + (snap.messagesSummary || []).length + ' messages</span>';
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
function togglePruneExpand(id) {
|
|
1594
|
+
var el = document.getElementById(id);
|
|
1595
|
+
if (!el) return;
|
|
1596
|
+
var shown = el.style.display !== 'none';
|
|
1597
|
+
el.style.display = shown ? 'none' : 'inline';
|
|
1598
|
+
var btn = el.previousElementSibling;
|
|
1599
|
+
if (btn && btn.classList.contains('diff-expand')) {
|
|
1600
|
+
btn.textContent = shown ? btn.textContent.replace('Hide', 'Show') : btn.textContent.replace('Show', 'Hide');
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// Expose to global scope for onclick handlers
|
|
1605
|
+
window.openContextPanel = openContextPanel;
|
|
1606
|
+
window.closeContextPanel = closeContextPanel;
|
|
1607
|
+
window.setViewLevel = setViewLevel;
|
|
1608
|
+
window.togglePruneExpand = togglePruneExpand;
|
|
1609
|
+
window.toggleHistory = toggleHistory;
|
|
1610
|
+
window.filterByAgent = filterByAgent;
|
|
1611
|
+
|
|
1612
|
+
})();
|
|
1613
|
+
</script>
|
|
1614
|
+
|
|
1615
|
+
</body>
|
|
1616
|
+
</html>
|