@desplega.ai/agent-swarm 2.2.0 → 2.4.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 (266) hide show
  1. package/README.md +1 -0
  2. package/dist/{acp-adapter-k0vwfyvn.js → acp-adapter-kedstxx6.js} +6 -6
  3. package/dist/{actions-w4pnb8vz.js → actions-vget6r1k.js} +18 -16
  4. package/dist/{amp-adapter-0cmxb2t5.js → amp-adapter-xeb5mktv.js} +2 -2
  5. package/dist/{app-zneem7g9.js → app-mqap8t8m.js} +3 -3
  6. package/dist/{assistant-wbzd2rnr.js → assistant-h0gv97mv.js} +20 -18
  7. package/dist/{boot-reembed-4kx69j3g.js → boot-reembed-qr8a894z.js} +1 -1
  8. package/dist/{boot-reembed-db0wwgy1.js → boot-reembed-rj85jjhw.js} +1 -1
  9. package/dist/{boot-scrub-sweep-tfv4d3mm.js → boot-scrub-sweep-eyfvn6v9.js} +3 -3
  10. package/dist/{boot-seeds-w2hmkxn5.js → boot-seeds-xs9a8dx1.js} +4 -4
  11. package/dist/{claude-adapter-5qrc2paq.js → claude-adapter-bm23cye7.js} +5 -5
  12. package/dist/{claude-managed-adapter-n7ahjyx6.js → claude-managed-adapter-1mwrsm9d.js} +2 -2
  13. package/dist/{claude-managed-setup-7ra32em4.js → claude-managed-setup-gkfgks4j.js} +1 -1
  14. package/dist/{claude-sdk-session-cd02gc76.js → claude-sdk-session-apb4ypz3.js} +5 -5
  15. package/dist/cli-02vhhssk.js +848 -0
  16. package/dist/{cli-4ync7m60.js → cli-0ebey9hg.js} +1 -1
  17. package/dist/{cli-665j3feb.js → cli-0xk505e8.js} +1 -1
  18. package/dist/{cli-8c1m9mhy.js → cli-1cevz46c.js} +5 -5
  19. package/dist/{cli-gzvb3d4a.js → cli-1jx0srjt.js} +3 -3
  20. package/dist/{cli-zj4d6afs.js → cli-1pa3rz69.js} +21 -6
  21. package/dist/{cli-vmbakbq0.js → cli-2rwet3r7.js} +1 -1
  22. package/dist/{cli-azs9bhc5.js → cli-3wjs22x3.js} +2 -2
  23. package/dist/{cli-tba8fxf9.js → cli-4fhyq711.js} +45 -36
  24. package/dist/{cli-ak4c12mh.js → cli-5ff2sjyt.js} +1 -1
  25. package/dist/{cli-6q7pyd3k.js → cli-5s5hx57y.js} +5 -3
  26. package/dist/{cli-h23mr2s6.js → cli-6ftrrwm1.js} +9 -371
  27. package/dist/{cli-9ac0mkg5.js → cli-8b9wvjps.js} +1 -1
  28. package/dist/cli-8cxkw1f5.js +382 -0
  29. package/dist/{cli-xhgtay7w.js → cli-8rwvksbc.js} +2 -2
  30. package/dist/{cli-e09hj50q.js → cli-8wkb09q5.js} +11 -11
  31. package/dist/{cli-ksrphveb.js → cli-a1esmmee.js} +14 -2
  32. package/dist/{cli-xnp2vkx7.js → cli-avmqhatn.js} +7 -7
  33. package/dist/{cli-xx13gqf3.js → cli-b80a1100.js} +1 -1
  34. package/dist/{cli-z1hh99j0.js → cli-bfc3k1x5.js} +3 -3
  35. package/dist/{cli-0k9rcwfy.js → cli-bhwa8rzc.js} +33 -15
  36. package/dist/{cli-8cvvytad.js → cli-bn26xbcs.js} +2 -2
  37. package/dist/{cli-k28p6xh7.js → cli-c8fwpgqb.js} +1 -1
  38. package/dist/{cli-rc3r0bnh.js → cli-ccn0eybf.js} +1 -1
  39. package/dist/{cli-9td5ss65.js → cli-dsgj3e7a.js} +2 -2
  40. package/dist/{cli-jrk8mj0t.js → cli-ecshf504.js} +2 -2
  41. package/dist/{cli-raejy0mp.js → cli-eqgfs202.js} +1 -1
  42. package/dist/{cli-ppapy34w.js → cli-f3nx1h8z.js} +2 -2
  43. package/dist/{cli-c9vrwcpb.js → cli-ffbrkh1r.js} +74 -35
  44. package/dist/{cli-mnry16kh.js → cli-kk2r80n5.js} +4 -4
  45. package/dist/{cli-c0dpzwnx.js → cli-kz116kvw.js} +3 -3
  46. package/dist/{cli-e5y285rg.js → cli-mvg2hf8p.js} +4 -4
  47. package/dist/{cli-2jm992rm.js → cli-mz1ezzzh.js} +2 -2
  48. package/dist/{cli-dw0njd9q.js → cli-nme7wery.js} +6 -6
  49. package/dist/{cli-mvsfbbgq.js → cli-nrrg0fvy.js} +18 -4
  50. package/dist/{cli-8jtrekpy.js → cli-p1m2b6gc.js} +7 -7
  51. package/dist/{cli-4c7k75rq.js → cli-rg7ntypm.js} +378 -84
  52. package/dist/{cli-z3dk189k.js → cli-rsd7ga7v.js} +10 -8
  53. package/dist/{cli-etvn5ej2.js → cli-sppc0cmw.js} +1 -1
  54. package/dist/{cli-8w65vwee.js → cli-tg9ksbn8.js} +1 -1
  55. package/dist/{cli-th8krqbf.js → cli-tgvpmnwz.js} +2 -2
  56. package/dist/{cli-hwc4a9xm.js → cli-tj4j2ts4.js} +6 -6
  57. package/dist/{cli-jhhb2v9n.js → cli-ttaz6c7m.js} +2 -2
  58. package/dist/{cli-w7z94e6c.js → cli-tx00c0h6.js} +4 -2
  59. package/dist/{cli-4g629sgr.js → cli-wrv09v7q.js} +20 -1
  60. package/dist/{cli-cby329ny.js → cli-wyrr38xm.js} +2 -2
  61. package/dist/{cli-5ks6x36q.js → cli-x1p5dhq2.js} +3 -3
  62. package/dist/{cli-bddxamg7.js → cli-x719j3vs.js} +220 -629
  63. package/dist/{cli-ztcxv5nv.js → cli-xfe63zx3.js} +1 -1
  64. package/dist/{cli-q6gw45wj.js → cli-ywyrffff.js} +1 -1
  65. package/dist/{cli-p46xdr1m.js → cli-zvze9rbm.js} +1 -1
  66. package/dist/cli.js +15 -15
  67. package/dist/{codex-adapter-m0fx3v3s.js → codex-adapter-pp2gq8gr.js} +3 -3
  68. package/dist/{codex-hook-x6tm6y5m.js → codex-hook-qwzccd64.js} +1 -1
  69. package/dist/{codex-session-runner-j9m2gfyy.js → codex-session-runner-0vzn09t8.js} +3 -3
  70. package/dist/{commands-ktnqwrv4.js → commands-psqhkttb.js} +2 -2
  71. package/dist/{controller-nex2he8v.js → controller-t20vz2k8.js} +2 -2
  72. package/dist/{controllers-connect-6yhwqv9v.js → controllers-connect-gkxmqw9d.js} +24 -20
  73. package/dist/{cursor-adapter-02s5ekva.js → cursor-adapter-3v542qe5.js} +1 -1
  74. package/dist/{db-jvr86v7f.js → db-2xbyenjr.js} +2 -2
  75. package/dist/{db-19tw9r5s.js → db-nc1rd869.js} +3 -2
  76. package/dist/{dispatcher-d3mnsg2y.js → dispatcher-ewceb8ts.js} +17 -15
  77. package/dist/{dsh-adapter-63rvej88.js → dsh-adapter-75xwh78t.js} +1 -1
  78. package/dist/{easter-egg-3d-6hg2r942.js → easter-egg-3d-xsc47jwf.js} +1 -1
  79. package/dist/{execute-bcqc69a5.js → execute-gzmw1mnw.js} +1 -1
  80. package/dist/{external-channel-44cwer32.js → external-channel-a5dsmxn4.js} +2 -2
  81. package/dist/{google-generative-ai-phpmwxvg.js → google-generative-ai-bmq8gkf3.js} +1 -1
  82. package/dist/{google-vertex-es9ybgdy.js → google-vertex-b1rvp52s.js} +1 -1
  83. package/dist/{grok-adapter-ysp907f6.js → grok-adapter-vj88xr23.js} +7 -7
  84. package/dist/handlers-zbs90bny.js +39 -0
  85. package/dist/{heartbeat-2etacsqq.js → heartbeat-t2m9r3wa.js} +20 -18
  86. package/dist/{hook-mzxxs5n2.js → hook-8femx1bg.js} +4 -4
  87. package/dist/{hook-3kvmax6d.js → hook-xa677f8r.js} +5 -5
  88. package/dist/{http-fn978m0g.js → http-xpmmq028.js} +940 -269
  89. package/dist/{index-e4tsa1h7.js → index-8pwc1jc8.js} +17 -15
  90. package/dist/{index-w2wr89xd.js → index-cdzyfrsp.js} +1 -1
  91. package/dist/{index-1r8hw4vd.js → index-dg2tmj99.js} +17 -15
  92. package/dist/index-nxa6me0q.js +41 -0
  93. package/dist/{index-039x2sag.js → index-s7ckdajq.js} +42 -14
  94. package/dist/{index-wgsw9xcz.js → index-syztqdt6.js} +1 -1
  95. package/dist/{index-x0q2a4f9.js → index-ysd45kgc.js} +1 -1
  96. package/dist/{index-g909y089.js → index-zgvqzjsw.js} +8 -8
  97. package/dist/{keepalive-d4nzs76x.js → keepalive-3nr4st3r.js} +3 -3
  98. package/dist/{lead-8h5n9k5f.js → lead-td8h2sfn.js} +33 -31
  99. package/dist/{maintenance-ea6jfw42.js → maintenance-verwpe2c.js} +1 -1
  100. package/dist/{oauth-refresh-sweep-3f25djq3.js → oauth-refresh-sweep-9q2t0y3q.js} +2 -2
  101. package/dist/{onboard-pvk7hdb2.js → onboard-aytqedmq.js} +2 -2
  102. package/dist/{opencode-adapter-eeesgfqk.js → opencode-adapter-f4hryjny.js} +4 -4
  103. package/dist/{otel-impl-qb7nhgdp.js → otel-impl-hckw4wt1.js} +1 -1
  104. package/dist/{pi-mono-adapter-zasp3ze3.js → pi-mono-adapter-hxby0ce1.js} +6 -6
  105. package/dist/{pricing-refresh-6ehwkcmp.js → pricing-refresh-5nvme3sm.js} +3 -3
  106. package/dist/{rbac-roles-km0f9cmm.js → rbac-roles-pd8c26m1.js} +5 -3
  107. package/dist/{rbac-roles-9648nh00.js → rbac-roles-vbpy21mg.js} +2 -2
  108. package/dist/{render-v2-xkze1wef.js → render-v2-7nqgvk2q.js} +3 -3
  109. package/dist/{resume-tq0bbvt5.js → resume-b6djcj3c.js} +6 -6
  110. package/dist/{runtime-ar8310ba.js → runtime-txrxc9za.js} +1 -1
  111. package/dist/session-access-fc3f9y12.js +25 -0
  112. package/dist/{setup-rnrazxvd.js → setup-n39szm6b.js} +1 -1
  113. package/dist/{virtual-modules-wzk6jf3a.js → virtual-modules-wyp2k6z3.js} +2 -2
  114. package/dist/{work-objects-e75q2rqs.js → work-objects-jem2fre0.js} +20 -18
  115. package/dist/{worker-4ww4hjef.js → worker-wr6xgfr2.js} +33 -31
  116. package/openapi.json +1011 -115
  117. package/package.json +2 -2
  118. package/src/apps/action-caller.ts +6 -0
  119. package/src/apps/script-types.ts +16 -3
  120. package/src/be/asset-key-audit.ts +4 -13
  121. package/src/be/db/active-sessions.ts +5 -0
  122. package/src/be/db/session-visibility.ts +29 -0
  123. package/src/be/db/tasks/read.ts +61 -3
  124. package/src/be/db/tasks/write.ts +15 -6
  125. package/src/be/db.ts +149 -66
  126. package/src/be/events.ts +11 -0
  127. package/src/be/migrations/227_inbox_item_state_last_read_at.sql +1 -0
  128. package/src/be/migrations/230_session_shares.sql +23 -0
  129. package/src/be/rbac-roles.ts +2 -0
  130. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +22 -4
  131. package/src/be/session-shares.ts +244 -0
  132. package/src/be/swarm-config-guard.ts +23 -2
  133. package/src/commands/runner.ts +11 -0
  134. package/src/heartbeat/heartbeat.ts +25 -7
  135. package/src/http/active-sessions.ts +13 -2
  136. package/src/http/agents.ts +10 -3
  137. package/src/http/all-routes.ts +1 -0
  138. package/src/http/api-keys.ts +3 -1
  139. package/src/http/approval-requests.ts +2 -1
  140. package/src/http/apps.ts +36 -11
  141. package/src/http/assets.ts +26 -4
  142. package/src/http/config.ts +3 -0
  143. package/src/http/context.ts +2 -0
  144. package/src/http/core.ts +39 -2
  145. package/src/http/events.ts +6 -2
  146. package/src/http/favorite-owner.ts +1 -1
  147. package/src/http/fs.ts +38 -3
  148. package/src/http/inbox-state.ts +58 -8
  149. package/src/http/index.ts +3 -1
  150. package/src/http/memory.ts +3 -1
  151. package/src/http/ranking.ts +367 -0
  152. package/src/http/realtime.ts +26 -21
  153. package/src/http/route-def.ts +77 -11
  154. package/src/http/script-runs.ts +2 -1
  155. package/src/http/scripts.ts +8 -0
  156. package/src/http/session-access.ts +179 -0
  157. package/src/http/session-data.ts +10 -2
  158. package/src/http/sessions.ts +212 -12
  159. package/src/http/stats.ts +7 -3
  160. package/src/http/tasks.ts +83 -7
  161. package/src/http/used-by.ts +11 -3
  162. package/src/kv-reserved-namespaces.ts +1 -0
  163. package/src/prompts/session-templates.ts +3 -1
  164. package/src/providers/amp-adapter.ts +1 -1
  165. package/src/ranking/telemetry-ranking.ts +147 -0
  166. package/src/ranking/usage.ts +57 -0
  167. package/src/rbac/admission.ts +22 -7
  168. package/src/rbac/legacy-policy.ts +2 -0
  169. package/src/rbac/permissions.ts +9 -0
  170. package/src/realtime/auth.ts +69 -0
  171. package/src/realtime/tickets.ts +4 -1
  172. package/src/realtime/transport.ts +77 -9
  173. package/src/slack/blocks.ts +18 -0
  174. package/src/slack/dm.ts +29 -0
  175. package/src/telemetry-contract/EVENTS.md +3 -0
  176. package/src/telemetry-contract/SOURCE.json +37 -4
  177. package/src/telemetry-contract/catalog.json +69 -0
  178. package/src/telemetry-contract/ranking/fixtures/expected.json +30 -0
  179. package/src/telemetry-contract/ranking/fixtures/invalid/bad-category.json +11 -0
  180. package/src/telemetry-contract/ranking/fixtures/invalid/bad-claim.json +11 -0
  181. package/src/telemetry-contract/ranking/fixtures/invalid/blank-description.json +11 -0
  182. package/src/telemetry-contract/ranking/fixtures/invalid/blank-name.json +11 -0
  183. package/src/telemetry-contract/ranking/fixtures/invalid/description-too-long.json +11 -0
  184. package/src/telemetry-contract/ranking/fixtures/invalid/domain-uppercase.json +11 -0
  185. package/src/telemetry-contract/ranking/fixtures/invalid/domain-with-scheme.json +11 -0
  186. package/src/telemetry-contract/ranking/fixtures/invalid/domain-with-www.json +11 -0
  187. package/src/telemetry-contract/ranking/fixtures/invalid/http-source-url.json +11 -0
  188. package/src/telemetry-contract/ranking/fixtures/invalid/missing-description.json +10 -0
  189. package/src/telemetry-contract/ranking/fixtures/invalid/missing-version.json +10 -0
  190. package/src/telemetry-contract/ranking/fixtures/invalid/name-not-string.json +11 -0
  191. package/src/telemetry-contract/ranking/fixtures/invalid/name-too-long.json +11 -0
  192. package/src/telemetry-contract/ranking/fixtures/invalid/not-object.json +1 -0
  193. package/src/telemetry-contract/ranking/fixtures/invalid/null.json +1 -0
  194. package/src/telemetry-contract/ranking/fixtures/invalid/other-with-subcategory.json +11 -0
  195. package/src/telemetry-contract/ranking/fixtures/invalid/oversize.json +1 -0
  196. package/src/telemetry-contract/ranking/fixtures/invalid/short-claim.json +11 -0
  197. package/src/telemetry-contract/ranking/fixtures/invalid/subcategory-other-category.json +11 -0
  198. package/src/telemetry-contract/ranking/fixtures/invalid/trailing-data.json +1 -0
  199. package/src/telemetry-contract/ranking/fixtures/invalid/unknown-field.json +12 -0
  200. package/src/telemetry-contract/ranking/fixtures/invalid/unsupported-version.json +11 -0
  201. package/src/telemetry-contract/ranking/fixtures/invalid/updated-at-not-date.json +11 -0
  202. package/src/telemetry-contract/ranking/fixtures/invalid/updated-at-offset.json +11 -0
  203. package/src/telemetry-contract/ranking/fixtures/invalid/wrong-case-field.json +11 -0
  204. package/src/telemetry-contract/ranking/fixtures/valid/category-without-subcategory.json +11 -0
  205. package/src/telemetry-contract/ranking/fixtures/valid/full.json +11 -0
  206. package/src/telemetry-contract/ranking/fixtures/valid/minimal.json +11 -0
  207. package/src/telemetry-contract/ranking/fixtures/valid/padded-text.json +11 -0
  208. package/src/telemetry-contract/ranking/fixtures/valid/unicode-name.json +11 -0
  209. package/src/telemetry-contract/ranking/listing.v1.schema.json +61 -0
  210. package/src/telemetry-contract/ranking/taxonomy.json +8 -0
  211. package/src/telemetry-contract/ranking-taxonomy.gen.ts +11 -0
  212. package/src/telemetry-contract/types.gen.ts +708 -1
  213. package/src/telemetry.ts +26 -2
  214. package/src/tests/apps-rbac.test.ts +189 -2
  215. package/src/tests/apps-script-types.test.ts +16 -3
  216. package/src/tests/asset-key-api.test.ts +128 -0
  217. package/src/tests/asset-key-invariants.test.ts +179 -3
  218. package/src/tests/asset-key-migration.test.ts +35 -0
  219. package/src/tests/check-pr-body.test.ts +23 -0
  220. package/src/tests/check-ranking-entry.test.ts +146 -0
  221. package/src/tests/db-extraction-contract.test.ts +1 -0
  222. package/src/tests/db-task-read-contract.test.ts +1 -0
  223. package/src/tests/docs-index-footprint.test.js +2 -2
  224. package/src/tests/heartbeat-checklist.test.ts +88 -0
  225. package/src/tests/heartbeat-jev-gate.test.ts +394 -10
  226. package/src/tests/inbox-state.test.ts +167 -0
  227. package/src/tests/kv-tool.test.ts +15 -0
  228. package/src/tests/org-identity.test.ts +44 -0
  229. package/src/tests/ranking-listing.test.ts +112 -0
  230. package/src/tests/ranking-route.test.ts +319 -0
  231. package/src/tests/ranking-usage.test.ts +50 -0
  232. package/src/tests/rbac-admission-e2e.test.ts +8 -0
  233. package/src/tests/rbac-admission.test.ts +38 -0
  234. package/src/tests/rbac-charact-http.test.ts +4 -3
  235. package/src/tests/rbac-engine.test.ts +2 -0
  236. package/src/tests/rbac-roles.test.ts +2 -0
  237. package/src/tests/realtime-room-surfaces.test.ts +3 -1
  238. package/src/tests/realtime-session-join-race.test.ts +142 -0
  239. package/src/tests/realtime-tickets.test.ts +24 -17
  240. package/src/tests/realtime-transport.test.ts +91 -0
  241. package/src/tests/script-connections.test.ts +2 -0
  242. package/src/tests/session-route-posture.test.ts +639 -0
  243. package/src/tests/session-sharing-routes.test.ts +282 -0
  244. package/src/tests/session-sharing.test.ts +526 -0
  245. package/src/tests/sessions-detail-batch.test.ts +7 -1
  246. package/src/tests/slack-tool-blocks.test.ts +101 -0
  247. package/src/tests/slack-update.test.ts +55 -0
  248. package/src/tests/slack-user-dm.test.ts +177 -0
  249. package/src/tests/used-by-route.test.ts +10 -0
  250. package/src/tools/slack-post.ts +18 -12
  251. package/src/tools/slack-read.ts +13 -1
  252. package/src/tools/slack-reply.ts +7 -10
  253. package/src/tools/slack-start-thread.ts +18 -11
  254. package/src/tools/slack-update.ts +2 -9
  255. package/src/types.ts +37 -0
  256. package/src/utils/org-identity.ts +33 -0
  257. package/templates/skills/apps/SKILL.md +17 -0
  258. package/templates/skills/apps/content.md +17 -0
  259. package/templates/skills/code-quality/content.md +2 -2
  260. package/templates/skills/pages/content.md +4 -0
  261. package/templates/skills/swarm-scripts/SKILL.md +6 -0
  262. package/templates/skills/swarm-scripts/content.md +6 -0
  263. package/dist/handlers-t5pbabwf.js +0 -37
  264. package/dist/index-qyjbb7br.js +0 -39
  265. package/dist/{cli-n5mrsg0f.js → cli-60cgh1rv.js} +3 -3
  266. /package/dist/{cli-j0v75y13.js → cli-y83zk5sb.js} +0 -0
