agentxchain 2.72.0 → 2.74.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
CHANGED
|
@@ -21,7 +21,7 @@ import { render as renderCoordinatorTimeouts } from './components/coordinator-ti
|
|
|
21
21
|
const VIEWS = {
|
|
22
22
|
timeline: { fetch: ['state', 'continuity', 'history', 'audit', 'annotations', 'connectors'], render: renderTimeline },
|
|
23
23
|
ledger: { fetch: ['state', 'ledger', 'coordinatorState', 'coordinatorLedger'], render: renderLedger },
|
|
24
|
-
hooks: { fetch: ['audit', 'annotations'], render: renderHooks },
|
|
24
|
+
hooks: { fetch: ['audit', 'annotations', 'coordinatorAudit', 'coordinatorAnnotations'], render: renderHooks },
|
|
25
25
|
blocked: { fetch: ['state', 'audit', 'coordinatorState', 'coordinatorAudit'], render: renderBlocked },
|
|
26
26
|
gate: { fetch: ['state', 'history', 'coordinatorState', 'coordinatorHistory', 'coordinatorBarriers'], render: renderGate },
|
|
27
27
|
initiative: { fetch: ['coordinatorState', 'coordinatorBarriers', 'barrierLedger', 'coordinatorBlockers'], render: renderInitiative },
|
|
@@ -46,6 +46,7 @@ const API_MAP = {
|
|
|
46
46
|
coordinatorBarriers: '/api/coordinator/barriers',
|
|
47
47
|
barrierLedger: '/api/coordinator/barrier-ledger',
|
|
48
48
|
coordinatorAudit: '/api/coordinator/hooks/audit',
|
|
49
|
+
coordinatorAnnotations: '/api/coordinator/hooks/annotations',
|
|
49
50
|
coordinatorBlockers: '/api/coordinator/blockers',
|
|
50
51
|
workflowKitArtifacts: '/api/workflow-kit-artifacts',
|
|
51
52
|
connectors: '/api/connectors',
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Hook Audit view — renders hook-audit.jsonl entries.
|
|
3
3
|
*
|
|
4
4
|
* Pure render function: takes data, returns HTML string. Testable in Node.js.
|
|
5
|
+
* Supports both repo-local and coordinator hook audit/annotation data.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
function esc(str) {
|
|
@@ -90,26 +91,82 @@ function collectHookNames(audit) {
|
|
|
90
91
|
return Array.from(unique).sort();
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
function renderAuditTable(entries, filter) {
|
|
95
|
+
const filtered = filterAudit(entries, filter);
|
|
96
|
+
let html = `<p class="section-subtitle">${filtered.length} of ${entries.length} hook execution${entries.length !== 1 ? 's' : ''}</p>
|
|
97
|
+
<table class="data-table">
|
|
98
|
+
<thead><tr><th>Time</th><th>Phase</th><th>Hook</th><th>Verdict</th><th>Action</th><th>Duration</th></tr></thead>
|
|
99
|
+
<tbody>`;
|
|
100
|
+
|
|
101
|
+
for (const entry of filtered) {
|
|
102
|
+
const duration = entry.duration_ms != null ? `${entry.duration_ms}ms` : '-';
|
|
103
|
+
const action = entry.orchestrator_action || entry.action || 'continued';
|
|
104
|
+
html += `<tr>
|
|
105
|
+
<td class="mono">${esc(entry.timestamp || '-')}</td>
|
|
106
|
+
<td class="mono">${esc(getHookPhase(entry))}</td>
|
|
107
|
+
<td>${esc(getHookName(entry))}</td>
|
|
108
|
+
<td>${verdictBadge(entry.verdict)}</td>
|
|
109
|
+
<td class="mono">${esc(action)}</td>
|
|
110
|
+
<td class="mono">${esc(duration)}</td>
|
|
111
|
+
</tr>`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
html += `</tbody></table>`;
|
|
115
|
+
return html;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function renderAnnotationList(entries) {
|
|
119
|
+
let html = `<p class="section-subtitle">${entries.length} annotation${entries.length !== 1 ? 's' : ''}</p>
|
|
120
|
+
<div class="annotation-list">`;
|
|
121
|
+
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
const annotationText = formatAnnotations(entry);
|
|
124
|
+
html += `<div class="annotation-card">
|
|
125
|
+
<span class="mono">${esc(entry.turn_id || '-')}</span>
|
|
126
|
+
<span class="mono">${esc(getHookName(entry))}</span>
|
|
127
|
+
<span>${esc(annotationText || JSON.stringify(entry))}</span>
|
|
128
|
+
</div>`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
html += `</div>`;
|
|
132
|
+
return html;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function render({
|
|
136
|
+
audit,
|
|
137
|
+
annotations,
|
|
138
|
+
coordinatorAudit = null,
|
|
139
|
+
coordinatorAnnotations = null,
|
|
140
|
+
filter = {},
|
|
141
|
+
}) {
|
|
94
142
|
const hasAudit = Array.isArray(audit) && audit.length > 0;
|
|
95
143
|
const hasAnnotations = Array.isArray(annotations) && annotations.length > 0;
|
|
144
|
+
const hasCoordinatorAudit = Array.isArray(coordinatorAudit) && coordinatorAudit.length > 0;
|
|
145
|
+
const hasCoordinatorAnnotations = Array.isArray(coordinatorAnnotations) && coordinatorAnnotations.length > 0;
|
|
146
|
+
const hasAnyData = hasAudit || hasAnnotations || hasCoordinatorAudit || hasCoordinatorAnnotations;
|
|
147
|
+
const hasCoordinatorData = hasCoordinatorAudit || hasCoordinatorAnnotations;
|
|
96
148
|
|
|
97
|
-
if (!
|
|
149
|
+
if (!hasAnyData) {
|
|
98
150
|
return `<div class="placeholder"><h2>Hook Audit</h2><p>No hook activity recorded.</p></div>`;
|
|
99
151
|
}
|
|
100
152
|
|
|
153
|
+
// Build combined audit for shared filter bar
|
|
154
|
+
const combinedAudit = [
|
|
155
|
+
...(Array.isArray(audit) ? audit : []),
|
|
156
|
+
...(Array.isArray(coordinatorAudit) ? coordinatorAudit : []),
|
|
157
|
+
];
|
|
158
|
+
const phases = collectHookPhases(combinedAudit);
|
|
159
|
+
const hookNames = collectHookNames(combinedAudit);
|
|
160
|
+
const selectedPhase = filter.phase || 'all';
|
|
161
|
+
const selectedVerdict = filter.verdict || 'all';
|
|
162
|
+
const selectedHookName = filter.hookName || 'all';
|
|
163
|
+
|
|
101
164
|
let html = `<div class="hooks-view">`;
|
|
102
165
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const selectedPhase = filter.phase || 'all';
|
|
108
|
-
const selectedVerdict = filter.verdict || 'all';
|
|
109
|
-
const selectedHookName = filter.hookName || 'all';
|
|
110
|
-
|
|
111
|
-
html += `<div class="section"><h3>Hook Audit Log</h3>
|
|
112
|
-
<p class="section-subtitle">${filtered.length} of ${audit.length} hook execution${audit.length !== 1 ? 's' : ''}</p>
|
|
166
|
+
// Shared filter bar (covers both repo-local and coordinator data)
|
|
167
|
+
if (hasAudit || hasCoordinatorAudit) {
|
|
168
|
+
html += `<div class="section"><h3>Hook Audit</h3>
|
|
169
|
+
<p class="section-subtitle">${hasCoordinatorData ? 'Repo-local and coordinator hook audit surfaces' : 'Hook audit surface'}</p>
|
|
113
170
|
<div class="filter-bar">
|
|
114
171
|
<label class="filter-control">
|
|
115
172
|
<span>Phase</span>
|
|
@@ -135,41 +192,37 @@ export function render({ audit, annotations, filter = {} }) {
|
|
|
135
192
|
</select>
|
|
136
193
|
</label>
|
|
137
194
|
</div>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<tbody>`;
|
|
141
|
-
|
|
142
|
-
for (const entry of filtered) {
|
|
143
|
-
const duration = entry.duration_ms != null ? `${entry.duration_ms}ms` : '-';
|
|
144
|
-
const action = entry.orchestrator_action || entry.action || 'continued';
|
|
145
|
-
html += `<tr>
|
|
146
|
-
<td class="mono">${esc(entry.timestamp || '-')}</td>
|
|
147
|
-
<td class="mono">${esc(getHookPhase(entry))}</td>
|
|
148
|
-
<td>${esc(getHookName(entry))}</td>
|
|
149
|
-
<td>${verdictBadge(entry.verdict)}</td>
|
|
150
|
-
<td class="mono">${esc(action)}</td>
|
|
151
|
-
<td class="mono">${esc(duration)}</td>
|
|
152
|
-
</tr>`;
|
|
153
|
-
}
|
|
195
|
+
</div>`;
|
|
196
|
+
}
|
|
154
197
|
|
|
155
|
-
|
|
198
|
+
// Repo-local audit section
|
|
199
|
+
if (hasAudit) {
|
|
200
|
+
const sectionTitle = hasCoordinatorData ? 'Repo Hook Audit Log' : 'Hook Audit Log';
|
|
201
|
+
html += `<div class="section"><h3>${sectionTitle}</h3>`;
|
|
202
|
+
html += renderAuditTable(audit, filter);
|
|
203
|
+
html += `</div>`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Coordinator audit section
|
|
207
|
+
if (hasCoordinatorAudit) {
|
|
208
|
+
html += `<div class="section"><h3>Coordinator Hook Audit Log</h3>`;
|
|
209
|
+
html += renderAuditTable(coordinatorAudit, filter);
|
|
210
|
+
html += `</div>`;
|
|
156
211
|
}
|
|
157
212
|
|
|
213
|
+
// Repo-local annotations section
|
|
158
214
|
if (hasAnnotations) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const annotationText = formatAnnotations(entry);
|
|
165
|
-
html += `<div class="annotation-card">
|
|
166
|
-
<span class="mono">${esc(entry.turn_id || '-')}</span>
|
|
167
|
-
<span class="mono">${esc(getHookName(entry))}</span>
|
|
168
|
-
<span>${esc(annotationText || JSON.stringify(entry))}</span>
|
|
169
|
-
</div>`;
|
|
170
|
-
}
|
|
215
|
+
const sectionTitle = hasCoordinatorData ? 'Repo Hook Annotations' : 'Hook Annotations';
|
|
216
|
+
html += `<div class="section"><h3>${sectionTitle}</h3>`;
|
|
217
|
+
html += renderAnnotationList(annotations);
|
|
218
|
+
html += `</div>`;
|
|
219
|
+
}
|
|
171
220
|
|
|
172
|
-
|
|
221
|
+
// Coordinator annotations section
|
|
222
|
+
if (hasCoordinatorAnnotations) {
|
|
223
|
+
html += `<div class="section"><h3>Coordinator Hook Annotations</h3>`;
|
|
224
|
+
html += renderAnnotationList(coordinatorAnnotations);
|
|
225
|
+
html += `</div>`;
|
|
173
226
|
}
|
|
174
227
|
|
|
175
228
|
html += `</div>`;
|
|
@@ -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)',
|
|
@@ -230,6 +263,8 @@ function renderConnectorHealthPanel(connectorsPayload) {
|
|
|
230
263
|
return html;
|
|
231
264
|
}
|
|
232
265
|
|
|
266
|
+
export { formatDuration, computeElapsed, formatTimestamp };
|
|
267
|
+
|
|
233
268
|
export function render({ state, continuity, history, annotations, audit, connectors }) {
|
|
234
269
|
if (!state) {
|
|
235
270
|
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>`;
|
|
@@ -257,11 +292,14 @@ export function render({ state, continuity, history, annotations, audit, connect
|
|
|
257
292
|
if (activeTurns.length > 0) {
|
|
258
293
|
html += `<div class="section"><h3>Active Turns</h3><div class="turn-list">`;
|
|
259
294
|
for (const turn of activeTurns) {
|
|
295
|
+
const elapsedMs = computeElapsed(turn.started_at);
|
|
296
|
+
const elapsedStr = formatDuration(elapsedMs);
|
|
260
297
|
html += `<div class="turn-card active">
|
|
261
298
|
<div class="turn-header">
|
|
262
299
|
${roleBadge(getRole(turn))}
|
|
263
300
|
<span class="mono">${esc(turn.turn_id)}</span>
|
|
264
301
|
<span class="turn-status">${esc(turn.status || 'assigned')}</span>
|
|
302
|
+
${elapsedStr ? `<span class="turn-timing">Elapsed: ${esc(elapsedStr)}</span>` : ''}
|
|
265
303
|
</div>
|
|
266
304
|
</div>`;
|
|
267
305
|
}
|
|
@@ -281,10 +319,14 @@ export function render({ state, continuity, history, annotations, audit, connect
|
|
|
281
319
|
|| entry.verification?.evidence_summary
|
|
282
320
|
|| null;
|
|
283
321
|
|
|
322
|
+
const durationStr = formatDuration(entry.duration_ms);
|
|
323
|
+
const acceptedStr = formatTimestamp(entry.accepted_at);
|
|
284
324
|
html += `<div class="turn-card" data-turn-expand="${esc(entry.turn_id)}">
|
|
285
325
|
<div class="turn-header">
|
|
286
326
|
${roleBadge(getRole(entry))}
|
|
287
327
|
<span class="mono">${esc(entry.turn_id)}</span>
|
|
328
|
+
${durationStr ? `<span class="turn-timing">${esc(durationStr)}</span>` : ''}
|
|
329
|
+
${acceptedStr ? `<span class="turn-timestamp">${esc(acceptedStr)}</span>` : ''}
|
|
288
330
|
</div>`;
|
|
289
331
|
|
|
290
332
|
if (entry.summary) {
|