@desplega.ai/agent-swarm 1.114.0 → 1.115.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.
Files changed (117) hide show
  1. package/dist/{actions-bm8vdxys.js → actions-17ya5wde.js} +6 -6
  2. package/dist/{app-bhwdxmvp.js → app-4f718yk0.js} +4 -4
  3. package/dist/{assistant-15mp2apr.js → assistant-ah7csnjz.js} +9 -9
  4. package/dist/{boot-reembed-awr2179r.js → boot-reembed-4jpxhb25.js} +4 -4
  5. package/dist/{boot-reembed-b9sbk87z.js → boot-reembed-qata34xa.js} +3 -3
  6. package/dist/{boot-scrub-logs-c1zsaw9m.js → boot-scrub-logs-tv48nn3z.js} +2 -2
  7. package/dist/{claude-adapter-2k45y7p7.js → claude-adapter-3csn5bks.js} +2 -2
  8. package/dist/{cli-zmmp2r79.js → cli-12vb0xg6.js} +13 -2
  9. package/dist/{cli-jzwtn8rm.js → cli-15y7qhzp.js} +188 -18
  10. package/dist/{cli-w3nq88yc.js → cli-3samkyap.js} +2 -2
  11. package/dist/{cli-7rgh0w0k.js → cli-52fg7s7x.js} +10 -3
  12. package/dist/{cli-5afkmcfj.js → cli-65ekje01.js} +6 -6
  13. package/dist/{cli-113wmegp.js → cli-6aq8xhvm.js} +1 -1
  14. package/dist/{cli-byjcad11.js → cli-7xje5amz.js} +1 -1
  15. package/dist/{cli-0kgcv878.js → cli-86fdnerz.js} +63 -2
  16. package/dist/{cli-f5pzfvwb.js → cli-b23axgz4.js} +1 -1
  17. package/dist/{cli-a0t7vapf.js → cli-bc89my0z.js} +1 -1
  18. package/dist/{cli-q4e2x252.js → cli-beea4a3y.js} +73 -24
  19. package/dist/{cli-w4wk3kx6.js → cli-cchksf5q.js} +2 -2
  20. package/dist/{cli-f9n2jg31.js → cli-cmw9rzy5.js} +2 -2
  21. package/dist/{cli-hvg05qwm.js → cli-cnwssmsr.js} +1 -1
  22. package/dist/{cli-2t4e4ayk.js → cli-dcca6j8m.js} +46 -5
  23. package/dist/{cli-tkmm5nf9.js → cli-e61fs2hp.js} +59 -2
  24. package/dist/{cli-e2xbxmhd.js → cli-hrhye2rm.js} +90 -32
  25. package/dist/{cli-312v3c41.js → cli-k3y4z3h5.js} +1 -1
  26. package/dist/{cli-6xd0bvf8.js → cli-ma04faw5.js} +1 -1
  27. package/dist/{cli-ggy8c7tk.js → cli-mnbgv3w7.js} +1 -1
  28. package/dist/{cli-hq39e630.js → cli-pet3aev6.js} +95 -10
  29. package/dist/{cli-a6tzw6vq.js → cli-q1rx7g0d.js} +6 -6
  30. package/dist/{cli-pnfyyp6h.js → cli-r9f6ngrf.js} +6 -3
  31. package/dist/{cli-q1z5d5ss.js → cli-shvaxw27.js} +4 -4
  32. package/dist/{cli-49j095n9.js → cli-t0vqevnm.js} +1 -1
  33. package/dist/{cli-k6h2zkw3.js → cli-tece6zgm.js} +1 -1
  34. package/dist/{cli-5ktc9fd4.js → cli-tffvrh6j.js} +1 -1
  35. package/dist/cli-wh91zby6.js +187222 -0
  36. package/dist/{cli-r3mtrrzp.js → cli-zsrdty74.js} +12 -12
  37. package/dist/cli.js +10 -10
  38. package/dist/{codex-adapter-4z8m9mb5.js → codex-adapter-de3w0nch.js} +2 -2
  39. package/dist/{codex-session-runner-rbct46dg.js → codex-session-runner-bnw8cysk.js} +2 -2
  40. package/dist/{commands-zk4d6esf.js → commands-hch4e2k1.js} +2 -2
  41. package/dist/{db-stn16jp2.js → db-p3h2j06x.js} +12 -2
  42. package/dist/{handlers-gshprvpf.js → handlers-9er4zx9q.js} +9 -9
  43. package/dist/{hook-amzx9tap.js → hook-4axs756z.js} +1 -1
  44. package/dist/{http-6g0hfzax.js → http-809fvtz2.js} +182 -65
  45. package/dist/{index-ty42rxw9.js → index-47em3d2n.js} +10 -10
  46. package/dist/{index-jbm7qcqk.js → index-9fqaktjr.js} +8 -8
  47. package/dist/{index-f03rnnrs.js → index-9ht8gr56.js} +9 -9
  48. package/dist/{index-df8fmphk.js → index-rm85cfdx.js} +331 -30
  49. package/dist/{keepalive-pm6g2wpw.js → keepalive-3cps6e3e.js} +4 -4
  50. package/dist/{lead-h79fzpzw.js → lead-fkk95wgq.js} +24 -24
  51. package/dist/{maintenance-433zndy1.js → maintenance-fweqh0yj.js} +4 -4
  52. package/dist/{oauth-refresh-sweep-mjsbmsk2.js → oauth-refresh-sweep-sycqtbph.js} +3 -3
  53. package/dist/{onboard-vdr9hc3f.js → onboard-cy0rkgvx.js} +2 -2
  54. package/dist/{opencode-adapter-mqsxr170.js → opencode-adapter-p6bxnbm4.js} +2 -2
  55. package/dist/{otel-impl-yswd0fty.js → otel-impl-wbns8hf7.js} +1 -1
  56. package/dist/{pi-mono-adapter-vbrvg1p8.js → pi-mono-adapter-05sck1ds.js} +1 -1
  57. package/dist/{pricing-refresh-2kk2ap9b.js → pricing-refresh-0hvazmkf.js} +4 -4
  58. package/dist/{rbac-roles-xdc5208b.js → rbac-roles-kfesjy9y.js} +4 -4
  59. package/dist/{rbac-roles-2wgh9b5t.js → rbac-roles-mwcwhbxc.js} +11 -4
  60. package/dist/{seed-pricing-m9h69d2t.js → seed-pricing-h2kdq4cq.js} +3 -3
  61. package/dist/{setup-eynkvafa.js → setup-8506q97w.js} +2 -2
  62. package/dist/{worker-136q8dc2.js → worker-4r5c6zyb.js} +24 -24
  63. package/openapi.json +8 -4
  64. package/package.json +1 -1
  65. package/src/be/db.ts +253 -20
  66. package/src/be/migrations/113_task_routing_affinity.sql +9 -0
  67. package/src/be/migrations/114_backfill_gpt_5_6_pricing.sql +15 -0
  68. package/src/be/modelsdev-cache.json +135 -0
  69. package/src/be/rbac-audit.ts +19 -8
  70. package/src/be/rbac-roles.ts +8 -1
  71. package/src/be/script-connections.ts +4 -4
  72. package/src/be/scripts/typecheck.ts +11 -0
  73. package/src/be/seed-scripts/catalog/catalog-report.inline.ts +325 -24
  74. package/src/be/seed-scripts/catalog/catalog-report.ts +325 -24
  75. package/src/heartbeat/heartbeat.ts +143 -21
  76. package/src/http/favorites.ts +1 -5
  77. package/src/http/mcp-servers.ts +38 -0
  78. package/src/http/poll.ts +7 -2
  79. package/src/http/scripts.ts +6 -0
  80. package/src/http/skills.ts +11 -0
  81. package/src/http/tasks.ts +21 -9
  82. package/src/providers/codex-models.ts +100 -30
  83. package/src/providers/modelsdev-reasoning.json +51 -0
  84. package/src/providers/reasoning-effort.ts +11 -5
  85. package/src/rbac/admission.ts +26 -2
  86. package/src/rbac/index.ts +2 -2
  87. package/src/rbac/legacy-policy.ts +9 -0
  88. package/src/rbac/permissions.ts +36 -0
  89. package/src/scripts-runtime/api-client.ts +82 -6
  90. package/src/scripts-runtime/types/swarm-sdk.d.ts +11 -0
  91. package/src/server-user.ts +114 -59
  92. package/src/slack/responses.ts +91 -1
  93. package/src/slack/watcher.ts +9 -6
  94. package/src/tasks/worker-follow-up.ts +100 -3
  95. package/src/tests/codex-adapter.test.ts +46 -23
  96. package/src/tests/heartbeat.test.ts +48 -3
  97. package/src/tests/model-control.test.ts +8 -2
  98. package/src/tests/pool-affinity.test.ts +401 -0
  99. package/src/tests/rbac-admission-e2e.test.ts +152 -7
  100. package/src/tests/rbac-admission.test.ts +60 -1
  101. package/src/tests/rbac-engine.test.ts +9 -0
  102. package/src/tests/rbac-mcp-admission-e2e.test.ts +362 -0
  103. package/src/tests/rbac-roles.test.ts +2 -0
  104. package/src/tests/reasoning-effort.test.ts +15 -2
  105. package/src/tests/script-apis-mcp.test.ts +37 -13
  106. package/src/tests/script-connections.test.ts +27 -1
  107. package/src/tests/scripts-runtime.test.ts +187 -0
  108. package/src/tests/seed-scripts.test.ts +36 -0
  109. package/src/tests/slack-inline-output.test.ts +65 -0
  110. package/src/tests/user-token-rest-auth.test.ts +110 -0
  111. package/src/tools/script-apis.ts +62 -3
  112. package/src/tools/send-task.ts +15 -1
  113. package/src/tools/task-action.ts +34 -2
  114. package/src/tools/templates.ts +48 -0
  115. package/src/tools/utils.ts +2 -0
  116. package/src/types.ts +27 -5
  117. package/dist/cli-46b6kc7f.js +0 -1698
