@desplega.ai/agent-swarm 1.113.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 (237) hide show
  1. package/README.md +2 -2
  2. package/dist/{actions-q4n7cz6e.js → actions-17ya5wde.js} +8 -9
  3. package/dist/{app-es4nzc71.js → app-4f718yk0.js} +6 -6
  4. package/dist/{assistant-0x8ey0vs.js → assistant-ah7csnjz.js} +19 -18
  5. package/dist/{boot-reembed-69agkhv6.js → boot-reembed-4jpxhb25.js} +6 -6
  6. package/dist/{boot-reembed-p0ny05t8.js → boot-reembed-qata34xa.js} +5 -5
  7. package/dist/{boot-scrub-logs-yybrkmvr.js → boot-scrub-logs-tv48nn3z.js} +4 -4
  8. package/dist/{claude-adapter-mma9a1yr.js → claude-adapter-3csn5bks.js} +3 -3
  9. package/dist/{claude-managed-adapter-pv2nbqy4.js → claude-managed-adapter-kysmkz89.js} +2 -2
  10. package/dist/cli-0pygq9r1.js +429 -0
  11. package/dist/{cli-hsetkkd3.js → cli-12vb0xg6.js} +33 -9
  12. package/dist/{cli-ncfgsqbd.js → cli-15y7qhzp.js} +262 -33
  13. package/dist/{cli-7ga43spr.js → cli-1yjrbg1f.js} +1 -1
  14. package/dist/{cli-36ymmpmp.js → cli-3samkyap.js} +2 -2
  15. package/dist/cli-52fg7s7x.js +269 -0
  16. package/dist/{cli-hjhvekf4.js → cli-65ekje01.js} +11 -8
  17. package/dist/{cli-h3k622d0.js → cli-6aq8xhvm.js} +90 -3
  18. package/dist/{cli-f3qa069v.js → cli-7xje5amz.js} +1 -1
  19. package/dist/cli-86fdnerz.js +405 -0
  20. package/dist/{cli-9cy6nc5m.js → cli-8fjwym53.js} +1 -1
  21. package/dist/{cli-c3frqf65.js → cli-b23axgz4.js} +3 -3
  22. package/dist/{cli-a0t7vapf.js → cli-bc89my0z.js} +1 -1
  23. package/dist/{cli-bnzbn8j8.js → cli-beea4a3y.js} +73 -24
  24. package/dist/{cli-q21d49ac.js → cli-bw80ck94.js} +1 -1
  25. package/dist/{cli-pwc5e83c.js → cli-cchksf5q.js} +12 -7
  26. package/dist/{cli-30bbaveh.js → cli-cmw9rzy5.js} +2 -2
  27. package/dist/{cli-y6mrptcn.js → cli-cnwssmsr.js} +1 -1
  28. package/dist/{cli-r5g6ngtn.js → cli-dcca6j8m.js} +46 -5
  29. package/dist/{cli-tkmm5nf9.js → cli-e61fs2hp.js} +59 -2
  30. package/dist/{cli-6wscj9z0.js → cli-e7306swv.js} +14 -14
  31. package/dist/{cli-dcfyyh3t.js → cli-ftyf58xj.js} +2 -2
  32. package/dist/{cli-4xyj9jtq.js → cli-hrhye2rm.js} +1062 -478
  33. package/dist/cli-jkhwmmgg.js +136 -0
  34. package/dist/{cli-y4ycrnjc.js → cli-k3y4z3h5.js} +1 -1
  35. package/dist/{cli-6xd0bvf8.js → cli-ma04faw5.js} +1 -1
  36. package/dist/{cli-2fc97xm5.js → cli-mnbgv3w7.js} +4 -4
  37. package/dist/{cli-trmapn9k.js → cli-pet3aev6.js} +95 -10
  38. package/dist/{cli-z46pnksz.js → cli-q1rx7g0d.js} +175 -28
  39. package/dist/{cli-qwgtacd6.js → cli-r9f6ngrf.js} +1244 -336
  40. package/dist/{cli-gtkqc144.js → cli-shvaxw27.js} +13 -7
  41. package/dist/{cli-ywez6yps.js → cli-sq7f72d6.js} +1 -1
  42. package/dist/cli-t0vqevnm.js +393 -0
  43. package/dist/{cli-tbw13sx8.js → cli-tece6zgm.js} +1 -1
  44. package/dist/{cli-bgvskydy.js → cli-tffvrh6j.js} +1 -1
  45. package/dist/cli-wh91zby6.js +187222 -0
  46. package/dist/{cli-anrj584m.js → cli-xz9aq0rf.js} +1 -1
  47. package/dist/{cli-mq580e06.js → cli-zsrdty74.js} +18 -18
  48. package/dist/cli.js +32 -15
  49. package/dist/{codex-adapter-5cmy858w.js → codex-adapter-de3w0nch.js} +10 -10
  50. package/dist/{codex-session-runner-46q8mrba.js → codex-session-runner-bnw8cysk.js} +10 -10
  51. package/dist/{commands-z5dbwta3.js → commands-hch4e2k1.js} +4 -4
  52. package/dist/{db-bbgahh4z.js → db-p3h2j06x.js} +14 -4
  53. package/dist/{handlers-6b44317z.js → handlers-9er4zx9q.js} +17 -31
  54. package/dist/{hook-sbp5fmps.js → hook-4axs756z.js} +7 -7
  55. package/dist/{http-2xz43gz3.js → http-809fvtz2.js} +1934 -886
  56. package/dist/{index-wz1ea15p.js → index-2dgfj9av.js} +3 -3
  57. package/dist/{index-nhat35em.js → index-47em3d2n.js} +14 -11
  58. package/dist/{index-5ghxn8s6.js → index-9fqaktjr.js} +12 -9
  59. package/dist/{index-0gzmqj4k.js → index-9ht8gr56.js} +13 -10
  60. package/dist/{index-vfedgdw6.js → index-rm85cfdx.js} +368 -40
  61. package/dist/{index-ykvs43z4.js → index-t2snazez.js} +4 -4
  62. package/dist/{keepalive-62gfj91d.js → keepalive-3cps6e3e.js} +7 -6
  63. package/dist/{lead-ag0akn0v.js → lead-fkk95wgq.js} +36 -32
  64. package/dist/{maintenance-xxrrzk5x.js → maintenance-fweqh0yj.js} +6 -6
  65. package/dist/{mistral-conversations-26yrnbf1.js → mistral-conversations-7gqnz2wt.js} +7 -7
  66. package/dist/oauth-refresh-sweep-sycqtbph.js +114 -0
  67. package/dist/{onboard-64zyfcbs.js → onboard-cy0rkgvx.js} +2 -2
  68. package/dist/{opencode-adapter-mqsxr170.js → opencode-adapter-p6bxnbm4.js} +2 -2
  69. package/dist/{otel-zjd9zaxs.js → otel-g299k41b.js} +2 -2
  70. package/dist/{otel-impl-b86qseaa.js → otel-impl-wbns8hf7.js} +1 -1
  71. package/dist/{pi-mono-adapter-kxjxpfgy.js → pi-mono-adapter-05sck1ds.js} +17 -109
  72. package/dist/{pricing-refresh-cgtkffpa.js → pricing-refresh-0hvazmkf.js} +6 -6
  73. package/dist/rbac-roles-kfesjy9y.js +28 -0
  74. package/dist/rbac-roles-mwcwhbxc.js +283 -0
  75. package/dist/{seed-pricing-1r1y1xkq.js → seed-pricing-h2kdq4cq.js} +5 -5
  76. package/dist/{setup-mkcgkjf7.js → setup-8506q97w.js} +2 -2
  77. package/dist/{worker-351sze63.js → worker-4r5c6zyb.js} +36 -32
  78. package/openapi.json +1327 -20
  79. package/package.json +3 -3
  80. package/src/agentmail/handlers.ts +16 -6
  81. package/src/agentmail/templates.ts +25 -5
  82. package/src/be/audit-user.ts +28 -4
  83. package/src/be/db-queries/oauth.ts +42 -0
  84. package/src/be/db.ts +314 -28
  85. package/src/be/identity.ts +79 -0
  86. package/src/be/mcp-proxy.ts +132 -0
  87. package/src/be/migrations/109_rbac_roles.sql +79 -0
  88. package/src/be/migrations/111_oauth_credential_bindings.sql +6 -0
  89. package/src/be/migrations/112_script_connections_graphql.sql +96 -0
  90. package/src/be/migrations/113_task_routing_affinity.sql +9 -0
  91. package/src/be/migrations/114_backfill_gpt_5_6_pricing.sql +15 -0
  92. package/src/be/modelsdev-cache.json +135 -0
  93. package/src/be/oauth-credential-bindings.ts +35 -0
  94. package/src/be/oauth-refresh-sweep.ts +159 -0
  95. package/src/be/rbac-audit.ts +52 -13
  96. package/src/be/rbac-roles.ts +424 -0
  97. package/src/be/script-connections.ts +459 -40
  98. package/src/be/script-credential-broker.ts +29 -3
  99. package/src/be/scripts/typecheck.ts +51 -7
  100. package/src/be/seed-scripts/catalog/catalog-report.inline.ts +325 -24
  101. package/src/be/seed-scripts/catalog/catalog-report.ts +325 -24
  102. package/src/be/users.ts +24 -0
  103. package/src/cli.tsx +17 -0
  104. package/src/github/handlers.ts +10 -9
  105. package/src/github/templates.ts +45 -9
  106. package/src/gitlab/handlers.ts +20 -8
  107. package/src/gitlab/templates.ts +18 -6
  108. package/src/heartbeat/heartbeat.ts +143 -21
  109. package/src/http/all-routes.ts +3 -0
  110. package/src/http/approval-requests.ts +12 -0
  111. package/src/http/config.ts +44 -10
  112. package/src/http/core.ts +30 -3
  113. package/src/http/favorites.ts +1 -5
  114. package/src/http/index.ts +25 -1
  115. package/src/http/mcp-oauth.ts +2 -0
  116. package/src/http/mcp-servers.ts +44 -3
  117. package/src/http/oauth-generic.ts +105 -0
  118. package/src/http/poll.ts +20 -3
  119. package/src/http/script-connection-proxy.ts +116 -0
  120. package/src/http/script-connections.ts +1657 -0
  121. package/src/http/scripts.ts +27 -7
  122. package/src/http/skills.ts +11 -0
  123. package/src/http/tasks.ts +27 -13
  124. package/src/http/x.ts +6 -2
  125. package/src/integrations/kapso/inbound.ts +23 -6
  126. package/src/jira/sync.ts +111 -17
  127. package/src/jira/templates.ts +10 -2
  128. package/src/mcp-client/http-client.ts +194 -0
  129. package/src/oauth/app-validation.ts +16 -0
  130. package/src/oauth/ensure-token.ts +69 -14
  131. package/src/oauth/mcp-wrapper.ts +15 -0
  132. package/src/oauth/wrapper.ts +48 -20
  133. package/src/prompts/session-templates.ts +3 -3
  134. package/src/providers/codex-models.ts +100 -30
  135. package/src/providers/modelsdev-reasoning.json +51 -0
  136. package/src/providers/pi-mono-mcp-client.ts +2 -133
  137. package/src/providers/reasoning-effort.ts +11 -5
  138. package/src/rbac/admission.ts +68 -0
  139. package/src/rbac/index.ts +2 -0
  140. package/src/rbac/legacy-policy.ts +12 -0
  141. package/src/rbac/permissions.ts +48 -0
  142. package/src/scheduler/scheduler.ts +9 -1
  143. package/src/scripts-runtime/api-client.ts +151 -19
  144. package/src/scripts-runtime/api-types.ts +38 -6
  145. package/src/scripts-runtime/credential-broker/default-bindings.ts +1 -0
  146. package/src/scripts-runtime/credential-broker/types.ts +8 -0
  147. package/src/scripts-runtime/ctx.ts +11 -1
  148. package/src/scripts-runtime/eval-harness.ts +5 -1
  149. package/src/scripts-runtime/executors/types.ts +2 -1
  150. package/src/scripts-runtime/loader.ts +3 -1
  151. package/src/scripts-runtime/mcp-client.ts +87 -0
  152. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  153. package/src/scripts-runtime/types/stdlib.d.ts +6 -0
  154. package/src/scripts-runtime/types/swarm-sdk.d.ts +19 -0
  155. package/src/server-user.ts +114 -59
  156. package/src/server.ts +12 -0
  157. package/src/slack/assistant.ts +14 -6
  158. package/src/slack/enrich.ts +17 -0
  159. package/src/slack/handlers.ts +8 -22
  160. package/src/slack/responses.ts +91 -1
  161. package/src/slack/thread-buffer.ts +6 -3
  162. package/src/slack/watcher.ts +9 -6
  163. package/src/tasks/worker-follow-up.ts +100 -3
  164. package/src/tests/agentmail-handlers.test.ts +32 -3
  165. package/src/tests/approval-requests.test.ts +61 -0
  166. package/src/tests/audit-user.test.ts +47 -1
  167. package/src/tests/codex-adapter.test.ts +46 -23
  168. package/src/tests/credential-broker.test.ts +5 -2
  169. package/src/tests/delete-page-tool.test.ts +197 -0
  170. package/src/tests/github-handlers.test.ts +28 -2
  171. package/src/tests/gitlab-handlers.test.ts +125 -0
  172. package/src/tests/heartbeat.test.ts +48 -3
  173. package/src/tests/identity.test.ts +117 -0
  174. package/src/tests/jira-sync.test.ts +99 -1
  175. package/src/tests/kapso-inbound.test.ts +7 -0
  176. package/src/tests/mcp-tools-user.test.ts +60 -6
  177. package/src/tests/model-control.test.ts +8 -2
  178. package/src/tests/oauth-credential-bindings.test.ts +490 -0
  179. package/src/tests/oauth-refresh-sweep.test.ts +160 -0
  180. package/src/tests/pool-affinity.test.ts +401 -0
  181. package/src/tests/prompt-template-remaining.test.ts +2 -2
  182. package/src/tests/rbac-admission-e2e.test.ts +386 -0
  183. package/src/tests/rbac-admission.test.ts +492 -0
  184. package/src/tests/rbac-e2e-helpers.ts +3 -0
  185. package/src/tests/rbac-engine.test.ts +50 -0
  186. package/src/tests/rbac-mcp-admission-e2e.test.ts +362 -0
  187. package/src/tests/rbac-roles.test.ts +328 -0
  188. package/src/tests/reasoning-effort.test.ts +15 -2
  189. package/src/tests/schedule-target-type.test.ts +50 -0
  190. package/src/tests/scheduled-tasks.test.ts +40 -0
  191. package/src/tests/script-apis-mcp.test.ts +37 -13
  192. package/src/tests/script-connections-http.test.ts +1158 -0
  193. package/src/tests/script-connections-mcp.test.ts +894 -0
  194. package/src/tests/script-connections.test.ts +754 -6
  195. package/src/tests/scripts-external-api.test.ts +49 -1
  196. package/src/tests/scripts-runtime-secret-egress.test.ts +1 -0
  197. package/src/tests/scripts-runtime.test.ts +291 -0
  198. package/src/tests/seed-scripts.test.ts +36 -0
  199. package/src/tests/slack-identity-resolution.test.ts +37 -3
  200. package/src/tests/slack-inline-output.test.ts +65 -0
  201. package/src/tests/slack-thread-buffer.test.ts +28 -0
  202. package/src/tests/tool-annotations.test.ts +1 -0
  203. package/src/tests/user-token-rest-auth.test.ts +140 -1
  204. package/src/tests/workflow-swarm-script.test.ts +44 -0
  205. package/src/tests/workflow-triggers-v2.test.ts +300 -1
  206. package/src/tools/credential-bindings/tool.ts +277 -18
  207. package/src/tools/delete-page.ts +145 -0
  208. package/src/tools/mcp-servers/mcp-server-delete.ts +7 -4
  209. package/src/tools/request-human-input.ts +2 -0
  210. package/src/tools/resolve-user.ts +77 -27
  211. package/src/tools/script-apis.ts +62 -3
  212. package/src/tools/script-connections/tool.ts +275 -31
  213. package/src/tools/send-task.ts +15 -1
  214. package/src/tools/task-action.ts +34 -2
  215. package/src/tools/templates.ts +53 -1
  216. package/src/tools/tool-config.ts +2 -1
  217. package/src/tools/utils.ts +2 -0
  218. package/src/tools/workflows/create-workflow.ts +8 -1
  219. package/src/tools/workflows/update-workflow.ts +11 -2
  220. package/src/types.ts +90 -11
  221. package/src/utils/scoped-resource.ts +25 -0
  222. package/src/workflows/executors/human-in-the-loop.ts +1 -0
  223. package/src/workflows/executors/swarm-script.ts +7 -1
  224. package/src/workflows/triggers.ts +146 -18
  225. package/templates/skills/swarm-scripts/content.md +33 -8
  226. package/dist/cli-bgef578c.js +0 -771
  227. package/dist/cli-c9mtm09b.js +0 -43
  228. package/dist/cli-ceewqajw.js +0 -1698
  229. package/dist/{anthropic-messages-8nc6r2dw.js → anthropic-messages-kq3pjhbp.js} +6 -6
  230. package/dist/{azure-openai-responses-6yfzw3qw.js → azure-openai-responses-gm1ejtmn.js} +7 -7
  231. package/dist/{cli-vbraamxr.js → cli-6b4wwmm8.js} +9 -9
  232. package/dist/{cli-2r7s8jhp.js → cli-sznberje.js} +3 -3
  233. package/dist/{google-generative-ai-vfq6ymz9.js → google-generative-ai-hyrra2jm.js} +3 -3
  234. package/dist/{google-vertex-mj5y5rfj.js → google-vertex-8bs2pzgn.js} +3 -3
  235. package/dist/{openai-codex-responses-8h6kvfv8.js → openai-codex-responses-3nkvje79.js} +4 -4
  236. package/dist/{openai-completions-x6kjnqjd.js → openai-completions-0asftkwg.js} +11 -11
  237. package/dist/{openai-responses-7jwyz3ds.js → openai-responses-pmatek45.js} +10 -10
