agentxchain 2.73.0 → 2.75.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/dashboard/app.js +1 -1
- package/dashboard/components/timeline.js +99 -35
- package/package.json +1 -1
package/dashboard/app.js
CHANGED
|
@@ -19,7 +19,7 @@ import { render as renderTimeouts } from './components/timeouts.js';
|
|
|
19
19
|
import { render as renderCoordinatorTimeouts } from './components/coordinator-timeouts.js';
|
|
20
20
|
|
|
21
21
|
const VIEWS = {
|
|
22
|
-
timeline: { fetch: ['state', 'continuity', 'history', 'audit', 'annotations', 'connectors'], render: renderTimeline },
|
|
22
|
+
timeline: { fetch: ['state', 'continuity', 'history', 'audit', 'annotations', 'connectors', 'coordinatorAudit', 'coordinatorAnnotations'], render: renderTimeline },
|
|
23
23
|
ledger: { fetch: ['state', 'ledger', 'coordinatorState', 'coordinatorLedger'], render: renderLedger },
|
|
24
24
|
hooks: { fetch: ['audit', 'annotations', 'coordinatorAudit', 'coordinatorAnnotations'], render: renderHooks },
|
|
25
25
|
blocked: { fetch: ['state', 'audit', 'coordinatorState', 'coordinatorAudit'], render: renderBlocked },
|
|
@@ -55,6 +55,39 @@ function renderList(items, type) {
|
|
|
55
55
|
.join('');
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function formatDuration(ms) {
|
|
59
|
+
if (ms == null || ms < 0 || !Number.isFinite(ms)) return null;
|
|
60
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
61
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
62
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
63
|
+
const seconds = totalSeconds % 60;
|
|
64
|
+
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
65
|
+
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
66
|
+
return `${seconds}s`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function computeElapsed(startedAt) {
|
|
70
|
+
if (!startedAt) return null;
|
|
71
|
+
try {
|
|
72
|
+
const start = new Date(startedAt).getTime();
|
|
73
|
+
if (Number.isNaN(start)) return null;
|
|
74
|
+
return Math.max(0, Date.now() - start);
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function formatTimestamp(iso) {
|
|
81
|
+
if (!iso) return null;
|
|
82
|
+
try {
|
|
83
|
+
const d = new Date(iso);
|
|
84
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
85
|
+
return d.toLocaleString();
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
58
91
|
function statusBadge(status) {
|
|
59
92
|
const colors = {
|
|
60
93
|
running: 'var(--green)',
|
|
@@ -81,52 +114,74 @@ function roleBadge(role) {
|
|
|
81
114
|
return `<span class="badge" style="color:${color};border-color:${color}">${esc(role)}</span>`;
|
|
82
115
|
}
|
|
83
116
|
|
|
84
|
-
function
|
|
117
|
+
function renderAuditSection(title, auditEntries) {
|
|
118
|
+
if (auditEntries.length === 0) return '';
|
|
119
|
+
let html = `<div class="turn-detail"><span class="detail-label">${esc(title)} (${auditEntries.length}):</span>
|
|
120
|
+
<table class="data-table">
|
|
121
|
+
<thead><tr><th>Phase</th><th>Hook</th><th>Verdict</th></tr></thead>
|
|
122
|
+
<tbody>`;
|
|
123
|
+
for (const entry of auditEntries) {
|
|
124
|
+
const phase = entry.hook_phase || entry.phase || '';
|
|
125
|
+
const hook = entry.hook_name || entry.hook || entry.name || '';
|
|
126
|
+
html += `<tr>
|
|
127
|
+
<td class="mono">${esc(phase)}</td>
|
|
128
|
+
<td>${esc(hook)}</td>
|
|
129
|
+
<td>${esc(entry.verdict || '')}</td>
|
|
130
|
+
</tr>`;
|
|
131
|
+
}
|
|
132
|
+
html += `</tbody></table></div>`;
|
|
133
|
+
return html;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function renderAnnotationSection(title, annotationEntries) {
|
|
137
|
+
if (annotationEntries.length === 0) return '';
|
|
138
|
+
let html = `<div class="turn-detail"><span class="detail-label">${esc(title)} (${annotationEntries.length}):</span><ul>`;
|
|
139
|
+
for (const ann of annotationEntries) {
|
|
140
|
+
const hookName = ann.hook_name || ann.hook || ann.name || '';
|
|
141
|
+
if (Array.isArray(ann.annotations)) {
|
|
142
|
+
for (const a of ann.annotations) {
|
|
143
|
+
html += `<li>${esc(hookName)}: ${esc(a.key || '')} = ${esc(a.value || '')}</li>`;
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
const text = ann.annotation || ann.message || '';
|
|
147
|
+
html += `<li>${esc(hookName)}: ${esc(text)}</li>`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
html += `</ul></div>`;
|
|
151
|
+
return html;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function renderTurnDetailPanel(turnId, annotations, audit, coordinatorAnnotations, coordinatorAudit) {
|
|
85
155
|
const turnAnnotations = Array.isArray(annotations)
|
|
86
156
|
? annotations.filter((a) => a.turn_id === turnId)
|
|
87
157
|
: [];
|
|
88
158
|
const turnAudit = Array.isArray(audit)
|
|
89
159
|
? audit.filter((a) => a.turn_id === turnId)
|
|
90
160
|
: [];
|
|
161
|
+
const turnCoordAnnotations = Array.isArray(coordinatorAnnotations)
|
|
162
|
+
? coordinatorAnnotations.filter((a) => a.turn_id === turnId)
|
|
163
|
+
: [];
|
|
164
|
+
const turnCoordAudit = Array.isArray(coordinatorAudit)
|
|
165
|
+
? coordinatorAudit.filter((a) => a.turn_id === turnId)
|
|
166
|
+
: [];
|
|
167
|
+
|
|
168
|
+
const hasRepo = turnAnnotations.length > 0 || turnAudit.length > 0;
|
|
169
|
+
const hasCoord = turnCoordAnnotations.length > 0 || turnCoordAudit.length > 0;
|
|
91
170
|
|
|
92
|
-
if (
|
|
171
|
+
if (!hasRepo && !hasCoord) {
|
|
93
172
|
return `<div class="turn-detail-panel"><p class="turn-detail">No hook evidence for this turn.</p></div>`;
|
|
94
173
|
}
|
|
95
174
|
|
|
175
|
+
const dual = hasRepo && hasCoord;
|
|
96
176
|
let html = `<div class="turn-detail-panel">`;
|
|
97
177
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<thead><tr><th>Phase</th><th>Hook</th><th>Verdict</th></tr></thead>
|
|
102
|
-
<tbody>`;
|
|
103
|
-
for (const entry of turnAudit) {
|
|
104
|
-
const phase = entry.hook_phase || entry.phase || '';
|
|
105
|
-
const hook = entry.hook_name || entry.hook || entry.name || '';
|
|
106
|
-
html += `<tr>
|
|
107
|
-
<td class="mono">${esc(phase)}</td>
|
|
108
|
-
<td>${esc(hook)}</td>
|
|
109
|
-
<td>${esc(entry.verdict || '')}</td>
|
|
110
|
-
</tr>`;
|
|
111
|
-
}
|
|
112
|
-
html += `</tbody></table></div>`;
|
|
113
|
-
}
|
|
178
|
+
// Repo-local hook evidence
|
|
179
|
+
html += renderAuditSection(dual ? 'Repo Hook Audit Log' : 'Hook Audit Log', turnAudit);
|
|
180
|
+
html += renderAnnotationSection(dual ? 'Repo Hook Annotations' : 'Hook Annotations', turnAnnotations);
|
|
114
181
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const hookName = ann.hook_name || ann.hook || ann.name || '';
|
|
119
|
-
if (Array.isArray(ann.annotations)) {
|
|
120
|
-
for (const a of ann.annotations) {
|
|
121
|
-
html += `<li>${esc(hookName)}: ${esc(a.key || '')} = ${esc(a.value || '')}</li>`;
|
|
122
|
-
}
|
|
123
|
-
} else {
|
|
124
|
-
const text = ann.annotation || ann.message || '';
|
|
125
|
-
html += `<li>${esc(hookName)}: ${esc(text)}</li>`;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
html += `</ul></div>`;
|
|
129
|
-
}
|
|
182
|
+
// Coordinator hook evidence
|
|
183
|
+
html += renderAuditSection(dual ? 'Coordinator Hook Audit Log' : 'Hook Audit Log', turnCoordAudit);
|
|
184
|
+
html += renderAnnotationSection(dual ? 'Coordinator Hook Annotations' : 'Hook Annotations', turnCoordAnnotations);
|
|
130
185
|
|
|
131
186
|
html += `</div>`;
|
|
132
187
|
return html;
|
|
@@ -230,7 +285,9 @@ function renderConnectorHealthPanel(connectorsPayload) {
|
|
|
230
285
|
return html;
|
|
231
286
|
}
|
|
232
287
|
|
|
233
|
-
export
|
|
288
|
+
export { formatDuration, computeElapsed, formatTimestamp };
|
|
289
|
+
|
|
290
|
+
export function render({ state, continuity, history, annotations, audit, connectors, coordinatorAudit = null, coordinatorAnnotations = null }) {
|
|
234
291
|
if (!state) {
|
|
235
292
|
return `<div class="placeholder"><h2>No Run</h2><p>No governed run found. Start one with <code class="mono">agentxchain init --governed</code></p></div>`;
|
|
236
293
|
}
|
|
@@ -257,11 +314,14 @@ export function render({ state, continuity, history, annotations, audit, connect
|
|
|
257
314
|
if (activeTurns.length > 0) {
|
|
258
315
|
html += `<div class="section"><h3>Active Turns</h3><div class="turn-list">`;
|
|
259
316
|
for (const turn of activeTurns) {
|
|
317
|
+
const elapsedMs = computeElapsed(turn.started_at);
|
|
318
|
+
const elapsedStr = formatDuration(elapsedMs);
|
|
260
319
|
html += `<div class="turn-card active">
|
|
261
320
|
<div class="turn-header">
|
|
262
321
|
${roleBadge(getRole(turn))}
|
|
263
322
|
<span class="mono">${esc(turn.turn_id)}</span>
|
|
264
323
|
<span class="turn-status">${esc(turn.status || 'assigned')}</span>
|
|
324
|
+
${elapsedStr ? `<span class="turn-timing">Elapsed: ${esc(elapsedStr)}</span>` : ''}
|
|
265
325
|
</div>
|
|
266
326
|
</div>`;
|
|
267
327
|
}
|
|
@@ -281,10 +341,14 @@ export function render({ state, continuity, history, annotations, audit, connect
|
|
|
281
341
|
|| entry.verification?.evidence_summary
|
|
282
342
|
|| null;
|
|
283
343
|
|
|
344
|
+
const durationStr = formatDuration(entry.duration_ms);
|
|
345
|
+
const acceptedStr = formatTimestamp(entry.accepted_at);
|
|
284
346
|
html += `<div class="turn-card" data-turn-expand="${esc(entry.turn_id)}">
|
|
285
347
|
<div class="turn-header">
|
|
286
348
|
${roleBadge(getRole(entry))}
|
|
287
349
|
<span class="mono">${esc(entry.turn_id)}</span>
|
|
350
|
+
${durationStr ? `<span class="turn-timing">${esc(durationStr)}</span>` : ''}
|
|
351
|
+
${acceptedStr ? `<span class="turn-timestamp">${esc(acceptedStr)}</span>` : ''}
|
|
288
352
|
</div>`;
|
|
289
353
|
|
|
290
354
|
if (entry.summary) {
|
|
@@ -311,7 +375,7 @@ export function render({ state, continuity, history, annotations, audit, connect
|
|
|
311
375
|
html += `<div class="turn-detail"><span class="detail-label">Verification:</span> ${esc(verificationSummary)}</div>`;
|
|
312
376
|
}
|
|
313
377
|
|
|
314
|
-
html += renderTurnDetailPanel(entry.turn_id, annotations, audit);
|
|
378
|
+
html += renderTurnDetailPanel(entry.turn_id, annotations, audit, coordinatorAnnotations, coordinatorAudit);
|
|
315
379
|
|
|
316
380
|
html += `</div>`;
|
|
317
381
|
}
|