@@ -20,24 +20,24 @@ import {
20
20
  stopWaitPoller,
21
21
  subscribeWaitToBus,
22
22
  validateTemplateVariables
23
- } from "./cli-a6tzw6vq.js";
23
+ } from "./cli-q1rx7g0d.js";
24
24
  import"./cli-f14fvzag.js";
25
- import"./cli-pnfyyp6h.js";
25
+ import"./cli-r9f6ngrf.js";
26
26
  import"./cli-jkhwmmgg.js";
27
- import"./cli-f9n2jg31.js";
28
- import"./cli-byjcad11.js";
27
+ import"./cli-cmw9rzy5.js";
28
+ import"./cli-7xje5amz.js";
29
29
  import"./cli-q25ejbyn.js";
30
- import"./cli-49j095n9.js";
30
+ import"./cli-t0vqevnm.js";
31
31
  import"./cli-0pygq9r1.js";
32
- import"./cli-312v3c41.js";
32
+ import"./cli-k3y4z3h5.js";
33
33
  import"./cli-dh55d5fg.js";
34
34
  import {
35
35
  interpolate
36
- } from "./cli-jzwtn8rm.js";
36
+ } from "./cli-15y7qhzp.js";
37
37
  import"./cli-z2zcxes1.js";
38
38
  import"./cli-bw80ck94.js";
