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/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
- lines.push(`| ${parentRole} | ${parentTurn} | ${outcome} | ${reviewTurn} | \`${delegation.delegation_id}\` \`${delegation.to_role}\` | \`${delegation.child_turn_id || 'pending'}\` | \`${delegation.status}\` | ${charter} |`);
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
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> &rarr; <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 || '?')} &rarr; ${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 || '?')} &rarr; ${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 || '?')} &rarr; ${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 || '?')} &rarr; ${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 || '?')} &rarr; ${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
+ }