@@ -47,6 +47,7 @@ function catalogReportHumanLabel(value: string): string {
47
47
 
48
48
  function catalogReportFormatMetric(value: unknown): string {
49
49
  if (typeof value === "number") return new Intl.NumberFormat("en-US").format(value);
50
+ if (typeof value === "boolean") return value ? "Yes" : "No";
50
51
  return catalogReportAsText(value);
51
52
  }
52
53
 
@@ -57,6 +58,25 @@ function catalogReportSeverityTone(value?: string): string {
57
58
  return "low";
58
59
  }
59
60
 
61
+ function catalogReportIsScalar(value: unknown): boolean {
62
+ return value == null || ["string", "number", "boolean"].includes(typeof value);
63
+ }
64
+
65
+ function catalogReportIsRecord(value: unknown): value is Record<string, unknown> {
66
+ return value !== null && typeof value === "object" && !Array.isArray(value);
67
+ }
68
+
69
+ function catalogReportValueClass(value: unknown): string {
70
+ if (typeof value === "number") return "numeric";
71
+ if (typeof value === "boolean") return value ? "positive" : "muted-value";
72
+ return "";
73
+ }
74
+
75
+ function catalogReportClassAttr(value: unknown): string {
76
+ const className = catalogReportValueClass(value);
77
+ return className ? ` class="${catalogReportHtmlEscape(className)}"` : "";
78
+ }
79
+
60
80
  function catalogReportRenderSampleValue(value: unknown): string {
61
81
  if (Array.isArray(value)) {
62
82
  return value
@@ -94,9 +114,116 @@ function catalogReportRenderSamples(samples?: unknown[]): string {
94
114
  </div>`;
95
115
  }
96
116
 
117
+ function catalogReportTableRows(rows: Record<string, unknown>[], limit = 12): string {
118
+ if (rows.length === 0) return '<p class="empty">No rows.</p>';
119
+ const columns = Array.from(new Set(rows.flatMap((row) => Object.keys(row).slice(0, 8)))).slice(0, 8);
120
+ const visible = rows.slice(0, limit);
121
+ const hiddenCount = Math.max(0, rows.length - visible.length);
122
+ return `<div class="data-table" role="region" aria-label="Data table">
123
+ <table>
124
+ <thead><tr>${columns.map((column) => `<th>${catalogReportHtmlEscape(catalogReportHumanLabel(column))}</th>`).join("")}</tr></thead>
125
+ <tbody>
126
+ ${visible
127
+ .map(
128
+ (row) =>
129
+ `<tr>${columns
130
+ .map((column) => {
131
+ const value = row[column];
132
+ return `<td${catalogReportClassAttr(value)}>${catalogReportHtmlEscape(
133
+ catalogReportRenderSampleValue(value),
134
+ )}</td>`;
135
+ })
136
+ .join("")}</tr>`,
137
+ )
138
+ .join("")}
139
+ </tbody>
140
+ </table>
141
+ ${hiddenCount ? `<p class="table-note">Showing ${visible.length} of ${rows.length} rows. Full payload remains in the appendix.</p>` : ""}
142
+ </div>`;
143
+ }
144
+
145
+ function catalogReportRenderDataValue(value: unknown): string {
146
+ if (catalogReportIsScalar(value)) {
147
+ return `<strong${catalogReportClassAttr(value)}>${catalogReportHtmlEscape(
148
+ catalogReportFormatMetric(value),
149
+ )}</strong>`;
150
+ }
151
+ if (Array.isArray(value)) {
152
+ if (value.every((item) => catalogReportIsRecord(item))) {
153
+ return catalogReportTableRows(value as Record<string, unknown>[]);
154
+ }
155
+ return `<div class="value-list">${value
156
+ .slice(0, 12)
157
+ .map((item) => `<span>${catalogReportHtmlEscape(catalogReportRenderSampleValue(item))}</span>`)
158
+ .join("")}</div>`;
159
+ }
160
+ if (catalogReportIsRecord(value)) {
161
+ const entries = Object.entries(value);
162
+ const scalarEntries = entries.filter(([, entryValue]) => catalogReportIsScalar(entryValue));
163
+ const complexEntries = entries.filter(([, entryValue]) => !catalogReportIsScalar(entryValue));
164
+ return `<div class="data-object">
165
+ ${
166
+ scalarEntries.length
167
+ ? `<div class="object-stats">${scalarEntries
168
+ .map(
169
+ ([label, entryValue]) =>
170
+ `<div><span>${catalogReportHtmlEscape(catalogReportHumanLabel(label))}</span><strong${catalogReportClassAttr(
171
+ entryValue,
172
+ )}>${catalogReportHtmlEscape(catalogReportFormatMetric(entryValue))}</strong></div>`,
173
+ )
174
+ .join("")}</div>`
175
+ : ""
176
+ }
177
+ ${complexEntries
178
+ .map(
179
+ ([label, entryValue]) => `<section class="data-nested">
180
+ <h4>${catalogReportHtmlEscape(catalogReportHumanLabel(label))}</h4>
181
+ ${catalogReportRenderDataValue(entryValue)}
182
+ </section>`,
183
+ )
184
+ .join("")}
185
+ </div>`;
186
+ }
187
+ return `<pre>${catalogReportHtmlEscape(JSON.stringify(value, null, 2))}</pre>`;
188
+ }
189
+
190
+ function catalogReportRenderDataPanels(entries: Array<[string, unknown]>): string {
191
+ if (entries.length === 0) return "";
192
+ return `<div class="data-panels">
193
+ ${entries
194
+ .map(
195
+ ([label, value]) => `<section class="data-panel">
196
+ <div class="data-panel-head">
197
+ <h3>${catalogReportHtmlEscape(catalogReportHumanLabel(label))}</h3>
198
+ <span>${catalogReportHtmlEscape(
199
+ Array.isArray(value)
200
+ ? `${value.length} rows`
201
+ : catalogReportIsRecord(value)
202
+ ? `${Object.keys(value).length} fields`
203
+ : "value",
204
+ )}</span>
205
+ </div>
206
+ ${catalogReportRenderDataValue(value)}
207
+ </section>`,
208
+ )
209
+ .join("")}
210
+ </div>`;
211
+ }
212
+
97
213
  export function renderCatalogReportPage(report: CatalogReport): string {
214
+ const nav = report.sections
215
+ .map(
216
+ (section) =>
217
+ `<a href="#${catalogReportHtmlEscape(section.key)}"><span>${catalogReportHtmlEscape(
218
+ section.label || catalogReportHumanLabel(section.key),
219
+ )}</span><strong>${catalogReportHtmlEscape(catalogReportFormatMetric(section.findingCount ?? section.findings?.length ?? 0))}</strong></a>`,
220
+ )
221
+ .join("");
98
222
  const sections = report.sections
99
223
  .map((section) => {
224
+ const checkEntries = Object.entries(section.checks || {});
225
+ const scalarChecks = checkEntries.filter(([, value]) => catalogReportIsScalar(value));
226
+ const dataChecks = checkEntries.filter(([, value]) => !catalogReportIsScalar(value));
100
227
  const findings = (section.findings || [])
101
228
  .map(
102
229
  (finding) => `<article class="finding ${catalogReportHtmlEscape(catalogReportSeverityTone(finding.severity))}">
@@ -114,25 +241,26 @@ export function renderCatalogReportPage(report: CatalogReport): string {
114
241
  </article>`,
115
242
  )
116
243
  .join("");
117
- const checks = Object.entries(section.checks || {})
244
+ const checks = scalarChecks
118
245
  .map(
119
246
  ([label, value]) =>
120
- `<div class="check"><span>${catalogReportHtmlEscape(catalogReportHumanLabel(label))}</span><strong>${catalogReportHtmlEscape(
121
- catalogReportFormatMetric(value),
122
- )}</strong></div>`,
247
+ `<div class="check"><span>${catalogReportHtmlEscape(catalogReportHumanLabel(label))}</span><strong${catalogReportClassAttr(
248
+ value,
249
+ )}>${catalogReportHtmlEscape(catalogReportFormatMetric(value))}</strong></div>`,
123
250
  )
124
251
  .join("");
125
- return `<section class="section">
252
+ return `<section class="section" id="${catalogReportHtmlEscape(section.key)}">
126
253
  <div class="section-grid">
127
254
  <aside class="checks">
128
255
  <p class="section-kicker">${catalogReportHtmlEscape(section.label || catalogReportHumanLabel(section.key))}</p>
129
- <div class="check-list">${checks}</div>
256
+ <div class="check-list">${checks || '<p class="empty">No scalar checks.</p>'}</div>
130
257
  </aside>
131
258
  <div>
132
259
  <div class="section-head">
133
260
  <h2>${catalogReportHtmlEscape(section.goal)}</h2>
134
261
  <span>${catalogReportHtmlEscape(catalogReportFormatMetric(section.findingCount ?? section.findings?.length ?? 0))} finding(s)</span>
135
262
  </div>
263
+ ${catalogReportRenderDataPanels(dataChecks)}
136
264
  <div class="findings">
137
265
  ${findings || '<p class="empty">No actionable findings in this cluster.</p>'}
138
266
  </div>
@@ -153,10 +281,14 @@ export function renderCatalogReportPage(report: CatalogReport): string {
153
281
  color-scheme: light;
154
282
  --bg: #f5f2ea;
155
283
  --panel: #ffffff;
284
+ --panel-soft: #fbfaf6;
156
285
  --ink: #18181b;
157
286
  --muted: #5f6368;
287
+ --muted-strong: #3f3f46;
158
288
  --line: #ded8cb;
289
+ --line-soft: #ebe5d7;
159
290
  --accent: #255c99;
291
+ --accent-soft: #edf5ff;
160
292
  --danger: #b42318;
161
293
  --danger-bg: #fff1f0;
162
294
  --warn: #b54708;
@@ -178,11 +310,19 @@ export function renderCatalogReportPage(report: CatalogReport): string {
178
310
  line-height: 1.55;
179
311
  }
180
312
  main {
181
- width: min(1120px, calc(100% - 32px));
313
+ width: min(1240px, calc(100% - 32px));
182
314
  margin: 0 auto;
183
315
  padding: 48px 0 72px;
184
316
  }
185
- header { margin-bottom: 28px; }
317
+ header {
318
+ display: grid;
319
+ grid-template-columns: minmax(0, 1fr) auto;
320
+ gap: 24px;
321
+ align-items: end;
322
+ margin-bottom: 24px;
323
+ padding-bottom: 22px;
324
+ border-bottom: 1px solid var(--line);
325
+ }
186
326
  .eyebrow {
187
327
  margin: 0 0 8px;
188
328
  color: var(--muted);
@@ -194,34 +334,48 @@ export function renderCatalogReportPage(report: CatalogReport): string {
194
334
  h1 {
195
335
  margin: 0;
196
336
  max-width: 860px;
197
- font-size: clamp(2rem, 4vw, 3rem);
337
+ font-size: clamp(2rem, 3.8vw, 3.25rem);
198
338
  line-height: 1.05;
199
339
  letter-spacing: 0;
200
340
  }
201
341
  .lede {
202
- max-width: 780px;
342
+ max-width: 840px;
203
343
  margin: 16px 0 0;
204
- color: var(--muted);
344
+ color: var(--muted-strong);
205
345
  font-size: 18px;
206
346
  }
347
+ .description {
348
+ max-width: 340px;
349
+ margin: 0;
350
+ color: var(--muted);
351
+ font-size: 14px;
352
+ text-align: right;
353
+ }
207
354
  .metrics {
208
355
  display: grid;
209
- grid-template-columns: repeat(4, minmax(0, 1fr));
356
+ grid-template-columns: repeat(6, minmax(0, 1fr));
210
357
  gap: 12px;
211
- margin: 32px 0;
358
+ margin: 24px 0 16px;
212
359
  }
213
- .metric, .section, details {
360
+ .metric, .section, details, .report-nav {
214
361
  background: var(--panel);
215
362
  border: 1px solid var(--line);
216
363
  border-radius: var(--radius);
217
364
  box-shadow: var(--shadow);
218
365
  }
219
- .metric { padding: 18px; }
366
+ .metric {
367
+ min-height: 112px;
368
+ padding: 16px;
369
+ display: flex;
370
+ flex-direction: column;
371
+ justify-content: space-between;
372
+ }
220
373
  .metric strong {
221
374
  display: block;
222
- font-size: 32px;
375
+ font-size: 30px;
223
376
  line-height: 1;
224
377
  font-variant-numeric: tabular-nums;
378
+ overflow-wrap: anywhere;
225
379
  }
226
380
  .metric span {
227
381
  display: block;
@@ -230,14 +384,45 @@ export function renderCatalogReportPage(report: CatalogReport): string {
230
384
  font-size: 13px;
231
385
  font-weight: 650;
232
386
  }
387
+ .report-nav {
388
+ display: grid;
389
+ grid-template-columns: repeat(6, minmax(0, 1fr));
390
+ gap: 1px;
391
+ overflow: hidden;
392
+ margin: 0 0 20px;
393
+ background: var(--line);
394
+ }
395
+ .report-nav a {
396
+ display: flex;
397
+ align-items: center;
398
+ justify-content: space-between;
399
+ gap: 10px;
400
+ min-height: 48px;
401
+ padding: 11px 13px;
402
+ background: var(--panel);
403
+ color: var(--muted-strong);
404
+ text-decoration: none;
405
+ font-size: 13px;
406
+ font-weight: 700;
407
+ }
408
+ .report-nav a:hover { background: var(--accent-soft); }
409
+ .report-nav strong {
410
+ color: var(--accent);
411
+ font-variant-numeric: tabular-nums;
412
+ }
233
413
  .section {
234
- margin-top: 18px;
235
- padding: 24px;
414
+ margin-top: 16px;
415
+ padding: 22px;
236
416
  }
237
417
  .section-grid {
238
418
  display: grid;
239
- grid-template-columns: 260px minmax(0, 1fr);
240
- gap: 28px;
419
+ grid-template-columns: 240px minmax(0, 1fr);
420
+ gap: 24px;
421
+ }
422
+ .checks {
423
+ position: sticky;
424
+ top: 18px;
425
+ align-self: start;
241
426
  }
242
427
  .section-kicker {
243
428
  margin: 0 0 12px;
@@ -259,6 +444,7 @@ export function renderCatalogReportPage(report: CatalogReport): string {
259
444
  padding: 10px 0;
260
445
  border-bottom: 1px solid var(--line);
261
446
  }
447
+ .check:last-child { border-bottom: 0; }
262
448
  .check span {
263
449
  color: var(--muted);
264
450
  font-size: 13px;
@@ -266,6 +452,8 @@ export function renderCatalogReportPage(report: CatalogReport): string {
266
452
  .check strong {
267
453
  font-size: 18px;
268
454
  font-variant-numeric: tabular-nums;
455
+ text-align: right;
456
+ overflow-wrap: anywhere;
269
457
  }
270
458
  .section-head {
271
459
  display: flex;
@@ -283,11 +471,113 @@ export function renderCatalogReportPage(report: CatalogReport): string {
283
471
  }
284
472
  .section-head > span {
285
473
  flex: 0 0 auto;
286
- color: var(--muted);
474
+ color: var(--accent);
475
+ background: var(--accent-soft);
476
+ border: 1px solid #cfe5ff;
477
+ border-radius: 999px;
478
+ padding: 5px 10px;
287
479
  font-size: 13px;
288
480
  font-weight: 700;
289
481
  white-space: nowrap;
290
482
  }
483
+ .data-panels {
484
+ display: grid;
485
+ gap: 12px;
486
+ margin: 0 0 16px;
487
+ }
488
+ .data-panel {
489
+ border: 1px solid var(--line-soft);
490
+ border-radius: var(--radius);
491
+ background: var(--panel-soft);
492
+ overflow: hidden;
493
+ }
494
+ .data-panel-head {
495
+ display: flex;
496
+ align-items: baseline;
497
+ justify-content: space-between;
498
+ gap: 16px;
499
+ padding: 12px 14px;
500
+ border-bottom: 1px solid var(--line-soft);
501
+ background: #fffdf8;
502
+ }
503
+ .data-panel-head h3,
504
+ .data-nested h4 {
505
+ margin: 0;
506
+ color: var(--muted-strong);
507
+ font-size: 14px;
508
+ line-height: 1.25;
509
+ letter-spacing: 0;
510
+ }
511
+ .data-panel-head span,
512
+ .table-note {
513
+ color: var(--muted);
514
+ font-size: 12px;
515
+ font-weight: 700;
516
+ }
517
+ .data-object {
518
+ display: grid;
519
+ gap: 12px;
520
+ padding: 14px;
521
+ }
522
+ .object-stats {
523
+ display: grid;
524
+ grid-template-columns: repeat(3, minmax(0, 1fr));
525
+ gap: 10px;
526
+ }
527
+ .object-stats > div {
528
+ min-height: 76px;
529
+ padding: 12px;
530
+ border: 1px solid var(--line-soft);
531
+ border-radius: var(--radius);
532
+ background: var(--panel);
533
+ }
534
+ .object-stats span,
535
+ .value-list span {
536
+ display: block;
537
+ color: var(--muted);
538
+ font-size: 12px;
539
+ font-weight: 700;
540
+ }
541
+ .object-stats strong {
542
+ display: block;
543
+ margin-top: 6px;
544
+ color: var(--ink);
545
+ font-size: 20px;
546
+ line-height: 1.1;
547
+ font-variant-numeric: tabular-nums;
548
+ overflow-wrap: anywhere;
549
+ }
550
+ .data-nested {
551
+ display: grid;
552
+ gap: 8px;
553
+ }
554
+ .data-table {
555
+ overflow-x: auto;
556
+ background: var(--panel);
557
+ }
558
+ .data-table .table-note {
559
+ margin: 0;
560
+ padding: 10px 12px;
561
+ border-top: 1px solid var(--line-soft);
562
+ background: var(--panel-soft);
563
+ }
564
+ .value-list {
565
+ display: flex;
566
+ flex-wrap: wrap;
567
+ gap: 8px;
568
+ padding: 14px;
569
+ }
570
+ .value-list span {
571
+ padding: 6px 9px;
572
+ border: 1px solid var(--line-soft);
573
+ border-radius: 999px;
574
+ background: var(--panel);
575
+ color: var(--muted-strong);
576
+ overflow-wrap: anywhere;
577
+ }
578
+ .numeric { font-variant-numeric: tabular-nums; }
579
+ .positive { color: var(--low); }
580
+ .muted-value { color: var(--muted); }
291
581
  .findings {
292
582
  display: grid;
293
583
  gap: 12px;
@@ -396,26 +686,36 @@ export function renderCatalogReportPage(report: CatalogReport): string {
396
686
  }
397
687
  @media (max-width: 860px) {
398
688
  main { width: min(100% - 24px, 1120px); padding-top: 32px; }
689
+ header { grid-template-columns: 1fr; }
690
+ .description { max-width: none; text-align: left; }
399
691
  .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
692
+ .report-nav { grid-template-columns: repeat(2, minmax(0, 1fr)); }
400
693
  .section-grid { grid-template-columns: 1fr; gap: 18px; }
694
+ .checks { position: static; }
401
695
  .section { padding: 18px; }
402
696
  .section-head { display: block; }
403
697
  .section-head > span { display: block; margin-top: 8px; }
698
+ .object-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
404
699
  }
405
700
  @media (max-width: 520px) {
406
701
  .metrics { grid-template-columns: 1fr; }
702
+ .report-nav { grid-template-columns: 1fr; }
407
703
  .lede { font-size: 16px; }
408
704
  .finding-head { display: block; }
409
705
  .pill { margin-top: 10px; }
706
+ .object-stats { grid-template-columns: 1fr; }
410
707
  }
411
708
  </style>
412
709
  </head>
413
710
  <body>
414
711
  <main>
415
712
  <header>
416
- <p class="eyebrow">Generated ${catalogReportHtmlEscape(report.generatedAt)}</p>
417
- <h1>${catalogReportHtmlEscape(report.title)}</h1>
418
- <p class="lede">${catalogReportHtmlEscape(report.lede)}</p>
713
+ <div>
714
+ <p class="eyebrow">Generated ${catalogReportHtmlEscape(report.generatedAt)}</p>
715
+ <h1>${catalogReportHtmlEscape(report.title)}</h1>
716
+ <p class="lede">${catalogReportHtmlEscape(report.lede)}</p>
717
+ </div>
718
+ <p class="description">${catalogReportHtmlEscape(report.description)}</p>
419
719
  </header>
420
720
  <section class="metrics" aria-label="Audit summary">
421
721
  ${report.metrics
@@ -427,6 +727,7 @@ export function renderCatalogReportPage(report: CatalogReport): string {
427
727
  )
428
728
  .join("")}
429
729
  </section>
730
+ <nav class="report-nav" aria-label="Report sections">${nav}</nav>
430
731
  ${sections}
431
732
  <details>
432
733
  <summary>Compressed JSON appendix</summary>
package/src/be/users.ts CHANGED
@@ -135,6 +135,30 @@ export function findUserByEmail(email: string): User | null {
135
135
  return null;
136
136
  }
137
137
 
138
+ /**
139
+ * Look up users by display name — exact match (case-insensitive) first;
140
+ * falls back to a first-token prefix match (e.g. "Alberto" matches both
141
+ * "Alberto Maurel" and "Alberto Dubois"). Deterministic, no fuzzy matching:
142
+ * `resolve-user`'s name lookup treats more than one match as ambiguous
143
+ * rather than guessing.
144
+ */
145
+ export function findUsersByName(name: string): User[] {
146
+ const trimmed = name.trim();
147
+ if (!trimmed) return [];
148
+ const db = getDb();
149
+
150
+ const exact = db
151
+ .prepare<UserRow, string>("SELECT * FROM users WHERE LOWER(name) = LOWER(?)")
152
+ .all(trimmed);
153
+ if (exact.length > 0) return exact.map(rowToUser);
154
+
155
+ const firstToken = trimmed.split(/\s+/)[0] ?? trimmed;
156
+ const prefixed = db
157
+ .prepare<UserRow, string>("SELECT * FROM users WHERE LOWER(name) LIKE LOWER(?) || '%'")
158
+ .all(firstToken);
159
+ return prefixed.map(rowToUser);
160
+ }
161
+
138
162
  /**
139
163
  * Return all `(kind, externalId)` mappings for a user — used by the People
140
164
  * page detail view to render identity badges in one request.
package/src/cli.tsx CHANGED
@@ -269,6 +269,13 @@ const COMMAND_HELP: Record<
269
269
  options: " -h, --help Show this help",
270
270
  examples: ` ${binName} scripts reembed`,
271
271
  },
272
+ rbac: {
273
+ usage: `${binName} rbac bootstrap`,
274
+ description:
275
+ "RBAC role management.\nBootstraps built-in RBAC roles and attaches the default role to users with zero roles.",
276
+ options: " -h, --help Show this help",
277
+ examples: ` ${binName} rbac bootstrap`,
278
+ },
272
279
  "codex-login": {
273
280
  usage: `${binName} codex-login [options]`,
274
281
  description:
@@ -379,6 +386,7 @@ function printHelp(command?: string) {
379
386
  ["artifact", "Manage agent artifacts"],
380
387
  ["x", "Execute external command routes"],
381
388
  ["scripts", "Reusable scripts maintenance"],
389
+ ["rbac", "RBAC role management (bootstrap)"],
382
390
  ["docs", "Open documentation (--open to launch in browser)"],
383
391
  ["codex-login", "Authenticate Codex via ChatGPT OAuth"],
384
392
  ["claude-managed-setup", "Bootstrap Anthropic Managed Agents (agent + env + skills)"],
@@ -686,6 +694,15 @@ if (args.showHelp || args.command === "help" || args.command === undefined) {
686
694
  const { runScriptsMaintenanceCommand } = await import("./be/scripts/maintenance");
687
695
  await runScriptsMaintenanceCommand(scriptsArgs);
688
696
  console.log("Scripts re-embedded.");
697
+ } else if (args.command === "rbac") {
698
+ const rbacArgs = process.argv.slice(process.argv.indexOf("rbac") + 1);
699
+ const isValidRbacCommand = rbacArgs.length === 1 && rbacArgs[0] === "bootstrap";
700
+ if (args.showHelp || !isValidRbacCommand) {
701
+ printHelp("rbac");
702
+ process.exit(args.showHelp ? 0 : 1);
703
+ }
704
+ const { runRbacCliCommand } = await import("./be/rbac-roles");
705
+ await runRbacCliCommand(rbacArgs);
689
706
  } else if (args.command === "codex-login") {
690
707
  const { runCodexLogin } = await import("./commands/codex-login");
691
708
  const codexLoginArgs = process.argv.slice(process.argv.indexOf("codex-login") + 1);
@@ -1,4 +1,5 @@
1
1
  import { failTask, findTaskByVcs, getAllAgents, getSwarmConfigs, incrKv, upsertKv } from "../be/db";
2
+ import { renderIdentity, resolveIdentity } from "../be/identity";
2
3
  import { findUserByExternalId } from "../be/users";
3
4
  import { resolveTemplate } from "../prompts/resolver";
4
5
  import { githubContextKey } from "../tasks/context-key";
@@ -239,7 +240,7 @@ export async function handlePullRequest(
239
240
  pr_number: pr.number,
240
241
  pr_title: pr.title,
241
242
  bot_name: GITHUB_BOT_NAME,
242
- sender_login: sender.login,
243
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
243
244
  repo_full_name: repository.full_name,
244
245
  head_ref: pr.head.ref,
245
246
  base_ref: pr.base.ref,
@@ -347,7 +348,7 @@ export async function handlePullRequest(
347
348
  pr_number: pr.number,
348
349
  pr_title: pr.title,
349
350
  bot_name: GITHUB_BOT_NAME,
350
- sender_login: sender.login,
351
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
351
352
  repo_full_name: repository.full_name,
352
353
  head_ref: pr.head.ref,
353
354
  base_ref: pr.base.ref,
@@ -447,7 +448,7 @@ export async function handlePullRequest(
447
448
  pr_number: pr.number,
448
449
  pr_title: pr.title,
449
450
  label_name: labelName,
450
- sender_login: sender.login,
451
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
451
452
  repo_full_name: repository.full_name,
452
453
  head_ref: pr.head.ref,
453
454
  base_ref: pr.base.ref,
@@ -536,7 +537,7 @@ export async function handlePullRequest(
536
537
  {
537
538
  pr_number: pr.number,
538
539
  pr_title: pr.title,
539
- sender_login: sender.login,
540
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
540
541
  repo_full_name: repository.full_name,
541
542
  head_ref: pr.head.ref,
542
543
  base_ref: pr.base.ref,
@@ -618,7 +619,7 @@ export async function handleIssue(
618
619
  issue_number: issue.number,
619
620
  issue_title: issue.title,
620
621
  bot_name: GITHUB_BOT_NAME,
621
- sender_login: sender.login,
622
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
622
623
  repo_full_name: repository.full_name,
623
624
  issue_url: issue.html_url,
624
625
  context: issue.body || issue.title,
@@ -714,7 +715,7 @@ export async function handleIssue(
714
715
  issue_number: issue.number,
715
716
  issue_title: issue.title,
716
717
  label_name: labelName,
717
- sender_login: sender.login,
718
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
718
719
  repo_full_name: repository.full_name,
719
720
  issue_url: issue.html_url,
720
721
  context: issue.body || issue.title,
@@ -785,7 +786,7 @@ export async function handleIssue(
785
786
  {
786
787
  issue_number: issue.number,
787
788
  issue_title: issue.title,
788
- sender_login: sender.login,
789
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
789
790
  repo_full_name: repository.full_name,
790
791
  issue_url: issue.html_url,
791
792
  context,
@@ -887,7 +888,7 @@ export async function handleComment(
887
888
  target_type: targetType,
888
889
  target_number: targetNumber,
889
890
  target_title: targetTitle,
890
- sender_login: sender.login,
891
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
891
892
  repo_full_name: repository.full_name,
892
893
  comment_url: comment.html_url,
893
894
  context,
@@ -1188,7 +1189,7 @@ export async function handlePullRequestReview(
1188
1189
  pr_number: pr.number,
1189
1190
  review_label: label,
1190
1191
  pr_title: pr.title,
1191
- sender_login: sender.login,
1192
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
1192
1193
  repo_full_name: repository.full_name,
1193
1194
  review_url: review.html_url,
1194
1195
  review_body_section: reviewBodySection,