39
39
  import"./cli-xz9aq0rf.js";
40
- import"./cli-f5pzfvwb.js";
40
+ import"./cli-b23axgz4.js";
41
41
  import"./cli-4j2mrzag.js";
42
42
  import {
43
43
  workflowEventBus
@@ -4,23 +4,23 @@ import {
4
4
  runScheduleNow,
5
5
  startScheduler,
6
6
  stopScheduler
7
- } from "./cli-q1z5d5ss.js";
8
- import"./cli-a6tzw6vq.js";
7
+ } from "./cli-shvaxw27.js";
8
+ import"./cli-q1rx7g0d.js";
9
9
  import"./cli-f14fvzag.js";
10
- import"./cli-pnfyyp6h.js";
10
+ import"./cli-r9f6ngrf.js";
11
11
  import"./cli-jkhwmmgg.js";
12
- import"./cli-f9n2jg31.js";
13
- import"./cli-byjcad11.js";
12
+ import"./cli-cmw9rzy5.js";
13
+ import"./cli-7xje5amz.js";
14
14
  import"./cli-q25ejbyn.js";
15
- import"./cli-49j095n9.js";
15
+ import"./cli-t0vqevnm.js";
16
16
  import"./cli-0pygq9r1.js";
17
- import"./cli-312v3c41.js";
17
+ import"./cli-k3y4z3h5.js";
18
18
  import"./cli-dh55d5fg.js";
19
- import"./cli-jzwtn8rm.js";
19
+ import"./cli-15y7qhzp.js";
20
20
  import"./cli-z2zcxes1.js";
21
21
  import"./cli-bw80ck94.js";
22
22
  import"./cli-xz9aq0rf.js";
23
- import"./cli-f5pzfvwb.js";
23
+ import"./cli-b23axgz4.js";
24
24
  import"./cli-4j2mrzag.js";
25
25
  import"./cli-9aryf9tv.js";
26
26
  import"./cli-p9swy5t3.js";
@@ -3,15 +3,15 @@ import {
3
3
  extractArgsJsonSchema,
4
4
  extractScriptSignature,
5
5
  typecheckScript
6
- } from "./cli-zmmp2r79.js";
6
+ } from "./cli-12vb0xg6.js";
7
7
  import {
8
8
  getScript,
9
9
  upsertScriptByName,
10
10
  validateScriptImports
11
- } from "./cli-pnfyyp6h.js";
11
+ } from "./cli-r9f6ngrf.js";
12
12
  import"./cli-jkhwmmgg.js";
13
- import"./cli-f9n2jg31.js";
14
- import"./cli-byjcad11.js";
13
+ import"./cli-cmw9rzy5.js";
14
+ import"./cli-7xje5amz.js";
15
15
  import"./cli-q25ejbyn.js";
