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 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 renderTurnDetailPanel(turnId, annotations, audit) {
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 (turnAnnotations.length === 0 && turnAudit.length === 0) {
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
- if (turnAudit.length > 0) {
99
- html += `<div class="turn-detail"><span class="detail-label">Hook Audit (${turnAudit.length}):</span>
100
- <table class="data-table">
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
- if (turnAnnotations.length > 0) {
116
- html += `<div class="turn-detail"><span class="detail-label">Annotations (${turnAnnotations.length}):</span><ul>`;
117
- for (const ann of turnAnnotations) {
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 function render({ state, continuity, history, annotations, audit, connectors }) {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.73.0",
3
+ "version": "2.75.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {