agentxchain 2.91.0 → 2.93.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/bin/agentxchain.js +12 -2
- package/package.json +1 -1
- package/src/commands/audit.js +7 -1
- package/src/commands/decisions.js +94 -0
- package/src/commands/doctor.js +19 -1
- package/src/commands/report.js +7 -1
- package/src/commands/status.js +14 -0
- package/src/lib/adapters/local-cli-adapter.js +1 -1
- package/src/lib/dispatch-bundle.js +9 -0
- package/src/lib/export.js +60 -0
- package/src/lib/governed-state.js +135 -3
- package/src/lib/repo-decisions.js +100 -0
- package/src/lib/repo-observer.js +39 -5
- package/src/lib/report.js +618 -0
- package/src/lib/run-loop.js +16 -0
- package/src/lib/schemas/turn-result.schema.json +10 -0
- package/src/lib/turn-result-validator.js +13 -0
package/src/lib/report.js
CHANGED
|
@@ -5,6 +5,7 @@ import { normalizeRunProvenance, summarizeRunProvenance } from './run-provenance
|
|
|
5
5
|
export const GOVERNANCE_REPORT_VERSION = '0.1';
|
|
6
6
|
|
|
7
7
|
const VALID_DELEGATION_OUTCOMES = new Set(['completed', 'failed', 'mixed', 'pending']);
|
|
8
|
+
const VALID_DASHBOARD_SESSION_STATUSES = new Set(['running', 'pid_only', 'stale', 'not_running']);
|
|
8
9
|
|
|
9
10
|
function normalizeDelegationSummary(summary) {
|
|
10
11
|
if (!summary || typeof summary !== 'object' || Array.isArray(summary)) return null;
|
|
@@ -58,6 +59,37 @@ function extractDelegationSummary(artifact) {
|
|
|
58
59
|
return normalizeDelegationSummary(buildDelegationSummary(artifact.files || {}));
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
function normalizeDashboardSessionSummary(summary) {
|
|
63
|
+
if (!summary || typeof summary !== 'object' || Array.isArray(summary)) return null;
|
|
64
|
+
if (!VALID_DASHBOARD_SESSION_STATUSES.has(summary.status)) return null;
|
|
65
|
+
return {
|
|
66
|
+
status: summary.status,
|
|
67
|
+
pid: Number.isInteger(summary.pid) ? summary.pid : null,
|
|
68
|
+
url: typeof summary.url === 'string' && summary.url.length > 0 ? summary.url : null,
|
|
69
|
+
started_at: typeof summary.started_at === 'string' && summary.started_at.length > 0 ? summary.started_at : null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function extractDashboardSessionSummary(artifact) {
|
|
74
|
+
return normalizeDashboardSessionSummary(artifact.summary?.dashboard_session);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatDashboardSessionLine(session) {
|
|
78
|
+
if (!session) return null;
|
|
79
|
+
switch (session.status) {
|
|
80
|
+
case 'running':
|
|
81
|
+
return `running at ${session.url || 'unknown url'} (PID: ${session.pid || '?'})`;
|
|
82
|
+
case 'pid_only':
|
|
83
|
+
return `pid_only (PID: ${session.pid || '?'}, session metadata missing)`;
|
|
84
|
+
case 'stale':
|
|
85
|
+
return `stale session files${session.pid ? ` (PID: ${session.pid})` : ''}${session.url ? ` at ${session.url}` : ''}`;
|
|
86
|
+
case 'not_running':
|
|
87
|
+
return 'not_running';
|
|
88
|
+
default:
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
61
93
|
function yesNo(value) {
|
|
62
94
|
return value ? 'yes' : 'no';
|
|
63
95
|
}
|
|
@@ -916,6 +948,7 @@ function buildRunSubject(artifact) {
|
|
|
916
948
|
const continuity = extractContinuityMetadata(artifact);
|
|
917
949
|
const governanceEvents = extractGovernanceEventDigest(artifact);
|
|
918
950
|
const delegationSummary = extractDelegationSummary(artifact);
|
|
951
|
+
const dashboardSession = extractDashboardSessionSummary(artifact);
|
|
919
952
|
|
|
920
953
|
return {
|
|
921
954
|
kind: 'governed_run',
|
|
@@ -942,6 +975,7 @@ function buildRunSubject(artifact) {
|
|
|
942
975
|
active_roles: activeRoles,
|
|
943
976
|
budget_status: normalizeBudgetStatus(artifact.state?.budget_status),
|
|
944
977
|
cost_summary: computeCostSummary(turns),
|
|
978
|
+
dashboard_session: dashboardSession,
|
|
945
979
|
created_at: timing.created_at,
|
|
946
980
|
completed_at: timing.completed_at,
|
|
947
981
|
duration_seconds: timing.duration_seconds,
|
|
@@ -958,6 +992,7 @@ function buildRunSubject(artifact) {
|
|
|
958
992
|
recovery_summary: recoverySummary,
|
|
959
993
|
continuity,
|
|
960
994
|
workflow_kit_artifacts: extractWorkflowKitArtifacts(artifact),
|
|
995
|
+
repo_decisions: artifact.summary?.repo_decisions || null,
|
|
961
996
|
},
|
|
962
997
|
artifacts: {
|
|
963
998
|
history_entries: artifact.summary?.history_entries || 0,
|
|
@@ -1209,6 +1244,9 @@ export function formatGovernanceReportText(report) {
|
|
|
1209
1244
|
if (run.inherited_context?.parent_run_id) {
|
|
1210
1245
|
lines.push(`Inherited from: ${run.inherited_context.parent_run_id} (${run.inherited_context.parent_status || 'unknown'})`);
|
|
1211
1246
|
}
|
|
1247
|
+
if (run.dashboard_session) {
|
|
1248
|
+
lines.push(`Dashboard session: ${formatDashboardSessionLine(run.dashboard_session)}`);
|
|
1249
|
+
}
|
|
1212
1250
|
|
|
1213
1251
|
lines.push(
|
|
1214
1252
|
`History entries: ${artifacts.history_entries}`,
|
|
@@ -1256,6 +1294,14 @@ export function formatGovernanceReportText(report) {
|
|
|
1256
1294
|
}
|
|
1257
1295
|
}
|
|
1258
1296
|
|
|
1297
|
+
if (run.repo_decisions?.active?.length > 0) {
|
|
1298
|
+
lines.push('', 'Repo Decisions:');
|
|
1299
|
+
lines.push(` Active: ${run.repo_decisions.active_count} Overridden: ${run.repo_decisions.overridden_count}`);
|
|
1300
|
+
for (const d of run.repo_decisions.active) {
|
|
1301
|
+
lines.push(` - ${d.id} (${d.category}): ${d.statement}`);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1259
1305
|
if (run.turns && run.turns.length > 0) {
|
|
1260
1306
|
lines.push('', 'Turn Timeline:');
|
|
1261
1307
|
for (let i = 0; i < run.turns.length; i++) {
|
|
@@ -1688,6 +1734,9 @@ export function formatGovernanceReportMarkdown(report) {
|
|
|
1688
1734
|
if (run.inherited_context?.parent_run_id) {
|
|
1689
1735
|
lines.push(`- Inherited from: \`${run.inherited_context.parent_run_id}\` (${run.inherited_context.parent_status || 'unknown'})`);
|
|
1690
1736
|
}
|
|
1737
|
+
if (run.dashboard_session) {
|
|
1738
|
+
lines.push(`- Dashboard session: \`${formatDashboardSessionLine(run.dashboard_session)}\``);
|
|
1739
|
+
}
|
|
1691
1740
|
|
|
1692
1741
|
lines.push(
|
|
1693
1742
|
`- History entries: ${artifacts.history_entries}`,
|
|
@@ -1738,6 +1787,16 @@ export function formatGovernanceReportMarkdown(report) {
|
|
|
1738
1787
|
}
|
|
1739
1788
|
}
|
|
1740
1789
|
|
|
1790
|
+
if (run.repo_decisions?.active?.length > 0) {
|
|
1791
|
+
lines.push('', '## Repo Decisions', '');
|
|
1792
|
+
lines.push(`Active: ${run.repo_decisions.active_count} | Overridden: ${run.repo_decisions.overridden_count}`, '');
|
|
1793
|
+
lines.push('| ID | Category | Statement | Role | Run |', '|----|----------|-----------|------|-----|');
|
|
1794
|
+
for (const d of run.repo_decisions.active) {
|
|
1795
|
+
const stmt = (d.statement || '').replace(/\|/g, '\\|');
|
|
1796
|
+
lines.push(`| ${d.id} | ${d.category} | ${stmt} | ${d.role || '—'} | \`${(d.run_id || '').slice(0, 12)}\` |`);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1741
1800
|
if (run.turns && run.turns.length > 0) {
|
|
1742
1801
|
lines.push('', '## Turn Timeline', '', '| # | Role | Phase | Summary | Files | Cost | Time |', '|---|------|-------|---------|-------|------|------|');
|
|
1743
1802
|
for (let i = 0; i < run.turns.length; i++) {
|
|
@@ -2117,3 +2176,562 @@ export function formatGovernanceReportMarkdown(report) {
|
|
|
2117
2176
|
}));
|
|
2118
2177
|
return mdLines.join('\n');
|
|
2119
2178
|
}
|
|
2179
|
+
|
|
2180
|
+
// --- HTML governance report formatter ---
|
|
2181
|
+
|
|
2182
|
+
function esc(str) {
|
|
2183
|
+
if (typeof str !== 'string') return '';
|
|
2184
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
function badge(status) {
|
|
2188
|
+
const colors = {
|
|
2189
|
+
pass: '#22c55e', running: '#3b82f6', completed: '#22c55e',
|
|
2190
|
+
failed: '#ef4444', error: '#ef4444', fail: '#ef4444',
|
|
2191
|
+
blocked: '#f59e0b', pending: '#a855f7', mixed: '#f59e0b',
|
|
2192
|
+
paused: '#6b7280', not_running: '#6b7280', stale: '#f59e0b',
|
|
2193
|
+
pid_only: '#f59e0b',
|
|
2194
|
+
};
|
|
2195
|
+
const color = colors[status] || '#6b7280';
|
|
2196
|
+
return `<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:0.85em;font-weight:600;color:#fff;background:${color}">${esc(String(status))}</span>`;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
function htmlTable(headers, rows) {
|
|
2200
|
+
const lines = ['<table>', '<thead><tr>'];
|
|
2201
|
+
for (const h of headers) lines.push(`<th>${esc(h)}</th>`);
|
|
2202
|
+
lines.push('</tr></thead>', '<tbody>');
|
|
2203
|
+
for (const row of rows) {
|
|
2204
|
+
lines.push('<tr>');
|
|
2205
|
+
for (const cell of row) lines.push(`<td>${cell}</td>`);
|
|
2206
|
+
lines.push('</tr>');
|
|
2207
|
+
}
|
|
2208
|
+
lines.push('</tbody>', '</table>');
|
|
2209
|
+
return lines.join('');
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
function htmlSection(title, content, level = 2) {
|
|
2213
|
+
const tag = `h${level}`;
|
|
2214
|
+
return `<${tag}>${esc(title)}</${tag}>\n${content}`;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
function htmlDl(pairs) {
|
|
2218
|
+
const lines = ['<dl>'];
|
|
2219
|
+
for (const [label, value] of pairs) {
|
|
2220
|
+
lines.push(`<dt>${esc(label)}</dt><dd>${value}</dd>`);
|
|
2221
|
+
}
|
|
2222
|
+
lines.push('</dl>');
|
|
2223
|
+
return lines.join('');
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
const HTML_STYLES = `
|
|
2227
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
2228
|
+
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;line-height:1.6;color:#1a1a2e;background:#f8fafc;padding:2rem;max-width:1100px;margin:0 auto}
|
|
2229
|
+
h1{font-size:1.6rem;margin-bottom:0.5rem;border-bottom:2px solid #e2e8f0;padding-bottom:0.5rem}
|
|
2230
|
+
h2{font-size:1.2rem;margin-top:2rem;margin-bottom:0.75rem;color:#334155}
|
|
2231
|
+
h3{font-size:1.05rem;margin-top:1.5rem;margin-bottom:0.5rem;color:#475569}
|
|
2232
|
+
h4{font-size:0.95rem;margin-top:1rem;margin-bottom:0.4rem;color:#64748b}
|
|
2233
|
+
dl{display:grid;grid-template-columns:max-content 1fr;gap:0.3rem 1rem;margin-bottom:1rem}
|
|
2234
|
+
dt{font-weight:600;color:#475569;white-space:nowrap}
|
|
2235
|
+
dd{color:#1e293b}
|
|
2236
|
+
table{width:100%;border-collapse:collapse;margin:0.75rem 0 1.5rem;font-size:0.9rem}
|
|
2237
|
+
th{background:#f1f5f9;font-weight:600;text-align:left;padding:0.5rem 0.75rem;border-bottom:2px solid #cbd5e1;color:#334155}
|
|
2238
|
+
td{padding:0.4rem 0.75rem;border-bottom:1px solid #e2e8f0}
|
|
2239
|
+
tr:hover td{background:#f8fafc}
|
|
2240
|
+
code{font-family:"SF Mono",Menlo,monospace;font-size:0.85em;background:#f1f5f9;padding:1px 4px;border-radius:3px}
|
|
2241
|
+
.header{display:flex;align-items:center;gap:0.75rem;margin-bottom:1rem}
|
|
2242
|
+
.header-brand{font-size:0.85rem;color:#64748b}
|
|
2243
|
+
.meta{background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:1.25rem;margin-bottom:1.5rem}
|
|
2244
|
+
.section{background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:1.25rem;margin-bottom:1rem}
|
|
2245
|
+
ul{margin:0.5rem 0;padding-left:1.5rem}
|
|
2246
|
+
li{margin-bottom:0.25rem}
|
|
2247
|
+
.warn{color:#d97706;font-weight:600}
|
|
2248
|
+
@media(prefers-color-scheme:dark){
|
|
2249
|
+
body{background:#0f172a;color:#e2e8f0}
|
|
2250
|
+
h1{border-bottom-color:#334155}
|
|
2251
|
+
h2,h3,h4{color:#94a3b8}
|
|
2252
|
+
dt{color:#94a3b8}dd{color:#e2e8f0}
|
|
2253
|
+
th{background:#1e293b;border-bottom-color:#475569;color:#cbd5e1}
|
|
2254
|
+
td{border-bottom-color:#334155}
|
|
2255
|
+
tr:hover td{background:#1e293b}
|
|
2256
|
+
code{background:#1e293b}
|
|
2257
|
+
.meta,.section{background:#1e293b;border-color:#334155}
|
|
2258
|
+
.header-brand{color:#94a3b8}
|
|
2259
|
+
}
|
|
2260
|
+
@media print{
|
|
2261
|
+
body{background:#fff;color:#000;padding:1rem}
|
|
2262
|
+
.meta,.section{border:1px solid #ccc}
|
|
2263
|
+
th{background:#eee}
|
|
2264
|
+
}
|
|
2265
|
+
`;
|
|
2266
|
+
|
|
2267
|
+
function wrapHtml(title, bodyContent) {
|
|
2268
|
+
return `<!DOCTYPE html>
|
|
2269
|
+
<html lang="en">
|
|
2270
|
+
<head>
|
|
2271
|
+
<meta charset="utf-8">
|
|
2272
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
2273
|
+
<title>${esc(title)}</title>
|
|
2274
|
+
<style>${HTML_STYLES}</style>
|
|
2275
|
+
</head>
|
|
2276
|
+
<body>
|
|
2277
|
+
<div class="header">
|
|
2278
|
+
<h1>AgentXchain Governance Report</h1>
|
|
2279
|
+
<span class="header-brand">agentxchain.dev</span>
|
|
2280
|
+
</div>
|
|
2281
|
+
${bodyContent}
|
|
2282
|
+
</body>
|
|
2283
|
+
</html>`;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
function renderHtmlGovEventDetail(evt) {
|
|
2287
|
+
const parts = [];
|
|
2288
|
+
switch (evt.type) {
|
|
2289
|
+
case 'policy_escalation':
|
|
2290
|
+
for (const v of evt.violations || []) {
|
|
2291
|
+
parts.push(`<li>Violation: <code>${esc(v.policy_id || '?')}</code> / <code>${esc(v.rule || '?')}</code> — ${esc(v.message || 'n/a')}</li>`);
|
|
2292
|
+
}
|
|
2293
|
+
break;
|
|
2294
|
+
case 'conflict_detected':
|
|
2295
|
+
if (evt.conflicting_files?.length > 0) parts.push(`<li>Files: ${evt.conflicting_files.map((f) => `<code>${esc(f)}</code>`).join(', ')}</li>`);
|
|
2296
|
+
if (evt.overlap_ratio != null) parts.push(`<li>Overlap: ${(evt.overlap_ratio * 100).toFixed(0)}%</li>`);
|
|
2297
|
+
break;
|
|
2298
|
+
case 'operator_escalated':
|
|
2299
|
+
if (evt.reason) parts.push(`<li>Reason: ${esc(evt.reason)}</li>`);
|
|
2300
|
+
if (evt.blocked_on) parts.push(`<li>Blocked on: <code>${esc(evt.blocked_on)}</code></li>`);
|
|
2301
|
+
break;
|
|
2302
|
+
case 'escalation_resolved':
|
|
2303
|
+
if (evt.resolved_via) parts.push(`<li>Resolved via: <code>${esc(evt.resolved_via)}</code></li>`);
|
|
2304
|
+
break;
|
|
2305
|
+
}
|
|
2306
|
+
return parts.length ? `<ul>${parts.join('')}</ul>` : '';
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
function renderRunHtml(report) {
|
|
2310
|
+
const { project, run, artifacts } = report.subject;
|
|
2311
|
+
const sections = [];
|
|
2312
|
+
|
|
2313
|
+
// Meta section
|
|
2314
|
+
const metaPairs = [
|
|
2315
|
+
['Input', `<code>${esc(report.input)}</code>`],
|
|
2316
|
+
['Export kind', `<code>${esc(report.export_kind)}</code>`],
|
|
2317
|
+
['Verification', badge('pass')],
|
|
2318
|
+
['Project', `${esc(project.name || 'unknown')} (<code>${esc(project.id || 'unknown')}</code>)`],
|
|
2319
|
+
];
|
|
2320
|
+
if (project.goal) metaPairs.push(['Goal', esc(project.goal)]);
|
|
2321
|
+
metaPairs.push(
|
|
2322
|
+
['Template', `<code>${esc(project.template)}</code>`],
|
|
2323
|
+
['Protocol', `<code>${esc(project.protocol_mode || 'unknown')}</code> (schema <code>${esc(project.schema_version || 'unknown')}</code>)`],
|
|
2324
|
+
['Run ID', `<code>${esc(run.run_id || 'none')}</code>`],
|
|
2325
|
+
['Status', badge(run.status || 'unknown')],
|
|
2326
|
+
['Phase', `<code>${esc(run.phase || 'unknown')}</code>`],
|
|
2327
|
+
['Blocked on', `<code>${esc(summarizeBlockedState(run))}</code>`],
|
|
2328
|
+
['Active turns', `${run.active_turn_count}${run.active_turn_ids.length ? ` (${run.active_turn_ids.map((id) => `<code>${esc(id)}</code>`).join(', ')})` : ''}`],
|
|
2329
|
+
['Retained turns', `${run.retained_turn_count}${run.retained_turn_ids.length ? ` (${run.retained_turn_ids.map((id) => `<code>${esc(id)}</code>`).join(', ')})` : ''}`],
|
|
2330
|
+
['Active roles', run.active_roles.length ? run.active_roles.map((r) => `<code>${esc(r)}</code>`).join(', ') : '<code>none</code>'],
|
|
2331
|
+
);
|
|
2332
|
+
|
|
2333
|
+
if (run.budget_status) {
|
|
2334
|
+
const warnTag = run.budget_status.warn_mode ? ' <span class="warn">[OVER BUDGET]</span>' : '';
|
|
2335
|
+
metaPairs.push(['Budget', `spent ${formatUsd(run.budget_status.spent_usd)}, remaining ${formatUsd(run.budget_status.remaining_usd)}${warnTag}`]);
|
|
2336
|
+
}
|
|
2337
|
+
if (run.created_at) metaPairs.push(['Started', `<code>${esc(run.created_at)}</code>`]);
|
|
2338
|
+
if (run.completed_at) metaPairs.push(['Completed', `<code>${esc(run.completed_at)}</code>`]);
|
|
2339
|
+
if (run.duration_seconds != null) metaPairs.push(['Duration', `<code>${run.duration_seconds}s</code>`]);
|
|
2340
|
+
if (summarizeRunProvenance(run.provenance)) metaPairs.push(['Provenance', `<code>${esc(summarizeRunProvenance(run.provenance))}</code>`]);
|
|
2341
|
+
if (run.inherited_context?.parent_run_id) metaPairs.push(['Inherited from', `<code>${esc(run.inherited_context.parent_run_id)}</code> (${esc(run.inherited_context.parent_status || 'unknown')})`]);
|
|
2342
|
+
if (run.dashboard_session) metaPairs.push(['Dashboard', `<code>${esc(formatDashboardSessionLine(run.dashboard_session))}</code>`]);
|
|
2343
|
+
|
|
2344
|
+
metaPairs.push(
|
|
2345
|
+
['History entries', String(artifacts.history_entries)],
|
|
2346
|
+
['Decision entries', String(artifacts.decision_entries)],
|
|
2347
|
+
['Hook audit entries', String(artifacts.hook_audit_entries)],
|
|
2348
|
+
['Notification entries', String(artifacts.notification_audit_entries)],
|
|
2349
|
+
['Dispatch files', String(artifacts.dispatch_artifact_files)],
|
|
2350
|
+
['Staging files', String(artifacts.staging_artifact_files)],
|
|
2351
|
+
['Intake artifacts', artifacts.intake_present ? 'yes' : 'no'],
|
|
2352
|
+
['Coordinator artifacts', artifacts.coordinator_present ? 'yes' : 'no'],
|
|
2353
|
+
);
|
|
2354
|
+
|
|
2355
|
+
sections.push(`<div class="meta">${htmlDl(metaPairs)}</div>`);
|
|
2356
|
+
|
|
2357
|
+
// Cost Summary
|
|
2358
|
+
if (run.cost_summary) {
|
|
2359
|
+
const cs = run.cost_summary;
|
|
2360
|
+
let costHtml = `<p><strong>Total:</strong> ${formatUsd(cs.total_usd)} across ${cs.turn_count} turn${cs.turn_count !== 1 ? 's' : ''} (${cs.costed_turn_count} with cost data)</p>`;
|
|
2361
|
+
if (cs.total_input_tokens != null || cs.total_output_tokens != null) {
|
|
2362
|
+
costHtml += `<p><strong>Tokens:</strong> ${formatTokenCount(cs.total_input_tokens)} input / ${formatTokenCount(cs.total_output_tokens)} output</p>`;
|
|
2363
|
+
}
|
|
2364
|
+
if (cs.by_role.length > 0) {
|
|
2365
|
+
costHtml += htmlTable(
|
|
2366
|
+
['Role', 'Cost', 'Turns', 'Input Tokens', 'Output Tokens'],
|
|
2367
|
+
cs.by_role.map((r) => [esc(r.role), formatUsd(r.usd), String(r.turns), formatTokenCount(r.input_tokens), formatTokenCount(r.output_tokens)]),
|
|
2368
|
+
);
|
|
2369
|
+
}
|
|
2370
|
+
if (cs.by_phase.length > 0) {
|
|
2371
|
+
costHtml += htmlTable(
|
|
2372
|
+
['Phase', 'Cost', 'Turns'],
|
|
2373
|
+
cs.by_phase.map((p) => [esc(p.phase), formatUsd(p.usd), String(p.turns)]),
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
sections.push(`<div class="section">${htmlSection('Cost Summary', costHtml)}</div>`);
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
// Delegation Summary
|
|
2380
|
+
if (run.delegation_summary?.delegation_chains?.length > 0) {
|
|
2381
|
+
const ds = run.delegation_summary;
|
|
2382
|
+
let delHtml = `<p>Total delegations issued: ${ds.total_delegations_issued}</p>`;
|
|
2383
|
+
const rows = [];
|
|
2384
|
+
for (const chain of ds.delegation_chains) {
|
|
2385
|
+
for (let i = 0; i < chain.delegations.length; i++) {
|
|
2386
|
+
const d = chain.delegations[i];
|
|
2387
|
+
rows.push([
|
|
2388
|
+
i === 0 ? esc(chain.parent_role) : '',
|
|
2389
|
+
i === 0 ? `<code>${esc(chain.parent_turn_id)}</code>` : '',
|
|
2390
|
+
i === 0 ? badge(chain.outcome) : '',
|
|
2391
|
+
i === 0 ? `<code>${esc(chain.review_turn_id || 'pending')}</code>` : '',
|
|
2392
|
+
`<code>${esc(d.delegation_id)}</code> → <code>${esc(d.to_role)}</code>`,
|
|
2393
|
+
`<code>${esc(d.child_turn_id || 'pending')}</code>`,
|
|
2394
|
+
badge(d.status),
|
|
2395
|
+
esc(d.charter),
|
|
2396
|
+
]);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
delHtml += htmlTable(['Parent Role', 'Parent Turn', 'Outcome', 'Review Turn', 'Delegation', 'Child Turn', 'Status', 'Charter'], rows);
|
|
2400
|
+
sections.push(`<div class="section">${htmlSection('Delegation Summary', delHtml)}</div>`);
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
// Repo Decisions
|
|
2404
|
+
if (run.repo_decisions?.active?.length > 0) {
|
|
2405
|
+
let rdHtml = `<p>Active: ${run.repo_decisions.active_count} | Overridden: ${run.repo_decisions.overridden_count}</p>`;
|
|
2406
|
+
rdHtml += htmlTable(
|
|
2407
|
+
['ID', 'Category', 'Statement', 'Role', 'Run'],
|
|
2408
|
+
run.repo_decisions.active.map((d) => [esc(d.id), esc(d.category), esc(d.statement || ''), esc(d.role || '\u2014'), `<code>${esc((d.run_id || '').slice(0, 12))}</code>`]),
|
|
2409
|
+
);
|
|
2410
|
+
sections.push(`<div class="section">${htmlSection('Repo Decisions', rdHtml)}</div>`);
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// Turn Timeline
|
|
2414
|
+
if (run.turns && run.turns.length > 0) {
|
|
2415
|
+
const turnRows = run.turns.map((t, i) => {
|
|
2416
|
+
const cost = t.cost_usd != null ? formatUsd(t.cost_usd) : 'n/a';
|
|
2417
|
+
const phase = t.phase_transition ? `${esc(t.phase || '?')} → ${esc(t.phase_transition)}` : esc(t.phase || '?');
|
|
2418
|
+
const sibNote = Array.isArray(t.sibling_attributed_files) ? ` (${t.sibling_attributed_files.length} sibling)` : '';
|
|
2419
|
+
return [String(i + 1), esc(t.role), phase, esc(t.summary || '(no summary)'), `${t.files_changed_count}${sibNote}`, cost, esc(formatTurnTimelineTime(t))];
|
|
2420
|
+
});
|
|
2421
|
+
sections.push(`<div class="section">${htmlSection('Turn Timeline', htmlTable(['#', 'Role', 'Phase', 'Summary', 'Files', 'Cost', 'Time'], turnRows))}</div>`);
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
// Decisions
|
|
2425
|
+
if (run.decisions && run.decisions.length > 0) {
|
|
2426
|
+
const decList = run.decisions.map((d) => `<li><strong>${esc(d.id)}</strong> (${esc(d.role || '?')}, ${esc(d.phase || '?')} phase): ${esc(d.statement)}</li>`).join('');
|
|
2427
|
+
sections.push(`<div class="section">${htmlSection('Decisions', `<ul>${decList}</ul>`)}</div>`);
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
// Gate Outcomes
|
|
2431
|
+
if (run.gate_summary && run.gate_summary.length > 0) {
|
|
2432
|
+
const gateList = run.gate_summary.map((g) => `<li><code>${esc(g.gate_id)}</code>: ${badge(g.status)}</li>`).join('');
|
|
2433
|
+
sections.push(`<div class="section">${htmlSection('Gate Outcomes', `<ul>${gateList}</ul>`)}</div>`);
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
// Gate Failures
|
|
2437
|
+
if (run.gate_failures && run.gate_failures.length > 0) {
|
|
2438
|
+
let gfHtml = '<ul>';
|
|
2439
|
+
for (const failure of run.gate_failures) {
|
|
2440
|
+
const request = failure.gate_type === 'run_completion' ? 'run completion' : `${esc(failure.from_phase || failure.phase || '?')} → ${esc(failure.to_phase || '?')}`;
|
|
2441
|
+
gfHtml += `<li><code>${esc(failure.gate_id || 'unknown')}</code> (${esc(failure.gate_type || 'unknown')}) at <code>${esc(failure.failed_at || 'n/a')}</code>: ${request}`;
|
|
2442
|
+
if (failure.reasons?.length) {
|
|
2443
|
+
gfHtml += '<ul>' + failure.reasons.map((r) => `<li>${esc(r)}</li>`).join('') + '</ul>';
|
|
2444
|
+
}
|
|
2445
|
+
gfHtml += '</li>';
|
|
2446
|
+
}
|
|
2447
|
+
gfHtml += '</ul>';
|
|
2448
|
+
sections.push(`<div class="section">${htmlSection('Gate Failures', gfHtml)}</div>`);
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
// Approval Policy
|
|
2452
|
+
if (run.approval_policy_events && run.approval_policy_events.length > 0) {
|
|
2453
|
+
let apHtml = '<ul>';
|
|
2454
|
+
for (const evt of run.approval_policy_events) {
|
|
2455
|
+
const transition = evt.gate_type === 'run_completion' ? 'run completion' : `${esc(evt.from_phase || '?')} → ${esc(evt.to_phase || '?')}`;
|
|
2456
|
+
apHtml += `<li><strong>${esc(evt.action || 'unknown')}</strong> (${esc(evt.gate_type || 'unknown')}) ${transition} at <code>${esc(evt.timestamp || 'n/a')}</code>`;
|
|
2457
|
+
if (evt.reason) apHtml += `<br>${esc(evt.reason)}`;
|
|
2458
|
+
apHtml += '</li>';
|
|
2459
|
+
}
|
|
2460
|
+
apHtml += '</ul>';
|
|
2461
|
+
sections.push(`<div class="section">${htmlSection('Approval Policy', apHtml)}</div>`);
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
// Governance Events
|
|
2465
|
+
if (run.governance_events && run.governance_events.length > 0) {
|
|
2466
|
+
let geHtml = '<ul>';
|
|
2467
|
+
for (const evt of run.governance_events) {
|
|
2468
|
+
geHtml += `<li><strong>${esc(evt.type)}</strong> (<code>${esc(evt.role || '?')}</code>, <code>${esc(evt.phase || '?')}</code> phase) at <code>${esc(evt.timestamp || 'n/a')}</code>${renderHtmlGovEventDetail(evt)}</li>`;
|
|
2469
|
+
}
|
|
2470
|
+
geHtml += '</ul>';
|
|
2471
|
+
sections.push(`<div class="section">${htmlSection('Governance Events', geHtml)}</div>`);
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
// Timeout Events
|
|
2475
|
+
if (run.timeout_events && run.timeout_events.length > 0) {
|
|
2476
|
+
let teHtml = '<ul>';
|
|
2477
|
+
for (const evt of run.timeout_events) {
|
|
2478
|
+
const label = evt.type === 'timeout_warning' ? 'Warning' : evt.type === 'timeout_skip' ? 'Skip' : evt.type === 'timeout_skip_failed' ? 'Skip Failed' : 'Escalation';
|
|
2479
|
+
const elapsed = evt.elapsed_minutes != null ? `${evt.elapsed_minutes}m` : '?';
|
|
2480
|
+
const limit = evt.limit_minutes != null ? `${evt.limit_minutes}m` : '?';
|
|
2481
|
+
const exceeded = evt.exceeded_by_minutes != null ? ` (+${evt.exceeded_by_minutes}m)` : '';
|
|
2482
|
+
teHtml += `<li><strong>${label}</strong> (<code>${esc(evt.scope || '?')}</code> scope) \u2014 ${elapsed}/${limit}${exceeded}, action: <code>${esc(evt.action || 'n/a')}</code>, phase: <code>${esc(evt.phase || 'n/a')}</code> at <code>${esc(evt.timestamp || 'n/a')}</code></li>`;
|
|
2483
|
+
}
|
|
2484
|
+
teHtml += '</ul>';
|
|
2485
|
+
sections.push(`<div class="section">${htmlSection('Timeout Events', teHtml)}</div>`);
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
// Intake Linkage
|
|
2489
|
+
if (run.intake_links && run.intake_links.length > 0) {
|
|
2490
|
+
const ilRows = run.intake_links.map((intake) => [
|
|
2491
|
+
`<code>${esc(intake.intent_id)}</code>`,
|
|
2492
|
+
badge(intake.status || 'unknown'),
|
|
2493
|
+
`<code>${esc(intake.event_id || 'n/a')}</code>`,
|
|
2494
|
+
`<code>${esc(intake.target_turn || 'n/a')}</code>`,
|
|
2495
|
+
`<code>${esc(intake.started_at || 'n/a')}</code>`,
|
|
2496
|
+
]);
|
|
2497
|
+
sections.push(`<div class="section">${htmlSection('Intake Linkage', htmlTable(['Intent', 'Status', 'Event', 'Target Turn', 'Started'], ilRows))}</div>`);
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
// Hook Activity
|
|
2501
|
+
if (run.hook_summary) {
|
|
2502
|
+
const eventList = Object.entries(run.hook_summary.events).sort(([a], [b]) => a.localeCompare(b, 'en')).map(([e, c]) => `${esc(e)}(${c})`).join(', ');
|
|
2503
|
+
const hookHtml = htmlDl([
|
|
2504
|
+
['Total executions', String(run.hook_summary.total)],
|
|
2505
|
+
['Blocked', String(run.hook_summary.blocked)],
|
|
2506
|
+
...(eventList ? [['Events', eventList]] : []),
|
|
2507
|
+
]);
|
|
2508
|
+
sections.push(`<div class="section">${htmlSection('Hook Activity', hookHtml)}</div>`);
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// Recovery
|
|
2512
|
+
if (run.recovery_summary) {
|
|
2513
|
+
const rs = run.recovery_summary;
|
|
2514
|
+
sections.push(`<div class="section">${htmlSection('Recovery', htmlDl([
|
|
2515
|
+
['Category', `<code>${esc(rs.category || 'unknown')}</code>`],
|
|
2516
|
+
['Typed reason', `<code>${esc(rs.typed_reason || 'unknown')}</code>`],
|
|
2517
|
+
['Owner', `<code>${esc(rs.owner || 'unknown')}</code>`],
|
|
2518
|
+
['Action', `<code>${esc(rs.recovery_action || 'n/a')}</code>`],
|
|
2519
|
+
['Detail', esc(rs.detail || 'n/a')],
|
|
2520
|
+
['Turn retained', rs.turn_retained == null ? 'n/a' : (rs.turn_retained ? 'yes' : 'no')],
|
|
2521
|
+
]))}</div>`);
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// Continuity
|
|
2525
|
+
if (run.continuity) {
|
|
2526
|
+
const pairs = [
|
|
2527
|
+
['Session', `<code>${esc(run.continuity.session_id || 'unknown')}</code>`],
|
|
2528
|
+
['Checkpoint', `<code>${esc(run.continuity.checkpoint_reason || 'unknown')}</code> at <code>${esc(run.continuity.last_checkpoint_at || 'n/a')}</code>`],
|
|
2529
|
+
['Last turn', `<code>${esc(run.continuity.last_turn_id || 'none')}</code>`],
|
|
2530
|
+
['Last role', `<code>${esc(run.continuity.last_role || 'unknown')}</code>`],
|
|
2531
|
+
['Last phase', `<code>${esc(run.continuity.last_phase || 'unknown')}</code>`],
|
|
2532
|
+
];
|
|
2533
|
+
if (run.continuity.stale_checkpoint) {
|
|
2534
|
+
pairs.push(['Warning', `<span class="warn">checkpoint tracks run <code>${esc(run.continuity.run_id)}</code>, but export tracks <code>${esc(run.run_id)}</code></span>`]);
|
|
2535
|
+
}
|
|
2536
|
+
sections.push(`<div class="section">${htmlSection('Continuity', htmlDl(pairs))}</div>`);
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
// Workflow Artifacts
|
|
2540
|
+
if (Array.isArray(run.workflow_kit_artifacts) && run.workflow_kit_artifacts.length > 0) {
|
|
2541
|
+
let waHtml = `<p>Phase: <code>${esc(run.phase || 'unknown')}</code></p>`;
|
|
2542
|
+
waHtml += htmlTable(
|
|
2543
|
+
['Artifact', 'Required', 'Semantics', 'Owner', 'Resolution', 'Status'],
|
|
2544
|
+
run.workflow_kit_artifacts.map((art) => [
|
|
2545
|
+
`<code>${esc(art.path)}</code>`,
|
|
2546
|
+
art.required ? 'yes' : 'no',
|
|
2547
|
+
art.semantics ? `<code>${esc(art.semantics)}</code>` : 'none',
|
|
2548
|
+
art.owned_by ? `<code>${esc(art.owned_by)}</code>` : 'none',
|
|
2549
|
+
esc(art.owner_resolution),
|
|
2550
|
+
art.exists ? 'exists' : '<strong class="warn">missing</strong>',
|
|
2551
|
+
]),
|
|
2552
|
+
);
|
|
2553
|
+
sections.push(`<div class="section">${htmlSection('Workflow Artifacts', waHtml)}</div>`);
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
return wrapHtml('AgentXchain Governance Report', sections.join('\n'));
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
function renderCoordinatorHtml(report) {
|
|
2560
|
+
const { coordinator, run, artifacts, repos, coordinator_timeline, barrier_summary, barrier_ledger_timeline, decision_digest, approval_policy_events, governance_events, timeout_events, recovery_report: coordRecoveryReport } = report.subject;
|
|
2561
|
+
const sections = [];
|
|
2562
|
+
|
|
2563
|
+
const metaPairs = [
|
|
2564
|
+
['Input', `<code>${esc(report.input)}</code>`],
|
|
2565
|
+
['Export kind', `<code>${esc(report.export_kind)}</code>`],
|
|
2566
|
+
['Verification', badge('pass')],
|
|
2567
|
+
['Workspace', `${esc(coordinator.project_name || 'unknown')} (<code>${esc(coordinator.project_id || 'unknown')}</code>)`],
|
|
2568
|
+
['Schema', `<code>${esc(coordinator.schema_version || 'unknown')}</code>`],
|
|
2569
|
+
['Super run', `<code>${esc(run.super_run_id || 'none')}</code>`],
|
|
2570
|
+
['Status', badge(run.status || 'unknown')],
|
|
2571
|
+
['Phase', `<code>${esc(run.phase || 'unknown')}</code>`],
|
|
2572
|
+
['Blocked reason', `<code>${esc(run.blocked_reason || 'none')}</code>`],
|
|
2573
|
+
];
|
|
2574
|
+
|
|
2575
|
+
if (run.run_id_mismatches?.length > 0) {
|
|
2576
|
+
metaPairs.push(['Run ID mismatches', `<strong class="warn">${run.run_id_mismatches.length}</strong>`]);
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
metaPairs.push(
|
|
2580
|
+
['Started', `<code>${esc(run.created_at || 'n/a')}</code>`],
|
|
2581
|
+
['Repos', `${coordinator.repo_count} total, ${run.repo_ok_count} exported, ${run.repo_error_count} failed`],
|
|
2582
|
+
['Workstreams', String(coordinator.workstream_count)],
|
|
2583
|
+
['Barriers', String(run.barrier_count)],
|
|
2584
|
+
['Repo statuses', formatStatusCounts(run.repo_status_counts)],
|
|
2585
|
+
['History entries', String(artifacts.history_entries)],
|
|
2586
|
+
['Decision entries', String(artifacts.decision_entries)],
|
|
2587
|
+
);
|
|
2588
|
+
if (run.completed_at) metaPairs.push(['Completed', `<code>${esc(run.completed_at)}</code>`]);
|
|
2589
|
+
if (run.duration_seconds != null) metaPairs.push(['Duration', `<code>${run.duration_seconds}s</code>`]);
|
|
2590
|
+
if (run.pending_gate) metaPairs.push(['Pending gate', `<code>${esc(run.pending_gate.gate)}</code> (<code>${esc(run.pending_gate.gate_type)}</code>)`]);
|
|
2591
|
+
|
|
2592
|
+
sections.push(`<div class="meta">${htmlDl(metaPairs)}</div>`);
|
|
2593
|
+
|
|
2594
|
+
// Next Actions
|
|
2595
|
+
if (run.next_actions?.length > 0) {
|
|
2596
|
+
const naHtml = '<ol>' + run.next_actions.map((a) => `<li><code>${esc(a.command)}</code>: ${esc(a.reason)}</li>`).join('') + '</ol>';
|
|
2597
|
+
sections.push(`<div class="section">${htmlSection('Next Actions', naHtml)}</div>`);
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
// Coordinator Timeline
|
|
2601
|
+
if (coordinator_timeline?.length > 0) {
|
|
2602
|
+
const tlRows = coordinator_timeline.map((ev, i) => [String(i + 1), `<code>${esc(ev.type)}</code>`, `<code>${esc(ev.timestamp || 'n/a')}</code>`, esc(ev.summary)]);
|
|
2603
|
+
sections.push(`<div class="section">${htmlSection('Coordinator Timeline', htmlTable(['#', 'Type', 'Time', 'Summary'], tlRows))}</div>`);
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
// Barrier Summary
|
|
2607
|
+
if (barrier_summary?.length > 0) {
|
|
2608
|
+
const bRows = barrier_summary.map((b) => [
|
|
2609
|
+
`<code>${esc(b.barrier_id)}</code>`,
|
|
2610
|
+
`<code>${esc(b.workstream_id || 'unknown')}</code>`,
|
|
2611
|
+
`<code>${esc(b.type)}</code>`,
|
|
2612
|
+
badge(b.status),
|
|
2613
|
+
`${b.satisfied_repos.length}/${b.required_repos.length} repos`,
|
|
2614
|
+
]);
|
|
2615
|
+
sections.push(`<div class="section">${htmlSection('Barrier Summary', htmlTable(['Barrier', 'Workstream', 'Type', 'Status', 'Satisfied'], bRows))}</div>`);
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
// Barrier Transitions
|
|
2619
|
+
if (barrier_ledger_timeline?.length > 0) {
|
|
2620
|
+
const btRows = barrier_ledger_timeline.map((t, i) => [String(i + 1), `<code>${esc(t.timestamp || 'n/a')}</code>`, `<code>${esc(t.barrier_id)}</code>`, `<code>${esc(t.previous_status)}</code>`, `<code>${esc(t.new_status)}</code>`, esc(t.summary)]);
|
|
2621
|
+
sections.push(`<div class="section">${htmlSection('Barrier Transitions', htmlTable(['#', 'Time', 'Barrier', 'From', 'To', 'Summary'], btRows))}</div>`);
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
// Coordinator Decisions
|
|
2625
|
+
if (decision_digest?.length > 0) {
|
|
2626
|
+
const ddList = decision_digest.map((d) => `<li><strong>${esc(d.id)}</strong> (${esc(d.role || '?')}, ${esc(d.phase || '?')} phase): ${esc(d.statement)}</li>`).join('');
|
|
2627
|
+
sections.push(`<div class="section">${htmlSection('Coordinator Decisions', `<ul>${ddList}</ul>`)}</div>`);
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
// Approval Policy
|
|
2631
|
+
if (approval_policy_events?.length > 0) {
|
|
2632
|
+
let apHtml = '<ul>';
|
|
2633
|
+
for (const evt of approval_policy_events) {
|
|
2634
|
+
const transition = evt.gate_type === 'run_completion' ? 'run completion' : `${esc(evt.from_phase || '?')} → ${esc(evt.to_phase || '?')}`;
|
|
2635
|
+
apHtml += `<li><strong>${esc(evt.action || 'unknown')}</strong> (${esc(evt.gate_type || 'unknown')}) ${transition} at <code>${esc(evt.timestamp || 'n/a')}</code>`;
|
|
2636
|
+
if (evt.reason) apHtml += `<br>${esc(evt.reason)}`;
|
|
2637
|
+
apHtml += '</li>';
|
|
2638
|
+
}
|
|
2639
|
+
apHtml += '</ul>';
|
|
2640
|
+
sections.push(`<div class="section">${htmlSection('Approval Policy', apHtml)}</div>`);
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
// Governance Events
|
|
2644
|
+
if (governance_events?.length > 0) {
|
|
2645
|
+
let geHtml = '<ul>';
|
|
2646
|
+
for (const evt of governance_events) {
|
|
2647
|
+
geHtml += `<li><strong>${esc(evt.type)}</strong> (<code>${esc(evt.role || '?')}</code>, <code>${esc(evt.phase || '?')}</code> phase) at <code>${esc(evt.timestamp || 'n/a')}</code>${renderHtmlGovEventDetail(evt)}</li>`;
|
|
2648
|
+
}
|
|
2649
|
+
geHtml += '</ul>';
|
|
2650
|
+
sections.push(`<div class="section">${htmlSection('Governance Events', geHtml)}</div>`);
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
// Timeout Events
|
|
2654
|
+
if (timeout_events?.length > 0) {
|
|
2655
|
+
let teHtml = '<ul>';
|
|
2656
|
+
for (const evt of timeout_events) {
|
|
2657
|
+
const label = evt.type === 'timeout_warning' ? 'Warning' : evt.type === 'timeout_skip' ? 'Skip' : 'Escalation';
|
|
2658
|
+
const elapsed = evt.elapsed_minutes != null ? `${evt.elapsed_minutes}m` : '?';
|
|
2659
|
+
const limit = evt.limit_minutes != null ? `${evt.limit_minutes}m` : '?';
|
|
2660
|
+
teHtml += `<li><strong>${label}</strong> (<code>${esc(evt.scope || '?')}</code>) \u2014 ${elapsed}/${limit}, action: <code>${esc(evt.action || 'n/a')}</code> at <code>${esc(evt.timestamp || 'n/a')}</code></li>`;
|
|
2661
|
+
}
|
|
2662
|
+
teHtml += '</ul>';
|
|
2663
|
+
sections.push(`<div class="section">${htmlSection('Timeout Events', teHtml)}</div>`);
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
// Recovery Report
|
|
2667
|
+
if (coordRecoveryReport) {
|
|
2668
|
+
sections.push(`<div class="section">${htmlSection('Recovery Report', htmlDl([
|
|
2669
|
+
['Trigger', esc(coordRecoveryReport.trigger || 'n/a')],
|
|
2670
|
+
['Impact', esc(coordRecoveryReport.impact || 'n/a')],
|
|
2671
|
+
['Mitigation', esc(coordRecoveryReport.mitigation || 'n/a')],
|
|
2672
|
+
['Owner', esc(coordRecoveryReport.owner || 'n/a')],
|
|
2673
|
+
['Exit Condition', esc(coordRecoveryReport.exit_condition || 'n/a')],
|
|
2674
|
+
]))}</div>`);
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
// Repo Details
|
|
2678
|
+
if (repos?.length > 0) {
|
|
2679
|
+
let repoHtml = '';
|
|
2680
|
+
for (const repo of repos) {
|
|
2681
|
+
if (!repo.ok) {
|
|
2682
|
+
repoHtml += `<h3>${esc(repo.repo_id)}</h3><p>Failed export: ${esc(repo.error || 'unknown error')}, path <code>${esc(repo.path || 'unknown')}</code></p>`;
|
|
2683
|
+
continue;
|
|
2684
|
+
}
|
|
2685
|
+
const repoPairs = [
|
|
2686
|
+
['Status', badge(repo.status || 'unknown')],
|
|
2687
|
+
['Run', `<code>${esc(repo.run_id || 'none')}</code>`],
|
|
2688
|
+
['Phase', `<code>${esc(repo.phase || 'unknown')}</code>`],
|
|
2689
|
+
['Path', `<code>${esc(repo.path || 'unknown')}</code>`],
|
|
2690
|
+
];
|
|
2691
|
+
if (repo.blocked_on) repoPairs.push(['Blocked on', `<code>${esc(summarizeBlockedOn(repo.blocked_on))}</code>`]);
|
|
2692
|
+
repoHtml += `<h3>${esc(repo.repo_id)}</h3>${htmlDl(repoPairs)}`;
|
|
2693
|
+
|
|
2694
|
+
if (repo.turns?.length > 0) {
|
|
2695
|
+
const turnRows = repo.turns.map((t, i) => {
|
|
2696
|
+
const cost = t.cost_usd != null ? formatUsd(t.cost_usd) : 'n/a';
|
|
2697
|
+
const phase = t.phase_transition ? `${esc(t.phase || '?')} → ${esc(t.phase_transition)}` : esc(t.phase || '?');
|
|
2698
|
+
return [String(i + 1), esc(t.role), phase, esc(t.summary || '(no summary)'), String(t.files_changed_count), cost, esc(formatTurnTimelineTime(t))];
|
|
2699
|
+
});
|
|
2700
|
+
repoHtml += htmlSection('Turn Timeline', htmlTable(['#', 'Role', 'Phase', 'Summary', 'Files', 'Cost', 'Time'], turnRows), 4);
|
|
2701
|
+
}
|
|
2702
|
+
if (repo.decisions?.length > 0) {
|
|
2703
|
+
repoHtml += htmlSection('Decisions', '<ul>' + repo.decisions.map((d) => `<li><strong>${esc(d.id)}</strong> (${esc(d.role || '?')}, ${esc(d.phase || '?')} phase): ${esc(d.statement)}</li>`).join('') + '</ul>', 4);
|
|
2704
|
+
}
|
|
2705
|
+
if (repo.gate_summary?.length > 0) {
|
|
2706
|
+
repoHtml += htmlSection('Gate Outcomes', '<ul>' + repo.gate_summary.map((g) => `<li><code>${esc(g.gate_id)}</code>: ${badge(g.status)}</li>`).join('') + '</ul>', 4);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
sections.push(`<div class="section">${htmlSection('Repo Details', repoHtml)}</div>`);
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
return wrapHtml('AgentXchain Governance Report — Coordinator', sections.join('\n'));
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
export function formatGovernanceReportHtml(report) {
|
|
2716
|
+
if (report.overall === 'error') {
|
|
2717
|
+
return wrapHtml('AgentXchain Governance Report — Error', `
|
|
2718
|
+
<div class="meta">
|
|
2719
|
+
${htmlDl([['Input', `<code>${esc(report.input)}</code>`], ['Status', badge('error')], ['Message', esc(report.message)]])}
|
|
2720
|
+
</div>`);
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
if (report.overall === 'fail') {
|
|
2724
|
+
const errorList = (report.verification?.errors || []).map((e) => `<li>${esc(e)}</li>`).join('');
|
|
2725
|
+
return wrapHtml('AgentXchain Governance Report — Fail', `
|
|
2726
|
+
<div class="meta">
|
|
2727
|
+
${htmlDl([['Input', `<code>${esc(report.input)}</code>`], ['Verification', badge('fail')], ['Message', esc(report.message)]])}
|
|
2728
|
+
</div>
|
|
2729
|
+
${errorList ? `<div class="section"><h2>Verification Errors</h2><ul>${errorList}</ul></div>` : ''}`);
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
if (report.subject?.kind === 'governed_run') {
|
|
2733
|
+
return renderRunHtml(report);
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
return renderCoordinatorHtml(report);
|
|
2737
|
+
}
|