@@ -69,28 +69,6 @@ import {
69
69
  resolveKvView,
70
70
  toolErr,
71
71
  createToolRegistrar,
72
- duplicateGuardedQueryKey,
73
- warnIfCorsAllowsAnyOrigin,
74
- isOriginAllowedForCredentials,
75
- allowedCredentialRedirect,
76
- setCorsHeaders,
77
- parseQueryParams,
78
- getPathSegments,
79
- wireHttpSpanLifecycle,
80
- safeRequestUrlForLog,
81
- agentWithCapacity,
82
- parseBody,
83
- BODY_TOO_LARGE,
84
- enforceContentLengthCap,
85
- json,
86
- jsonError,
87
- writeUnhandledError,
88
- triggerSchemaErrorResponse,
89
- deriveApiBaseUrl,
90
- httpServerSemconvAttributes,
91
- runtimeInstanceHeader,
92
- describeRequestRoute,
93
- route,
94
72
  handleMcpBridge,
95
73
  Redacted,
96
74
  SCRIPT_SDK_RESPONSE_LIMIT_BYTES,
@@ -199,9 +177,6 @@ import {
199
177
  scheduleIntegrationsReload,
200
178
  handleCore,
201
179
  userCtx,
202
- resolveTaskAuditUserId,
203
- resolveHttpAuditUserId,
204
- resolveHttpFavoriteOwner,
205
180
  handleApps,
206
181
  cancelApprovalRequest,
207
182
  getUserCommsPrefs,
@@ -262,6 +237,8 @@ import {
262
237
  resolveRoomNamespace,
263
238
  roomRequestInfo,
264
239
  authorizeRoomNamespace,
240
+ sessionAccessTopic,
241
+ sessionPresenceRoot,
265
242
  RoomOperationSchema,
266
243
  removeAwarenessStates,
267
244
  roomTopic,
@@ -303,7 +280,7 @@ import {
303
280
  hasCapability,
304
281
  getEnabledCapabilities,
305
282
  createServer
306
- } from "./cli-bddxamg7.js";
283
+ } from "./cli-x719j3vs.js";
307
284
  import {
308
285
  chatThreadOfContextKey,
309
286
  agentContextKey,
@@ -440,6 +417,17 @@ import {
440
417
  CHAT_SDK_ADAPTER_NAMES,
441
418
  isAdapterEntryEnabled,
442
419
  init_schema,
420
+ TELEMETRY_RANKING_KEY,
421
+ PUBLIC_RANKING_ENABLED_KEY,
422
+ RANKING_CATEGORIES,
423
+ readTelemetryRanking,
424
+ rankingFormSchema,
425
+ mintRankingClaim,
426
+ buildListing,
427
+ rankingEntryFile,
428
+ rankingPrUrl,
429
+ rankingLeaveUrl,
430
+ init_telemetry_ranking,
443
431
  API_DRAINING_HEADER,
444
432
  init_api_drain,
445
433
  parseTaskToolManifest,
@@ -724,26 +712,26 @@ import {
724
712
  countScriptRunJournalSteps2,
725
713
  countScriptRunJournalAgentTaskSteps2,
726
714
  init_db3
727
- } from "./cli-4c7k75rq.js";
715
+ } from "./cli-rg7ntypm.js";
728
716
  import {
729
717
  getUserGrant2,
730
718
  init_rbac_roles
731
- } from "./cli-6q7pyd3k.js";
732
- import"./cli-mnry16kh.js";
719
+ } from "./cli-5s5hx57y.js";
720
+ import"./cli-kk2r80n5.js";
733
721
  import {
734
722
  normalizeModelKey
735
- } from "./cli-8w65vwee.js";
723
+ } from "./cli-tg9ksbn8.js";
736
724
  import {
737
725
  upsertModelCatalogOverlay,
738
726
  deleteModelCatalogOverlay,
739
727
  loadModelsCatalog,
740
728
  requestModelCatalogRefresh2
741
- } from "./cli-jhhb2v9n.js";
729
+ } from "./cli-ttaz6c7m.js";
742
730
  import {
743
731
  AMP_ESTIMATE_MODELS,
744
732
  ampBillsCacheWrites,
745
733
  resolveAmpModel
746
- } from "./cli-vmbakbq0.js";
734
+ } from "./cli-2rwet3r7.js";
747
735
  import {
748
736
  automationIntegrationFixUrl,
749
737
  preflightAutomation,
@@ -755,7 +743,7 @@ import {
755
743
  cancelWorkflowRun2,
756
744
  TriggerSchemaError,
757
745
  startWorkflowExecution2
758
- } from "./cli-dw0njd9q.js";
746
+ } from "./cli-nme7wery.js";
759
747
  import {
760
748
  require_dist2,
761
749
  isInvalidClientError,
@@ -835,14 +823,14 @@ import {
835
823
  typecheckWithAmbient,
836
824
  typecheckScript,
837
825
  parseSkillContent
838
- } from "./cli-mvsfbbgq.js";
826
+ } from "./cli-nrrg0fvy.js";
839
827
  import {
840
828
  buildAuthorizationUrl,
841
829
  exchangeAuthorizationCode,
842
830
  oauthAppRowToProviderConfig,
843
831
  forceRefreshAuthorizationOrThrow,
844
832
  forceRefreshTokenOrThrow
845
- } from "./cli-xx13gqf3.js";
833
+ } from "./cli-b80a1100.js";
846
834
  import {
847
835
  signalProcessGroup,
848
836
  detachedProcessGroup,
@@ -870,15 +858,15 @@ import {
870
858
  getSlackApp2,
871
859
  startSlackApp2,
872
860
  stopSlackApp2
873
- } from "./cli-tba8fxf9.js";
874
- import"./cli-ztcxv5nv.js";
861
+ } from "./cli-4fhyq711.js";
862
+ import"./cli-xfe63zx3.js";
875
863
  import {
876
864
  createApprovalFollowUpTask,
877
865
  generateEdges,
878
866
  definitionNodeIds,
879
867
  validateDefinition
880
- } from "./cli-hwc4a9xm.js";
881
- import"./cli-8cvvytad.js";
868
+ } from "./cli-tj4j2ts4.js";
869
+ import"./cli-bn26xbcs.js";
882
870
  import {
883
871
  _fetchHolder,
884
872
  CLIENT_ID,
@@ -894,6 +882,48 @@ import {
894
882
  workflowEventBus2,
895
883
  init_event_bus
896
884
  } from "./cli-ptkpaqfy.js";
885
+ import {
886
+ duplicateGuardedQueryKey,
887
+ warnIfCorsAllowsAnyOrigin,
888
+ isOriginAllowedForCredentials,
889
+ allowedCredentialRedirect,
890
+ setCorsHeaders,
891
+ parseQueryParams,
892
+ getPathSegments,
893
+ wireHttpSpanLifecycle,
894
+ safeRequestUrlForLog,
895
+ agentWithCapacity,
896
+ parseBody,
897
+ BODY_TOO_LARGE,
898
+ enforceContentLengthCap,
899
+ json,
900
+ jsonError,
901
+ writeUnhandledError,
902
+ triggerSchemaErrorResponse,
903
+ deriveApiBaseUrl,
904
+ httpServerSemconvAttributes,
905
+ hasSessionAccess,
906
+ getSessionRoot,
907
+ sessionAccessForRoot,
908
+ listActiveSessionShares,
909
+ toSessionSharesState,
910
+ setSessionShares,
911
+ resolveTaskAuditUserId,
912
+ resolveHttpAuditUserId,
913
+ dashboardUserIdHeader,
914
+ resolveHttpFavoriteOwner,
915
+ resolveSessionActor2,
916
+ sessionAccessFor2,
917
+ SESSION_READ_DENIED2,
918
+ canReadSession2,
919
+ readableByFor2,
920
+ SESSION_WRITE_DENIED2,
921
+ runtimeInstanceHeader,
922
+ bodyTask,
923
+ bodyListTask,
924
+ describeRequestRoute,
925
+ route
926
+ } from "./cli-02vhhssk.js";
897
927
  import {
898
928
  isMultiRuntimeEnabled
899
929
  } from "./cli-7a91w22y.js";
@@ -906,24 +936,6 @@ import {
906
936
  } from "./cli-qje9k2nh.js";
907
937
  import {
908
938
  RuntimePoolError2,
909
- registerSessionTokenConnection,
910
- init_session_token_connections,
911
- findUserById,
912
- getUserIdentities,
913
- listUserEvents,
914
- listUserTokens,
915
- recordIdentityEvent,
916
- linkIdentity,
917
- unlinkIdentity,
918
- mintToken,
919
- revokeToken,
920
- resolveUserByToken,
921
- InvalidRuntimeTokenError,
922
- mintSessionToken,
923
- mintRuntimeToken,
924
- revokeSessionToken,
925
- fingerprintApiKey,
926
- init_users,
927
939
  mintControllerToken,
928
940
  init_tokens,
929
941
  listRuntimeControllers2,
@@ -942,7 +954,7 @@ import {
942
954
  resolvePoolDelivery2,
943
955
  createPoolAgent2,
944
956
  init_db
945
- } from "./cli-h23mr2s6.js";
957
+ } from "./cli-6ftrrwm1.js";
946
958
  import {
947
959
  RATING_MODEL_MAX_LENGTH,
948
960
  REFERENCES_SOURCE_MAX_LENGTH,
@@ -958,8 +970,8 @@ import {
958
970
  checkHeartbeatChecklist2,
959
971
  runHeartbeatSweep2,
960
972
  stopHeartbeat2
961
- } from "./cli-0k9rcwfy.js";
962
- import"./cli-raejy0mp.js";
973
+ } from "./cli-bhwa8rzc.js";
974
+ import"./cli-eqgfs202.js";
963
975
  import {
964
976
  import_websocket,
965
977
  import_websocket_server
@@ -968,7 +980,7 @@ import {
968
980
  runtimeSupervisorStatus,
969
981
  startRuntimeSupervisor,
970
982
  stopRuntimeSupervisor
971
- } from "./cli-z3dk189k.js";
983
+ } from "./cli-rsd7ga7v.js";
972
984
  import"./cli-ns59dc6r.js";
973
985
  import {
974
986
  ACP_TARGET_IDS
@@ -980,20 +992,22 @@ import {
980
992
  import {
981
993
  MAX_RATE_LIMIT_RESET_MS
982
994
  } from "./cli-gx4rm0qx.js";
983
- import {
984
- inviteEmailToSharedOrg,
985
- ensureAgentFsCredentialsForAgent
986
- } from "./cli-ak4c12mh.js";
987
- import {
988
- Anthropic
989
- } from "./cli-er83cjbn.js";
990
995
  import {
991
996
  isLeadChannelMonitoringEnabled,
997
+ ORG_IDENTITY_KEYS,
998
+ orgIdentityStatus,
992
999
  SIMILARITY_THRESHOLD,
993
1000
  memoryRelevance,
994
1001
  isUnknownModelError,
995
1002
  writeSkillsToFilesystem
996
- } from "./cli-4g629sgr.js";
1003
+ } from "./cli-wrv09v7q.js";
1004
+ import {
1005
+ inviteEmailToSharedOrg,
1006
+ ensureAgentFsCredentialsForAgent
1007
+ } from "./cli-5ff2sjyt.js";
1008
+ import {
1009
+ Anthropic
1010
+ } from "./cli-er83cjbn.js";
997
1011
  import {
998
1012
  isCodexOAuthConfigKey
999
1013
  } from "./cli-j4bwnpkm.js";
@@ -1005,7 +1019,27 @@ import"./cli-13p25grh.js";
1005
1019
  import"./cli-0adyhvq2.js";
1006
1020
  import {
1007
1021
  require_content_type
1008
- } from "./cli-gzvb3d4a.js";
1022
+ } from "./cli-1jx0srjt.js";
1023
+ import {
1024
+ registerSessionTokenConnection,
1025
+ init_session_token_connections,
1026
+ findUserById,
1027
+ getUserIdentities,
1028
+ listUserEvents,
1029
+ listUserTokens,
1030
+ recordIdentityEvent,
1031
+ linkIdentity,
1032
+ unlinkIdentity,
1033
+ mintToken,
1034
+ revokeToken,
1035
+ resolveUserByToken,
1036
+ InvalidRuntimeTokenError,
1037
+ mintSessionToken,
1038
+ mintRuntimeToken,
1039
+ revokeSessionToken,
1040
+ fingerprintApiKey,
1041
+ init_users
1042
+ } from "./cli-8cxkw1f5.js";
1009
1043
  import"./cli-v41kn3cd.js";
1010
1044
  import"./cli-wgtk7xxr.js";
1011
1045
  import {
@@ -1019,7 +1053,7 @@ import {
1019
1053
  clearAuditSink,
1020
1054
  can,
1021
1055
  init_rbac
1022
- } from "./cli-zj4d6afs.js";
1056
+ } from "./cli-1pa3rz69.js";
1023
1057
  import {
1024
1058
  isSafeBundlePath,
1025
1059
  init_bundle_path,
@@ -1039,6 +1073,10 @@ import {
1039
1073
  ProviderNameSchema,
1040
1074
  ModelProfileNameSchema,
1041
1075
  ModelProfileSchema,
1076
+ SessionAccessSchema,
1077
+ SessionOwnerSchema,
1078
+ SessionSharesSchema,
1079
+ SessionSharesUpdateSchema,
1042
1080
  SteerModeSchema,
1043
1081
  SteeringSourceSchema,
1044
1082
  SteeringMessageSchema,
@@ -1156,12 +1194,12 @@ import {
1156
1194
  KvKeySchema,
1157
1195
  KvEntrySchema,
1158
1196
  init_types
1159
- } from "./cli-ksrphveb.js";
1197
+ } from "./cli-a1esmmee.js";
1160
1198
  import {
1161
1199
  IDENTITY_FIELD_BUDGETS,
1162
1200
  IdentityFieldBudgetError,
1163
1201
  init_identity_field_budget
1164
- } from "./cli-cby329ny.js";
1202
+ } from "./cli-wyrr38xm.js";
1165
1203
  import {
1166
1204
  getSlackConfiguration,
1167
1205
  init_config
@@ -1179,7 +1217,7 @@ import {
1179
1217
  resolveTemplate,
1180
1218
  init_resolver,
1181
1219
  init_session_templates
1182
- } from "./cli-w7z94e6c.js";
1220
+ } from "./cli-tx00c0h6.js";
1183
1221
  import {
1184
1222
  MODEL_SCOPED_WINDOWS,
1185
1223
  activeModelBlocks,
@@ -1201,6 +1239,7 @@ import {
1201
1239
  import {
1202
1240
  NEVER,
1203
1241
  toJSONSchema,
1242
+ ZodError,
1204
1243
  string2,
1205
1244
  email,
1206
1245
  uuid,
@@ -1228,7 +1267,7 @@ import {
1228
1267
  import {
1229
1268
  init_package,
1230
1269
  package_default
1231
- } from "./cli-2jm992rm.js";
1270
+ } from "./cli-mz1ezzzh.js";
1232
1271
  import {
1233
1272
  realtimeBus,
1234
1273
  init_bus
@@ -2367,7 +2406,8 @@ function identityFromHttpAuth(req, auth) {
2367
2406
  agentId,
2368
2407
  sourceTaskId: auth.kind === "agent" ? auth.taskId ?? undefined : header(req, "x-source-task-id"),
2369
2408
  userId: auth.kind === "user" ? auth.userId : undefined,
2370
- isOperator: auth.kind === "operator"
2409
+ isOperator: auth.kind === "operator",
2410
+ dashboardUserId: auth.kind === "operator" ? auth.dashboardUserId : undefined
2371
2411
  },
2372
2412
  me: {
2373
2413
  userId: auth.kind === "user" ? auth.userId : agentId ?? guestId,
@@ -2560,6 +2600,9 @@ function connect(ws, auth, runtimeId) {
2560
2600
  if (namespace.startsWith("presence:comb:") && msg.name !== "default") {
2561
2601
  throw new Error("Comb presence uses the default room");
2562
2602
  }
2603
+ if (namespace.startsWith("presence:session:") && msg.name !== "default") {
2604
+ throw new Error("Session presence uses the default room");
2605
+ }
2563
2606
  const denied = await authorizeRoomNamespace(namespace, auth.identity, msg.op, msg.name);
2564
2607
  if (denied)
2565
2608
  throw new Error(typeof denied === "string" ? denied : "Forbidden");
@@ -2600,7 +2643,7 @@ function connect(ws, auth, runtimeId) {
2600
2643
  } catch (error) {
2601
2644
  if (!(error instanceof Error) || error.message !== "room does not exist")
2602
2645
  throw error;
2603
- const creationDenied = await authorizeRoomNamespace(namespace, auth.identity, namespace.startsWith("presence:comb:") ? "join" : true);
2646
+ const creationDenied = await authorizeRoomNamespace(namespace, auth.identity, namespace.startsWith("presence:comb:") || namespace.startsWith("presence:session:") ? "join" : true);
2604
2647
  if (creationDenied)
2605
2648
  throw new Error(creationDenied);
2606
2649
  if (closed)
@@ -2610,6 +2653,8 @@ function connect(ws, auth, runtimeId) {
2610
2653
  if (closed)
2611
2654
  return;
2612
2655
  const stopRoom = realtimeBus.subscribe(key, (event) => {
2656
+ if (rooms.get(key)?.suspended)
2657
+ return;
2613
2658
  const view = event;
2614
2659
  const type = view.type;
2615
2660
  if (type === "deleted") {
@@ -2631,15 +2676,62 @@ function connect(ws, auth, runtimeId) {
2631
2676
  }).catch(() => ws.close(1011, "Room reset failed"));
2632
2677
  }
2633
2678
  });
2634
- const stopPresence = realtimeBus.subscribe(`presence:${key}`, (peers) => send({ type: "presence", name: msg.name, namespace, peers }));
2679
+ const stopPresence = realtimeBus.subscribe(`presence:${key}`, (peers) => {
2680
+ if (rooms.get(key)?.suspended)
2681
+ return;
2682
+ send({ type: "presence", name: msg.name, namespace, peers });
2683
+ });
2684
+ const sessionRoot = sessionPresenceRoot(namespace);
2685
+ let accessCheck = 0;
2686
+ const stopAccess = sessionRoot ? realtimeBus.subscribe(sessionAccessTopic(sessionRoot), () => {
2687
+ const joined = rooms.get(key);
2688
+ if (!joined)
2689
+ return;
2690
+ joined.suspended = true;
2691
+ const check = ++accessCheck;
2692
+ authorizeRoomNamespace(namespace, auth.identity, "join").catch(() => "Session access check failed").then((denied) => {
2693
+ const current = rooms.get(key);
2694
+ if (closed || !current || check !== accessCheck)
2695
+ return;
2696
+ if (denied) {
2697
+ leave(key);
2698
+ send({ type: "error", name: msg.name, namespace, error: denied });
2699
+ return;
2700
+ }
2701
+ current.suspended = false;
2702
+ send({
2703
+ type: "presence",
2704
+ name: msg.name,
2705
+ namespace,
2706
+ peers: [...current.room.awareness.getStates().values()]
2707
+ });
2708
+ });
2709
+ }) : undefined;
2635
2710
  rooms.set(key, {
2636
2711
  room,
2637
2712
  version: msg.schemaVersion,
2638
2713
  stop: () => {
2639
2714
  stopRoom();
2640
2715
  stopPresence();
2641
- }
2716
+ stopAccess?.();
2717
+ },
2718
+ suspended: sessionRoot !== undefined
2642
2719
  });
2720
+ if (sessionRoot) {
2721
+ const check = ++accessCheck;
2722
+ const denied = await authorizeRoomNamespace(namespace, auth.identity, "join").catch(() => "Session access check failed");
2723
+ if (closed)
2724
+ return;
2725
+ if (denied) {
2726
+ leave(key);
2727
+ throw new Error(denied);
2728
+ }
2729
+ const current = rooms.get(key);
2730
+ if (!current)
2731
+ throw new Error("You don't have access to this session");
2732
+ if (check === accessCheck)
2733
+ current.suspended = false;
2734
+ }
2643
2735
  room.awareness.states.set(clientId, { ...auth.me, data: {} });
2644
2736
  room.awareness.meta.set(clientId, { clock: 1, lastUpdated: Date.now() });
2645
2737
  publishPresence(room);
@@ -3863,6 +3955,7 @@ var createActiveSession = route({
3863
3955
  method: "post",
3864
3956
  path: "/api/active-sessions",
3865
3957
  pattern: ["api", "active-sessions"],
3958
+ session: bodyTask("taskId"),
3866
3959
  summary: "Create a new active session",
3867
3960
  tags: ["Active Sessions"],
3868
3961
  body: object2({
@@ -3883,6 +3976,7 @@ var deleteSessionByTask = route({
3883
3976
  method: "delete",
3884
3977
  path: "/api/active-sessions/by-task/{taskId}",
3885
3978
  pattern: ["api", "active-sessions", "by-task", null],
3979
+ session: { taskParam: "taskId" },
3886
3980
  summary: "Delete active session by task ID",
3887
3981
  tags: ["Active Sessions"],
3888
3982
  params: object2({ taskId: string2() }),
@@ -3894,6 +3988,7 @@ var deleteSessionById = route({
3894
3988
  method: "delete",
3895
3989
  path: "/api/active-sessions/{id}",
3896
3990
  pattern: ["api", "active-sessions", null],
3991
+ session: { activeSessionParam: "id" },
3897
3992
  summary: "Delete active session by ID",
3898
3993
  tags: ["Active Sessions"],
3899
3994
  params: object2({ id: string2() }),
@@ -3905,6 +4000,7 @@ var heartbeatSession = route({
3905
4000
  method: "put",
3906
4001
  path: "/api/active-sessions/heartbeat/{taskId}",
3907
4002
  pattern: ["api", "active-sessions", "heartbeat", null],
4003
+ session: { taskParam: "taskId" },
3908
4004
  summary: "Update heartbeat for an active session",
3909
4005
  tags: ["Active Sessions"],
3910
4006
  params: object2({ taskId: string2() }),
@@ -3916,6 +4012,7 @@ var updateProviderSession = route({
3916
4012
  method: "put",
3917
4013
  path: "/api/active-sessions/provider-session/{taskId}",
3918
4014
  pattern: ["api", "active-sessions", "provider-session", null],
4015
+ session: { taskParam: "taskId" },
3919
4016
  summary: "Update provider session ID on an active session",
3920
4017
  tags: ["Active Sessions"],
3921
4018
  params: object2({ taskId: string2() }),
@@ -3928,6 +4025,7 @@ var cleanupSessions = route({
3928
4025
  method: "post",
3929
4026
  path: "/api/active-sessions/cleanup",
3930
4027
  pattern: ["api", "active-sessions", "cleanup"],
4028
+ session: { agentWide: "Deletes every session of an agent, or every stale session." },
3931
4029
  summary: "Clean up stale sessions",
3932
4030
  tags: ["Active Sessions"],
3933
4031
  body: object2({
@@ -3942,6 +4040,7 @@ var recoverOrphanedTasks = route({
3942
4040
  method: "post",
3943
4041
  path: "/api/active-sessions/recover-orphaned-tasks",
3944
4042
  pattern: ["api", "active-sessions", "recover-orphaned-tasks"],
4043
+ session: { none: "Its handler admits only the calling agent, never a user." },
3945
4044
  summary: "Recover orphaned in-progress tasks for an agent",
3946
4045
  tags: ["Active Sessions"],
3947
4046
  body: object2({
@@ -3961,7 +4060,7 @@ async function handleActiveSessions(req, res, pathSegments, queryParams, myAgent
3961
4060
  const parsed = await listActiveSessions.parse(req, res, pathSegments, queryParams);
3962
4061
  if (!parsed)
3963
4062
  return true;
3964
- const sessions = await getActiveSessions2(parsed.query.agentId || undefined);
4063
+ const sessions = await getActiveSessions2(parsed.query.agentId || undefined, readableByFor2(await resolveSessionActor2(req, myAgentId)));
3965
4064
  listActiveSessions.respond(res, 200, { sessions });
3966
4065
  return true;
3967
4066
  }
@@ -4610,14 +4709,17 @@ async function handleAgentRegister(req, res, pathSegments, myAgentId) {
4610
4709
  }
4611
4710
  return false;
4612
4711
  }
4613
- async function handleAgentsRest(req, res, pathSegments, queryParams, _myAgentId) {
4712
+ async function handleAgentsRest(req, res, pathSegments, queryParams, myAgentId) {
4614
4713
  if (listAgents.match(req.method, pathSegments)) {
4615
4714
  const parsed = await listAgents.parse(req, res, pathSegments, queryParams);
4616
4715
  if (!parsed)
4617
4716
  return true;
4618
4717
  const includeTasks = parsed.query.include === "tasks";
4619
4718
  const slim = parsed.query.fields !== "full";
4620
- const agents = includeTasks ? await getAllAgentsWithTasks2({ slim }) : await getAllAgents2({ slim });
4719
+ const agents = includeTasks ? await getAllAgentsWithTasks2({
4720
+ slim,
4721
+ readableBy: readableByFor2(await resolveSessionActor2(req, myAgentId))
4722
+ }) : await getAllAgents2({ slim });
4621
4723
  const [agentsWithCapacity, transports] = await Promise.all([
4622
4724
  Promise.all(agents.map(agentWithCapacity)),
4623
4725
  resolveListClaudeTransports(agents)
@@ -5155,7 +5257,7 @@ async function handleAgentsRest(req, res, pathSegments, queryParams, _myAgentId)
5155
5257
  if (!parsed)
5156
5258
  return true;
5157
5259
  const includeTasks = parsed.query.include === "tasks";
5158
- const agent = includeTasks ? await getAgentWithTasks2(parsed.params.id) : await getAgentById2(parsed.params.id);
5260
+ const agent = includeTasks ? await getAgentWithTasks2(parsed.params.id, readableByFor2(await resolveSessionActor2(req, myAgentId))) : await getAgentById2(parsed.params.id);
5159
5261
  if (!agent) {
5160
5262
  jsonError(res, "Agent not found", 404);
5161
5263
  return true;
@@ -5234,6 +5336,7 @@ var reportUsage = route({
5234
5336
  method: "post",
5235
5337
  path: "/api/keys/report-usage",
5236
5338
  pattern: ["api", "keys", "report-usage"],
5339
+ session: bodyTask("taskId"),
5237
5340
  summary: "Record which API key was used for a task",
5238
5341
  tags: ["API Keys"],
5239
5342
  body: object2({
@@ -5277,6 +5380,7 @@ var reportAuthFailure = route({
5277
5380
  method: "post",
5278
5381
  path: "/api/keys/report-auth-failure",
5279
5382
  pattern: ["api", "keys", "report-auth-failure"],
5383
+ session: bodyTask("taskId"),
5280
5384
  summary: "Record an auth failure for a pooled key; bench it after 2 in a row",
5281
5385
  tags: ["API Keys"],
5282
5386
  body: object2({
@@ -5974,6 +6078,7 @@ var createRoute = route({
5974
6078
  method: "post",
5975
6079
  path: "/api/approval-requests",
5976
6080
  pattern: ["api", "approval-requests"],
6081
+ session: bodyTask("sourceTaskId"),
5977
6082
  summary: "Create a new approval request",
5978
6083
  tags: ["ApprovalRequests"],
5979
6084
  body: object2({
@@ -6522,10 +6627,14 @@ function ensureOperator(req, res) {
6522
6627
  return false;
6523
6628
  }
6524
6629
  var moveAssetBodySchema = object2({ key: AssetKeySchema });
6630
+ var moveGenericAssetBodySchema = moveAssetBodySchema.extend({
6631
+ cascade: boolean().optional().describe("For tasks only, also move every descendant task recursively through parentTaskId.")
6632
+ });
6525
6633
  var moveAssetSuccessSchema = object2({
6526
6634
  entityType: AssetEntityTypeSchema,
6527
6635
  id: string2(),
6528
- key: AssetKeySchema
6636
+ key: AssetKeySchema,
6637
+ movedTaskIds: array2(string2()).optional()
6529
6638
  });
6530
6639
  var moveAssetResponses = {
6531
6640
  200: { description: "Asset namespace updated", schema: moveAssetSuccessSchema },
@@ -6564,7 +6673,7 @@ var moveAssetRoute = route({
6564
6673
  description: "Updates namespace metadata only. Provider-backed files keep the same provider key, org, and drive; no remote move occurs. Personal keys are labels, not a privacy guarantee.",
6565
6674
  tags: ["Assets"],
6566
6675
  params: object2({ entityType: AssetEntityTypeSchema, id: string2().min(1) }),
6567
- body: moveAssetBodySchema,
6676
+ body: moveGenericAssetBodySchema,
6568
6677
  responses: moveAssetResponses,
6569
6678
  rbac: {
6570
6679
  ungated: "preserves each entity's current mutation posture; task moves use task.fs.mutate, app moves use app.manage, scripts preserve agent ownership/global-lead rules, file moves require operator authentication, and personal destinations require a matching trusted user"
@@ -6671,18 +6780,27 @@ async function handleAssets(req, res, pathSegments, queryParams, myAgentId) {
6671
6780
  const parsed = await moveAssetRoute.parse(req, res, pathSegments, queryParams);
6672
6781
  if (!parsed)
6673
6782
  return true;
6783
+ if (parsed.body.cascade && parsed.params.entityType !== "task") {
6784
+ jsonError(res, "Cascade is only supported for task namespace moves", 400);
6785
+ return true;
6786
+ }
6674
6787
  if (parsed.params.entityType === "file" && !ensureOperator(req, res))
6675
6788
  return true;
6789
+ let descendantTaskIds;
6676
6790
  if (parsed.params.entityType === "task") {
6677
6791
  const task = await getTaskById2(parsed.params.id);
6678
6792
  if (!task) {
6679
6793
  jsonError(res, "Asset not found", 404);
6680
6794
  return true;
6681
6795
  }
6682
- if (!await canMutateTaskNamespace(task, myAgentId, req)) {
6683
- jsonError(res, "Not authorized to move this task namespace", 403);
6684
- return true;
6796
+ const tasks = parsed.body.cascade ? await getRootTaskChain2(task.id) : [task];
6797
+ for (const target of tasks) {
6798
+ if (!await canMutateTaskNamespace(target, myAgentId, req)) {
6799
+ jsonError(res, "Not authorized to move this task namespace", 403);
6800
+ return true;
6801
+ }
6685
6802
  }
6803
+ descendantTaskIds = tasks.filter((target) => target.id !== task.id).map((target) => target.id);
6686
6804
  }
6687
6805
  if (parsed.params.entityType === "app") {
6688
6806
  if (!await getApp(parsed.params.id)) {
@@ -6712,7 +6830,8 @@ async function handleAssets(req, res, pathSegments, queryParams, myAgentId) {
6712
6830
  entityType: parsed.params.entityType,
6713
6831
  id: parsed.params.id,
6714
6832
  key,
6715
- changedBy: actor ?? undefined
6833
+ changedBy: actor ?? undefined,
6834
+ descendantTaskIds
6716
6835
  });
6717
6836
  if (!moved) {
6718
6837
  jsonError(res, "Asset not found", 404);
@@ -6721,7 +6840,8 @@ async function handleAssets(req, res, pathSegments, queryParams, myAgentId) {
6721
6840
  moveAssetRoute.respond(res, 200, {
6722
6841
  entityType: parsed.params.entityType,
6723
6842
  id: parsed.params.id,
6724
- key
6843
+ key,
6844
+ ...moved
6725
6845
  });
6726
6846
  } catch (error) {
6727
6847
  if (error instanceof AssetKeyAuthorizationError) {
@@ -7575,6 +7695,396 @@ init_rbac();
7575
7695
  init_types();
7576
7696
  init_request_auth_context();
7577
7697
  init_secret_scrubber();
7698
+
7699
+ // src/http/ranking.ts
7700
+ init_zod();
7701
+ init_db3();
7702
+ init_telemetry_ranking();
7703
+ init_telemetry();
7704
+ init_env_flag();
7705
+
7706
+ // src/http/used-by.ts
7707
+ init_zod();
7708
+ init_db3();
7709
+ init_telemetry();
7710
+ init_org_website();
7711
+ var CONSENT_KEY = "SWARM_USED_BY_CONSENT";
7712
+ var CONSENT_AT_KEY = "SWARM_USED_BY_CONSENT_AT";
7713
+ var GRANTED_NAME_KEY = "SWARM_USED_BY_ORG_NAME";
7714
+ var GRANTED_WEBSITE_KEY = "SWARM_USED_BY_ORG_WEBSITE";
7715
+ var TIMEOUT_MS = 1e4;
7716
+ var usedBy = route({
7717
+ method: "post",
7718
+ path: "/api/used-by",
7719
+ pattern: ["api", "used-by"],
7720
+ summary: "Grant or revoke the 'used by' logo consent",
7721
+ description: "Records whether agent-swarm.dev may show this organization's logo as a company that uses Agent Swarm. Sends the organization name and website to the telemetry proxy, where a person approves each logo by hand. An explicit admin action, so it is not gated by the telemetry opt-out.",
7722
+ tags: ["Telemetry"],
7723
+ body: object2({ granted: boolean() }),
7724
+ responses: {
7725
+ 200: {
7726
+ description: "Consent recorded",
7727
+ schema: object2({ granted: boolean(), changedAt: string2() })
7728
+ },
7729
+ 400: {
7730
+ description: "Organization name or website is not set (a revoke falls back to the identity of the last grant)"
7731
+ },
7732
+ 401: { description: "Unauthorized" },
7733
+ 403: { description: "Only a config admin can change the consent" },
7734
+ 502: { description: "The telemetry proxy did not accept the consent" }
7735
+ },
7736
+ rbac: { permission: "config.write.any" }
7737
+ });
7738
+ async function globalConfig(key) {
7739
+ const stored = (await getSwarmConfigs2({ scope: "global", key }))?.[0]?.value?.trim();
7740
+ return stored || process.env[key]?.trim() || undefined;
7741
+ }
7742
+ async function telemetryProxyEndpoint(path) {
7743
+ const feedback = await globalConfig("feedback_endpoint");
7744
+ if (feedback && /^https?:\/\/[^/]+\/v1\/feedback\/?$/.test(feedback)) {
7745
+ return feedback.replace(/\/v1\/feedback\/?$/, path);
7746
+ }
7747
+ return `${TELEMETRY_PROXY_ORIGIN}${path}`;
7748
+ }
7749
+ function usedByEndpoint() {
7750
+ return telemetryProxyEndpoint("/v1/used-by");
7751
+ }
7752
+ async function handleUsedBy(req, res, pathSegments, queryParams, myAgentId) {
7753
+ if (!usedBy.match(req.method, pathSegments))
7754
+ return false;
7755
+ const parsed = await usedBy.parse(req, res, pathSegments, queryParams);
7756
+ if (!parsed)
7757
+ return true;
7758
+ if (!await ensureConfigAdmin(req, res, "config.write.any"))
7759
+ return true;
7760
+ const { granted } = parsed.body;
7761
+ const current = orgIdentityStatus({
7762
+ SWARM_ORG_NAME: await globalConfig("SWARM_ORG_NAME"),
7763
+ SWARM_ORG_WEBSITE: await globalConfig("SWARM_ORG_WEBSITE")
7764
+ });
7765
+ const currentName = current.name ?? undefined;
7766
+ const currentWebsite = current.website;
7767
+ const grantedName = granted ? undefined : await globalConfig(GRANTED_NAME_KEY);
7768
+ const grantedWebsite = granted ? null : normalizeOrgWebsite(await globalConfig(GRANTED_WEBSITE_KEY));
7769
+ const [orgName, orgWebsite] = grantedName && grantedWebsite ? [grantedName, grantedWebsite] : [currentName, currentWebsite];
7770
+ if (!orgName || !orgWebsite) {
7771
+ jsonError(res, "Set the organization name and website first", 400);
7772
+ return true;
7773
+ }
7774
+ const installId = isTelemetryEnabled() ? await globalConfig("telemetry_installation_id") : undefined;
7775
+ const orgDomain = await globalConfig("telemetry_org_domain");
7776
+ const changedAt = new Date().toISOString();
7777
+ try {
7778
+ const response = await fetch(await usedByEndpoint(), {
7779
+ method: "POST",
7780
+ headers: { "Content-Type": "application/json" },
7781
+ body: JSON.stringify({
7782
+ ...installId ? { install_id: installId } : {},
7783
+ org_name: orgName,
7784
+ org_website: orgWebsite,
7785
+ ...orgDomain ? { org_domain: orgDomain } : {},
7786
+ granted,
7787
+ changed_at: changedAt
7788
+ }),
7789
+ signal: AbortSignal.timeout(TIMEOUT_MS)
7790
+ });
7791
+ if (!response.ok) {
7792
+ jsonError(res, `The telemetry proxy answered ${response.status}`, 502);
7793
+ return true;
7794
+ }
7795
+ } catch {
7796
+ jsonError(res, "Could not reach the telemetry proxy", 502);
7797
+ return true;
7798
+ }
7799
+ await upsertSwarmConfig2({ scope: "global", key: CONSENT_KEY, value: String(granted) });
7800
+ await upsertSwarmConfig2({ scope: "global", key: CONSENT_AT_KEY, value: changedAt });
7801
+ if (granted) {
7802
+ await upsertSwarmConfig2({ scope: "global", key: GRANTED_NAME_KEY, value: orgName });
7803
+ await upsertSwarmConfig2({ scope: "global", key: GRANTED_WEBSITE_KEY, value: orgWebsite });
7804
+ }
7805
+ scheduleIntegrationsReload();
7806
+ const owner = await resolveHttpFavoriteOwner(req, myAgentId);
7807
+ trackRelayed("used_by.consent_changed", { granted }, { actor: owner?.userId ? { userId: owner.userId } : undefined, emitter: "ui" });
7808
+ usedBy.respond(res, 200, { granted, changedAt });
7809
+ return true;
7810
+ }
7811
+
7812
+ // src/http/ranking.ts
7813
+ var ListingSchema = object2({
7814
+ v: literal2(1),
7815
+ claim: string2(),
7816
+ name: string2(),
7817
+ domain: string2(),
7818
+ category: _enum2(RANKING_CATEGORIES),
7819
+ subcategory: string2().nullable(),
7820
+ description: string2().nullable(),
7821
+ sourceCodeUrl: string2().nullable(),
7822
+ updatedAt: string2()
7823
+ });
7824
+ var RankingStateSchema = object2({
7825
+ eligible: boolean(),
7826
+ missing: array2(_enum2(ORG_IDENTITY_KEYS)),
7827
+ telemetryOn: boolean(),
7828
+ enabled: boolean(),
7829
+ name: string2().nullable(),
7830
+ domain: string2().nullable(),
7831
+ listing: ListingSchema.nullable(),
7832
+ listingMatchesDomain: boolean(),
7833
+ fileJson: string2().nullable(),
7834
+ prUrl: string2().nullable(),
7835
+ verifyUrl: string2().nullable()
7836
+ });
7837
+ var OptInResponseSchema = object2({
7838
+ domain: string2(),
7839
+ claim: string2(),
7840
+ listing: ListingSchema,
7841
+ fileJson: string2(),
7842
+ prUrl: string2(),
7843
+ verifyUrl: string2()
7844
+ });
7845
+ var OptOutResponseSchema = object2({
7846
+ enabled: literal2(false),
7847
+ leaveUrl: string2().nullable()
7848
+ });
7849
+ var getRanking = route({
7850
+ method: "get",
7851
+ path: "/api/ranking",
7852
+ pattern: ["api", "ranking"],
7853
+ summary: "Public ranking state",
7854
+ description: "Whether this install can join the public agent-swarm.dev ranking (telemetry on, organization name and website set), whether it is opted in, and its stored listing. The listing carries the private ranking claim, so only a config admin can read it.",
7855
+ tags: ["Telemetry"],
7856
+ responses: {
7857
+ 200: { description: "Ranking state", schema: RankingStateSchema },
7858
+ 401: { description: "Unauthorized" },
7859
+ 403: { description: "Only a config admin can read the ranking state" }
7860
+ },
7861
+ rbac: { permission: "config.write.any" }
7862
+ });
7863
+ var optIn = route({
7864
+ method: "post",
7865
+ path: "/api/ranking/opt-in",
7866
+ pattern: ["api", "ranking", "opt-in"],
7867
+ summary: "Opt this install into the public ranking",
7868
+ description: "Builds the telemetry_ranking listing from the organization name and website plus the chosen category, stores it, turns PUBLIC_RANKING_ENABLED on and sends ranking.opted_in. The install is ranked only after a ranking PR with the returned file is merged into desplega-ai/agent-swarm.",
7869
+ tags: ["Telemetry"],
7870
+ body: rankingFormSchema,
7871
+ responses: {
7872
+ 200: { description: "Opted in", schema: OptInResponseSchema },
7873
+ 400: { description: "A listing field is invalid" },
7874
+ 401: { description: "Unauthorized" },
7875
+ 403: { description: "Only a config admin can opt in" },
7876
+ 409: {
7877
+ description: "telemetry_off: telemetry is off or the install has no ID. org_identity_missing: the organization name or website is not set (`missing` lists which)."
7878
+ }
7879
+ },
7880
+ rbac: { permission: "config.write.any" }
7881
+ });
7882
+ var optOut = route({
7883
+ method: "post",
7884
+ path: "/api/ranking/opt-out",
7885
+ pattern: ["api", "ranking", "opt-out"],
7886
+ summary: "Leave the public ranking",
7887
+ description: "Turns PUBLIC_RANKING_ENABLED off and sends ranking.opted_out, so workers stop sending ranking.usage. Returns the GitHub page that deletes the ranking entry.",
7888
+ tags: ["Telemetry"],
7889
+ responses: {
7890
+ 200: { description: "Opted out", schema: OptOutResponseSchema },
7891
+ 401: { description: "Unauthorized" },
7892
+ 403: { description: "Only a config admin can opt out" }
7893
+ },
7894
+ rbac: { permission: "config.write.any" }
7895
+ });
7896
+ var RANKING_CONFIG_KEYS = new Set([...ORG_IDENTITY_KEYS, PUBLIC_RANKING_ENABLED_KEY]);
7897
+ async function currentIdentity(changed) {
7898
+ const read = async (key) => changed?.key === key ? changed.value : await globalConfig(key);
7899
+ return orgIdentityStatus({
7900
+ SWARM_ORG_NAME: await read("SWARM_ORG_NAME"),
7901
+ SWARM_ORG_WEBSITE: await read("SWARM_ORG_WEBSITE")
7902
+ });
7903
+ }
7904
+ async function storedListing() {
7905
+ return readTelemetryRanking(await globalConfig(TELEMETRY_RANKING_KEY));
7906
+ }
7907
+ async function rankingEnabled() {
7908
+ const raw = await globalConfig(PUBLIC_RANKING_ENABLED_KEY);
7909
+ return isEnvFlagEnabled(PUBLIC_RANKING_ENABLED_KEY, false, {
7910
+ [PUBLIC_RANKING_ENABLED_KEY]: raw
7911
+ });
7912
+ }
7913
+ async function telemetryReady() {
7914
+ return isTelemetryEnabled() && Boolean(await globalConfig("telemetry_installation_id"));
7915
+ }
7916
+ async function verifyUrl(listing) {
7917
+ const params = new URLSearchParams({ domain: listing.domain, claim: listing.claim });
7918
+ return `${await telemetryProxyEndpoint("/v1/ranking/verify")}?${params.toString()}`;
7919
+ }
7920
+ async function setEnabled(enabled) {
7921
+ await upsertSwarmConfig2({
7922
+ scope: "global",
7923
+ key: PUBLIC_RANKING_ENABLED_KEY,
7924
+ value: String(enabled)
7925
+ });
7926
+ }
7927
+ function emitOptedIn(listing) {
7928
+ telemetry.ranking("opted_in", { listing: JSON.stringify(listing) }, { orgName: listing.name, orgWebsite: listing.domain });
7929
+ }
7930
+ function emitOptedOut(domain) {
7931
+ telemetry.ranking("opted_out", { domain });
7932
+ }
7933
+ async function buildRankingState() {
7934
+ const [identity, listing, enabled, telemetryOn] = await Promise.all([
7935
+ currentIdentity(),
7936
+ storedListing(),
7937
+ rankingEnabled(),
7938
+ telemetryReady()
7939
+ ]);
7940
+ const listingMatchesDomain = Boolean(listing && listing.domain === identity.website);
7941
+ const current = listingMatchesDomain ? listing : null;
7942
+ return {
7943
+ eligible: telemetryOn && identity.ok,
7944
+ missing: identity.missing,
7945
+ telemetryOn,
7946
+ enabled: enabled && listingMatchesDomain,
7947
+ name: identity.name,
7948
+ domain: identity.website,
7949
+ listing,
7950
+ listingMatchesDomain,
7951
+ fileJson: current ? rankingEntryFile(current) : null,
7952
+ prUrl: current ? rankingPrUrl(current) : null,
7953
+ verifyUrl: current ? await verifyUrl(current) : null
7954
+ };
7955
+ }
7956
+ async function syncRankingAfterConfigChange(key, value) {
7957
+ if (!RANKING_CONFIG_KEYS.has(key))
7958
+ return;
7959
+ const listing = await storedListing();
7960
+ if (!listing)
7961
+ return;
7962
+ if (key === PUBLIC_RANKING_ENABLED_KEY) {
7963
+ if (!isEnvFlagEnabled(key, false, { [key]: value ?? undefined })) {
7964
+ emitOptedOut(listing.domain);
7965
+ }
7966
+ return;
7967
+ }
7968
+ if (!await rankingEnabled())
7969
+ return;
7970
+ const identity = await currentIdentity({ key, value });
7971
+ let rebuilt = null;
7972
+ if (identity.ok && identity.website === listing.domain) {
7973
+ if (identity.name === listing.name)
7974
+ return;
7975
+ try {
7976
+ rebuilt = buildListing(identity, listing, listing.claim);
7977
+ } catch {}
7978
+ }
7979
+ if (!rebuilt) {
7980
+ await setEnabled(false);
7981
+ emitOptedOut(listing.domain);
7982
+ return;
7983
+ }
7984
+ await upsertSwarmConfig2({
7985
+ scope: "global",
7986
+ key: TELEMETRY_RANKING_KEY,
7987
+ value: JSON.stringify(rebuilt)
7988
+ });
7989
+ emitOptedIn(rebuilt);
7990
+ }
7991
+ function conflict(res, body) {
7992
+ res.writeHead(409, { "Content-Type": "application/json" });
7993
+ res.end(JSON.stringify(body));
7994
+ }
7995
+ async function handleRanking(req, res, pathSegments, queryParams) {
7996
+ if (getRanking.match(req.method, pathSegments)) {
7997
+ const parsed = await getRanking.parse(req, res, pathSegments, queryParams);
7998
+ if (!parsed)
7999
+ return true;
8000
+ if (!await ensureConfigAdmin(req, res, "config.write.any"))
8001
+ return true;
8002
+ getRanking.respond(res, 200, await buildRankingState());
8003
+ return true;
8004
+ }
8005
+ if (optIn.match(req.method, pathSegments)) {
8006
+ const parsed = await optIn.parse(req, res, pathSegments, queryParams);
8007
+ if (!parsed)
8008
+ return true;
8009
+ if (!await ensureConfigAdmin(req, res, "config.write.any"))
8010
+ return true;
8011
+ if (!await telemetryReady()) {
8012
+ conflict(res, {
8013
+ error: "telemetry_off",
8014
+ message: "Turn telemetry on first: the ranking counts tokens from telemetry."
8015
+ });
8016
+ return true;
8017
+ }
8018
+ const identity = await currentIdentity();
8019
+ if (!identity.ok || !identity.website) {
8020
+ conflict(res, {
8021
+ error: "org_identity_missing",
8022
+ message: "Set the organization name and website first",
8023
+ missing: identity.missing
8024
+ });
8025
+ return true;
8026
+ }
8027
+ const website = identity.website;
8028
+ const db = getDbClient2();
8029
+ const built = await db.transaction(async () => {
8030
+ const stored = await storedListing();
8031
+ const claim = stored?.domain === website ? stored.claim : mintRankingClaim();
8032
+ let listing;
8033
+ try {
8034
+ listing = buildListing(identity, parsed.body, claim);
8035
+ } catch (error) {
8036
+ return { ok: false, error };
8037
+ }
8038
+ await upsertSwarmConfig2({
8039
+ scope: "global",
8040
+ key: TELEMETRY_RANKING_KEY,
8041
+ value: JSON.stringify(listing)
8042
+ });
8043
+ await setEnabled(true);
8044
+ db.afterCommit(() => emitOptedIn(listing));
8045
+ return { ok: true, listing };
8046
+ });
8047
+ if (!built.ok) {
8048
+ const issue = built.error instanceof ZodError ? built.error.issues[0] : undefined;
8049
+ res.writeHead(400, { "Content-Type": "application/json" });
8050
+ res.end(JSON.stringify({
8051
+ error: issue ? `Invalid ${issue.path.join(".") || "listing"}: ${issue.message}` : "Invalid listing"
8052
+ }));
8053
+ return true;
8054
+ }
8055
+ const { listing } = built;
8056
+ scheduleIntegrationsReload();
8057
+ optIn.respond(res, 200, {
8058
+ domain: listing.domain,
8059
+ claim: listing.claim,
8060
+ listing,
8061
+ fileJson: rankingEntryFile(listing),
8062
+ prUrl: rankingPrUrl(listing),
8063
+ verifyUrl: await verifyUrl(listing)
8064
+ });
8065
+ return true;
8066
+ }
8067
+ if (optOut.match(req.method, pathSegments)) {
8068
+ const parsed = await optOut.parse(req, res, pathSegments, queryParams);
8069
+ if (!parsed)
8070
+ return true;
8071
+ if (!await ensureConfigAdmin(req, res, "config.write.any"))
8072
+ return true;
8073
+ const listing = await storedListing();
8074
+ await setEnabled(false);
8075
+ if (listing)
8076
+ emitOptedOut(listing.domain);
8077
+ scheduleIntegrationsReload();
8078
+ optOut.respond(res, 200, {
8079
+ enabled: false,
8080
+ leaveUrl: listing ? rankingLeaveUrl(listing.domain) : null
8081
+ });
8082
+ return true;
8083
+ }
8084
+ return false;
8085
+ }
8086
+
8087
+ // src/http/config.ts
7578
8088
  var MAX_ENV_PRESENCE_KEYS = 200;
7579
8089
  var SECRETS_FORCE_MASK_NOTE = " (secret values masked: this token cannot read secrets in this scope)";
7580
8090
  function stripApiOnlyKeys(configs) {
@@ -7944,6 +8454,8 @@ async function handleConfig(req, res, pathSegments, queryParams) {
7944
8454
  if (scope === "global" && key === "SWARM_ORG_WEBSITE") {
7945
8455
  await fillOrgLogoFromWebsite(String(value));
7946
8456
  }
8457
+ if (scope === "global")
8458
+ await syncRankingAfterConfigChange(key, String(value));
7947
8459
  if (scope === "global") {
7948
8460
  scheduleIntegrationsReload();
7949
8461
  }
@@ -7979,6 +8491,7 @@ async function handleConfig(req, res, pathSegments, queryParams) {
7979
8491
  return true;
7980
8492
  }
7981
8493
  if (existing.scope === "global") {
8494
+ await syncRankingAfterConfigChange(existing.key, null);
7982
8495
  scheduleIntegrationsReload();
7983
8496
  }
7984
8497
  if (existing.scope === "agent" && existing.scopeId && existing.key === AGENT_MAX_TASKS_CONFIG_KEY) {
@@ -8815,6 +9328,7 @@ var postContext = route({
8815
9328
  method: "post",
8816
9329
  path: "/api/tasks/{id}/context",
8817
9330
  pattern: ["api", "tasks", null, "context"],
9331
+ session: { taskParam: "id" },
8818
9332
  summary: "Record a context usage snapshot for a task",
8819
9333
  tags: ["Tasks"],
8820
9334
  params: object2({ id: string2() }),
@@ -8841,6 +9355,7 @@ var getContext = route({
8841
9355
  method: "get",
8842
9356
  path: "/api/tasks/{id}/context",
8843
9357
  pattern: ["api", "tasks", null, "context"],
9358
+ session: { taskParam: "id" },
8844
9359
  summary: "Get context usage history for a task",
8845
9360
  tags: ["Tasks"],
8846
9361
  params: object2({ id: string2() }),
@@ -8921,6 +9436,7 @@ var createEventRoute = route({
8921
9436
  method: "post",
8922
9437
  path: "/api/events",
8923
9438
  pattern: ["api", "events"],
9439
+ session: bodyTask("taskId"),
8924
9440
  summary: "Store a single event",
8925
9441
  tags: ["Events"],
8926
9442
  body: eventBodySchema,
@@ -8937,6 +9453,7 @@ var createEventsBatchRoute = route({
8937
9453
  method: "post",
8938
9454
  path: "/api/events/batch",
8939
9455
  pattern: ["api", "events", "batch"],
9456
+ session: bodyListTask("events", "taskId"),
8940
9457
  summary: "Store multiple events in a batch",
8941
9458
  tags: ["Events"],
8942
9459
  body: object2({
@@ -9011,7 +9528,7 @@ var getEventCountsRoute = route({
9011
9528
  auth: { apiKey: true }
9012
9529
  });
9013
9530
  var MAX_LATEST_PAIRS = 50;
9014
- async function handleEvents(req, res, pathSegments, queryParams, _myAgentId) {
9531
+ async function handleEvents(req, res, pathSegments, queryParams, myAgentId) {
9015
9532
  if (createEventsBatchRoute.match(req.method, pathSegments)) {
9016
9533
  const parsed = await createEventsBatchRoute.parse(req, res, pathSegments, queryParams);
9017
9534
  if (!parsed)
@@ -9085,7 +9602,8 @@ async function handleEvents(req, res, pathSegments, queryParams, _myAgentId) {
9085
9602
  until: parsed.query.until || undefined,
9086
9603
  limit: parsed.query.limit ?? 100,
9087
9604
  events: eventFilter,
9088
- latestPerDataField: latestPerDataField ? { events: latestEvents, dataFields } : undefined
9605
+ latestPerDataField: latestPerDataField ? { events: latestEvents, dataFields } : undefined,
9606
+ readableBy: readableByFor2(await resolveSessionActor2(req, myAgentId))
9089
9607
  });
9090
9608
  getEventsRoute.respond(res, 200, latestPerDataField ? { events, latestPerDataField: true } : { events });
9091
9609
  return true;
@@ -12509,110 +13027,6 @@ async function getLocalFleetStats(now = new Date) {
12509
13027
 
12510
13028
  // src/http/fleet-benchmark.ts
12511
13029
  init_telemetry();
12512
-
12513
- // src/http/used-by.ts
12514
- init_zod();
12515
- init_db3();
12516
- init_telemetry();
12517
- init_org_website();
12518
- var CONSENT_KEY = "SWARM_USED_BY_CONSENT";
12519
- var CONSENT_AT_KEY = "SWARM_USED_BY_CONSENT_AT";
12520
- var GRANTED_NAME_KEY = "SWARM_USED_BY_ORG_NAME";
12521
- var GRANTED_WEBSITE_KEY = "SWARM_USED_BY_ORG_WEBSITE";
12522
- var TIMEOUT_MS = 1e4;
12523
- var usedBy = route({
12524
- method: "post",
12525
- path: "/api/used-by",
12526
- pattern: ["api", "used-by"],
12527
- summary: "Grant or revoke the 'used by' logo consent",
12528
- description: "Records whether agent-swarm.dev may show this organization's logo as a company that uses Agent Swarm. Sends the organization name and website to the telemetry proxy, where a person approves each logo by hand. An explicit admin action, so it is not gated by the telemetry opt-out.",
12529
- tags: ["Telemetry"],
12530
- body: object2({ granted: boolean() }),
12531
- responses: {
12532
- 200: {
12533
- description: "Consent recorded",
12534
- schema: object2({ granted: boolean(), changedAt: string2() })
12535
- },
12536
- 400: {
12537
- description: "Organization name or website is not set (a revoke falls back to the identity of the last grant)"
12538
- },
12539
- 401: { description: "Unauthorized" },
12540
- 403: { description: "Only a config admin can change the consent" },
12541
- 502: { description: "The telemetry proxy did not accept the consent" }
12542
- },
12543
- rbac: { permission: "config.write.any" }
12544
- });
12545
- async function globalConfig(key) {
12546
- const stored = (await getSwarmConfigs2({ scope: "global", key }))?.[0]?.value?.trim();
12547
- return stored || process.env[key]?.trim() || undefined;
12548
- }
12549
- async function telemetryProxyEndpoint(path) {
12550
- const feedback = await globalConfig("feedback_endpoint");
12551
- if (feedback && /^https?:\/\/[^/]+\/v1\/feedback\/?$/.test(feedback)) {
12552
- return feedback.replace(/\/v1\/feedback\/?$/, path);
12553
- }
12554
- return `${TELEMETRY_PROXY_ORIGIN}${path}`;
12555
- }
12556
- function usedByEndpoint() {
12557
- return telemetryProxyEndpoint("/v1/used-by");
12558
- }
12559
- async function handleUsedBy(req, res, pathSegments, queryParams, myAgentId) {
12560
- if (!usedBy.match(req.method, pathSegments))
12561
- return false;
12562
- const parsed = await usedBy.parse(req, res, pathSegments, queryParams);
12563
- if (!parsed)
12564
- return true;
12565
- if (!await ensureConfigAdmin(req, res, "config.write.any"))
12566
- return true;
12567
- const { granted } = parsed.body;
12568
- const currentName = await globalConfig("SWARM_ORG_NAME");
12569
- const currentWebsite = normalizeOrgWebsite(await globalConfig("SWARM_ORG_WEBSITE"));
12570
- const grantedName = granted ? undefined : await globalConfig(GRANTED_NAME_KEY);
12571
- const grantedWebsite = granted ? null : normalizeOrgWebsite(await globalConfig(GRANTED_WEBSITE_KEY));
12572
- const [orgName, orgWebsite] = grantedName && grantedWebsite ? [grantedName, grantedWebsite] : [currentName, currentWebsite];
12573
- if (!orgName || !orgWebsite) {
12574
- jsonError(res, "Set the organization name and website first", 400);
12575
- return true;
12576
- }
12577
- const installId = isTelemetryEnabled() ? await globalConfig("telemetry_installation_id") : undefined;
12578
- const orgDomain = await globalConfig("telemetry_org_domain");
12579
- const changedAt = new Date().toISOString();
12580
- try {
12581
- const response = await fetch(await usedByEndpoint(), {
12582
- method: "POST",
12583
- headers: { "Content-Type": "application/json" },
12584
- body: JSON.stringify({
12585
- ...installId ? { install_id: installId } : {},
12586
- org_name: orgName,
12587
- org_website: orgWebsite,
12588
- ...orgDomain ? { org_domain: orgDomain } : {},
12589
- granted,
12590
- changed_at: changedAt
12591
- }),
12592
- signal: AbortSignal.timeout(TIMEOUT_MS)
12593
- });
12594
- if (!response.ok) {
12595
- jsonError(res, `The telemetry proxy answered ${response.status}`, 502);
12596
- return true;
12597
- }
12598
- } catch {
12599
- jsonError(res, "Could not reach the telemetry proxy", 502);
12600
- return true;
12601
- }
12602
- await upsertSwarmConfig2({ scope: "global", key: CONSENT_KEY, value: String(granted) });
12603
- await upsertSwarmConfig2({ scope: "global", key: CONSENT_AT_KEY, value: changedAt });
12604
- if (granted) {
12605
- await upsertSwarmConfig2({ scope: "global", key: GRANTED_NAME_KEY, value: orgName });
12606
- await upsertSwarmConfig2({ scope: "global", key: GRANTED_WEBSITE_KEY, value: orgWebsite });
12607
- }
12608
- scheduleIntegrationsReload();
12609
- const owner = await resolveHttpFavoriteOwner(req, myAgentId);
12610
- trackRelayed("used_by.consent_changed", { granted }, { actor: owner?.userId ? { userId: owner.userId } : undefined, emitter: "ui" });
12611
- usedBy.respond(res, 200, { granted, changedAt });
12612
- return true;
12613
- }
12614
-
12615
- // src/http/fleet-benchmark.ts
12616
13030
  var FLEET_TTL_MS = 24 * 60 * 60 * 1000;
12617
13031
  var FLEET_FAILURE_TTL_MS = 60 * 60 * 1000;
12618
13032
  var LOCAL_TTL_MS = 10 * 60 * 1000;
@@ -12871,6 +13285,7 @@ var listTaskFilesRoute = route({
12871
13285
  method: "get",
12872
13286
  path: "/api/fs/tasks/{taskId}/files",
12873
13287
  pattern: ["api", "fs", "tasks", null, "files"],
13288
+ session: { taskParam: "taskId" },
12874
13289
  summary: "List task file attachments",
12875
13290
  tags: ["FS"],
12876
13291
  params: taskParams,
@@ -12886,6 +13301,7 @@ var uploadTaskFileRoute = route({
12886
13301
  method: "post",
12887
13302
  path: "/api/fs/tasks/{taskId}/files",
12888
13303
  pattern: ["api", "fs", "tasks", null, "files"],
13304
+ session: { taskParam: "taskId" },
12889
13305
  summary: "Upload a binary task file attachment",
12890
13306
  description: "Accepts a raw binary request body. Pass the display/path name as the `name` query parameter.",
12891
13307
  tags: ["FS"],
@@ -12905,6 +13321,7 @@ var getTaskFileRoute = route({
12905
13321
  method: "get",
12906
13322
  path: "/api/fs/tasks/{taskId}/files/{attachmentId}",
12907
13323
  pattern: ["api", "fs", "tasks", null, "files", null],
13324
+ session: { taskParam: "taskId" },
12908
13325
  summary: "Get task file attachment metadata",
12909
13326
  tags: ["FS"],
12910
13327
  params: attachmentParams,
@@ -12917,6 +13334,7 @@ var downloadTaskFileRoute = route({
12917
13334
  method: "get",
12918
13335
  path: "/api/fs/tasks/{taskId}/files/{attachmentId}/raw",
12919
13336
  pattern: ["api", "fs", "tasks", null, "files", null, "raw"],
13337
+ session: { taskParam: "taskId" },
12920
13338
  summary: "Download raw task file bytes",
12921
13339
  description: "Streams raw bytes. File content is not secret-scrubbed.",
12922
13340
  tags: ["FS"],
@@ -12933,6 +13351,7 @@ var signedUrlTaskFileRoute = route({
12933
13351
  method: "get",
12934
13352
  path: "/api/fs/tasks/{taskId}/files/{attachmentId}/signed-url",
12935
13353
  pattern: ["api", "fs", "tasks", null, "files", null, "signed-url"],
13354
+ session: { taskParam: "taskId" },
12936
13355
  summary: "Create a provider signed GET URL for a task file",
12937
13356
  tags: ["FS"],
12938
13357
  params: attachmentParams,
@@ -12947,6 +13366,7 @@ var deleteTaskFileRoute = route({
12947
13366
  method: "delete",
12948
13367
  path: "/api/fs/tasks/{taskId}/files/{attachmentId}",
12949
13368
  pattern: ["api", "fs", "tasks", null, "files", null],
13369
+ session: { taskParam: "taskId" },
12950
13370
  summary: "Delete a task file attachment",
12951
13371
  tags: ["FS"],
12952
13372
  params: attachmentParams,
@@ -12995,7 +13415,7 @@ async function handleFs(req, res, pathSegments, queryParams, myAgentId) {
12995
13415
  const parsed = await downloadTaskFileRoute.parse(req, res, pathSegments, queryParams);
12996
13416
  if (!parsed)
12997
13417
  return true;
12998
- const attachment = await findAttachment(parsed.params.taskId, parsed.params.attachmentId, res);
13418
+ const attachment = await findAttachment(req, myAgentId, parsed.params.taskId, parsed.params.attachmentId, res);
12999
13419
  if (!attachment)
13000
13420
  return true;
13001
13421
  return sendDownload(res, attachment);
@@ -13004,7 +13424,7 @@ async function handleFs(req, res, pathSegments, queryParams, myAgentId) {
13004
13424
  const parsed = await signedUrlTaskFileRoute.parse(req, res, pathSegments, queryParams);
13005
13425
  if (!parsed)
13006
13426
  return true;
13007
- const attachment = await findAttachment(parsed.params.taskId, parsed.params.attachmentId, res);
13427
+ const attachment = await findAttachment(req, myAgentId, parsed.params.taskId, parsed.params.attachmentId, res);
13008
13428
  if (!attachment)
13009
13429
  return true;
13010
13430
  return sendSignedUrl(res, attachment, parsed.query.expiresIn);
@@ -13013,7 +13433,7 @@ async function handleFs(req, res, pathSegments, queryParams, myAgentId) {
13013
13433
  const parsed = await getTaskFileRoute.parse(req, res, pathSegments, queryParams);
13014
13434
  if (!parsed)
13015
13435
  return true;
13016
- const attachment = await findAttachment(parsed.params.taskId, parsed.params.attachmentId, res);
13436
+ const attachment = await findAttachment(req, myAgentId, parsed.params.taskId, parsed.params.attachmentId, res);
13017
13437
  if (!attachment)
13018
13438
  return true;
13019
13439
  getTaskFileRoute.respond(res, 200, attachment);
@@ -13052,6 +13472,10 @@ async function handleFs(req, res, pathSegments, queryParams, myAgentId) {
13052
13472
  return true;
13053
13473
  if (!await getTaskById2(parsed.params.taskId))
13054
13474
  return notFound(res, "Task not found");
13475
+ if (!await canReadSession2(req, myAgentId, parsed.params.taskId)) {
13476
+ jsonError(res, SESSION_READ_DENIED2, 403);
13477
+ return true;
13478
+ }
13055
13479
  listTaskFilesRoute.respond(res, 200, {
13056
13480
  attachments: await getTaskAttachments2(parsed.params.taskId)
13057
13481
  });
@@ -13187,11 +13611,15 @@ function backingProviderId(attachment) {
13187
13611
  return "local-fs";
13188
13612
  return null;
13189
13613
  }
13190
- async function findAttachment(taskId, attachmentId, res) {
13614
+ async function findAttachment(req, myAgentId, taskId, attachmentId, res) {
13191
13615
  if (!await getTaskById2(taskId)) {
13192
13616
  notFound(res, "Task not found");
13193
13617
  return null;
13194
13618
  }
13619
+ if (!await canReadSession2(req, myAgentId, taskId)) {
13620
+ jsonError(res, SESSION_READ_DENIED2, 403);
13621
+ return null;
13622
+ }
13195
13623
  const attachment = (await getTaskAttachments2(taskId)).find((item) => item.id === attachmentId);
13196
13624
  if (!attachment) {
13197
13625
  notFound(res, "Attachment not found");
@@ -13333,20 +13761,21 @@ var listState = route({
13333
13761
  },
13334
13762
  auth: { apiKey: true }
13335
13763
  });
13764
+ var inboxStateItemSchema = object2({
13765
+ itemType: InboxItemTypeSchema,
13766
+ itemId: string2().min(1),
13767
+ status: InboxItemStatusSchema,
13768
+ snoozeUntil: string2().datetime().optional(),
13769
+ readAt: string2().datetime().optional(),
13770
+ lastReadAt: string2().datetime().nullable().optional().describe("Omit to preserve the read cursor, send null to clear it, or send a timestamp to set it, including an earlier time.")
13771
+ });
13336
13772
  var upsertState = route({
13337
13773
  method: "patch",
13338
13774
  path: "/api/inbox-state",
13339
13775
  pattern: ["api", "inbox-state"],
13340
13776
  summary: "Upsert per-user dismiss/snooze/done state for an inbox item",
13341
13777
  tags: ["Inbox State"],
13342
- body: object2({
13343
- userId: string2(),
13344
- itemType: InboxItemTypeSchema,
13345
- itemId: string2().min(1),
13346
- status: InboxItemStatusSchema,
13347
- snoozeUntil: string2().datetime().optional(),
13348
- readAt: string2().datetime().optional()
13349
- }),
13778
+ body: inboxStateItemSchema.extend({ userId: string2() }),
13350
13779
  responses: {
13351
13780
  200: {
13352
13781
  description: "Upserted inbox state row",
@@ -13357,6 +13786,27 @@ var upsertState = route({
13357
13786
  },
13358
13787
  auth: { apiKey: true }
13359
13788
  });
13789
+ var bulkUpsertState = route({
13790
+ method: "patch",
13791
+ path: "/api/inbox-state/bulk",
13792
+ pattern: ["api", "inbox-state", "bulk"],
13793
+ summary: "Upsert inbox-item state in one transaction",
13794
+ tags: ["Inbox State"],
13795
+ body: object2({
13796
+ userId: string2(),
13797
+ items: array2(inboxStateItemSchema).min(1).max(200)
13798
+ }),
13799
+ responses: {
13800
+ 200: {
13801
+ description: "Upserted inbox state rows in request order",
13802
+ schema: object2({ items: array2(InboxItemStateSchema) })
13803
+ },
13804
+ 400: { description: "Validation error" },
13805
+ 401: { description: "Unauthorized" }
13806
+ },
13807
+ auth: { apiKey: true },
13808
+ rbac: { ungated: "per-user inbox UI state, same posture as PATCH /api/inbox-state" }
13809
+ });
13360
13810
  async function handleInboxState(req, res, pathSegments, queryParams) {
13361
13811
  if (listState.match(req.method, pathSegments)) {
13362
13812
  const parsed = await listState.parse(req, res, pathSegments, queryParams);
@@ -13370,6 +13820,24 @@ async function handleInboxState(req, res, pathSegments, queryParams) {
13370
13820
  listState.respond(res, 200, { items });
13371
13821
  return true;
13372
13822
  }
13823
+ if (bulkUpsertState.match(req.method, pathSegments)) {
13824
+ const parsed = await bulkUpsertState.parse(req, res, pathSegments, queryParams);
13825
+ if (!parsed)
13826
+ return true;
13827
+ try {
13828
+ const items = await getDbClient2().transaction(async () => {
13829
+ const rows = [];
13830
+ for (const item of parsed.body.items) {
13831
+ rows.push(await upsertInboxState2({ ...item, userId: parsed.body.userId }));
13832
+ }
13833
+ return rows;
13834
+ });
13835
+ bulkUpsertState.respond(res, 200, { items });
13836
+ } catch (err) {
13837
+ jsonError(res, err instanceof Error ? err.message : "Failed to upsert inbox state", 500);
13838
+ }
13839
+ return true;
13840
+ }
13373
13841
  if (upsertState.match(req.method, pathSegments)) {
13374
13842
  const parsed = await upsertState.parse(req, res, pathSegments, queryParams);
13375
13843
  if (!parsed)
@@ -13381,7 +13849,8 @@ async function handleInboxState(req, res, pathSegments, queryParams) {
13381
13849
  itemId: parsed.body.itemId,
13382
13850
  status: parsed.body.status,
13383
13851
  snoozeUntil: parsed.body.snoozeUntil,
13384
- readAt: parsed.body.readAt
13852
+ readAt: parsed.body.readAt,
13853
+ lastReadAt: parsed.body.lastReadAt
13385
13854
  });
13386
13855
  upsertState.respond(res, 200, { item });
13387
13856
  } catch (err) {
@@ -17790,6 +18259,7 @@ var indexMemory = route({
17790
18259
  method: "post",
17791
18260
  path: "/api/memory/index",
17792
18261
  pattern: ["api", "memory", "index"],
18262
+ session: bodyTask("sourceTaskId"),
17793
18263
  summary: "Ingest content into memory system (async embedding)",
17794
18264
  tags: ["Memory"],
17795
18265
  rbac: { permission: "memory.write.consolidated" },
@@ -18267,6 +18737,7 @@ var rateMemory = route({
18267
18737
  method: "post",
18268
18738
  path: "/api/memory/rate",
18269
18739
  pattern: ["api", "memory", "rate"],
18740
+ session: { none: "worker-only: it needs X-Agent-ID, which a user token cannot send" },
18270
18741
  summary: "Submit RatingEvents to update memory usefulness posteriors",
18271
18742
  tags: ["Memory"],
18272
18743
  auth: { apiKey: true, agentId: true },
@@ -21714,7 +22185,7 @@ var listTasks = route({
21714
22185
  path: "/api/tasks",
21715
22186
  pattern: ["api", "tasks"],
21716
22187
  summary: "List tasks with filters",
21717
- description: "Returns tasks with the full `task` text replaced by a bounded `taskPreview` and completion/integration blobs dropped by default — list views only need the preview. Pass `fields=full` to restore the full `AgentTask`, or `fields=timeline` for the narrow shape the dashboard timeline draws. Fetch a single task in full via `GET /api/tasks/{id}`. `total` (the filtered row count, ignoring limit/offset) is computed only with `includeTotal=true`.",
22188
+ description: "Returns tasks with the full `task` text replaced by a bounded `taskPreview` and completion/integration blobs dropped by default — list views only need the preview. Pass `fields=full` to restore the full `AgentTask`, or `fields=timeline` for the narrow shape the dashboard timeline draws. Every shape gives a user caller only tasks of sessions they can read. Fetch a single task in full via `GET /api/tasks/{id}`. `total` (the filtered row count, ignoring limit/offset) is computed only with `includeTotal=true`.",
21718
22189
  tags: ["Tasks"],
21719
22190
  query: object2({
21720
22191
  status: string2().optional(),
@@ -21753,6 +22224,7 @@ var createTask = route({
21753
22224
  method: "post",
21754
22225
  path: "/api/tasks",
21755
22226
  pattern: ["api", "tasks"],
22227
+ session: bodyTask("parentTaskId"),
21756
22228
  summary: "Create a new task",
21757
22229
  tags: ["Tasks"],
21758
22230
  body: object2({
@@ -21808,6 +22280,7 @@ var promoteDraftTaskRoute = route({
21808
22280
  method: "post",
21809
22281
  path: "/api/tasks/{id}/promote-draft",
21810
22282
  pattern: ["api", "tasks", null, "promote-draft"],
22283
+ session: { taskParam: "id" },
21811
22284
  summary: "Promote a draft task out of the pre-dispatch draft state",
21812
22285
  description: "Transitions a `draft` task (#1240 — created with attachments still uploading) to its normal dispatch-eligible status: `offered` if it was offered to an agent, `pending` if it has an owning agent (the common case — UI-composer tasks default to Lead), otherwise `unassigned`. Called by the UI composer once its attachment upload batch settles, whether every file uploaded, some failed, or all failed — a draft must never be stranded by an upload error. Idempotent: calling it on a task that already left `draft` returns the current task unchanged rather than erroring, so a retried request is safe.",
21813
22286
  tags: ["Tasks"],
@@ -21823,6 +22296,7 @@ var updateSession = route({
21823
22296
  method: "put",
21824
22297
  path: "/api/tasks/{id}/session",
21825
22298
  pattern: ["api", "tasks", null, "session"],
22299
+ session: { taskParam: "id" },
21826
22300
  summary: "Update provider session ID and harness metadata for a task",
21827
22301
  tags: ["Tasks"],
21828
22302
  params: object2({ id: string2() }),
@@ -21856,6 +22330,7 @@ var cancelTaskRoute = route({
21856
22330
  method: "post",
21857
22331
  path: "/api/tasks/{id}/cancel",
21858
22332
  pattern: ["api", "tasks", null, "cancel"],
22333
+ session: { taskParam: "id" },
21859
22334
  summary: "Cancel a pending or in-progress task",
21860
22335
  tags: ["Tasks"],
21861
22336
  params: object2({ id: string2() }),
@@ -21870,6 +22345,7 @@ var steerTaskRoute = route({
21870
22345
  method: "post",
21871
22346
  path: "/api/tasks/{id}/steer",
21872
22347
  pattern: ["api", "tasks", null, "steer"],
22348
+ session: { taskParam: "id" },
21873
22349
  summary: "Deliver a steering message to a running task",
21874
22350
  tags: ["Tasks"],
21875
22351
  params: object2({ id: string2() }),
@@ -21898,6 +22374,7 @@ var getTaskSteeringMessagesRoute = route({
21898
22374
  method: "get",
21899
22375
  path: "/api/tasks/{id}/steering-messages",
21900
22376
  pattern: ["api", "tasks", null, "steering-messages"],
22377
+ session: { taskParam: "id" },
21901
22378
  summary: "List steering messages for a task",
21902
22379
  tags: ["Tasks"],
21903
22380
  params: object2({ id: string2() }),
@@ -21924,6 +22401,7 @@ var markSteeringDeliveredRoute = route({
21924
22401
  method: "post",
21925
22402
  path: "/api/steering-messages/{id}/delivered",
21926
22403
  pattern: ["api", "steering-messages", null, "delivered"],
22404
+ session: { steeringParam: "id" },
21927
22405
  summary: "Mark a steering message delivered",
21928
22406
  tags: ["Tasks"],
21929
22407
  params: object2({ id: string2() }),
@@ -21947,6 +22425,7 @@ var markSteeringHandledRoute = route({
21947
22425
  method: "post",
21948
22426
  path: "/api/steering-messages/{id}/handled",
21949
22427
  pattern: ["api", "steering-messages", null, "handled"],
22428
+ session: { steeringParam: "id" },
21950
22429
  summary: "Mark a steering message handled",
21951
22430
  description: "Optionally accepts a JSON body `{ note?: string }` — a short acceptance note describing how the steering was incorporated, persisted as `handledNote`.",
21952
22431
  tags: ["Tasks"],
@@ -21969,6 +22448,7 @@ var markSteeringUndeliverableRoute = route({
21969
22448
  method: "post",
21970
22449
  path: "/api/steering-messages/{id}/undeliverable",
21971
22450
  pattern: ["api", "steering-messages", null, "undeliverable"],
22451
+ session: { steeringParam: "id" },
21972
22452
  summary: "Promote an undeliverable steering message",
21973
22453
  tags: ["Tasks"],
21974
22454
  params: object2({ id: string2() }),
@@ -21992,6 +22472,7 @@ var getTask = route({
21992
22472
  method: "get",
21993
22473
  path: "/api/tasks/{id}",
21994
22474
  pattern: ["api", "tasks", null],
22475
+ session: { taskParam: "id" },
21995
22476
  summary: "Get task details with logs and attachments",
21996
22477
  description: "Returns the full `AgentTask` row decorated with `logs` (capped by `logsLimit`) and `attachments` (pointer-based artifacts stored on the task, ordered by `created_at`).",
21997
22478
  tags: ["Tasks"],
@@ -22001,6 +22482,7 @@ var getTask = route({
22001
22482
  }),
22002
22483
  responses: {
22003
22484
  200: { description: "Task with logs and attachments", schema: GetTaskResponseSchema },
22485
+ 403: { description: "A user caller cannot read the task's session" },
22004
22486
  404: { description: "Task not found" }
22005
22487
  }
22006
22488
  });
@@ -22008,6 +22490,7 @@ var updateTaskProgressRoute = route({
22008
22490
  method: "post",
22009
22491
  path: "/api/tasks/{id}/progress",
22010
22492
  pattern: ["api", "tasks", null, "progress"],
22493
+ session: { taskParam: "id" },
22011
22494
  summary: "Update task progress text",
22012
22495
  tags: ["Tasks"],
22013
22496
  params: object2({ id: string2() }),
@@ -22025,6 +22508,7 @@ var finishTask = route({
22025
22508
  method: "post",
22026
22509
  path: "/api/tasks/{id}/finish",
22027
22510
  pattern: ["api", "tasks", null, "finish"],
22511
+ session: { taskParam: "id" },
22028
22512
  summary: "Mark task as completed or failed (runner endpoint)",
22029
22513
  tags: ["Tasks"],
22030
22514
  params: object2({ id: string2() }),
@@ -22070,6 +22554,7 @@ var pauseTaskRoute = route({
22070
22554
  method: "post",
22071
22555
  path: "/api/tasks/{id}/pause",
22072
22556
  pattern: ["api", "tasks", null, "pause"],
22557
+ session: { taskParam: "id" },
22073
22558
  summary: "Pause an in-progress task",
22074
22559
  tags: ["Tasks"],
22075
22560
  params: object2({ id: string2() }),
@@ -22084,6 +22569,7 @@ var resumeTaskRoute = route({
22084
22569
  method: "post",
22085
22570
  path: "/api/tasks/{id}/resume",
22086
22571
  pattern: ["api", "tasks", null, "resume"],
22572
+ session: { taskParam: "id" },
22087
22573
  summary: "Resume a paused task",
22088
22574
  tags: ["Tasks"],
22089
22575
  params: object2({ id: string2() }),
@@ -22098,6 +22584,7 @@ var supersedeTaskRoute = route({
22098
22584
  method: "post",
22099
22585
  path: "/api/tasks/{id}/supersede",
22100
22586
  pattern: ["api", "tasks", null, "supersede"],
22587
+ session: { taskParam: "id" },
22101
22588
  summary: "Supersede an in-progress task (terminate + spawn resume follow-up)",
22102
22589
  description: 'Marks the original task `superseded` (terminal) and creates a fresh `taskType="resume"` follow-up so a worker can pick up the work in a new provider session. Workflow-step tasks (those with `workflowRunStepId`) are carved out: the original is marked `failed` with reason `superseded_workflow_task` and no follow-up is created — the workflow engine\'s retry/failure policy applies.',
22103
22590
  tags: ["Tasks"],
@@ -22118,6 +22605,7 @@ var updateTaskVcsRoute = route({
22118
22605
  method: "patch",
22119
22606
  path: "/api/tasks/{id}/vcs",
22120
22607
  pattern: ["api", "tasks", null, "vcs"],
22608
+ session: { taskParam: "id" },
22121
22609
  summary: "Update VCS (PR/MR) info for a task",
22122
22610
  tags: ["Tasks"],
22123
22611
  params: object2({ id: string2() }),
@@ -22138,6 +22626,7 @@ var updateTaskTitleRoute = route({
22138
22626
  method: "patch",
22139
22627
  path: "/api/tasks/{id}/title",
22140
22628
  pattern: ["api", "tasks", null, "title"],
22629
+ session: { taskParam: "id" },
22141
22630
  summary: "Set or clear a task's display title (session rename)",
22142
22631
  description: "Sets a human-facing display title override on a task. The sessions UI only reads this from root tasks (session list items), but titles on child tasks are harmless. Pass `title: null` (or an empty string) to clear the override and fall back to the task prompt.",
22143
22632
  tags: ["Tasks"],
@@ -22154,17 +22643,21 @@ var updateTaskTitleRoute = route({
22154
22643
  ungated: "mirrors the pre-RBAC PATCH /api/tasks/{id}/vcs sibling route: bearer auth only"
22155
22644
  }
22156
22645
  });
22157
- async function canSteerTask(req, myAgentId, task) {
22646
+ async function hasSessionWrite(req, myAgentId, taskId) {
22647
+ const access = await sessionAccessFor2(await resolveSessionActor2(req, myAgentId), taskId);
22648
+ return hasSessionAccess(access, "write");
22649
+ }
22650
+ async function canSteerTask(req, myAgentId, task, sessionAccess = "none") {
22651
+ const auth = getRequestAuth(req);
22158
22652
  const resource = {
22159
22653
  kind: "task",
22160
22654
  taskId: task.id,
22161
- requestedByUserId: task.requestedByUserId,
22655
+ requestedByUserId: auth?.kind === "user" && sessionAccess === "write" ? auth.userId : task.requestedByUserId,
22162
22656
  creatorAgentId: task.creatorAgentId,
22163
22657
  agentId: task.agentId
22164
22658
  };
22165
22659
  let principal;
22166
22660
  let verb;
22167
- const auth = getRequestAuth(req);
22168
22661
  if (auth?.kind === "operator") {
22169
22662
  principal = { kind: "operator" };
22170
22663
  verb = "task.steer.own";
@@ -22273,6 +22766,7 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
22273
22766
  if (validated.length > 0)
22274
22767
  source = validated;
22275
22768
  }
22769
+ const actor = await resolveSessionActor2(req, myAgentId);
22276
22770
  const filters = {
22277
22771
  status,
22278
22772
  agentId: parsed.query.agentId || undefined,
@@ -22288,7 +22782,8 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
22288
22782
  requestedByUserIdIsNull: parsed.query.requestedByUserId === "none" || undefined,
22289
22783
  orderBy: parsed.query.orderBy,
22290
22784
  limit: parsed.query.limit,
22291
- offset: parsed.query.offset
22785
+ offset: parsed.query.offset,
22786
+ readableBy: actor.kind === "user" ? { userId: actor.userId } : undefined
22292
22787
  };
22293
22788
  const tasks = parsed.query.fields === "full" ? await getAllTasks2(filters) : parsed.query.fields === "timeline" ? await getAllTasks2(filters, { fields: "timeline" }) : await getAllTasks2(filters, { slim: true });
22294
22789
  if (parsed.query.includeTotal === "true") {
@@ -22346,6 +22841,10 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
22346
22841
  jsonError(res, `You don't have context for parent task ${parentTask.id}.`, 403);
22347
22842
  return true;
22348
22843
  }
22844
+ if (parentTask && !await hasSessionWrite(req, myAgentId, parentTask.id)) {
22845
+ jsonError(res, SESSION_WRITE_DENIED2, 403);
22846
+ return true;
22847
+ }
22349
22848
  if (principal.kind === "agent" && parsed.body.contextKey && parsed.body.contextKey !== parentTask?.contextKey && chatThreadOfContextKey(parsed.body.contextKey) && !can({ principal, verb: "integration.chat.post", resource: { kind: "none" }, source: "http" }).allow) {
22350
22849
  jsonError(res, "Permission denied: only a lead can bind a task to a chat thread.", 403);
22351
22850
  return true;
@@ -22545,7 +23044,13 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
22545
23044
  jsonError(res, "Task not found", 404);
22546
23045
  return true;
22547
23046
  }
22548
- if (!await canSteerTask(req, myAgentId, task)) {
23047
+ const sessionActor = await resolveSessionActor2(req, myAgentId);
23048
+ const steerAccess = await sessionAccessFor2(sessionActor, task.id);
23049
+ if (!hasSessionAccess(steerAccess, "write")) {
23050
+ jsonError(res, SESSION_WRITE_DENIED2, 403);
23051
+ return true;
23052
+ }
23053
+ if (!await canSteerTask(req, myAgentId, task, steerAccess)) {
22549
23054
  jsonError(res, "Forbidden: caller cannot steer this task", 403);
22550
23055
  return true;
22551
23056
  }
@@ -22585,6 +23090,10 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
22585
23090
  jsonError(res, "Task not found", 404);
22586
23091
  return true;
22587
23092
  }
23093
+ if (!await canReadSession2(req, myAgentId, parsed.params.id)) {
23094
+ jsonError(res, SESSION_READ_DENIED2, 403);
23095
+ return true;
23096
+ }
22588
23097
  getTaskSteeringMessagesRoute.respond(res, 200, {
22589
23098
  messages: await getSteeringMessagesForTask2(parsed.params.id)
22590
23099
  });
@@ -22603,13 +23112,18 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
22603
23112
  return true;
22604
23113
  }
22605
23114
  if (parsed.query.taskId) {
23115
+ if (!await canReadSession2(req, myAgentId, parsed.query.taskId)) {
23116
+ jsonError(res, SESSION_READ_DENIED2, 403);
23117
+ return true;
23118
+ }
22606
23119
  const task = await getTaskById2(parsed.query.taskId);
22607
23120
  const messages = task?.agentId === myAgentId ? await getPendingSteeringForTask2(parsed.query.taskId) : [];
22608
23121
  getPendingSteeringMessagesRoute.respond(res, 200, { messages });
22609
23122
  return true;
22610
23123
  }
23124
+ const actor = await resolveSessionActor2(req, myAgentId);
22611
23125
  getPendingSteeringMessagesRoute.respond(res, 200, {
22612
- messages: await getPendingSteeringForAgent2(myAgentId)
23126
+ messages: await getPendingSteeringForAgent2(myAgentId, readableByFor2(actor))
22613
23127
  });
22614
23128
  return true;
22615
23129
  }
@@ -22750,6 +23264,11 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
22750
23264
  jsonError(res, "Task not found", 404);
22751
23265
  return true;
22752
23266
  }
23267
+ const readAccess = await sessionAccessFor2(await resolveSessionActor2(req, myAgentId), task.id);
23268
+ if (!hasSessionAccess(readAccess, "read")) {
23269
+ jsonError(res, "You don't have access to this session. Ask the owner to share it.", 403);
23270
+ return true;
23271
+ }
22753
23272
  const logs = await getLogsByTaskId2(parsed.params.id, parsed.query.logsLimit ?? 200);
22754
23273
  const attachments = await getTaskAttachments2(parsed.params.id);
22755
23274
  getTask.respond(res, 200, {
@@ -22901,7 +23420,7 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
22901
23420
  jsonError(res, "Missing X-Agent-ID header", 400);
22902
23421
  return true;
22903
23422
  }
22904
- const pausedTasks = await getPausedTasksForAgent2(myAgentId);
23423
+ const pausedTasks = await getPausedTasksForAgent2(myAgentId, readableByFor2(await resolveSessionActor2(req, myAgentId)));
22905
23424
  const tasks = await Promise.all(pausedTasks.map(async (task) => {
22906
23425
  const requestedBy = await buildTaskRequestedBy(task);
22907
23426
  return {
@@ -26764,6 +27283,7 @@ ${templateBody}` : templateBody;
26764
27283
 
26765
27284
  // src/http/realtime.ts
26766
27285
  init_zod();
27286
+ init_users();
26767
27287
  init_rbac();
26768
27288
 
26769
27289
  // src/realtime/browser.generated.txt
@@ -26777,14 +27297,14 @@ var realtimeTicketRoute = route({
26777
27297
  pattern: ["api", "realtime", "ticket"],
26778
27298
  summary: "Issue a one-shot dashboard realtime ticket",
26779
27299
  tags: ["Rooms"],
26780
- rbac: { permission: "comb.presence" },
27300
+ description: "The ticket authorizes one dashboard socket for presence rooms: Comb drives (`comb.presence`) and sessions (`session.presence`). An operator-key tab's picked user (`X-Swarm-User-Id`) rides on the ticket so session presence can check that user's access.",
27301
+ rbac: { permission: "session.presence", alsoAdmits: ["comb.presence"] },
26781
27302
  responses: {
26782
27303
  200: {
26783
27304
  description: "One-shot realtime ticket",
26784
27305
  schema: object2({ ticket: string2(), expiresAt: number2().int() })
26785
27306
  },
26786
- 403: { description: "Comb presence is not allowed" },
26787
- 404: { description: "Comb is not enabled" }
27307
+ 403: { description: "Presence is not allowed" }
26788
27308
  }
26789
27309
  });
26790
27310
  route({
@@ -26801,29 +27321,28 @@ route({
26801
27321
  async function handleRealtimeTicket(req, res, pathSegments, queryParams) {
26802
27322
  if (!realtimeTicketRoute.match(req.method, pathSegments))
26803
27323
  return false;
26804
- if (!getCombConfig().enabled) {
26805
- jsonError(res, "Comb is not enabled", 404);
26806
- return true;
26807
- }
26808
27324
  const parsed = await realtimeTicketRoute.parse(req, res, pathSegments, queryParams);
26809
27325
  if (!parsed)
26810
27326
  return true;
26811
27327
  const auth = getRequestAuth(req);
26812
27328
  if (!auth || auth.kind === "agent" || auth.kind === "guest" || auth.kind === "controller") {
26813
- jsonError(res, "Comb presence requires dashboard authentication", 403);
27329
+ jsonError(res, "Presence requires dashboard authentication", 403);
26814
27330
  return true;
26815
27331
  }
26816
27332
  const principal = auth.kind === "operator" ? { kind: "operator" } : { kind: "user", userId: auth.userId };
26817
- if (!can({
26818
- principal,
26819
- verb: "comb.presence",
26820
- resource: { kind: "none" },
26821
- source: "http"
26822
- }).allow) {
26823
- jsonError(res, "Comb presence requires the comb.presence permission", 403);
27333
+ const allowed = (verb) => can({ principal, verb, resource: { kind: "none" }, source: "http" }).allow;
27334
+ if (!allowed("comb.presence") && !allowed("session.presence")) {
27335
+ jsonError(res, "Presence requires the comb.presence or session.presence permission", 403);
26824
27336
  return true;
26825
27337
  }
26826
- realtimeTicketRoute.respond(res, 200, issueRealtimeTicket(auth));
27338
+ let dashboardUserId;
27339
+ if (auth.kind === "operator" && !auth.page) {
27340
+ const picked = dashboardUserIdHeader(req);
27341
+ const user = picked ? await findUserById(picked) : null;
27342
+ if (user?.status === "active")
27343
+ dashboardUserId = user.id;
27344
+ }
27345
+ realtimeTicketRoute.respond(res, 200, issueRealtimeTicket(dashboardUserId ? { ...auth, dashboardUserId } : auth));
26827
27346
  return true;
26828
27347
  }
26829
27348
  async function handleRealtimeAsset(req, res) {
@@ -30712,6 +31231,7 @@ var agentTaskRoute = route({
30712
31231
  method: "post",
30713
31232
  path: "/api/internal/script-runs/{runId}/agent-task",
30714
31233
  pattern: ["api", "internal", "script-runs", null, "agent-task"],
31234
+ session: bodyTask("parentTaskId"),
30715
31235
  operationId: "script_runs_internal_agent_task",
30716
31236
  summary: "Create or wait for a script workflow agent task step",
30717
31237
  tags: ["Script Runs"],
@@ -31191,6 +31711,9 @@ var upsertRoute2 = route({
31191
31711
  method: "post",
31192
31712
  path: "/api/scripts/upsert",
31193
31713
  pattern: ["api", "scripts", "upsert"],
31714
+ auth: {
31715
+ userNamesAgent: "dashboard script playground: a user runs or saves a script in a chosen agent's scope"
31716
+ },
31194
31717
  operationId: "scripts_upsert",
31195
31718
  summary: "Create or update a reusable script",
31196
31719
  description: "Explicit script upserts run a TypeScript typecheck before writing.",
@@ -31215,6 +31738,9 @@ var runRoute = route({
31215
31738
  method: "post",
31216
31739
  path: "/api/scripts/run",
31217
31740
  pattern: ["api", "scripts", "run"],
31741
+ auth: {
31742
+ userNamesAgent: "dashboard script playground: a user runs or saves a script in a chosen agent's scope"
31743
+ },
31218
31744
  operationId: "scripts_run",
31219
31745
  summary: "Run a reusable or inline script",
31220
31746
  description: "Inline source skips typecheck and is auto-saved as a scratch script only on success.",
@@ -32185,6 +32711,7 @@ var createSessionLogsRoute = route({
32185
32711
  method: "post",
32186
32712
  path: "/api/session-logs",
32187
32713
  pattern: ["api", "session-logs"],
32714
+ session: bodyTask("taskId"),
32188
32715
  summary: "Store session logs",
32189
32716
  tags: ["Session Data"],
32190
32717
  body: object2({
@@ -32206,6 +32733,7 @@ var getSessionLogsByTask = route({
32206
32733
  method: "get",
32207
32734
  path: "/api/tasks/{taskId}/session-logs",
32208
32735
  pattern: ["api", "tasks", null, "session-logs"],
32736
+ session: { taskParam: "taskId" },
32209
32737
  summary: "Get session logs for a task",
32210
32738
  tags: ["Session Data"],
32211
32739
  params: object2({ taskId: string2() }),
@@ -32224,6 +32752,7 @@ var createSessionCostRoute = route({
32224
32752
  method: "post",
32225
32753
  path: "/api/session-costs",
32226
32754
  pattern: ["api", "session-costs"],
32755
+ session: bodyTask("taskId"),
32227
32756
  summary: "Store session cost record",
32228
32757
  tags: ["Session Data"],
32229
32758
  body: object2({
@@ -32322,7 +32851,7 @@ var listSessionCosts = route({
32322
32851
  }
32323
32852
  }
32324
32853
  });
32325
- async function handleSessionData(req, res, pathSegments, queryParams, _myAgentId) {
32854
+ async function handleSessionData(req, res, pathSegments, queryParams, myAgentId) {
32326
32855
  if (createSessionLogsRoute.match(req.method, pathSegments)) {
32327
32856
  const parsed = await createSessionLogsRoute.parse(req, res, pathSegments, queryParams);
32328
32857
  if (!parsed)
@@ -32361,6 +32890,10 @@ async function handleSessionData(req, res, pathSegments, queryParams, _myAgentId
32361
32890
  jsonError(res, "Task not found", 404);
32362
32891
  return true;
32363
32892
  }
32893
+ if (!await canReadSession2(req, myAgentId, task.id)) {
32894
+ jsonError(res, SESSION_READ_DENIED2, 403);
32895
+ return true;
32896
+ }
32364
32897
  const logs = await getSessionLogsByTaskId2(parsed.params.taskId, parsed.query?.limit);
32365
32898
  getSessionLogsByTask.respond(res, 200, { logs });
32366
32899
  return true;
@@ -32507,6 +33040,8 @@ init_zod();
32507
33040
  init_db3();
32508
33041
  init_steering();
32509
33042
  init_users();
33043
+ init_rbac();
33044
+ init_bus();
32510
33045
  init_types();
32511
33046
  init_request_auth_context();
32512
33047
  init_runtime_pools_flag();
@@ -32555,6 +33090,9 @@ var mintSessionTokenRoute = route({
32555
33090
  method: "post",
32556
33091
  path: "/api/sessions/tokens",
32557
33092
  pattern: ["api", "sessions", "tokens"],
33093
+ session: {
33094
+ none: "user tokens cannot mint: the handler admits only the operator key or the task's own runtime"
33095
+ },
32558
33096
  summary: "Mint an ephemeral task or runtime token",
32559
33097
  description: "Operators can mint task or runtime tokens. Runtime tokens can mint task tokens for their own assigned tasks. Child tokens expire with their parent runtime.",
32560
33098
  tags: ["Sessions"],
@@ -32617,7 +33155,8 @@ var listSessions = route({
32617
33155
  q: string2().optional(),
32618
33156
  requestedByUserId: string2().min(1).optional(),
32619
33157
  contextKeyPrefix: string2().min(1).optional(),
32620
- fields: _enum2(["full", "slim"]).optional()
33158
+ fields: _enum2(["full", "slim"]).optional(),
33159
+ scope: _enum2(["mine", "shared"]).optional()
32621
33160
  }),
32622
33161
  responses: {
32623
33162
  200: {
@@ -32637,23 +33176,68 @@ var getSession = route({
32637
33176
  method: "get",
32638
33177
  path: "/api/sessions/{rootTaskId}",
32639
33178
  pattern: ["api", "sessions", null],
33179
+ session: { taskParam: "rootTaskId" },
32640
33180
  summary: "Get a session — root task + the entire descendant chain",
32641
33181
  tags: ["Sessions"],
32642
33182
  params: object2({ rootTaskId: string2() }),
32643
33183
  responses: {
32644
33184
  200: {
32645
- description: "Root task + chain (ordered by createdAt)",
33185
+ description: "Root task + chain (ordered by createdAt), plus the caller's `access` and the session `owner`",
32646
33186
  schema: object2({
32647
33187
  root: TaskWithSteeringSchema,
32648
- chain: array2(TaskWithSteeringSchema)
33188
+ chain: array2(TaskWithSteeringSchema),
33189
+ access: SessionAccessSchema,
33190
+ owner: SessionOwnerSchema.nullable()
32649
33191
  })
32650
33192
  },
32651
33193
  401: { description: "Unauthorized" },
33194
+ 403: { description: "The caller has no access to this session" },
33195
+ 404: { description: "Root task not found" }
33196
+ },
33197
+ auth: { apiKey: true }
33198
+ });
33199
+ var getSessionShares = route({
33200
+ method: "get",
33201
+ path: "/api/sessions/{rootTaskId}/shares",
33202
+ pattern: ["api", "sessions", null, "shares"],
33203
+ session: { taskParam: "rootTaskId" },
33204
+ summary: "List a session's active shares",
33205
+ description: "Active shares only. `everyone` is the access every user has; `users` are per-person shares. A session with no shares is private to its owner.",
33206
+ tags: ["Sessions"],
33207
+ params: object2({ rootTaskId: string2() }),
33208
+ responses: {
33209
+ 200: { description: "Active shares", schema: SessionSharesSchema },
33210
+ 401: { description: "Unauthorized" },
33211
+ 403: { description: "The caller has no access to this session" },
32652
33212
  404: { description: "Root task not found" }
32653
33213
  },
32654
33214
  auth: { apiKey: true }
32655
33215
  });
32656
- async function handleSessions(req, res, pathSegments, queryParams) {
33216
+ var putSessionShares = route({
33217
+ method: "put",
33218
+ path: "/api/sessions/{rootTaskId}/shares",
33219
+ pattern: ["api", "sessions", null, "shares"],
33220
+ session: { taskParam: "rootTaskId" },
33221
+ summary: "Set who can view or edit a session",
33222
+ description: "Owner only. The body is the full desired state: users left out lose access. Changed targets have their active share revoked and a new one inserted in one transaction; shares are never deleted.",
33223
+ tags: ["Sessions"],
33224
+ params: object2({ rootTaskId: string2() }),
33225
+ body: SessionSharesUpdateSchema,
33226
+ responses: {
33227
+ 200: { description: "Active shares after the change", schema: SessionSharesSchema },
33228
+ 400: { description: "Validation error or unknown user" },
33229
+ 401: { description: "Unauthorized" },
33230
+ 403: { description: "Only the session owner can share it" },
33231
+ 404: { description: "Root task not found" },
33232
+ 409: { description: "The session has no owner, so it is already open to everyone" }
33233
+ },
33234
+ auth: { apiKey: true },
33235
+ rbac: { permission: "session.share" }
33236
+ });
33237
+ async function accessForRoot(actor, root) {
33238
+ return actor.kind === "bypass" ? "owner" : sessionAccessForRoot(actor.userId, root);
33239
+ }
33240
+ async function handleSessions(req, res, pathSegments, queryParams, myAgentId) {
32657
33241
  if (mintSessionTokenRoute.match(req.method, pathSegments)) {
32658
33242
  const auth = getRequestAuth(req);
32659
33243
  if (auth?.kind !== "operator" && !(auth?.kind === "agent" && auth.runtimeId)) {
@@ -32730,21 +33314,30 @@ async function handleSessions(req, res, pathSegments, queryParams) {
32730
33314
  if (!parsed)
32731
33315
  return true;
32732
33316
  const sources = parsed.query.source ? parsed.query.source.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
32733
- const baseOpts = {
32734
- limit: parsed.query.limit,
32735
- offset: parsed.query.offset,
33317
+ const actor = await resolveSessionActor2(req, myAgentId);
33318
+ if (actor.kind === "bypass" && parsed.query.scope) {
33319
+ listSessions.respond(res, 200, {
33320
+ sessions: [],
33321
+ total: 0,
33322
+ limit: parsed.query.limit ?? 25,
33323
+ offset: parsed.query.offset ?? 0
33324
+ });
33325
+ return true;
33326
+ }
33327
+ const filterOpts = {
32736
33328
  source: sources,
32737
33329
  q: parsed.query.q,
32738
33330
  requestedByUserId: parsed.query.requestedByUserId,
32739
- contextKeyPrefix: parsed.query.contextKeyPrefix
33331
+ contextKeyPrefix: parsed.query.contextKeyPrefix,
33332
+ ...actor.kind === "user" ? { viewer: { userId: actor.userId, scope: parsed.query.scope ?? "all" } } : {}
33333
+ };
33334
+ const baseOpts = {
33335
+ ...filterOpts,
33336
+ limit: parsed.query.limit,
33337
+ offset: parsed.query.offset
32740
33338
  };
32741
33339
  const sessions = parsed.query.fields === "full" ? await listRecentSessions2(baseOpts) : await listRecentSessions2({ ...baseOpts, slim: true });
32742
- const total = await countSessions2({
32743
- source: sources,
32744
- q: parsed.query.q,
32745
- requestedByUserId: parsed.query.requestedByUserId,
32746
- contextKeyPrefix: parsed.query.contextKeyPrefix
32747
- });
33340
+ const total = await countSessions2(filterOpts);
32748
33341
  listSessions.respond(res, 200, {
32749
33342
  sessions,
32750
33343
  total,
@@ -32762,6 +33355,20 @@ async function handleSessions(req, res, pathSegments, queryParams) {
32762
33355
  jsonError(res, "Root task not found", 404);
32763
33356
  return true;
32764
33357
  }
33358
+ const sessionRoot = await getSessionRoot(root.id) ?? {
33359
+ id: root.id,
33360
+ requestedByUserId: root.requestedByUserId ?? null
33361
+ };
33362
+ const access = await accessForRoot(await resolveSessionActor2(req, myAgentId), sessionRoot);
33363
+ if (access === "none") {
33364
+ jsonError(res, "You don't have access to this session. Ask the owner to share it.", 403);
33365
+ return true;
33366
+ }
33367
+ const ownerUser = sessionRoot.requestedByUserId ? await findUserById(sessionRoot.requestedByUserId) : null;
33368
+ const owner = sessionRoot.requestedByUserId ? {
33369
+ id: sessionRoot.requestedByUserId,
33370
+ name: ownerUser?.name ?? sessionRoot.requestedByUserId
33371
+ } : null;
32765
33372
  const chain = await getRootTaskChain2(parsed.params.rootTaskId);
32766
33373
  const tasks = [root, ...chain];
32767
33374
  const [steering, citations] = await Promise.all([
@@ -32773,7 +33380,69 @@ async function handleSessions(req, res, pathSegments, queryParams) {
32773
33380
  ...steering.get(task.id),
32774
33381
  citations: citations.get(task.id)
32775
33382
  });
32776
- getSession.respond(res, 200, { root: decorate(root), chain: chain.map(decorate) });
33383
+ getSession.respond(res, 200, {
33384
+ root: decorate(root),
33385
+ chain: chain.map(decorate),
33386
+ access,
33387
+ owner
33388
+ });
33389
+ return true;
33390
+ }
33391
+ if (getSessionShares.match(req.method, pathSegments)) {
33392
+ const parsed = await getSessionShares.parse(req, res, pathSegments, queryParams);
33393
+ if (!parsed)
33394
+ return true;
33395
+ const root = await getSessionRoot(parsed.params.rootTaskId);
33396
+ if (!root) {
33397
+ jsonError(res, "Root task not found", 404);
33398
+ return true;
33399
+ }
33400
+ const access = await accessForRoot(await resolveSessionActor2(req, myAgentId), root);
33401
+ if (access === "none") {
33402
+ jsonError(res, "You don't have access to this session. Ask the owner to share it.", 403);
33403
+ return true;
33404
+ }
33405
+ getSessionShares.respond(res, 200, toSessionSharesState(await listActiveSessionShares(root.id)));
33406
+ return true;
33407
+ }
33408
+ if (putSessionShares.match(req.method, pathSegments)) {
33409
+ const parsed = await putSessionShares.parse(req, res, pathSegments, queryParams);
33410
+ if (!parsed)
33411
+ return true;
33412
+ const auth = getRequestAuth(req);
33413
+ const actor = await resolveSessionActor2(req, myAgentId);
33414
+ const principal = auth?.kind === "user" ? { kind: "user", userId: auth.userId } : auth?.kind === "operator" && !myAgentId ? { kind: "operator" } : null;
33415
+ if (!principal || !can({ principal, verb: "session.share", resource: { kind: "none" }, source: "http" }).allow) {
33416
+ jsonError(res, "Only the session owner can share it", 403);
33417
+ return true;
33418
+ }
33419
+ const root = await getSessionRoot(parsed.params.rootTaskId);
33420
+ if (!root) {
33421
+ jsonError(res, "Root task not found", 404);
33422
+ return true;
33423
+ }
33424
+ if (!root.requestedByUserId) {
33425
+ jsonError(res, "This session has no owner, so it is already open to everyone", 409);
33426
+ return true;
33427
+ }
33428
+ if (actor.kind === "user" && actor.userId !== root.requestedByUserId) {
33429
+ jsonError(res, "Only the session owner can share it", 403);
33430
+ return true;
33431
+ }
33432
+ const userIds = [...new Set(parsed.body.users.map((user) => user.userId))];
33433
+ if (userIds.length !== parsed.body.users.length) {
33434
+ jsonError(res, "Each user may appear only once", 400);
33435
+ return true;
33436
+ }
33437
+ for (const userId of userIds) {
33438
+ if (!await findUserById(userId)) {
33439
+ jsonError(res, `Unknown user ${userId}`, 400);
33440
+ return true;
33441
+ }
33442
+ }
33443
+ const state = await setSessionShares(root, parsed.body, actor.kind === "user" ? actor.userId : null);
33444
+ realtimeBus.publish(sessionAccessTopic(root.id), null);
33445
+ putSessionShares.respond(res, 200, state);
32777
33446
  return true;
32778
33447
  }
32779
33448
  return false;
@@ -34175,11 +34844,12 @@ async function handleStats(req, res, pathSegments, queryParams, myAgentId) {
34175
34844
  return true;
34176
34845
  const limit = parsed.query.limit ?? 100;
34177
34846
  const agentId = parsed.query.agentId;
34847
+ const readableBy = readableByFor2(await resolveSessionActor2(req, myAgentId));
34178
34848
  let logs = [];
34179
34849
  if (agentId) {
34180
- logs = (await getLogsByAgentId2(agentId)).slice(0, limit);
34850
+ logs = (await getLogsByAgentId2(agentId, readableBy)).slice(0, limit);
34181
34851
  } else {
34182
- logs = await getAllLogs2(limit);
34852
+ logs = await getAllLogs2(limit, readableBy);
34183
34853
  }
34184
34854
  listLogs.respond(res, 200, { logs });
34185
34855
  return true;
@@ -34235,7 +34905,7 @@ async function handleStats(req, res, pathSegments, queryParams, myAgentId) {
34235
34905
  return true;
34236
34906
  }
34237
34907
  if (getConcurrentContextRoute.match(req.method, pathSegments)) {
34238
- const context = await getConcurrentContext2();
34908
+ const context = await getConcurrentContext2(readableByFor2(await resolveSessionActor2(req, myAgentId)));
34239
34909
  getConcurrentContextRoute.respond(res, 200, context);
34240
34910
  return true;
34241
34911
  }
@@ -38306,10 +38976,11 @@ var httpServer = createHttpServer(async (req, res) => {
38306
38976
  () => handleExtensions(req, res, pathSegments, queryParams, myAgentId),
38307
38977
  () => handleFavorites(req, res, pathSegments, queryParams, myAgentId),
38308
38978
  () => handleUsers(req, res, pathSegments, queryParams),
38309
- () => handleSessions(req, res, pathSegments, queryParams),
38979
+ () => handleSessions(req, res, pathSegments, queryParams, myAgentId),
38310
38980
  () => handleInboxState(req, res, pathSegments, queryParams),
38311
38981
  () => handleTelemetryUi(req, res, pathSegments, queryParams, myAgentId),
38312
38982
  () => handleUsedBy(req, res, pathSegments, queryParams, myAgentId),
38983
+ () => handleRanking(req, res, pathSegments, queryParams),
38313
38984
  () => handleFleetBenchmark(req, res, pathSegments, queryParams),
38314
38985
  () => handleTaskTemplates(req, res, pathSegments, queryParams),
38315
38986
  () => handleMcp(req, res, transports, transportActivity, mcpSessionAgents),
@@ -38341,7 +39012,7 @@ var httpServer = createHttpServer(async (req, res) => {
38341
39012
  });
38342
39013
  });
38343
39014
  var detachRealtime = attachRealtimeTransport(httpServer);
38344
- var { attachControllerTransport } = await import("./controllers-connect-6yhwqv9v.js");
39015
+ var { attachControllerTransport } = await import("./controllers-connect-gkxmqw9d.js");
38345
39016
  var detachControllers = attachControllerTransport(httpServer);
38346
39017
  var stopWorkerNudges = startWorkerNudgeListener();
38347
39018
  var stopRoomDeletion = realtimeBus.subscribe("room:namespace-deleted", (namespace) => {
@@ -38370,7 +39041,7 @@ async function shutdown() {
38370
39041
  sessionsProcessed: getServerSessionsProcessed()
38371
39042
  });
38372
39043
  if (hasCapability("scheduling")) {
38373
- const { stopScheduler } = await import("./index-e4tsa1h7.js");
39044
+ const { stopScheduler } = await import("./index-8pwc1jc8.js");
38374
39045
  stopScheduler();
38375
39046
  }
38376
39047
  stopHeartbeat2();
@@ -38382,7 +39053,7 @@ async function shutdown() {
38382
39053
  await stopSlackApp2();
38383
39054
  await stopChatSdk();
38384
39055
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
38385
- const { stopOAuthKeepalive } = await import("./keepalive-d4nzs76x.js");
39056
+ const { stopOAuthKeepalive } = await import("./keepalive-3nr4st3r.js");
38386
39057
  await stopOAuthKeepalive();
38387
39058
  }
38388
39059
  stopOAuthPendingGc();
@@ -38462,21 +39133,21 @@ try {
38462
39133
  console.warn("[startup] CAPABILITIES upgrade seed failed (non-fatal):", err);
38463
39134
  }
38464
39135
  try {
38465
- const { ensurePricingSeeded } = await import("./boot-seeds-w2hmkxn5.js");
39136
+ const { ensurePricingSeeded } = await import("./boot-seeds-xs9a8dx1.js");
38466
39137
  ensurePricingSeeded();
38467
- const { startPricingRefreshLoop } = await import("./pricing-refresh-6ehwkcmp.js");
39138
+ const { startPricingRefreshLoop } = await import("./pricing-refresh-5nvme3sm.js");
38468
39139
  startPricingRefreshLoop();
38469
39140
  } catch (err) {
38470
39141
  console.error("[startup] Failed to seed pricing rows:", err);
38471
39142
  }
38472
39143
  try {
38473
- const { ensureModelProfilesSeeded } = await import("./boot-seeds-w2hmkxn5.js");
39144
+ const { ensureModelProfilesSeeded } = await import("./boot-seeds-xs9a8dx1.js");
38474
39145
  await ensureModelProfilesSeeded();
38475
39146
  } catch (err) {
38476
39147
  console.error("[startup] Failed to seed model profiles:", err);
38477
39148
  }
38478
39149
  try {
38479
- const { ensureRbacSeeded } = await import("./boot-seeds-w2hmkxn5.js");
39150
+ const { ensureRbacSeeded } = await import("./boot-seeds-xs9a8dx1.js");
38480
39151
  ensureRbacSeeded();
38481
39152
  } catch (err) {
38482
39153
  console.error("[startup] Failed to sync RBAC seed rows:", err);
@@ -38484,7 +39155,7 @@ try {
38484
39155
  throw err;
38485
39156
  }
38486
39157
  try {
38487
- const { runAllSeeders } = await import("./index-039x2sag.js");
39158
+ const { runAllSeeders } = await import("./index-s7ckdajq.js");
38488
39159
  await runAllSeeders({ scriptEmbeddingMode: "skip" });
38489
39160
  } catch (err) {
38490
39161
  console.error("[startup] Failed to seed built-in entities:", err);
@@ -38546,39 +39217,39 @@ httpServer.listen(port, async () => {
38546
39217
  await initWorkflows2();
38547
39218
  await startScriptRunSupervisor(getMcpBaseUrl());
38548
39219
  if (hasCapability("scheduling")) {
38549
- const { startScheduler } = await import("./index-e4tsa1h7.js");
38550
- const { getExecutorRegistry } = await import("./index-1r8hw4vd.js");
39220
+ const { startScheduler } = await import("./index-8pwc1jc8.js");
39221
+ const { getExecutorRegistry } = await import("./index-dg2tmj99.js");
38551
39222
  const intervalMs = Number(process.env.SCHEDULER_INTERVAL_MS) || 1e4;
38552
39223
  startScheduler(getExecutorRegistry(), intervalMs, {
38553
39224
  runId: globalState.__runId
38554
39225
  });
38555
39226
  }
38556
39227
  if (!isEnvFlagEnabled("HEARTBEAT_DISABLE", false)) {
38557
- const { startHeartbeat } = await import("./index-qyjbb7br.js");
39228
+ const { startHeartbeat } = await import("./index-nxa6me0q.js");
38558
39229
  const heartbeatMs = Number(process.env.HEARTBEAT_INTERVAL_MS) || 90000;
38559
39230
  startHeartbeat(heartbeatMs);
38560
39231
  }
38561
39232
  startPoolRouting();
38562
39233
  startRuntimeSupervisor();
38563
39234
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
38564
- const { startOAuthKeepalive } = await import("./keepalive-d4nzs76x.js");
39235
+ const { startOAuthKeepalive } = await import("./keepalive-3nr4st3r.js");
38565
39236
  startOAuthKeepalive();
38566
39237
  }
38567
39238
  if (process.env.OAUTH_REFRESH_SWEEP_DISABLE !== "true") {
38568
- const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-3f25djq3.js");
39239
+ const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-9q2t0y3q.js");
38569
39240
  startOAuthRefreshSweep();
38570
39241
  }
38571
39242
  startOAuthPendingGc();
38572
39243
  await startMemoryGc();
38573
39244
  await startDbRetention();
38574
39245
  startHumanFreeDrain();
38575
- import("./boot-reembed-4kx69j3g.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
39246
+ import("./boot-reembed-qr8a894z.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
38576
39247
  console.error("[boot-reembed] startup backfill failed (non-fatal):", err);
38577
39248
  });
38578
- import("./boot-reembed-db0wwgy1.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
39249
+ import("./boot-reembed-rj85jjhw.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
38579
39250
  console.error("[boot-reembed-scripts] startup backfill failed (non-fatal):", err);
38580
39251
  });
38581
- import("./boot-scrub-sweep-tfv4d3mm.js").then(({ runBootScrubSweep }) => runBootScrubSweep()).catch((err) => {
39252
+ import("./boot-scrub-sweep-eyfvn6v9.js").then(({ runBootScrubSweep }) => runBootScrubSweep()).catch((err) => {
38582
39253
  console.error("[boot-scrub-sweep] startup scrub failed (non-fatal):", err);
38583
39254
  });
38584
39255
  }).on("error", (err) => {