16
16
  import"./cli-0pygq9r1.js";
17
17
  import {
@@ -21,11 +21,11 @@ import {
21
21
  getSkillByName,
22
22
  init_db,
23
23
  updateSkill
24
- } from "./cli-jzwtn8rm.js";
24
+ } from "./cli-15y7qhzp.js";
25
25
  import"./cli-z2zcxes1.js";
26
26
  import"./cli-bw80ck94.js";
27
27
  import"./cli-xz9aq0rf.js";
28
- import"./cli-f5pzfvwb.js";
28
+ import"./cli-b23axgz4.js";
29
29
  import"./cli-4j2mrzag.js";
30
30
  import"./cli-p9swy5t3.js";
31
31
 
@@ -318,6 +318,7 @@ function catalogReportHumanLabel(value: string): string {
318
318
 
319
319
  function catalogReportFormatMetric(value: unknown): string {
320
320
  if (typeof value === "number") return new Intl.NumberFormat("en-US").format(value);
321
+ if (typeof value === "boolean") return value ? "Yes" : "No";
321
322
  return catalogReportAsText(value);
322
323
  }
323
324
 
@@ -328,6 +329,25 @@ function catalogReportSeverityTone(value?: string): string {
328
329
  return "low";
329
330
  }
330
331
 
332
+ function catalogReportIsScalar(value: unknown): boolean {
333
+ return value == null || ["string", "number", "boolean"].includes(typeof value);
334
+ }
335
+
336
+ function catalogReportIsRecord(value: unknown): value is Record<string, unknown> {
337
+ return value !== null && typeof value === "object" && !Array.isArray(value);
338
+ }
339
+
340
+ function catalogReportValueClass(value: unknown): string {
341
+ if (typeof value === "number") return "numeric";
342
+ if (typeof value === "boolean") return value ? "positive" : "muted-value";
343
+ return "";
344
+ }
345
+
346
+ function catalogReportClassAttr(value: unknown): string {
347
+ const className = catalogReportValueClass(value);
348
+ return className ? \` class="\${catalogReportHtmlEscape(className)}"\` : "";
349
+ }
350
+
331
351
  function catalogReportRenderSampleValue(value: unknown): string {
332
352
  if (Array.isArray(value)) {
333
353
  return value
@@ -365,9 +385,116 @@ function catalogReportRenderSamples(samples?: unknown[]): string {
365
385
  </div>\`;
366
386
  }
367
387
 
388
+ function catalogReportTableRows(rows: Record<string, unknown>[], limit = 12): string {
389
+ if (rows.length === 0) return '<p class="empty">No rows.</p>';
390
+ const columns = Array.from(new Set(rows.flatMap((row) => Object.keys(row).slice(0, 8)))).slice(0, 8);
391
+ const visible = rows.slice(0, limit);
392
+ const hiddenCount = Math.max(0, rows.length - visible.length);
393
+ return \`<div class="data-table" role="region" aria-label="Data table">
394
+ <table>
395
+ <thead><tr>\${columns.map((column) => \`<th>\${catalogReportHtmlEscape(catalogReportHumanLabel(column))}</th>\`).join("")}</tr></thead>
396
+ <tbody>
397
+ \${visible
398
+ .map(
399
+ (row) =>
400
+ \`<tr>\${columns
401
+ .map((column) => {
402
+ const value = row[column];
403
+ return \`<td\${catalogReportClassAttr(value)}>\${catalogReportHtmlEscape(
404
+ catalogReportRenderSampleValue(value),
405
+ )}</td>\`;
406
+ })
407
+ .join("")}</tr>\`,
408
+ )
409
+ .join("")}
410
+ </tbody>
411
+ </table>
412
+ \${hiddenCount ? \`<p class="table-note">Showing \${visible.length} of \${rows.length} rows. Full payload remains in the appendix.</p>\` : ""}
413
+ </div>\`;
414
+ }
415
+
416
+ function catalogReportRenderDataValue(value: unknown): string {
417
+ if (catalogReportIsScalar(value)) {
418
+ return \`<strong\${catalogReportClassAttr(value)}>\${catalogReportHtmlEscape(
419
+ catalogReportFormatMetric(value),
420
+ )}</strong>\`;
421
+ }
422
+ if (Array.isArray(value)) {
423
+ if (value.every((item) => catalogReportIsRecord(item))) {
424
+ return catalogReportTableRows(value as Record<string, unknown>[]);
425
+ }
426
+ return \`<div class="value-list">\${value
427
+ .slice(0, 12)
428
+ .map((item) => \`<span>\${catalogReportHtmlEscape(catalogReportRenderSampleValue(item))}</span>\`)
429
+ .join("")}</div>\`;
430
+ }
431
+ if (catalogReportIsRecord(value)) {
432
+ const entries = Object.entries(value);
433
+ const scalarEntries = entries.filter(([, entryValue]) => catalogReportIsScalar(entryValue));
434
+ const complexEntries = entries.filter(([, entryValue]) => !catalogReportIsScalar(entryValue));
435
+ return \`<div class="data-object">
436
+ \${
437
+ scalarEntries.length
438
+ ? \`<div class="object-stats">\${scalarEntries
439
+ .map(
440
+ ([label, entryValue]) =>
441
+ \`<div><span>\${catalogReportHtmlEscape(catalogReportHumanLabel(label))}</span><strong\${catalogReportClassAttr(
442
+ entryValue,
443
+ )}>\${catalogReportHtmlEscape(catalogReportFormatMetric(entryValue))}</strong></div>\`,
444
+ )
445
+ .join("")}</div>\`
446
+ : ""
447
+ }
448
+ \${complexEntries
449
+ .map(
450
+ ([label, entryValue]) => \`<section class="data-nested">
451
+ <h4>\${catalogReportHtmlEscape(catalogReportHumanLabel(label))}</h4>
452
+ \${catalogReportRenderDataValue(entryValue)}
453
+ </section>\`,
454
+ )
455
+ .join("")}
456
+ </div>\`;
457
+ }
458
+ return \`<pre>\${catalogReportHtmlEscape(JSON.stringify(value, null, 2))}</pre>\`;
459
+ }
460
+
461
+ function catalogReportRenderDataPanels(entries: Array<[string, unknown]>): string {
462
+ if (entries.length === 0) return "";
463
+ return \`<div class="data-panels">
464
+ \${entries
465
+ .map(
466
+ ([label, value]) => \`<section class="data-panel">
467
+ <div class="data-panel-head">
468
+ <h3>\${catalogReportHtmlEscape(catalogReportHumanLabel(label))}</h3>
469
+ <span>\${catalogReportHtmlEscape(
470
+ Array.isArray(value)
471
+ ? \`\${value.length} rows\`
472
+ : catalogReportIsRecord(value)
473
+ ? \`\${Object.keys(value).length} fields\`
474
+ : "value",
475
+ )}</span>
476
+ </div>
477
+ \${catalogReportRenderDataValue(value)}
478
+ </section>\`,
479
+ )
480
+ .join("")}
481
+ </div>\`;
482
+ }
483
+
368
484
  export function renderCatalogReportPage(report: CatalogReport): string {
485
+ const nav = report.sections
486
+ .map(
487
+ (section) =>
488
+ \`<a href="#\${catalogReportHtmlEscape(section.key)}"><span>\${catalogReportHtmlEscape(
489
+ section.label || catalogReportHumanLabel(section.key),
490
+ )}</span><strong>\${catalogReportHtmlEscape(catalogReportFormatMetric(section.findingCount ?? section.findings?.length ?? 0))}</strong></a>\`,
491
+ )
492
+ .join("");
369
493
  const sections = report.sections
370
494
  .map((section) => {
495
+ const checkEntries = Object.entries(section.checks || {});
496
+ const scalarChecks = checkEntries.filter(([, value]) => catalogReportIsScalar(value));
497
+ const dataChecks = checkEntries.filter(([, value]) => !catalogReportIsScalar(value));
371
498
  const findings = (section.findings || [])
372
499
  .map(
373
500
  (finding) => \`<article class="finding \${catalogReportHtmlEscape(catalogReportSeverityTone(finding.severity))}">
@@ -385,25 +512,26 @@ export function renderCatalogReportPage(report: CatalogReport): string {
385
512
  </article>\`,
386
513
  )
387
514
  .join("");
388
- const checks = Object.entries(section.checks || {})
515
+ const checks = scalarChecks
389
516
  .map(
390
517
  ([label, value]) =>
391
- \`<div class="check"><span>\${catalogReportHtmlEscape(catalogReportHumanLabel(label))}</span><strong>\${catalogReportHtmlEscape(
392
- catalogReportFormatMetric(value),
393
- )}</strong></div>\`,
518
+ \`<div class="check"><span>\${catalogReportHtmlEscape(catalogReportHumanLabel(label))}</span><strong\${catalogReportClassAttr(
519
+ value,
520
+ )}>\${catalogReportHtmlEscape(catalogReportFormatMetric(value))}</strong></div>\`,
394
521
  )
395
522
  .join("");
396
- return \`<section class="section">
523
+ return \`<section class="section" id="\${catalogReportHtmlEscape(section.key)}">
397
524
  <div class="section-grid">
398
525
  <aside class="checks">
399
526
  <p class="section-kicker">\${catalogReportHtmlEscape(section.label || catalogReportHumanLabel(section.key))}</p>
400
- <div class="check-list">\${checks}</div>
527
+ <div class="check-list">\${checks || '<p class="empty">No scalar checks.</p>'}</div>
401
528
  </aside>
402
529
  <div>
403
530
  <div class="section-head">
404
531
  <h2>\${catalogReportHtmlEscape(section.goal)}</h2>
405
532
  <span>\${catalogReportHtmlEscape(catalogReportFormatMetric(section.findingCount ?? section.findings?.length ?? 0))} finding(s)</span>
406
533
  </div>
534
+ \${catalogReportRenderDataPanels(dataChecks)}
407
535
  <div class="findings">
408
536
  \${findings || '<p class="empty">No actionable findings in this cluster.</p>'}
409
537
  </div>
@@ -424,10 +552,14 @@ export function renderCatalogReportPage(report: CatalogReport): string {
424
552
  color-scheme: light;
425
553
  --bg: #f5f2ea;
426
554
  --panel: #ffffff;
555
+ --panel-soft: #fbfaf6;
427
556
  --ink: #18181b;
428
557
  --muted: #5f6368;
558
+ --muted-strong: #3f3f46;
429
559
  --line: #ded8cb;
560
+ --line-soft: #ebe5d7;
430
561
  --accent: #255c99;
562
+ --accent-soft: #edf5ff;
431
563
  --danger: #b42318;
432
564
  --danger-bg: #fff1f0;
433
565
  --warn: #b54708;
@@ -449,11 +581,19 @@ export function renderCatalogReportPage(report: CatalogReport): string {
449
581
  line-height: 1.55;
450
582
  }
451
583
  main {
452
- width: min(1120px, calc(100% - 32px));
584
+ width: min(1240px, calc(100% - 32px));
453
585
  margin: 0 auto;
454
586
  padding: 48px 0 72px;
455
587
  }
456
- header { margin-bottom: 28px; }
588
+ header {
589
+ display: grid;
590
+ grid-template-columns: minmax(0, 1fr) auto;
591
+ gap: 24px;
592
+ align-items: end;
593
+ margin-bottom: 24px;
594
+ padding-bottom: 22px;
595
+ border-bottom: 1px solid var(--line);
596
+ }
457
597
  .eyebrow {
458
598
  margin: 0 0 8px;
459
599
  color: var(--muted);
@@ -465,34 +605,48 @@ export function renderCatalogReportPage(report: CatalogReport): string {
465
605
  h1 {
466
606
  margin: 0;
467
607
  max-width: 860px;
468
- font-size: clamp(2rem, 4vw, 3rem);
608
+ font-size: clamp(2rem, 3.8vw, 3.25rem);
469
609
  line-height: 1.05;
470
610
  letter-spacing: 0;
471
611
  }
472
612
  .lede {
473
- max-width: 780px;
613
+ max-width: 840px;
474
614
  margin: 16px 0 0;
475
- color: var(--muted);
615
+ color: var(--muted-strong);
476
616
  font-size: 18px;
477
617
  }
618
+ .description {
619
+ max-width: 340px;
620
+ margin: 0;
621
+ color: var(--muted);
622
+ font-size: 14px;
623
+ text-align: right;
624
+ }
478
625
  .metrics {
479
626
  display: grid;
480
- grid-template-columns: repeat(4, minmax(0, 1fr));
627
+ grid-template-columns: repeat(6, minmax(0, 1fr));
481
628
  gap: 12px;
482
- margin: 32px 0;
629
+ margin: 24px 0 16px;
483
630
  }
484
- .metric, .section, details {
631
+ .metric, .section, details, .report-nav {
485
632
  background: var(--panel);
486
633
  border: 1px solid var(--line);
487
634
  border-radius: var(--radius);
488
635
  box-shadow: var(--shadow);
489
636
  }
490
- .metric { padding: 18px; }
637
+ .metric {
638
+ min-height: 112px;
639
+ padding: 16px;
640
+ display: flex;
641
+ flex-direction: column;
642
+ justify-content: space-between;
643
+ }
491
644
  .metric strong {
492
645
  display: block;
493
- font-size: 32px;
646
+ font-size: 30px;
494
647
  line-height: 1;
495
648
  font-variant-numeric: tabular-nums;
649
+ overflow-wrap: anywhere;
496
650
  }
497
651
  .metric span {
498
652
  display: block;
@@ -501,14 +655,45 @@ export function renderCatalogReportPage(report: CatalogReport): string {
501
655
  font-size: 13px;
502
656
  font-weight: 650;
503
657
  }
658
+ .report-nav {
659
+ display: grid;
660
+ grid-template-columns: repeat(6, minmax(0, 1fr));
661
+ gap: 1px;
662
+ overflow: hidden;
663
+ margin: 0 0 20px;
664
+ background: var(--line);
665
+ }
666
+ .report-nav a {
667
+ display: flex;
668
+ align-items: center;
669
+ justify-content: space-between;
670
+ gap: 10px;
671
+ min-height: 48px;
672
+ padding: 11px 13px;
673
+ background: var(--panel);
674
+ color: var(--muted-strong);
675
+ text-decoration: none;
676
+ font-size: 13px;
677
+ font-weight: 700;
678
+ }
679
+ .report-nav a:hover { background: var(--accent-soft); }
680
+ .report-nav strong {
681
+ color: var(--accent);
682
+ font-variant-numeric: tabular-nums;
683
+ }
504
684
  .section {
505
- margin-top: 18px;
506
- padding: 24px;
685
+ margin-top: 16px;
686
+ padding: 22px;
507
687
  }
508
688
  .section-grid {
509
689
  display: grid;
510
- grid-template-columns: 260px minmax(0, 1fr);
511
- gap: 28px;
690
+ grid-template-columns: 240px minmax(0, 1fr);
691
+ gap: 24px;
692
+ }
693
+ .checks {
694
+ position: sticky;
695
+ top: 18px;
696
+ align-self: start;
512
697
  }
513
698
  .section-kicker {
514
699
  margin: 0 0 12px;
@@ -530,6 +715,7 @@ export function renderCatalogReportPage(report: CatalogReport): string {
530
715
  padding: 10px 0;
531
716
  border-bottom: 1px solid var(--line);
532
717
  }
718
+ .check:last-child { border-bottom: 0; }
533
719
  .check span {
534
720
  color: var(--muted);
535
721
  font-size: 13px;
@@ -537,6 +723,8 @@ export function renderCatalogReportPage(report: CatalogReport): string {
537
723
  .check strong {
538
724
  font-size: 18px;
539
725
  font-variant-numeric: tabular-nums;
726
+ text-align: right;
727
+ overflow-wrap: anywhere;
540
728
  }
541
729
  .section-head {
542
730
  display: flex;
@@ -554,11 +742,113 @@ export function renderCatalogReportPage(report: CatalogReport): string {
554
742
  }
555
743
  .section-head > span {
556
744
  flex: 0 0 auto;
557
- color: var(--muted);
745
+ color: var(--accent);
746
+ background: var(--accent-soft);
747
+ border: 1px solid #cfe5ff;
748
+ border-radius: 999px;
749
+ padding: 5px 10px;
558
750
  font-size: 13px;
559
751
  font-weight: 700;
560
752
  white-space: nowrap;
561
753
  }
754
+ .data-panels {
755
+ display: grid;
756
+ gap: 12px;
757
+ margin: 0 0 16px;
758
+ }
759
+ .data-panel {
760
+ border: 1px solid var(--line-soft);
761
+ border-radius: var(--radius);
762
+ background: var(--panel-soft);
763
+ overflow: hidden;
764
+ }
765
+ .data-panel-head {
766
+ display: flex;
767
+ align-items: baseline;
768
+ justify-content: space-between;
769
+ gap: 16px;
770
+ padding: 12px 14px;
771
+ border-bottom: 1px solid var(--line-soft);
772
+ background: #fffdf8;
773
+ }
774
+ .data-panel-head h3,
775
+ .data-nested h4 {
776
+ margin: 0;
777
+ color: var(--muted-strong);
778
+ font-size: 14px;
779
+ line-height: 1.25;
780
+ letter-spacing: 0;
781
+ }
782
+ .data-panel-head span,
783
+ .table-note {
784
+ color: var(--muted);
785
+ font-size: 12px;
786
+ font-weight: 700;
787
+ }
788
+ .data-object {
789
+ display: grid;
790
+ gap: 12px;
791
+ padding: 14px;
792
+ }
793
+ .object-stats {
794
+ display: grid;
795
+ grid-template-columns: repeat(3, minmax(0, 1fr));
796
+ gap: 10px;
797
+ }
798
+ .object-stats > div {
799
+ min-height: 76px;
800
+ padding: 12px;
801
+ border: 1px solid var(--line-soft);
802
+ border-radius: var(--radius);
803
+ background: var(--panel);
804
+ }
805
+ .object-stats span,
806
+ .value-list span {
807
+ display: block;
808
+ color: var(--muted);
809
+ font-size: 12px;
810
+ font-weight: 700;
811
+ }
812
+ .object-stats strong {
813
+ display: block;
814
+ margin-top: 6px;
815
+ color: var(--ink);
816
+ font-size: 20px;
817
+ line-height: 1.1;
818
+ font-variant-numeric: tabular-nums;
819
+ overflow-wrap: anywhere;
820
+ }
821
+ .data-nested {
822
+ display: grid;
823
+ gap: 8px;
824
+ }
825
+ .data-table {
826
+ overflow-x: auto;
827
+ background: var(--panel);
828
+ }
829
+ .data-table .table-note {
830
+ margin: 0;
831
+ padding: 10px 12px;
832
+ border-top: 1px solid var(--line-soft);
833
+ background: var(--panel-soft);
834
+ }
835
+ .value-list {
836
+ display: flex;
837
+ flex-wrap: wrap;
838
+ gap: 8px;
839
+ padding: 14px;
840
+ }
841
+ .value-list span {
842
+ padding: 6px 9px;
843
+ border: 1px solid var(--line-soft);
844
+ border-radius: 999px;
845
+ background: var(--panel);
846
+ color: var(--muted-strong);
847
+ overflow-wrap: anywhere;
848
+ }
849
+ .numeric { font-variant-numeric: tabular-nums; }
850
+ .positive { color: var(--low); }
851
+ .muted-value { color: var(--muted); }
562
852
  .findings {
563
853
  display: grid;
564
854
  gap: 12px;
@@ -667,26 +957,36 @@ export function renderCatalogReportPage(report: CatalogReport): string {
667
957
  }
668
958
  @media (max-width: 860px) {
669
959
  main { width: min(100% - 24px, 1120px); padding-top: 32px; }
960
+ header { grid-template-columns: 1fr; }
961
+ .description { max-width: none; text-align: left; }
670
962
  .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
963
+ .report-nav { grid-template-columns: repeat(2, minmax(0, 1fr)); }
671
964
  .section-grid { grid-template-columns: 1fr; gap: 18px; }
965
+ .checks { position: static; }
672
966
  .section { padding: 18px; }
673
967
  .section-head { display: block; }
674
968
  .section-head > span { display: block; margin-top: 8px; }
969
+ .object-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
675
970
  }
676
971
  @media (max-width: 520px) {
677
972
  .metrics { grid-template-columns: 1fr; }
973
+ .report-nav { grid-template-columns: 1fr; }
678
974
  .lede { font-size: 16px; }
679
975
  .finding-head { display: block; }
680
976
  .pill { margin-top: 10px; }
977
+ .object-stats { grid-template-columns: 1fr; }
681
978
  }
682
979
  </style>
683
980
  </head>
684
981
  <body>
685
982
  <main>
686
983
  <header>
687
- <p class="eyebrow">Generated \${catalogReportHtmlEscape(report.generatedAt)}</p>
688
- <h1>\${catalogReportHtmlEscape(report.title)}</h1>
689
- <p class="lede">\${catalogReportHtmlEscape(report.lede)}</p>
984
+ <div>
985
+ <p class="eyebrow">Generated \${catalogReportHtmlEscape(report.generatedAt)}</p>
986
+ <h1>\${catalogReportHtmlEscape(report.title)}</h1>
987
+ <p class="lede">\${catalogReportHtmlEscape(report.lede)}</p>
988
+ </div>
989
+ <p class="description">\${catalogReportHtmlEscape(report.description)}</p>
690
990
  </header>
691
991
  <section class="metrics" aria-label="Audit summary">
692
992
  \${report.metrics
@@ -698,6 +998,7 @@ export function renderCatalogReportPage(report: CatalogReport): string {
698
998
  )
699
999
  .join("")}
700
1000
  </section>
1001
+ <nav class="report-nav" aria-label="Report sections">\${nav}</nav>
701
1002
  \${sections}
702
1003
  <details>
703
1004
  <summary>Compressed JSON appendix</summary>
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  ensureTokenOrThrow
3
- } from "./cli-49j095n9.js";
3
+ } from "./cli-t0vqevnm.js";
4
4
  import"./cli-0pygq9r1.js";
5
- import"./cli-jzwtn8rm.js";
5
+ import"./cli-15y7qhzp.js";
6
6
  import"./cli-z2zcxes1.js";
7
7
  import"./cli-bw80ck94.js";
8
8
  import"./cli-xz9aq0rf.js";
9
- import"./cli-f5pzfvwb.js";
9
+ import"./cli-b23axgz4.js";
10
10
  import"./cli-4j2mrzag.js";
11
11
  import {
12
12
  __require
@@ -55,7 +55,7 @@ async function notifySlack(text) {
55
55
  return;
56
56
  }
57
57
  try {
58
- const { getSlackApp } = await import("./app-bhwdxmvp.js");
58
+ const { getSlackApp } = await import("./app-4f718yk0.js");
59
59
  const app = getSlackApp();
60
60
  if (!app) {
61
61
  console.warn("[OAuth Keepalive] Slack not available, cannot send notification");