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