agor-live 0.23.1 → 0.23.2

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 (469) hide show
  1. package/dist/core/claude/index.cjs +51 -27
  2. package/dist/core/claude/index.js +51 -27
  3. package/dist/core/claude-cli/index.cjs +13 -0
  4. package/dist/core/claude-cli/index.js +13 -0
  5. package/dist/core/claude-cli/pricing.d.ts.map +1 -1
  6. package/dist/core/client/index.cjs +17 -0
  7. package/dist/core/client/index.js +16 -0
  8. package/dist/core/db/index.cjs +618 -421
  9. package/dist/core/db/index.js +550 -353
  10. package/dist/core/db/repositories/boards.d.ts +27 -0
  11. package/dist/core/db/repositories/boards.d.ts.map +1 -1
  12. package/dist/core/db/repositories/branches.d.ts +11 -1
  13. package/dist/core/db/repositories/branches.d.ts.map +1 -1
  14. package/dist/core/db/repositories/gateway-channels.d.ts +9 -0
  15. package/dist/core/db/repositories/gateway-channels.d.ts.map +1 -1
  16. package/dist/core/db/repositories/sessions.d.ts +7 -0
  17. package/dist/core/db/repositories/sessions.d.ts.map +1 -1
  18. package/dist/core/environment/render-snapshot.cjs +6 -2
  19. package/dist/core/environment/render-snapshot.d.ts +2 -0
  20. package/dist/core/environment/render-snapshot.d.ts.map +1 -1
  21. package/dist/core/environment/render-snapshot.js +6 -2
  22. package/dist/core/environment/variable-resolver.cjs +3 -1
  23. package/dist/core/environment/variable-resolver.js +3 -1
  24. package/dist/core/index.cjs +684 -486
  25. package/dist/core/index.js +684 -486
  26. package/dist/core/lib/feathers-validation.cjs +3 -0
  27. package/dist/core/lib/feathers-validation.d.ts +1 -0
  28. package/dist/core/lib/feathers-validation.d.ts.map +1 -1
  29. package/dist/core/lib/feathers-validation.js +3 -0
  30. package/dist/core/local-actions/index.cjs +123 -56
  31. package/dist/core/local-actions/index.js +121 -54
  32. package/dist/core/mcp/index.cjs +21 -3
  33. package/dist/core/mcp/index.d.ts +1 -1
  34. package/dist/core/mcp/index.d.ts.map +1 -1
  35. package/dist/core/mcp/index.js +18 -3
  36. package/dist/core/mcp/template-patterns.d.ts +15 -0
  37. package/dist/core/mcp/template-patterns.d.ts.map +1 -0
  38. package/dist/core/mcp/template-resolver.d.ts +13 -0
  39. package/dist/core/mcp/template-resolver.d.ts.map +1 -1
  40. package/dist/core/models/browser.cjs +6 -0
  41. package/dist/core/models/browser.js +6 -0
  42. package/dist/core/models/claude.d.ts.map +1 -1
  43. package/dist/core/models/index.cjs +6 -0
  44. package/dist/core/models/index.js +6 -0
  45. package/dist/core/seed/index.cjs +360 -216
  46. package/dist/core/seed/index.js +329 -185
  47. package/dist/core/templates/handlebars-helpers.cjs +3 -1
  48. package/dist/core/templates/handlebars-helpers.d.ts +5 -0
  49. package/dist/core/templates/handlebars-helpers.d.ts.map +1 -1
  50. package/dist/core/templates/handlebars-helpers.js +3 -1
  51. package/dist/core/tools/mcp/auth-secrets.cjs +3460 -3
  52. package/dist/core/tools/mcp/auth-secrets.d.ts.map +1 -1
  53. package/dist/core/tools/mcp/auth-secrets.js +3477 -3
  54. package/dist/core/tools/mcp/http-headers.cjs +17 -1
  55. package/dist/core/tools/mcp/http-headers.d.ts.map +1 -1
  56. package/dist/core/tools/mcp/http-headers.js +15 -1
  57. package/dist/core/tools/mcp/oauth-refresh.cjs +67 -43
  58. package/dist/core/tools/mcp/oauth-refresh.js +65 -41
  59. package/dist/core/types/board.d.ts +12 -0
  60. package/dist/core/types/board.d.ts.map +1 -1
  61. package/dist/core/types/gateway.d.ts +14 -0
  62. package/dist/core/types/gateway.d.ts.map +1 -1
  63. package/dist/core/types/index.cjs +17 -0
  64. package/dist/core/types/index.js +16 -0
  65. package/dist/core/unix/index.cjs +67 -0
  66. package/dist/core/unix/index.js +67 -0
  67. package/dist/daemon/auth/executor-runtime-scope.d.ts.map +1 -1
  68. package/dist/daemon/auth/executor-runtime-scope.js +19 -5
  69. package/dist/daemon/auth/executor-session-token.d.ts +15 -0
  70. package/dist/daemon/auth/executor-session-token.d.ts.map +1 -0
  71. package/dist/daemon/auth/executor-session-token.js +17 -0
  72. package/dist/daemon/auth/issue-browser-tokens-hook.d.ts +24 -0
  73. package/dist/daemon/auth/issue-browser-tokens-hook.d.ts.map +1 -0
  74. package/dist/daemon/auth/issue-browser-tokens-hook.js +95 -0
  75. package/dist/daemon/auth/service-jwt-strategy.d.ts.map +1 -1
  76. package/dist/daemon/auth/service-jwt-strategy.js +14 -2
  77. package/dist/daemon/declarations.d.ts +1 -0
  78. package/dist/daemon/declarations.d.ts.map +1 -1
  79. package/dist/daemon/index.js +1365 -418
  80. package/dist/daemon/main.js +1365 -418
  81. package/dist/daemon/mcp/server.js +294 -31
  82. package/dist/daemon/mcp/tools/analytics.js +73 -24
  83. package/dist/daemon/mcp/tools/artifacts.js +73 -24
  84. package/dist/daemon/mcp/tools/boards.js +73 -24
  85. package/dist/daemon/mcp/tools/branches.js +73 -24
  86. package/dist/daemon/mcp/tools/card-types.js +73 -24
  87. package/dist/daemon/mcp/tools/cards.js +73 -24
  88. package/dist/daemon/mcp/tools/environment.js +73 -24
  89. package/dist/daemon/mcp/tools/gateway-channels.d.ts.map +1 -1
  90. package/dist/daemon/mcp/tools/gateway-channels.js +190 -38
  91. package/dist/daemon/mcp/tools/knowledge.js +73 -24
  92. package/dist/daemon/mcp/tools/mcp-servers.js +73 -24
  93. package/dist/daemon/mcp/tools/messages.js +73 -24
  94. package/dist/daemon/mcp/tools/proxies.js +73 -24
  95. package/dist/daemon/mcp/tools/repos.js +73 -24
  96. package/dist/daemon/mcp/tools/schedules.js +73 -24
  97. package/dist/daemon/mcp/tools/search.js +73 -24
  98. package/dist/daemon/mcp/tools/sessions.js +73 -24
  99. package/dist/daemon/mcp/tools/tasks.js +73 -24
  100. package/dist/daemon/mcp/tools/users.js +76 -27
  101. package/dist/daemon/mcp/tools/widgets.d.ts.map +1 -1
  102. package/dist/daemon/mcp/tools/widgets.js +257 -99
  103. package/dist/daemon/oauth-auth-helpers.d.ts +35 -0
  104. package/dist/daemon/oauth-auth-helpers.d.ts.map +1 -0
  105. package/dist/daemon/oauth-auth-helpers.js +14 -0
  106. package/dist/daemon/register-hooks.d.ts +1 -0
  107. package/dist/daemon/register-hooks.d.ts.map +1 -1
  108. package/dist/daemon/register-hooks.js +304 -53
  109. package/dist/daemon/register-routes.d.ts.map +1 -1
  110. package/dist/daemon/register-routes.js +738 -288
  111. package/dist/daemon/register-services.d.ts.map +1 -1
  112. package/dist/daemon/register-services.js +386 -155
  113. package/dist/daemon/services/boards.d.ts +5 -0
  114. package/dist/daemon/services/boards.d.ts.map +1 -1
  115. package/dist/daemon/services/boards.js +7 -0
  116. package/dist/daemon/services/branches.d.ts.map +1 -1
  117. package/dist/daemon/services/branches.js +31 -2
  118. package/dist/daemon/services/claude-models.d.ts.map +1 -1
  119. package/dist/daemon/services/claude-models.js +4 -1
  120. package/dist/daemon/services/gateway.d.ts +15 -0
  121. package/dist/daemon/services/gateway.d.ts.map +1 -1
  122. package/dist/daemon/services/gateway.js +34 -0
  123. package/dist/daemon/services/health-monitor.d.ts.map +1 -1
  124. package/dist/daemon/services/health-monitor.js +10 -7
  125. package/dist/daemon/services/session-streams.d.ts +29 -0
  126. package/dist/daemon/services/session-streams.d.ts.map +1 -0
  127. package/dist/daemon/services/session-streams.js +84 -0
  128. package/dist/daemon/services/session-token-service.d.ts.map +1 -1
  129. package/dist/daemon/services/session-token-service.js +8 -4
  130. package/dist/daemon/setup/index.js +43 -11
  131. package/dist/daemon/setup/socketio.d.ts.map +1 -1
  132. package/dist/daemon/setup/socketio.js +43 -11
  133. package/dist/daemon/startup.d.ts.map +1 -1
  134. package/dist/daemon/startup.js +37 -16
  135. package/dist/daemon/utils/authorization.js +19 -5
  136. package/dist/daemon/utils/emit-service-event.d.ts +56 -0
  137. package/dist/daemon/utils/emit-service-event.d.ts.map +1 -0
  138. package/dist/daemon/utils/emit-service-event.js +23 -0
  139. package/dist/daemon/utils/mcp-header-secrets.d.ts +3 -5
  140. package/dist/daemon/utils/mcp-header-secrets.d.ts.map +1 -1
  141. package/dist/daemon/utils/mcp-header-secrets.js +31 -2
  142. package/dist/daemon/utils/realtime-access-cache.d.ts +9 -0
  143. package/dist/daemon/utils/realtime-access-cache.d.ts.map +1 -1
  144. package/dist/daemon/utils/realtime-access-cache.js +24 -0
  145. package/dist/daemon/utils/realtime-publish.d.ts +24 -0
  146. package/dist/daemon/utils/realtime-publish.d.ts.map +1 -1
  147. package/dist/daemon/utils/realtime-publish.js +120 -2
  148. package/dist/daemon/utils/session-queue-tenant-scope.d.ts +24 -0
  149. package/dist/daemon/utils/session-queue-tenant-scope.d.ts.map +1 -0
  150. package/dist/daemon/utils/session-queue-tenant-scope.js +133 -0
  151. package/dist/daemon/widgets/gateway-token/index.d.ts +114 -0
  152. package/dist/daemon/widgets/gateway-token/index.d.ts.map +1 -0
  153. package/dist/daemon/widgets/gateway-token/index.js +14711 -0
  154. package/dist/daemon/widgets/index.d.ts.map +1 -1
  155. package/dist/daemon/widgets/index.js +190 -16
  156. package/dist/daemon/widgets/registry.d.ts +7 -0
  157. package/dist/daemon/widgets/registry.d.ts.map +1 -1
  158. package/dist/daemon/widgets/submissions.js +12 -9
  159. package/dist/executor/commands/git.d.ts.map +1 -1
  160. package/dist/executor/commands/git.js +2 -0
  161. package/dist/executor/db/feathers-repositories.d.ts +19 -1
  162. package/dist/executor/db/feathers-repositories.d.ts.map +1 -1
  163. package/dist/executor/db/feathers-repositories.js +31 -1
  164. package/dist/executor/handlers/sdk/claude.d.ts.map +1 -1
  165. package/dist/executor/handlers/sdk/claude.js +1 -1
  166. package/dist/executor/handlers/sdk/codex.d.ts.map +1 -1
  167. package/dist/executor/handlers/sdk/codex.js +1 -1
  168. package/dist/executor/handlers/sdk/copilot.d.ts.map +1 -1
  169. package/dist/executor/handlers/sdk/copilot.js +1 -1
  170. package/dist/executor/handlers/sdk/cursor.d.ts.map +1 -1
  171. package/dist/executor/handlers/sdk/cursor.js +1 -0
  172. package/dist/executor/handlers/sdk/gemini.d.ts.map +1 -1
  173. package/dist/executor/handlers/sdk/gemini.js +1 -1
  174. package/dist/executor/handlers/sdk/opencode.d.ts.map +1 -1
  175. package/dist/executor/handlers/sdk/opencode.js +1 -1
  176. package/dist/executor/payload-types.d.ts +5 -5
  177. package/dist/executor/sdk-handlers/base/mcp-scoping.d.ts +8 -1
  178. package/dist/executor/sdk-handlers/base/mcp-scoping.d.ts.map +1 -1
  179. package/dist/executor/sdk-handlers/base/mcp-scoping.js +58 -6
  180. package/dist/executor/sdk-handlers/claude/claude-tool.d.ts +2 -2
  181. package/dist/executor/sdk-handlers/claude/claude-tool.d.ts.map +1 -1
  182. package/dist/executor/sdk-handlers/claude/claude-tool.js +2 -2
  183. package/dist/executor/sdk-handlers/claude/prompt-service.d.ts +13 -3
  184. package/dist/executor/sdk-handlers/claude/prompt-service.d.ts.map +1 -1
  185. package/dist/executor/sdk-handlers/claude/prompt-service.js +16 -3
  186. package/dist/executor/sdk-handlers/claude/query-builder.d.ts +2 -1
  187. package/dist/executor/sdk-handlers/claude/query-builder.d.ts.map +1 -1
  188. package/dist/executor/sdk-handlers/claude/query-builder.js +2 -1
  189. package/dist/executor/sdk-handlers/codex/codex-tool.d.ts +2 -2
  190. package/dist/executor/sdk-handlers/codex/codex-tool.d.ts.map +1 -1
  191. package/dist/executor/sdk-handlers/codex/codex-tool.js +2 -2
  192. package/dist/executor/sdk-handlers/codex/prompt-service.d.ts +3 -2
  193. package/dist/executor/sdk-handlers/codex/prompt-service.d.ts.map +1 -1
  194. package/dist/executor/sdk-handlers/codex/prompt-service.js +70 -6
  195. package/dist/executor/sdk-handlers/copilot/copilot-tool.d.ts +2 -2
  196. package/dist/executor/sdk-handlers/copilot/copilot-tool.d.ts.map +1 -1
  197. package/dist/executor/sdk-handlers/copilot/copilot-tool.js +2 -2
  198. package/dist/executor/sdk-handlers/copilot/prompt-service.d.ts +3 -2
  199. package/dist/executor/sdk-handlers/copilot/prompt-service.d.ts.map +1 -1
  200. package/dist/executor/sdk-handlers/copilot/prompt-service.js +4 -1
  201. package/dist/executor/sdk-handlers/gemini/gemini-tool.d.ts +2 -2
  202. package/dist/executor/sdk-handlers/gemini/gemini-tool.d.ts.map +1 -1
  203. package/dist/executor/sdk-handlers/gemini/gemini-tool.js +2 -2
  204. package/dist/executor/sdk-handlers/gemini/prompt-service.d.ts +3 -2
  205. package/dist/executor/sdk-handlers/gemini/prompt-service.d.ts.map +1 -1
  206. package/dist/executor/sdk-handlers/gemini/prompt-service.js +4 -1
  207. package/dist/executor/sdk-handlers/opencode/opencode-tool.d.ts +3 -2
  208. package/dist/executor/sdk-handlers/opencode/opencode-tool.d.ts.map +1 -1
  209. package/dist/executor/sdk-handlers/opencode/opencode-tool.js +4 -1
  210. package/dist/executor/services/feathers-client.d.ts.map +1 -1
  211. package/dist/executor/services/feathers-client.js +119 -20
  212. package/dist/ui/assets/AgorEmojiPickerInner-leeNywMc.js +1 -0
  213. package/dist/ui/assets/App-Dpl4Cc0U.js +4 -0
  214. package/dist/ui/assets/App-Dpl4Cc0U.js.gz +0 -0
  215. package/dist/ui/assets/AppNode-wN3UZlFK.js +1 -0
  216. package/dist/ui/assets/AppNode-wN3UZlFK.js.gz +0 -0
  217. package/dist/ui/assets/{ArtifactConsentModal-oMMzRn8L.js → ArtifactConsentModal-DjzXcP_L.js} +1 -1
  218. package/dist/ui/assets/ArtifactConsentModal-DjzXcP_L.js.gz +0 -0
  219. package/dist/ui/assets/ArtifactFullscreenPage-B3b_EZ0F.js +9 -0
  220. package/dist/ui/assets/ArtifactFullscreenPage-B3b_EZ0F.js.gz +0 -0
  221. package/dist/ui/assets/ArtifactNode-DISy2T-6.js +9 -0
  222. package/dist/ui/assets/ArtifactNode-DISy2T-6.js.gz +0 -0
  223. package/dist/ui/assets/AutocompleteTextarea-DUpsoEGr.js +19 -0
  224. package/dist/ui/assets/AutocompleteTextarea-DUpsoEGr.js.gz +0 -0
  225. package/dist/ui/assets/BoardObjectNodes-BakeGpIX.js +34 -0
  226. package/dist/ui/assets/BoardObjectNodes-BakeGpIX.js.gz +0 -0
  227. package/dist/ui/assets/{CodeEditor.inner-Cuj6RuLL.js → CodeEditor.inner-HmSdTLW5.js} +1 -1
  228. package/dist/ui/assets/CodeEditor.inner-HmSdTLW5.js.gz +0 -0
  229. package/dist/ui/assets/ConversationView-mWaA-B1f.js +1 -0
  230. package/dist/ui/assets/ConversationView-mWaA-B1f.js.gz +0 -0
  231. package/dist/ui/assets/{EmbeddedTerminal-Lx5I9EOJ.js → EmbeddedTerminal-NNoyPA81.js} +1 -1
  232. package/dist/ui/assets/EmbeddedTerminal-NNoyPA81.js.gz +0 -0
  233. package/dist/ui/assets/KnowledgePage-CkxVnRvz.js +24 -0
  234. package/dist/ui/assets/KnowledgePage-CkxVnRvz.js.gz +0 -0
  235. package/dist/ui/assets/{MarketingScreenshotPage-CmDPBHOl.js → MarketingScreenshotPage-B86Gs8jx.js} +2 -2
  236. package/dist/ui/assets/MarketingScreenshotPage-B86Gs8jx.js.gz +0 -0
  237. package/dist/ui/assets/MobileApp-BPs-SXvz.js +1 -0
  238. package/dist/ui/assets/MobileApp-BPs-SXvz.js.gz +0 -0
  239. package/dist/ui/assets/SessionCanvas-xVfDoEtz.js +13 -0
  240. package/dist/ui/assets/SessionCanvas-xVfDoEtz.js.gz +0 -0
  241. package/dist/ui/assets/{StreamdownDemoPage-okKKR2QH.js → StreamdownDemoPage-CvWLzVsv.js} +2 -2
  242. package/dist/ui/assets/StreamdownDemoPage-CvWLzVsv.js.gz +0 -0
  243. package/dist/ui/assets/{TerminalModal-D9jMGZXv.js → TerminalModal-ExOgsuOP.js} +2 -2
  244. package/dist/ui/assets/TerminalModal-ExOgsuOP.js.gz +0 -0
  245. package/dist/ui/assets/ThemeSwitcher-Cgyp5ruu.js +1 -0
  246. package/dist/ui/assets/ThemeSwitcher-Cgyp5ruu.js.gz +0 -0
  247. package/dist/ui/assets/{ThemedSyntaxHighlighter.inner-C1FdIHn0.js → ThemedSyntaxHighlighter.inner-rpCN9S6K.js} +1 -1
  248. package/dist/ui/assets/{antd-DT5ackd0.js → antd-BCqimh1J.js} +5 -5
  249. package/dist/ui/assets/{antd-DT5ackd0.js.gz → antd-BCqimh1J.js.gz} +0 -0
  250. package/dist/ui/assets/architecture-U656AL7Q-BxSQIjrE.js +1 -0
  251. package/dist/ui/assets/{architectureDiagram-VXUJARFQ-DwTJGGoj.js → architectureDiagram-VXUJARFQ-pDpGHSqT.js} +1 -1
  252. package/dist/ui/assets/architectureDiagram-VXUJARFQ-pDpGHSqT.js.gz +0 -0
  253. package/dist/ui/assets/{blockDiagram-VD42YOAC-Dh1_TVW2.js → blockDiagram-VD42YOAC-uP8JTJpK.js} +1 -1
  254. package/dist/ui/assets/blockDiagram-VD42YOAC-uP8JTJpK.js.gz +0 -0
  255. package/dist/ui/assets/{c4Diagram-YG6GDRKO-B1e96AcB.js → c4Diagram-YG6GDRKO-qQe72Mm7.js} +1 -1
  256. package/dist/ui/assets/c4Diagram-YG6GDRKO-qQe72Mm7.js.gz +0 -0
  257. package/dist/ui/assets/channel-CoUBs6Zn.js +1 -0
  258. package/dist/ui/assets/{chunk-4BX2VUAB-BODpCjJx.js → chunk-4BX2VUAB-sz-YE8Nt.js} +1 -1
  259. package/dist/ui/assets/chunk-4ZMWKKQ3-3oKtfWXp.js +3 -0
  260. package/dist/ui/assets/chunk-4ZMWKKQ3-3oKtfWXp.js.gz +0 -0
  261. package/dist/ui/assets/chunk-55IACEB6-BS04dgKF.js +1 -0
  262. package/dist/ui/assets/{chunk-ABZYJK2D-DCTXWSzV.js → chunk-ABZYJK2D-t3Gkphds.js} +3 -3
  263. package/dist/ui/assets/chunk-ABZYJK2D-t3Gkphds.js.gz +0 -0
  264. package/dist/ui/assets/{chunk-AGHRB4JF-CbNphBjE.js → chunk-AGHRB4JF-D_QmWxbY.js} +1 -1
  265. package/dist/ui/assets/chunk-AGHRB4JF-D_QmWxbY.js.gz +0 -0
  266. package/dist/ui/assets/{chunk-ATLVNIR6-rWg3qxof.js → chunk-ATLVNIR6-Bmg7-Ufz.js} +1 -1
  267. package/dist/ui/assets/chunk-ATLVNIR6-Bmg7-Ufz.js.gz +0 -0
  268. package/dist/ui/assets/{chunk-B4BG7PRW-CgtLCbpE.js → chunk-B4BG7PRW-dnSEE1u-.js} +1 -1
  269. package/dist/ui/assets/chunk-B4BG7PRW-dnSEE1u-.js.gz +0 -0
  270. package/dist/ui/assets/{chunk-CVBHYZKI-DTL6Zgr0.js → chunk-CVBHYZKI-BStaYnF0.js} +1 -1
  271. package/dist/ui/assets/{chunk-DI55MBZ5-Dwvx2Kva.js → chunk-DI55MBZ5-28dvx6o3.js} +1 -1
  272. package/dist/ui/assets/chunk-DI55MBZ5-28dvx6o3.js.gz +0 -0
  273. package/dist/ui/assets/chunk-EXTU4WIE-X-FURkEh.js +1 -0
  274. package/dist/ui/assets/{chunk-FMBD7UC4-BogA_9CV.js → chunk-FMBD7UC4-C_63gC9S.js} +1 -1
  275. package/dist/ui/assets/{chunk-HN2XXSSU-2blQO3Gb.js → chunk-HN2XXSSU-UFD374KI.js} +1 -1
  276. package/dist/ui/assets/chunk-HN2XXSSU-UFD374KI.js.gz +0 -0
  277. package/dist/ui/assets/{chunk-JA3XYJ7Z-Cr1rmOui.js → chunk-JA3XYJ7Z-B60hLNeR.js} +1 -1
  278. package/dist/ui/assets/chunk-JA3XYJ7Z-B60hLNeR.js.gz +0 -0
  279. package/dist/ui/assets/{chunk-JZLCHNYA-BsgZ79Fa.js → chunk-JZLCHNYA-ButC6pwN.js} +1 -1
  280. package/dist/ui/assets/chunk-JZLCHNYA-ButC6pwN.js.gz +0 -0
  281. package/dist/ui/assets/{chunk-MI3HLSF2-dwYeUy5S.js → chunk-MI3HLSF2-BivYzoSV.js} +1 -1
  282. package/dist/ui/assets/chunk-MI3HLSF2-BivYzoSV.js.gz +0 -0
  283. package/dist/ui/assets/chunk-N4CR4FBY-mVWzo5wJ.js +2 -0
  284. package/dist/ui/assets/chunk-N4CR4FBY-mVWzo5wJ.js.gz +0 -0
  285. package/dist/ui/assets/{chunk-QN33PNHL-DxdIDCAa.js → chunk-QN33PNHL-tXtHZpaG.js} +1 -1
  286. package/dist/ui/assets/{chunk-QXUST7PY-BCK2V0i-.js → chunk-QXUST7PY-DATib_Vv.js} +1 -1
  287. package/dist/ui/assets/chunk-QXUST7PY-DATib_Vv.js.gz +0 -0
  288. package/dist/ui/assets/chunk-QZHKN3VN-y6l249e5.js +1 -0
  289. package/dist/ui/assets/{chunk-S3R3BYOJ-GP3zIxrB.js → chunk-S3R3BYOJ-BEtfmflI.js} +1 -1
  290. package/dist/ui/assets/chunk-S3R3BYOJ-BEtfmflI.js.gz +0 -0
  291. package/dist/ui/assets/{chunk-TZMSLE5B-dX5RaM6g.js → chunk-TZMSLE5B-BY1ZtogK.js} +1 -1
  292. package/dist/ui/assets/chunk-TZMSLE5B-BY1ZtogK.js.gz +0 -0
  293. package/dist/ui/assets/classDiagram-2ON5EDUG-BYvq-uPY.js +1 -0
  294. package/dist/ui/assets/classDiagram-v2-WZHVMYZB-BYvq-uPY.js +1 -0
  295. package/dist/ui/assets/constants-CYzQKfD1.js +1 -0
  296. package/dist/ui/assets/{cose-bilkent-S5V4N54A-Dx0BJVDl.js → cose-bilkent-S5V4N54A-DPvYrqGG.js} +1 -1
  297. package/dist/ui/assets/cose-bilkent-S5V4N54A-DPvYrqGG.js.gz +0 -0
  298. package/dist/ui/assets/{dagre-6UL2VRFP-DiQSUxqN.js → dagre-6UL2VRFP-D800J6qb.js} +1 -1
  299. package/dist/ui/assets/dagre-6UL2VRFP-D800J6qb.js.gz +0 -0
  300. package/dist/ui/assets/{dagre-Bekd9Vdj.js → dagre-TNc4wezg.js} +1 -1
  301. package/dist/ui/assets/dagre-TNc4wezg.js.gz +0 -0
  302. package/dist/ui/assets/{diagram-PSM6KHXK-DtJnDQjc.js → diagram-PSM6KHXK-CGdP9F-X.js} +1 -1
  303. package/dist/ui/assets/diagram-PSM6KHXK-CGdP9F-X.js.gz +0 -0
  304. package/dist/ui/assets/{diagram-QEK2KX5R-CobkCRdj.js → diagram-QEK2KX5R-BD5lOCe5.js} +1 -1
  305. package/dist/ui/assets/diagram-QEK2KX5R-BD5lOCe5.js.gz +0 -0
  306. package/dist/ui/assets/{diagram-S2PKOQOG-DHEZYivc.js → diagram-S2PKOQOG-cp3l22vN.js} +1 -1
  307. package/dist/ui/assets/diagram-S2PKOQOG-cp3l22vN.js.gz +0 -0
  308. package/dist/ui/assets/editor-Cr0W5BQI.js +39 -0
  309. package/dist/ui/assets/editor-Cr0W5BQI.js.gz +0 -0
  310. package/dist/ui/assets/{emoji-B0hLmSLZ.js → emoji-Bv3m24sO.js} +1 -1
  311. package/dist/ui/assets/emoji-Bv3m24sO.js.gz +0 -0
  312. package/dist/ui/assets/{erDiagram-Q2GNP2WA-BVNeTZPQ.js → erDiagram-Q2GNP2WA-CQSXRgqr.js} +1 -1
  313. package/dist/ui/assets/erDiagram-Q2GNP2WA-CQSXRgqr.js.gz +0 -0
  314. package/dist/ui/assets/{flowDiagram-NV44I4VS-C73VFdyk.js → flowDiagram-NV44I4VS-CKcKpkph.js} +1 -1
  315. package/dist/ui/assets/flowDiagram-NV44I4VS-CKcKpkph.js.gz +0 -0
  316. package/dist/ui/assets/{ganttDiagram-LVOFAZNH-B_RosuD1.js → ganttDiagram-LVOFAZNH-DlAHzXO5.js} +1 -1
  317. package/dist/ui/assets/ganttDiagram-LVOFAZNH-DlAHzXO5.js.gz +0 -0
  318. package/dist/ui/assets/{gitGraph-F6HP7TQM-DMFIqn4h.js → gitGraph-F6HP7TQM-DeuXy4B1.js} +1 -1
  319. package/dist/ui/assets/{gitGraphDiagram-NY62KEGX-hxbgwNA1.js → gitGraphDiagram-NY62KEGX-aB-AI4Vq.js} +1 -1
  320. package/dist/ui/assets/gitGraphDiagram-NY62KEGX-aB-AI4Vq.js.gz +0 -0
  321. package/dist/ui/assets/index-B-qHc0o5.js +63 -0
  322. package/dist/ui/assets/index-B-qHc0o5.js.gz +0 -0
  323. package/dist/ui/assets/{info-NVLQJR56-DZ3K-w48.js → info-NVLQJR56-B9PG7xsc.js} +1 -1
  324. package/dist/ui/assets/{infoDiagram-ER5ION4S-CxnBJQu6.js → infoDiagram-ER5ION4S-C5Wpoqim.js} +1 -1
  325. package/dist/ui/assets/{journeyDiagram-XKPGCS4Q-CHYNj-f8.js → journeyDiagram-XKPGCS4Q-CHLziqfS.js} +1 -1
  326. package/dist/ui/assets/journeyDiagram-XKPGCS4Q-CHLziqfS.js.gz +0 -0
  327. package/dist/ui/assets/{kanban-definition-3W4ZIXB7-DQPCCsj2.js → kanban-definition-3W4ZIXB7-Dq7GzQE_.js} +1 -1
  328. package/dist/ui/assets/kanban-definition-3W4ZIXB7-Dq7GzQE_.js.gz +0 -0
  329. package/dist/ui/assets/{line-DBRjKmfa.js → line-DHKNg83I.js} +1 -1
  330. package/dist/ui/assets/{linear-DFao5Wyp.js → linear-BvUfn0I8.js} +1 -1
  331. package/dist/ui/assets/linear-BvUfn0I8.js.gz +0 -0
  332. package/dist/ui/assets/{mermaid-parser.core-DYCfacHj.js → mermaid-parser.core-C6d9tKoo.js} +2 -2
  333. package/dist/ui/assets/mermaid-parser.core-C6d9tKoo.js.gz +0 -0
  334. package/dist/ui/assets/{mermaid.core-C6xGFSdE.js → mermaid.core-CxE5KezR.js} +3 -3
  335. package/dist/ui/assets/mermaid.core-CxE5KezR.js.gz +0 -0
  336. package/dist/ui/assets/{mindmap-definition-VGOIOE7T-L3Pw-YM7.js → mindmap-definition-VGOIOE7T-DmPoQ48w.js} +1 -1
  337. package/dist/ui/assets/mindmap-definition-VGOIOE7T-DmPoQ48w.js.gz +0 -0
  338. package/dist/ui/assets/{packet-BFZMPI3H-CpL6iNoa.js → packet-BFZMPI3H-JZEkVZxU.js} +1 -1
  339. package/dist/ui/assets/{particles-CT8hcJOA.js → particles-CxMmUw86.js} +1 -1
  340. package/dist/ui/assets/particles-CxMmUw86.js.gz +0 -0
  341. package/dist/ui/assets/{pie-7BOR55EZ-hTqChbTo.js → pie-7BOR55EZ-_1II5pYQ.js} +1 -1
  342. package/dist/ui/assets/{pieDiagram-ADFJNKIX-Bf4Nou75.js → pieDiagram-ADFJNKIX-DrNkH33i.js} +1 -1
  343. package/dist/ui/assets/pieDiagram-ADFJNKIX-DrNkH33i.js.gz +0 -0
  344. package/dist/ui/assets/{quadrantDiagram-AYHSOK5B-DpjUTLB1.js → quadrantDiagram-AYHSOK5B-DOpC-HEF.js} +1 -1
  345. package/dist/ui/assets/quadrantDiagram-AYHSOK5B-DOpC-HEF.js.gz +0 -0
  346. package/dist/ui/assets/{radar-NHE76QYJ-myy7OENC.js → radar-NHE76QYJ-DDmqhzEZ.js} +1 -1
  347. package/dist/ui/assets/{reactflow-Cubtnk8i.js → reactflow-D98uPOMq.js} +1 -1
  348. package/dist/ui/assets/reactflow-D98uPOMq.js.gz +0 -0
  349. package/dist/ui/assets/{requirementDiagram-UZGBJVZJ-Bfrfh-0-.js → requirementDiagram-UZGBJVZJ-Bzx0iEPP.js} +1 -1
  350. package/dist/ui/assets/requirementDiagram-UZGBJVZJ-Bzx0iEPP.js.gz +0 -0
  351. package/dist/ui/assets/{sandpack-C58bwfIS.js → sandpack-059Z4UWP.js} +1 -1
  352. package/dist/ui/assets/sandpack-059Z4UWP.js.gz +0 -0
  353. package/dist/ui/assets/sandpackDefaults-CPMNDsw7.js +1 -0
  354. package/dist/ui/assets/sandpackDefaults-CPMNDsw7.js.gz +0 -0
  355. package/dist/ui/assets/{sankeyDiagram-TZEHDZUN-ClZAZ6va.js → sankeyDiagram-TZEHDZUN-Do9pvfDv.js} +1 -1
  356. package/dist/ui/assets/sankeyDiagram-TZEHDZUN-Do9pvfDv.js.gz +0 -0
  357. package/dist/ui/assets/{sequenceDiagram-WL72ISMW-CNgly63p.js → sequenceDiagram-WL72ISMW-CN8neUTA.js} +1 -1
  358. package/dist/ui/assets/sequenceDiagram-WL72ISMW-CN8neUTA.js.gz +0 -0
  359. package/dist/ui/assets/{src-DAeyG22O.js → src-DEbsv3pp.js} +2 -2
  360. package/dist/ui/assets/src-DEbsv3pp.js.gz +0 -0
  361. package/dist/ui/assets/{stateDiagram-FKZM4ZOC-BrEAoDpE.js → stateDiagram-FKZM4ZOC-BluWJi8n.js} +1 -1
  362. package/dist/ui/assets/stateDiagram-FKZM4ZOC-BluWJi8n.js.gz +0 -0
  363. package/dist/ui/assets/stateDiagram-v2-4FDKWEC3-DeyqBMDR.js +1 -0
  364. package/dist/ui/assets/{streamdown-BdAVfkeF.js → streamdown-BT1twtFc.js} +5 -5
  365. package/dist/ui/assets/streamdown-BT1twtFc.js.gz +0 -0
  366. package/dist/ui/assets/{syntax-BOfTnQiy.js → syntax-CBu6nLfm.js} +1 -1
  367. package/dist/ui/assets/syntax-CBu6nLfm.js.gz +0 -0
  368. package/dist/ui/assets/{timeline-definition-IT6M3QCI-CaRH3k4l.js → timeline-definition-IT6M3QCI-WdUNkiRj.js} +1 -1
  369. package/dist/ui/assets/timeline-definition-IT6M3QCI-WdUNkiRj.js.gz +0 -0
  370. package/dist/ui/assets/{treemap-KMMF4GRG-CUaLOV_B.js → treemap-KMMF4GRG-CoriU4Jl.js} +1 -1
  371. package/dist/ui/assets/{useUserLocalStorage-C8sgXgXR.js → useUserLocalStorage-GidNiOPC.js} +1 -1
  372. package/dist/ui/assets/useUserLocalStorage-GidNiOPC.js.gz +0 -0
  373. package/dist/ui/assets/{xychartDiagram-PRI3JC2R-BlhkI9T_.js → xychartDiagram-PRI3JC2R-CgBAKNt2.js} +1 -1
  374. package/dist/ui/assets/xychartDiagram-PRI3JC2R-CgBAKNt2.js.gz +0 -0
  375. package/dist/ui/index.html +9 -8
  376. package/dist/ui/index.html.gz +0 -0
  377. package/package.json +2 -2
  378. package/dist/ui/assets/AgorEmojiPickerInner-DQjr8M3_.js +0 -1
  379. package/dist/ui/assets/App-hJtcOxHv.js +0 -5
  380. package/dist/ui/assets/App-hJtcOxHv.js.gz +0 -0
  381. package/dist/ui/assets/AppNode-BhuMpn8O.js +0 -1
  382. package/dist/ui/assets/AppNode-BhuMpn8O.js.gz +0 -0
  383. package/dist/ui/assets/ArtifactConsentModal-oMMzRn8L.js.gz +0 -0
  384. package/dist/ui/assets/ArtifactFullscreenPage-B3wT8t8f.js +0 -9
  385. package/dist/ui/assets/ArtifactFullscreenPage-B3wT8t8f.js.gz +0 -0
  386. package/dist/ui/assets/ArtifactNode-BzQcrwFS.js +0 -9
  387. package/dist/ui/assets/ArtifactNode-BzQcrwFS.js.gz +0 -0
  388. package/dist/ui/assets/AutocompleteTextarea-Dau-V_30.js +0 -19
  389. package/dist/ui/assets/AutocompleteTextarea-Dau-V_30.js.gz +0 -0
  390. package/dist/ui/assets/BoardObjectNodes-DdKWv_ZT.js +0 -34
  391. package/dist/ui/assets/BoardObjectNodes-DdKWv_ZT.js.gz +0 -0
  392. package/dist/ui/assets/CodeEditor.inner-Cuj6RuLL.js.gz +0 -0
  393. package/dist/ui/assets/ConversationView-9-3ma1u-.js +0 -1
  394. package/dist/ui/assets/ConversationView-9-3ma1u-.js.gz +0 -0
  395. package/dist/ui/assets/EmbeddedTerminal-Lx5I9EOJ.js.gz +0 -0
  396. package/dist/ui/assets/KnowledgePage-Dz8YAMYC.js +0 -24
  397. package/dist/ui/assets/KnowledgePage-Dz8YAMYC.js.gz +0 -0
  398. package/dist/ui/assets/MarketingScreenshotPage-CmDPBHOl.js.gz +0 -0
  399. package/dist/ui/assets/MobileApp-BpgSG_gz.js +0 -1
  400. package/dist/ui/assets/MobileApp-BpgSG_gz.js.gz +0 -0
  401. package/dist/ui/assets/SessionCanvas-B6_mtlxK.js +0 -13
  402. package/dist/ui/assets/SessionCanvas-B6_mtlxK.js.gz +0 -0
  403. package/dist/ui/assets/StreamdownDemoPage-okKKR2QH.js.gz +0 -0
  404. package/dist/ui/assets/TerminalModal-D9jMGZXv.js.gz +0 -0
  405. package/dist/ui/assets/ThemeSwitcher-D9HgT37l.js +0 -1
  406. package/dist/ui/assets/ThemeSwitcher-D9HgT37l.js.gz +0 -0
  407. package/dist/ui/assets/architecture-U656AL7Q-DMnxkQRZ.js +0 -1
  408. package/dist/ui/assets/architectureDiagram-VXUJARFQ-DwTJGGoj.js.gz +0 -0
  409. package/dist/ui/assets/blockDiagram-VD42YOAC-Dh1_TVW2.js.gz +0 -0
  410. package/dist/ui/assets/c4Diagram-YG6GDRKO-B1e96AcB.js.gz +0 -0
  411. package/dist/ui/assets/channel-Jv4dT4S5.js +0 -1
  412. package/dist/ui/assets/chunk-55IACEB6-lJXvUquf.js +0 -1
  413. package/dist/ui/assets/chunk-ABZYJK2D-DCTXWSzV.js.gz +0 -0
  414. package/dist/ui/assets/chunk-AGHRB4JF-CbNphBjE.js.gz +0 -0
  415. package/dist/ui/assets/chunk-ATLVNIR6-rWg3qxof.js.gz +0 -0
  416. package/dist/ui/assets/chunk-B4BG7PRW-CgtLCbpE.js.gz +0 -0
  417. package/dist/ui/assets/chunk-DI55MBZ5-Dwvx2Kva.js.gz +0 -0
  418. package/dist/ui/assets/chunk-EXTU4WIE-DHyaqTsP.js +0 -1
  419. package/dist/ui/assets/chunk-HN2XXSSU-2blQO3Gb.js.gz +0 -0
  420. package/dist/ui/assets/chunk-JA3XYJ7Z-Cr1rmOui.js.gz +0 -0
  421. package/dist/ui/assets/chunk-JZLCHNYA-BsgZ79Fa.js.gz +0 -0
  422. package/dist/ui/assets/chunk-MI3HLSF2-dwYeUy5S.js.gz +0 -0
  423. package/dist/ui/assets/chunk-N4CR4FBY-DEMgSRVe.js +0 -2
  424. package/dist/ui/assets/chunk-N4CR4FBY-DEMgSRVe.js.gz +0 -0
  425. package/dist/ui/assets/chunk-QXUST7PY-BCK2V0i-.js.gz +0 -0
  426. package/dist/ui/assets/chunk-QZHKN3VN-CALjGrEE.js +0 -1
  427. package/dist/ui/assets/chunk-S3R3BYOJ-GP3zIxrB.js.gz +0 -0
  428. package/dist/ui/assets/chunk-TZMSLE5B-dX5RaM6g.js.gz +0 -0
  429. package/dist/ui/assets/classDiagram-2ON5EDUG-C3_RVP70.js +0 -1
  430. package/dist/ui/assets/classDiagram-v2-WZHVMYZB-C3_RVP70.js +0 -1
  431. package/dist/ui/assets/constants-GjIDNVyt.js +0 -1
  432. package/dist/ui/assets/cose-bilkent-S5V4N54A-Dx0BJVDl.js.gz +0 -0
  433. package/dist/ui/assets/dagre-6UL2VRFP-DiQSUxqN.js.gz +0 -0
  434. package/dist/ui/assets/dagre-Bekd9Vdj.js.gz +0 -0
  435. package/dist/ui/assets/diagram-PSM6KHXK-DtJnDQjc.js.gz +0 -0
  436. package/dist/ui/assets/diagram-QEK2KX5R-CobkCRdj.js.gz +0 -0
  437. package/dist/ui/assets/diagram-S2PKOQOG-DHEZYivc.js.gz +0 -0
  438. package/dist/ui/assets/editor-DQIG-JYZ.js +0 -43
  439. package/dist/ui/assets/editor-DQIG-JYZ.js.gz +0 -0
  440. package/dist/ui/assets/emoji-B0hLmSLZ.js.gz +0 -0
  441. package/dist/ui/assets/erDiagram-Q2GNP2WA-BVNeTZPQ.js.gz +0 -0
  442. package/dist/ui/assets/flowDiagram-NV44I4VS-C73VFdyk.js.gz +0 -0
  443. package/dist/ui/assets/ganttDiagram-LVOFAZNH-B_RosuD1.js.gz +0 -0
  444. package/dist/ui/assets/gitGraphDiagram-NY62KEGX-hxbgwNA1.js.gz +0 -0
  445. package/dist/ui/assets/index-DTz1Dif6.js +0 -64
  446. package/dist/ui/assets/index-DTz1Dif6.js.gz +0 -0
  447. package/dist/ui/assets/journeyDiagram-XKPGCS4Q-CHYNj-f8.js.gz +0 -0
  448. package/dist/ui/assets/kanban-definition-3W4ZIXB7-DQPCCsj2.js.gz +0 -0
  449. package/dist/ui/assets/linear-DFao5Wyp.js.gz +0 -0
  450. package/dist/ui/assets/mermaid-parser.core-DYCfacHj.js.gz +0 -0
  451. package/dist/ui/assets/mermaid.core-C6xGFSdE.js.gz +0 -0
  452. package/dist/ui/assets/mindmap-definition-VGOIOE7T-L3Pw-YM7.js.gz +0 -0
  453. package/dist/ui/assets/particles-CT8hcJOA.js.gz +0 -0
  454. package/dist/ui/assets/pieDiagram-ADFJNKIX-Bf4Nou75.js.gz +0 -0
  455. package/dist/ui/assets/quadrantDiagram-AYHSOK5B-DpjUTLB1.js.gz +0 -0
  456. package/dist/ui/assets/reactflow-Cubtnk8i.js.gz +0 -0
  457. package/dist/ui/assets/requirementDiagram-UZGBJVZJ-Bfrfh-0-.js.gz +0 -0
  458. package/dist/ui/assets/sandpack-C58bwfIS.js.gz +0 -0
  459. package/dist/ui/assets/sandpackDefaults-Bkga8XAE.js +0 -1
  460. package/dist/ui/assets/sankeyDiagram-TZEHDZUN-ClZAZ6va.js.gz +0 -0
  461. package/dist/ui/assets/sequenceDiagram-WL72ISMW-CNgly63p.js.gz +0 -0
  462. package/dist/ui/assets/src-DAeyG22O.js.gz +0 -0
  463. package/dist/ui/assets/stateDiagram-FKZM4ZOC-BrEAoDpE.js.gz +0 -0
  464. package/dist/ui/assets/stateDiagram-v2-4FDKWEC3-BNL52_oN.js +0 -1
  465. package/dist/ui/assets/streamdown-BdAVfkeF.js.gz +0 -0
  466. package/dist/ui/assets/syntax-BOfTnQiy.js.gz +0 -0
  467. package/dist/ui/assets/timeline-definition-IT6M3QCI-CaRH3k4l.js.gz +0 -0
  468. package/dist/ui/assets/useUserLocalStorage-C8sgXgXR.js.gz +0 -0
  469. package/dist/ui/assets/xychartDiagram-PRI3JC2R-BlhkI9T_.js.gz +0 -0
@@ -31,6 +31,24 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  mod
32
32
  ));
33
33
 
34
+ // src/auth/executor-session-token.ts
35
+ function isExecutorSessionTokenPayload(payload) {
36
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
37
+ const record2 = payload;
38
+ return record2.type === EXECUTOR_SESSION_TOKEN_TYPE && record2.purpose === EXECUTOR_SESSION_TOKEN_PURPOSE;
39
+ }
40
+ function getExecutorSessionTokenSessionId(payload) {
41
+ return payload.session_id ?? payload.sessionId;
42
+ }
43
+ var EXECUTOR_SESSION_TOKEN_TYPE, EXECUTOR_SESSION_TOKEN_PURPOSE;
44
+ var init_executor_session_token = __esm({
45
+ "src/auth/executor-session-token.ts"() {
46
+ "use strict";
47
+ EXECUTOR_SESSION_TOKEN_TYPE = "executor-session";
48
+ EXECUTOR_SESSION_TOKEN_PURPOSE = "executor-task";
49
+ }
50
+ });
51
+
34
52
  // src/utils/branch-authorization.ts
35
53
  import { shortId } from "@agor/core/db";
36
54
  import { Forbidden as Forbidden2, NotAuthenticated, NotFound } from "@agor/core/feathers";
@@ -2648,7 +2666,7 @@ import {
2648
2666
  MCP_TOKEN_AUDIENCE,
2649
2667
  MCP_TOKEN_ISSUER
2650
2668
  } from "@agor/core/types";
2651
- import jwt3 from "jsonwebtoken";
2669
+ import jwt4 from "jsonwebtoken";
2652
2670
  import { MCP_TOKEN_AUDIENCE as MCP_TOKEN_AUDIENCE2, MCP_TOKEN_ISSUER as MCP_TOKEN_ISSUER2 } from "@agor/core/types";
2653
2671
  function mcpTokenDebug(...args) {
2654
2672
  if (DEBUG_MCP_TOKENS) {
@@ -2720,7 +2738,7 @@ async function generateSessionToken2(app, sessionId, userId) {
2720
2738
  exp: expSec,
2721
2739
  jti
2722
2740
  };
2723
- const token = jwt3.sign(payload, jwtSecret, { algorithm: "HS256" });
2741
+ const token = jwt4.sign(payload, jwtSecret, { algorithm: "HS256" });
2724
2742
  s.tokenCache.set(cacheKey, { token, expiresAtMs });
2725
2743
  mcpTokenDebug(
2726
2744
  `\u{1F3AB} MCP token issued: session=${shortId3(sessionId)} jti=${jti.substring(0, 8)} exp=+${Math.floor(s.expirationMs / 1e3)}s`
@@ -2736,15 +2754,15 @@ async function validateSessionToken(app, token) {
2736
2754
  }
2737
2755
  let payload;
2738
2756
  try {
2739
- payload = jwt3.verify(token, jwtSecret, {
2757
+ payload = jwt4.verify(token, jwtSecret, {
2740
2758
  audience: MCP_TOKEN_AUDIENCE,
2741
2759
  issuer: MCP_TOKEN_ISSUER,
2742
2760
  algorithms: ["HS256"]
2743
2761
  });
2744
2762
  } catch (err) {
2745
- if (err instanceof jwt3.TokenExpiredError) {
2763
+ if (err instanceof jwt4.TokenExpiredError) {
2746
2764
  console.warn("[mcp-tokens] token rejected: expired");
2747
- } else if (err instanceof jwt3.JsonWebTokenError) {
2765
+ } else if (err instanceof jwt4.JsonWebTokenError) {
2748
2766
  console.warn(`[mcp-tokens] token rejected: ${err.message}`);
2749
2767
  } else {
2750
2768
  console.error("[mcp-tokens] token verify error:", err);
@@ -3213,12 +3231,12 @@ import {
3213
3231
  permissionModeForCli,
3214
3232
  slugForCwd
3215
3233
  } from "@agor/core/claude-cli";
3216
- import { resolveMultiTenancyConfig as resolveMultiTenancyConfig2 } from "@agor/core/config";
3234
+ import { resolveMultiTenancyConfig as resolveMultiTenancyConfig3 } from "@agor/core/config";
3217
3235
  import {
3218
3236
  generateId as generateId4,
3219
3237
  getCurrentTenantId as getCurrentTenantId3,
3220
3238
  runWithoutTenantDatabaseScope as runWithoutTenantDatabaseScope2,
3221
- runWithTenantDatabaseScope as runWithTenantDatabaseScope2,
3239
+ runWithTenantDatabaseScope as runWithTenantDatabaseScope4,
3222
3240
  SessionRepository as SessionRepository3,
3223
3241
  shortId as shortId4,
3224
3242
  TaskRepository
@@ -3238,7 +3256,7 @@ function getDb(app) {
3238
3256
  }
3239
3257
  function getCliFallbackTenantId(app) {
3240
3258
  const config2 = app.get("config");
3241
- return resolveMultiTenancyConfig2(config2 ?? {}).static_tenant_id;
3259
+ return resolveMultiTenancyConfig3(config2 ?? {}).static_tenant_id;
3242
3260
  }
3243
3261
  function captureCliWatcherTenantId(app) {
3244
3262
  return getCurrentTenantId3() ?? getCliFallbackTenantId(app);
@@ -3250,7 +3268,7 @@ async function runCliCallbackDatabaseScope(app, sessionId, work) {
3250
3268
  const db = getDb(app);
3251
3269
  if (!db) return work();
3252
3270
  const tenantId = cliWatcherTenantBySession.get(sessionId) ?? captureCliWatcherTenantId(app);
3253
- return runWithTenantDatabaseScope2(db, tenantId, work);
3271
+ return runWithTenantDatabaseScope4(db, tenantId, work);
3254
3272
  }
3255
3273
  async function persistActiveTurnSnapshot(app, sessionId, turn) {
3256
3274
  const db = getDb(app);
@@ -4229,7 +4247,7 @@ __export(service_jwt_strategy_exports, {
4229
4247
  ServiceJWTStrategy: () => ServiceJWTStrategy
4230
4248
  });
4231
4249
  import { JWTStrategy } from "@agor/core/feathers";
4232
- import jwt7 from "jsonwebtoken";
4250
+ import jwt8 from "jsonwebtoken";
4233
4251
  function persistExecutorJwtPayloadOnConnection(params, accessToken, payload) {
4234
4252
  const connection = params?.connection;
4235
4253
  if (!connection) return;
@@ -4258,6 +4276,7 @@ var init_service_jwt_strategy = __esm({
4258
4276
  "src/auth/service-jwt-strategy.ts"() {
4259
4277
  "use strict";
4260
4278
  init_users();
4279
+ init_executor_session_token();
4261
4280
  init_runtime_tokens();
4262
4281
  init_token_invalidation();
4263
4282
  ServiceJWTStrategy = class extends JWTStrategy {
@@ -4299,7 +4318,7 @@ var init_service_jwt_strategy = __esm({
4299
4318
  */
4300
4319
  // biome-ignore lint/suspicious/noExplicitAny: Feathers type compatibility
4301
4320
  async authenticate(authentication, params) {
4302
- const decoded = jwt7.decode(authentication?.accessToken);
4321
+ const decoded = jwt8.decode(authentication?.accessToken);
4303
4322
  propagateTenantFromJwtPayload(params, decoded, this.tenantClaim);
4304
4323
  const result = await super.authenticate(authentication, params);
4305
4324
  const payload = result.authentication?.payload;
@@ -4318,14 +4337,14 @@ var init_service_jwt_strategy = __esm({
4318
4337
  };
4319
4338
  }
4320
4339
  if (payload?.type === "executor-session") {
4321
- if (payload.purpose !== "executor-task") {
4340
+ if (!isExecutorSessionTokenPayload(payload)) {
4322
4341
  throw new Error("Invalid executor token purpose");
4323
4342
  }
4324
4343
  const token = authentication?.accessToken;
4325
4344
  if (!token || !this.sessionTokenService) {
4326
4345
  throw new Error("Executor token validation unavailable");
4327
4346
  }
4328
- const sessionId = payload.session_id ?? payload.sessionId;
4347
+ const sessionId = getExecutorSessionTokenSessionId(payload);
4329
4348
  const sessionInfo = await this.sessionTokenService.validateToken(token, {
4330
4349
  sessionId,
4331
4350
  taskId: payload.task_id,
@@ -17672,7 +17691,7 @@ __export(schemas_exports2, {
17672
17691
  ipv4: () => ipv42,
17673
17692
  ipv6: () => ipv62,
17674
17693
  json: () => json,
17675
- jwt: () => jwt8,
17694
+ jwt: () => jwt9,
17676
17695
  keyof: () => keyof,
17677
17696
  ksuid: () => ksuid2,
17678
17697
  lazy: () => lazy,
@@ -17839,7 +17858,7 @@ function base64url2(params) {
17839
17858
  function e1642(params) {
17840
17859
  return _e164(ZodE164, params);
17841
17860
  }
17842
- function jwt8(params) {
17861
+ function jwt9(params) {
17843
17862
  return _jwt(ZodJWT, params);
17844
17863
  }
17845
17864
  function stringFormat(format, fnOrRegex, _params = {}) {
@@ -19729,7 +19748,7 @@ __export(external_exports, {
19729
19748
  ipv6: () => ipv62,
19730
19749
  iso: () => iso_exports2,
19731
19750
  json: () => json,
19732
- jwt: () => jwt8,
19751
+ jwt: () => jwt9,
19733
19752
  keyof: () => keyof,
19734
19753
  ksuid: () => ksuid2,
19735
19754
  lazy: () => lazy,
@@ -22863,11 +22882,15 @@ import {
22863
22882
  ThreadSessionMapRepository
22864
22883
  } from "@agor/core/db";
22865
22884
  import {
22866
- getConnector
22885
+ buildSlackManifest,
22886
+ getConnector,
22887
+ requiredBotEvents,
22888
+ requiredBotScopes
22867
22889
  } from "@agor/core/gateway";
22868
22890
  import {
22869
22891
  GATEWAY_REDACTED_SENTINEL as GATEWAY_REDACTED_SENTINEL2,
22870
22892
  GATEWAY_SENSITIVE_CONFIG_FIELDS as GATEWAY_SENSITIVE_CONFIG_FIELDS2,
22893
+ getRequiredSecretFields,
22871
22894
  hasMinimumRole as hasMinimumRole10,
22872
22895
  ROLES as ROLES12
22873
22896
  } from "@agor/core/types";
@@ -22876,6 +22899,19 @@ function requireAdmin3(ctx, action) {
22876
22899
  throw new Error(`Access denied: admin role required to ${action}`);
22877
22900
  }
22878
22901
  }
22902
+ async function resolveCallerSessionBranchId(ctx) {
22903
+ if (!ctx.sessionId) return null;
22904
+ const session = await new SessionRepository6(ctx.db).findById(ctx.sessionId);
22905
+ if (!session) {
22906
+ throw new Error("Gateway access denied: calling session not found");
22907
+ }
22908
+ return session.branch_id;
22909
+ }
22910
+ function sessionBranchReadDeniedError() {
22911
+ return new Error(
22912
+ "Gateway read denied: this Slack thread belongs to a gateway channel targeting a different branch than the calling session's. Sessions can read Slack thread history only through gateway channels whose target branch matches their own."
22913
+ );
22914
+ }
22879
22915
  async function canUseGatewayOutbound(ctx, branchRepo, branch) {
22880
22916
  if (hasMinimumRole10(ctx.authenticatedUser?.role, ROLES12.ADMIN)) return true;
22881
22917
  const userId = ctx.userId;
@@ -22956,16 +22992,23 @@ async function resolveSlackThreadHistoryTarget(ctx, args) {
22956
22992
  const channelRepo = new GatewayChannelRepository2(ctx.db);
22957
22993
  const threadMapRepo = new ThreadSessionMapRepository(ctx.db);
22958
22994
  const branchRepo = new BranchRepository5(ctx.db);
22995
+ const callerSessionBranchId = await resolveCallerSessionBranchId(ctx);
22959
22996
  if (args.sessionId) {
22960
22997
  const session = await ctx.app.service("sessions").get(args.sessionId, ctx.baseServiceParams);
22961
22998
  const mapping2 = await threadMapRepo.findBySession(session.session_id);
22962
22999
  if (!mapping2) {
22963
23000
  throw new Error(`No gateway thread mapping found for session ${session.session_id}.`);
22964
23001
  }
23002
+ if (callerSessionBranchId && mapping2.branch_id !== callerSessionBranchId) {
23003
+ throw sessionBranchReadDeniedError();
23004
+ }
22965
23005
  const channel2 = await channelRepo.findById(mapping2.channel_id);
22966
23006
  if (!channel2) {
22967
23007
  throw new Error(`Gateway channel not found for session mapping ${mapping2.id}.`);
22968
23008
  }
23009
+ if (callerSessionBranchId && channel2.target_branch_id !== callerSessionBranchId) {
23010
+ throw sessionBranchReadDeniedError();
23011
+ }
22969
23012
  const branch2 = await branchRepo.findById(mapping2.branch_id);
22970
23013
  return {
22971
23014
  channel: channel2,
@@ -22980,6 +23023,9 @@ async function resolveSlackThreadHistoryTarget(ctx, args) {
22980
23023
  if (!channel) {
22981
23024
  throw new Error(`Gateway channel not found: ${args.gatewayChannelId}`);
22982
23025
  }
23026
+ if (callerSessionBranchId && channel.target_branch_id !== callerSessionBranchId) {
23027
+ throw sessionBranchReadDeniedError();
23028
+ }
22983
23029
  const mapping = await threadMapRepo.findByChannelAndThread(channel.id, args.threadId);
22984
23030
  if (mapping) {
22985
23031
  const branch2 = await branchRepo.findById(mapping.branch_id);
@@ -23073,6 +23119,34 @@ function toServiceUpdateData(args) {
23073
23119
  }
23074
23120
  return updates;
23075
23121
  }
23122
+ function toSlackWizardOptions(args) {
23123
+ return {
23124
+ appName: args.appName,
23125
+ ...args.botDisplayName ? { botDisplayName: args.botDisplayName } : {},
23126
+ publicChannels: args.publicChannels,
23127
+ privateChannels: args.privateChannels,
23128
+ groupDms: args.groupDms,
23129
+ alignUsers: args.alignUsers,
23130
+ outbound: args.outbound
23131
+ };
23132
+ }
23133
+ function toCreateChannelConfigHint(args) {
23134
+ const config2 = {
23135
+ connection_mode: "socket",
23136
+ enable_channels: args.publicChannels,
23137
+ enable_groups: args.privateChannels,
23138
+ enable_mpim: args.groupDms,
23139
+ align_slack_users: args.alignUsers,
23140
+ outbound_enabled: args.outbound
23141
+ };
23142
+ if (args.restrictToChannelIds && args.restrictToChannelIds.length > 0) {
23143
+ config2.allowed_channel_ids = args.restrictToChannelIds;
23144
+ }
23145
+ return { channelType: "slack", config: config2 };
23146
+ }
23147
+ function identityModeSetupStep(alignUsers) {
23148
+ return alignUsers ? "Identity: the channel is set to align Slack users \u2014 each Slack user runs as their matched Agor account and unmatched users are rejected, so no run-as user is needed. Tell me if you would rather every message run as one fixed Agor user (that requires picking a user and passing agorUserId)." : "Identity: the channel is set to run every message as one fixed Agor user, which requires passing agorUserId to agor_gateway_channels_create. Tell me if you would rather align Slack users so each runs as their own matched Agor account instead.";
23149
+ }
23076
23150
  function registerGatewayChannelTools(server, ctx) {
23077
23151
  server.registerTool(
23078
23152
  "agor_gateway_channels_list",
@@ -23118,7 +23192,7 @@ function registerGatewayChannelTools(server, ctx) {
23118
23192
  server.registerTool(
23119
23193
  "agor_gateway_channels_create",
23120
23194
  {
23121
- description: 'Create a gateway channel definition (admin-only) through the same gateway-channels service used by the UI. Current connectors: Slack, GitHub, Teams. Slack example config: { bot_token, app_token, connection_mode:"socket", enable_channels:true, require_mention:true, allowed_channel_ids:["C123"] }. Secrets are encrypted by the service and returned redacted; prefer environment/template references where possible because raw secrets in tool arguments may appear in the MCP transcript.',
23195
+ description: 'Create a gateway channel definition (admin-only) through the same gateway-channels service used by the UI. Current connectors: Slack, GitHub, Teams. For interactive/agent-driven setup, create the channel disabled and without secrets (enabled:false, no tokens), then collect credentials with agor_widgets_request_gateway_token so the user enters them in a secure inline form \u2014 raw secrets passed in tool arguments leak into the MCP transcript. Passing secrets directly here is for programmatic/non-interactive use only. Non-interactive Slack example config: { bot_token, app_token, connection_mode:"socket", enable_channels:true, require_mention:true, allowed_channel_ids:["C123"] }. Secrets are encrypted by the service and returned redacted.',
23122
23196
  annotations: { destructiveHint: false, idempotentHint: false },
23123
23197
  inputSchema: gatewayChannelCreateSchema
23124
23198
  },
@@ -23128,12 +23202,50 @@ function registerGatewayChannelTools(server, ctx) {
23128
23202
  return textResult({
23129
23203
  gateway_channel: redactGatewayChannel(created),
23130
23204
  next_steps: [
23205
+ "If this channel was created disabled without tokens (the recommended interactive path), collect its credentials by calling agor_widgets_request_gateway_token for this channel so the user enters the tokens in the secure form that appears at the end of this message. Do NOT ask the user to paste xoxb-/xapp- tokens into the chat, and do NOT pass tokens as agor_gateway_channels_create arguments during interactive setup.",
23206
+ ...created.channel_type === "slack" ? [
23207
+ identityModeSetupStep(
23208
+ created.config?.align_slack_users === true
23209
+ )
23210
+ ] : [],
23131
23211
  "Verify the channel in Settings > Gateway Channels or with agor_gateway_channels_list.",
23132
23212
  "Channel credentials, env vars, and inbound channel keys are intentionally redacted from MCP responses."
23133
23213
  ]
23134
23214
  });
23135
23215
  }
23136
23216
  );
23217
+ server.registerTool(
23218
+ "agor_gateway_slack_manifest_generate",
23219
+ {
23220
+ description: "Generate a ready-to-install Slack app manifest from desired gateway capabilities (admin-only). The agent-driven equivalent of the Slack setup wizard, backed by the same core generator. Returns the manifest JSON, the derived bot scopes/events, ordered setup steps, and the channel config to pass to agor_gateway_channels_create. This is pure: it creates no Slack app, no Agor channel, and validates no tokens.",
23221
+ annotations: { readOnlyHint: true },
23222
+ inputSchema: slackManifestGenerateSchema
23223
+ },
23224
+ async (args) => {
23225
+ requireAdmin3(ctx, "generate Slack app manifests");
23226
+ const options = toSlackWizardOptions(args);
23227
+ return textResult({
23228
+ manifest: buildSlackManifest(options),
23229
+ bot_scopes: requiredBotScopes(options),
23230
+ bot_events: requiredBotEvents(options),
23231
+ setup_steps: [
23232
+ 'Open https://api.slack.com/apps?new_app=1 and choose "Create New App".',
23233
+ 'Select "From a manifest", pick the target workspace, paste the manifest JSON below, and click "Create".',
23234
+ 'Install the app to the workspace, then open OAuth & Permissions and copy the Bot User OAuth Token (starts with "xoxb-").',
23235
+ 'Open Basic Information \u2192 App-Level Tokens \u2192 Generate Token and Scopes, add the connections:write scope, generate it, and copy the App-Level Token (starts with "xapp-").',
23236
+ 'Once the app is installed and you hold the xoxb-/xapp- tokens, call agor_gateway_channels_create with channelType "slack", enabled:false, no tokens, and create_channel_config_hint below to create the channel as a draft.',
23237
+ "Then call agor_widgets_request_gateway_token for that channel so the user enters the xoxb-/xapp- tokens in the secure form that appears at the end of this message, which enables the channel. Do NOT ask the user to paste xoxb-/xapp- tokens into the chat, and do NOT pass tokens as agor_gateway_channels_create arguments.",
23238
+ identityModeSetupStep(args.alignUsers)
23239
+ ],
23240
+ create_channel_config_hint: toCreateChannelConfigHint(args),
23241
+ caveats: [
23242
+ "GENERATED ONLY \u2014 no Slack app created, no Agor channel created, no tokens validated, no event delivery verified.",
23243
+ "The app-level connections:write token is generated manually and is NOT part of the manifest bot scopes.",
23244
+ "restrictToChannelIds maps to config.allowed_channel_ids when you call create \u2014 it does NOT change the manifest scopes or events."
23245
+ ]
23246
+ });
23247
+ }
23248
+ );
23137
23249
  server.registerTool(
23138
23250
  "agor_gateway_channels_update",
23139
23251
  {
@@ -23157,7 +23269,7 @@ function registerGatewayChannelTools(server, ctx) {
23157
23269
  server.registerTool(
23158
23270
  "agor_gateway_outbound_targets_list",
23159
23271
  {
23160
- description: "List Slack gateway outbound targets the caller can use. Returns only outbound-enabled channels where the caller has branch all permission or admin access. Secrets and inbound channel keys are never returned.",
23272
+ description: "List Slack gateway outbound targets the caller can use. Returns only outbound-enabled channels where the caller has branch all permission or admin access; when called from a session, results are additionally scoped to channels targeting the session's branch. Secrets and inbound channel keys are never returned.",
23161
23273
  annotations: { readOnlyHint: true },
23162
23274
  inputSchema: external_exports.strictObject({
23163
23275
  branchId: mcpOptionalId("branchId", "Branch", "Filter by target branch ID."),
@@ -23172,15 +23284,24 @@ function registerGatewayChannelTools(server, ctx) {
23172
23284
  async (args) => {
23173
23285
  const channelRepo = new GatewayChannelRepository2(ctx.db);
23174
23286
  const branchRepo = new BranchRepository5(ctx.db);
23287
+ const callerSessionBranchId = await resolveCallerSessionBranchId(ctx);
23175
23288
  const branchFilter = args.branchId ? await branchRepo.findById(args.branchId) : null;
23176
- const branchFilterId = branchFilter?.branch_id;
23289
+ const requestedBranchId = branchFilter?.branch_id;
23290
+ if (callerSessionBranchId && args.branchId && requestedBranchId !== callerSessionBranchId) {
23291
+ return textResult({
23292
+ channels: [],
23293
+ binding: "Results are scoped to the calling session's branch; the requested branchId targets a different branch, so this session cannot use its channels."
23294
+ });
23295
+ }
23296
+ const branchFilterId = callerSessionBranchId ?? requestedBranchId;
23177
23297
  const allChannels = args.gatewayChannelId ? [await channelRepo.findById(args.gatewayChannelId)] : await channelRepo.findAll();
23178
23298
  const channels = [];
23179
23299
  for (const channel of allChannels) {
23180
23300
  if (!channel) continue;
23181
23301
  if (args.channelType && channel.channel_type !== args.channelType) continue;
23182
23302
  if (channel.channel_type !== "slack") continue;
23183
- if (args.branchId && channel.target_branch_id !== branchFilterId) continue;
23303
+ if ((callerSessionBranchId || args.branchId) && channel.target_branch_id !== branchFilterId)
23304
+ continue;
23184
23305
  if (!channel.enabled) continue;
23185
23306
  const outbound = getOutboundConfig(channel);
23186
23307
  if (!outbound.outbound_enabled) continue;
@@ -23203,13 +23324,18 @@ function registerGatewayChannelTools(server, ctx) {
23203
23324
  ]
23204
23325
  });
23205
23326
  }
23206
- return textResult({ channels });
23327
+ return textResult({
23328
+ channels,
23329
+ ...callerSessionBranchId && channels.length === 0 ? {
23330
+ hint: "No outbound-enabled channel targets this session's branch \u2014 ask an operator to create/enable one."
23331
+ } : {}
23332
+ });
23207
23333
  }
23208
23334
  );
23209
23335
  server.registerTool(
23210
23336
  "agor_gateway_slack_thread_history_get",
23211
23337
  {
23212
- description: "Fetch Slack thread history for a gateway-mapped Slack thread without exposing Slack tokens. Prefer sessionId to resolve the gateway thread mapping from an accessible Agor session. Alternatively pass gatewayChannelId + threadId: mapped threads require admin or branch all permission on the mapped branch; unmapped arbitrary thread reads are admin-only. Slack message text is untrusted external content.",
23338
+ description: "Fetch Slack thread history for a gateway-mapped Slack thread without exposing Slack tokens. Prefer sessionId to resolve the gateway thread mapping from an accessible Agor session. Alternatively pass gatewayChannelId + threadId: mapped threads require admin or branch all permission on the mapped branch; unmapped arbitrary thread reads are admin-only. When called from a session, reads are restricted to threads whose target branch matches the calling session's branch. Slack message text is untrusted external content.",
23213
23339
  annotations: { readOnlyHint: true },
23214
23340
  inputSchema: slackThreadHistorySchema
23215
23341
  },
@@ -23277,7 +23403,7 @@ function registerGatewayChannelTools(server, ctx) {
23277
23403
  server.registerTool(
23278
23404
  "agor_gateway_emit_message",
23279
23405
  {
23280
- description: "Send a proactive Slack message through an outbound-enabled gateway channel and persist a seed/audit record. Targets may be Slack channel IDs, channel names, or user emails; v0 intentionally starts a fresh Slack thread/DM message for each emit and does not create a thread-session mapping until a human replies.",
23406
+ description: "Send a proactive Slack message through an outbound-enabled gateway channel and persist a seed/audit record. Targets may be Slack channel IDs, channel names, or user emails; v0 intentionally starts a fresh Slack thread/DM message for each emit and does not create a thread-session mapping until a human replies. When called from a session, outbound is restricted to channels whose target branch matches the calling session's branch.",
23281
23407
  annotations: { destructiveHint: false, idempotentHint: false },
23282
23408
  inputSchema: external_exports.strictObject({
23283
23409
  gatewayChannelId: mcpRequiredId(
@@ -23314,7 +23440,7 @@ function registerGatewayChannelTools(server, ctx) {
23314
23440
  }
23315
23441
  );
23316
23442
  }
23317
- var configSchema, outboundTargetSchema, envVarSchema, agenticConfigSchema, gatewayChannelCreateSchema, slackThreadHistorySchema, gatewayChannelUpdateSchema;
23443
+ var configSchema, outboundTargetSchema, envVarSchema, agenticConfigSchema, gatewayChannelCreateSchema, slackThreadHistorySchema, gatewayChannelUpdateSchema, slackManifestGenerateSchema;
23318
23444
  var init_gateway_channels = __esm({
23319
23445
  "src/mcp/tools/gateway-channels.ts"() {
23320
23446
  "use strict";
@@ -23378,44 +23504,48 @@ var init_gateway_channels = __esm({
23378
23504
  agenticConfig: agenticConfigSchema.optional()
23379
23505
  }).superRefine((value, issue2) => {
23380
23506
  const config2 = value.config ?? {};
23381
- if (value.channelType === "slack") {
23382
- if (!config2.bot_token) {
23383
- issue2.addIssue({
23384
- code: "custom",
23385
- path: ["config", "bot_token"],
23386
- message: "config.bot_token is required for Slack. Prefer a bot token stored outside the transcript when possible."
23387
- });
23388
- }
23389
- if (config2.connection_mode === "socket" && !config2.app_token) {
23390
- issue2.addIssue({
23391
- code: "custom",
23392
- path: ["config", "app_token"],
23393
- message: "config.app_token is required for Slack Socket Mode."
23394
- });
23395
- }
23396
- }
23397
- if (value.channelType === "github") {
23398
- for (const field of ["app_id", "private_key", "installation_id", "watch_repos"]) {
23507
+ if (value.enabled !== false) {
23508
+ const requiredSecretMessages = {
23509
+ bot_token: "config.bot_token is required for Slack. Prefer a bot token stored outside the transcript when possible.",
23510
+ app_token: "config.app_token is required for Slack Socket Mode.",
23511
+ private_key: "config.private_key is required for GitHub gateway channels.",
23512
+ app_password: "config.app_password is required for Teams gateway channels."
23513
+ };
23514
+ for (const field of getRequiredSecretFields(value.channelType, config2)) {
23399
23515
  if (!config2[field]) {
23400
23516
  issue2.addIssue({
23401
23517
  code: "custom",
23402
23518
  path: ["config", field],
23403
- message: `config.${field} is required for GitHub gateway channels.`
23519
+ message: requiredSecretMessages[field] ?? `config.${field} is required for ${value.channelType} gateway channels.`
23404
23520
  });
23405
23521
  }
23406
23522
  }
23407
23523
  }
23408
- if (value.channelType === "teams") {
23409
- for (const field of ["app_id", "app_password"]) {
23524
+ if (value.channelType === "github") {
23525
+ for (const field of ["app_id", "installation_id", "watch_repos"]) {
23410
23526
  if (!config2[field]) {
23411
23527
  issue2.addIssue({
23412
23528
  code: "custom",
23413
23529
  path: ["config", field],
23414
- message: `config.${field} is required for Teams gateway channels.`
23530
+ message: `config.${field} is required for GitHub gateway channels.`
23415
23531
  });
23416
23532
  }
23417
23533
  }
23418
23534
  }
23535
+ if (value.channelType === "teams" && !config2.app_id) {
23536
+ issue2.addIssue({
23537
+ code: "custom",
23538
+ path: ["config", "app_id"],
23539
+ message: "config.app_id is required for Teams gateway channels."
23540
+ });
23541
+ }
23542
+ if (value.channelType === "slack" && config2.align_slack_users !== true && !value.agorUserId) {
23543
+ issue2.addIssue({
23544
+ code: "custom",
23545
+ path: ["agorUserId"],
23546
+ message: "Run-as-selected-user needs agorUserId \u2014 set config.align_slack_users:true to align by email, or pass agorUserId."
23547
+ });
23548
+ }
23419
23549
  });
23420
23550
  slackThreadHistorySchema = external_exports.strictObject({
23421
23551
  sessionId: mcpOptionalId(
@@ -23479,6 +23609,23 @@ var init_gateway_channels = __esm({
23479
23609
  ),
23480
23610
  agenticConfig: agenticConfigSchema.nullable().optional().describe("Replace agent/session defaults. null clears the gateway agentic config.")
23481
23611
  });
23612
+ slackManifestGenerateSchema = external_exports.strictObject({
23613
+ appName: mcpRequiredString("appName", 'Slack app display name, e.g. "Agor".'),
23614
+ botDisplayName: mcpOptionalNonEmptyString(
23615
+ "botDisplayName",
23616
+ "Bot user display name. Defaults to appName."
23617
+ ),
23618
+ publicChannels: external_exports.boolean().default(false).describe("Listen in public channels (#channel) via @mention."),
23619
+ privateChannels: external_exports.boolean().default(false).describe("Listen in private channels (groups) via @mention."),
23620
+ groupDms: external_exports.boolean().default(false).describe("Listen in group DMs (multi-person IMs) via @mention."),
23621
+ alignUsers: external_exports.boolean().default(true).describe(
23622
+ "Resolve Slack user email \u2192 Agor user (adds the users:read.email scope). Defaults to true: each Slack user runs as their matched Agor account and unmatched users are rejected, so no fixed run-as user is needed. Set false to run every message as one fixed Agor user (then pass agorUserId to agor_gateway_channels_create)."
23623
+ ),
23624
+ outbound: external_exports.boolean().default(false).describe("Proactive outbound: post to channels by name and DM users by email."),
23625
+ restrictToChannelIds: external_exports.array(external_exports.string().min(1)).optional().describe(
23626
+ "Slack channel ID whitelist. Maps to config.allowed_channel_ids when you call agor_gateway_channels_create; it does NOT change the manifest scopes or events."
23627
+ )
23628
+ });
23482
23629
  }
23483
23630
  });
23484
23631
 
@@ -28907,8 +29054,191 @@ var init_env_vars = __esm({
28907
29054
  }
28908
29055
  });
28909
29056
 
29057
+ // src/widgets/gateway-token/index.ts
29058
+ import { Forbidden as Forbidden13 } from "@agor/core/feathers";
29059
+ import {
29060
+ GATEWAY_SENSITIVE_CONFIG_FIELDS as GATEWAY_SENSITIVE_CONFIG_FIELDS3,
29061
+ hasMinimumRole as hasMinimumRole12,
29062
+ ROLES as ROLES15
29063
+ } from "@agor/core/types";
29064
+ function isSupportedGatewayTokenChannelType(channelType) {
29065
+ return SUPPORTED_CHANNEL_TYPES.includes(channelType);
29066
+ }
29067
+ function orderedFields(fields) {
29068
+ return [...fields].sort();
29069
+ }
29070
+ function classifyGatewayTokenTest(result, appTokenExpected) {
29071
+ const unprobable = result.failures.some((failure) => failure.capability === "connector");
29072
+ if (unprobable) {
29073
+ return {
29074
+ enable: false,
29075
+ status: "unverifiable",
29076
+ summary: "credentials cannot be auto-verified for this channel type yet"
29077
+ };
29078
+ }
29079
+ const isAppTokenFailure = (failure) => failure.capability === "app_token";
29080
+ const hardFailure = result.failures.find(
29081
+ (failure) => failure.slackError && HARD_CREDENTIAL_SLACK_ERRORS.has(failure.slackError) || failure.capability === "config" || appTokenExpected && isAppTokenFailure(failure)
29082
+ );
29083
+ if (hardFailure) {
29084
+ return { enable: false, status: "failed", summary: hardFailure.reason };
29085
+ }
29086
+ if (appTokenExpected && result.appTokenValid === false) {
29087
+ return {
29088
+ enable: false,
29089
+ status: "failed",
29090
+ summary: "The app-level token is missing or invalid; Socket Mode cannot connect."
29091
+ };
29092
+ }
29093
+ if (result.ok) {
29094
+ return { enable: true, status: "verified", summary: "passed" };
29095
+ }
29096
+ const followUps = result.failures.filter((failure) => appTokenExpected || !isAppTokenFailure(failure)).map((failure) => failure.reason).filter(Boolean);
29097
+ return {
29098
+ enable: true,
29099
+ status: "verified",
29100
+ summary: followUps.length > 0 ? `passed, with follow-ups: ${followUps.join("; ")}` : "passed"
29101
+ };
29102
+ }
29103
+ function assertGatewayTokenAdmin(ctx) {
29104
+ if (!hasMinimumRole12(ctx.submitterRole, ROLES15.ADMIN)) {
29105
+ throw new Forbidden13("Only admins can set gateway channel tokens");
29106
+ }
29107
+ }
29108
+ async function applyGatewayTokenSubmit(ctx, submit, params) {
29109
+ assertGatewayTokenAdmin(ctx);
29110
+ const channelsService = ctx.app.service("gateway-channels");
29111
+ const channel = await channelsService.get(params.gatewayChannelId);
29112
+ if (!channel) {
29113
+ throw new Forbidden13(`Gateway channel ${params.gatewayChannelId} not found`);
29114
+ }
29115
+ if (!isSupportedGatewayTokenChannelType(channel.channel_type)) {
29116
+ throw new Forbidden13(
29117
+ `Gateway channel type "${channel.channel_type}" does not support token entry`
29118
+ );
29119
+ }
29120
+ if (channel.channel_type !== params.channelType) {
29121
+ throw new Forbidden13("Gateway channel type does not match the widget request");
29122
+ }
29123
+ const sessionsService = ctx.app.service("sessions");
29124
+ const session = await sessionsService.get(ctx.sessionId);
29125
+ if (!session.branch_id || channel.target_branch_id !== session.branch_id) {
29126
+ throw new Forbidden13("Gateway channel is not bound to this session's branch");
29127
+ }
29128
+ const requested = new Set(params.fields);
29129
+ const extra = Object.keys(submit.tokens).filter((field) => !requested.has(field));
29130
+ if (extra.length > 0) {
29131
+ throw new Forbidden13(
29132
+ `Submitted token fields were not requested: ${orderedFields(extra).join(", ")}`
29133
+ );
29134
+ }
29135
+ const testService = ctx.app.service(
29136
+ "gateway-channels/test"
29137
+ );
29138
+ const testResult = await testService.create({
29139
+ gatewayChannelId: params.gatewayChannelId,
29140
+ config: submit.tokens
29141
+ });
29142
+ const appTokenExpected = params.fields.includes("app_token");
29143
+ const { enable, status, summary } = classifyGatewayTokenTest(testResult, appTokenExpected);
29144
+ await channelsService.patch(
29145
+ params.gatewayChannelId,
29146
+ { config: submit.tokens, enabled: enable },
29147
+ { user: { user_id: ctx.submitterUserId, role: ctx.submitterRole } }
29148
+ );
29149
+ submitOutcomes.set(submit, {
29150
+ channelId: channel.id,
29151
+ channelName: channel.name,
29152
+ channelType: channel.channel_type,
29153
+ enabled: enable,
29154
+ unverified: status === "unverifiable",
29155
+ test: { ok: testResult.ok, summary }
29156
+ });
29157
+ }
29158
+ function registerGatewayTokenWidget() {
29159
+ registerWidget(gatewayTokenWidget);
29160
+ }
29161
+ var SUPPORTED_CHANNEL_TYPES, MAX_TOKEN_LENGTH, gatewayTokenParamsSchema, gatewayTokenSubmitSchema, submitOutcomes, HARD_CREDENTIAL_SLACK_ERRORS, gatewayTokenWidget;
29162
+ var init_gateway_token = __esm({
29163
+ "src/widgets/gateway-token/index.ts"() {
29164
+ "use strict";
29165
+ init_zod();
29166
+ init_registry();
29167
+ SUPPORTED_CHANNEL_TYPES = [
29168
+ "slack",
29169
+ "github",
29170
+ "teams"
29171
+ ];
29172
+ MAX_TOKEN_LENGTH = 8192;
29173
+ gatewayTokenParamsSchema = external_exports.object({
29174
+ gatewayChannelId: external_exports.string().min(1).describe("Gateway channel whose credentials are being set."),
29175
+ channelType: external_exports.enum(["slack", "discord", "whatsapp", "telegram", "github", "teams"]).describe("Platform type of the channel; drives per-field placeholders."),
29176
+ channelName: external_exports.string().min(1).max(200).describe("Human-readable channel name for the form heading and prompts."),
29177
+ fields: external_exports.array(external_exports.enum(GATEWAY_SENSITIVE_CONFIG_FIELDS3)).min(1).max(GATEWAY_SENSITIVE_CONFIG_FIELDS3.length).refine((fields) => new Set(fields).size === fields.length, {
29178
+ message: "Token field names must be unique"
29179
+ }).describe("Secret config field names to collect (subset of the sensitive fields)."),
29180
+ reason: external_exports.string().min(1).max(200).describe("One sentence explaining why the tokens are needed. Renders as a muted line.")
29181
+ }).strict();
29182
+ gatewayTokenSubmitSchema = external_exports.object({
29183
+ tokens: external_exports.partialRecord(
29184
+ external_exports.enum(GATEWAY_SENSITIVE_CONFIG_FIELDS3),
29185
+ external_exports.string().min(1).max(MAX_TOKEN_LENGTH)
29186
+ ).describe("Map of sensitive field name \u2192 credential value.")
29187
+ }).strict();
29188
+ submitOutcomes = /* @__PURE__ */ new WeakMap();
29189
+ HARD_CREDENTIAL_SLACK_ERRORS = /* @__PURE__ */ new Set([
29190
+ "invalid_auth",
29191
+ "account_inactive",
29192
+ "token_revoked",
29193
+ "token_expired",
29194
+ "not_authed",
29195
+ "no_permission"
29196
+ ]);
29197
+ gatewayTokenWidget = {
29198
+ type: "gateway_token",
29199
+ schemaVersion: 1,
29200
+ paramsSchema: gatewayTokenParamsSchema,
29201
+ submitSchema: gatewayTokenSubmitSchema,
29202
+ buildResultMeta: (submit) => {
29203
+ const outcome = submitOutcomes.get(submit);
29204
+ return {
29205
+ channelId: outcome?.channelId ?? "",
29206
+ channelName: outcome?.channelName ?? "",
29207
+ channelType: outcome?.channelType ?? "slack",
29208
+ fieldsSet: orderedFields(Object.keys(submit.tokens)),
29209
+ enabled: outcome?.enabled ?? false,
29210
+ unverified: outcome?.unverified ?? false,
29211
+ test: outcome?.test ?? { ok: false, summary: "" }
29212
+ };
29213
+ },
29214
+ applySubmit: applyGatewayTokenSubmit,
29215
+ buildAutoResumePrompt: (rm3, params) => {
29216
+ const fields = orderedFields(rm3.fieldsSet).join(", ");
29217
+ const name = rm3.channelName || params.channelName;
29218
+ const channelType = rm3.channelType || params.channelType;
29219
+ if (rm3.enabled) {
29220
+ return `[Agor] User set the ${channelType} tokens (${fields}) for channel "${name}". Channel enabled. Connection test: ${rm3.test.summary || "passed"}.`;
29221
+ }
29222
+ if (rm3.unverified) {
29223
+ return `[Agor] Tokens saved for "${name}" (${fields}); ${channelType} credentials can't be auto-verified yet, so the channel is left disabled \u2014 enable it manually once confirmed.`;
29224
+ }
29225
+ return `[Agor] User set the ${channelType} tokens (${fields}) for channel "${name}", but it was left disabled; test failed: ${rm3.test.summary || "unknown error"}. Ask the user to double-check the tokens/scopes.`;
29226
+ },
29227
+ buildDismissedPrompt: (params) => `[Agor] User declined to provide tokens for "${params.channelName}"; it stays disabled. Don't immediately re-ask.`,
29228
+ // Dismissal is admin-only too — otherwise a member could terminally decline
29229
+ // an admin-only credential flow via the generic dismiss endpoint.
29230
+ authorizeDismiss: (ctx) => assertGatewayTokenAdmin(ctx)
29231
+ };
29232
+ }
29233
+ });
29234
+
28910
29235
  // src/mcp/tools/widgets.ts
28911
- import { MessageRole as MessageRole3 } from "@agor/core/types";
29236
+ import { getRequiredSecretFields as getRequiredSecretFields2, hasMinimumRole as hasMinimumRole13, MessageRole as MessageRole3, ROLES as ROLES16 } from "@agor/core/types";
29237
+ function requireAdmin4(ctx, action) {
29238
+ if (!hasMinimumRole13(ctx.authenticatedUser?.role, ROLES16.ADMIN)) {
29239
+ throw new Error(`Access denied: admin role required to ${action}`);
29240
+ }
29241
+ }
28912
29242
  function widgetContentPreview(params) {
28913
29243
  const list = params.names.join(", ");
28914
29244
  return params.names.length === 1 ? `Please provide ${list}: ${params.reason}` : `Please provide ${list}: ${params.reason}`;
@@ -29027,13 +29357,113 @@ function registerWidgetTools(server, ctx) {
29027
29357
  return textResult({ widget_id: widgetId, status: "requested" });
29028
29358
  }
29029
29359
  );
29360
+ server.registerTool(
29361
+ "agor_widgets_request_gateway_token",
29362
+ {
29363
+ description: "Ask an admin to securely provide a gateway channel's platform tokens (Slack bot/app tokens, GitHub private key, Teams app password) via a compact form that appears at the end of your message, so setup finishes without anyone pasting xoxb-/xapp- tokens into chat. FIRE-AND-FORGET: the widget renders inline at the end of your turn; end your turn after calling. You will receive a user-role message when it is resolved. Token values never enter your context \u2014 only the channel identity and field NAMES do. Admin-only: a non-admin agent cannot mint this widget. Keep `reason` to ONE short sentence (\u2264200 chars).",
29364
+ annotations: { destructiveHint: false, openWorldHint: false },
29365
+ inputSchema: external_exports.object({
29366
+ gatewayChannelId: external_exports.string().min(1).describe("Gateway channel ID (UUIDv7 or short ID) whose tokens are being set."),
29367
+ reason: external_exports.string().max(200).optional().describe("One short sentence explaining why the tokens are needed.")
29368
+ })
29369
+ },
29370
+ async (args) => {
29371
+ if (!ctx.sessionId) return sessionContextRequiredResult();
29372
+ const currentSessionId = ctx.sessionId;
29373
+ requireAdmin4(ctx, "set gateway channel tokens");
29374
+ const channel = await ctx.app.service("gateway-channels").get(args.gatewayChannelId, ctx.baseServiceParams);
29375
+ const channelType = channel.channel_type;
29376
+ if (!isSupportedGatewayTokenChannelType(channelType)) {
29377
+ throw new Error(
29378
+ `Gateway channel type "${channelType}" does not support token entry via this widget.`
29379
+ );
29380
+ }
29381
+ const fields = getRequiredSecretFields2(channelType, channel.config);
29382
+ if (fields.length === 0) {
29383
+ throw new Error(
29384
+ `Gateway channel "${channel.name}" has no required secret fields to collect.`
29385
+ );
29386
+ }
29387
+ const params = gatewayTokenParamsSchema.parse({
29388
+ gatewayChannelId: channel.id,
29389
+ channelType,
29390
+ channelName: channel.name,
29391
+ fields,
29392
+ reason: args.reason ?? `Provide the ${channelType} credentials to finish connecting "${channel.name}".`
29393
+ });
29394
+ const requestedAt = (/* @__PURE__ */ new Date()).toISOString();
29395
+ const baseWidgetMeta = {
29396
+ widget_type: "gateway_token",
29397
+ schema_version: 1,
29398
+ params,
29399
+ status: "pending",
29400
+ requested_at: requestedAt,
29401
+ auto_resume: true
29402
+ };
29403
+ const hostTask = await findHostTaskForSession(
29404
+ ctx.app,
29405
+ currentSessionId,
29406
+ ctx.baseServiceParams
29407
+ );
29408
+ const hostTaskId = hostTask?.task_id;
29409
+ const created = await appendSystemMessage({
29410
+ app: ctx.app,
29411
+ db: ctx.db,
29412
+ sessionId: currentSessionId,
29413
+ taskId: hostTaskId,
29414
+ content: `Please provide the ${channelType} tokens for "${channel.name}": ${params.reason}`,
29415
+ contentPreview: `Widget: gateway_token (${channel.name})`,
29416
+ type: "widget_request",
29417
+ role: MessageRole3.SYSTEM,
29418
+ metadata: {
29419
+ widget: {
29420
+ ...baseWidgetMeta,
29421
+ widget_id: "pending"
29422
+ }
29423
+ }
29424
+ });
29425
+ const widgetId = created.message_id;
29426
+ if (hostTask?.message_range) {
29427
+ try {
29428
+ await ctx.app.service("tasks").patch(
29429
+ hostTask.task_id,
29430
+ {
29431
+ message_range: {
29432
+ start_index: hostTask.message_range.start_index,
29433
+ end_index: created.index
29434
+ }
29435
+ },
29436
+ ctx.baseServiceParams
29437
+ );
29438
+ } catch (err) {
29439
+ console.warn(
29440
+ `[widgets] failed to extend task.message_range for widget ${widgetId}:`,
29441
+ err
29442
+ );
29443
+ }
29444
+ }
29445
+ await ctx.app.service("messages").patch(
29446
+ widgetId,
29447
+ {
29448
+ metadata: {
29449
+ ...created.metadata ?? {},
29450
+ widget: { ...baseWidgetMeta, widget_id: widgetId }
29451
+ }
29452
+ },
29453
+ ctx.baseServiceParams
29454
+ );
29455
+ return textResult({ widget_id: widgetId, status: "requested" });
29456
+ }
29457
+ );
29030
29458
  }
29031
29459
  var init_widgets = __esm({
29032
29460
  "src/mcp/tools/widgets.ts"() {
29033
29461
  "use strict";
29462
+ init_zod();
29034
29463
  init_append_system_message();
29035
29464
  init_session_tasks();
29036
29465
  init_env_vars();
29466
+ init_gateway_token();
29037
29467
  init_server();
29038
29468
  }
29039
29469
  });
@@ -31323,13 +31753,13 @@ var init_identity_token = __esm({
31323
31753
  function oidcFederationProvider(config2) {
31324
31754
  return async () => {
31325
31755
  requireSecureTokenEndpoint(config2.baseURL);
31326
- const jwt12 = await config2.identityTokenProvider();
31327
- if (jwt12.length > 16 * 1024) {
31328
- throw new WorkloadIdentityError(`Identity token is ${Math.ceil(jwt12.length / 1024)} KiB, exceeds the 16 KiB assertion limit`);
31756
+ const jwt13 = await config2.identityTokenProvider();
31757
+ if (jwt13.length > 16 * 1024) {
31758
+ throw new WorkloadIdentityError(`Identity token is ${Math.ceil(jwt13.length / 1024)} KiB, exceeds the 16 KiB assertion limit`);
31329
31759
  }
31330
31760
  const body = {
31331
31761
  grant_type: GRANT_TYPE_JWT_BEARER,
31332
- assertion: jwt12,
31762
+ assertion: jwt13,
31333
31763
  federation_rule_id: config2.federationRuleId,
31334
31764
  organization_id: config2.organizationId
31335
31765
  };
@@ -41711,7 +42141,7 @@ __export(session_token_service_exports, {
41711
42141
  SessionTokenService: () => SessionTokenService
41712
42142
  });
41713
42143
  import { getCurrentTenantId as getCurrentTenantId6 } from "@agor/core/db";
41714
- import jwt11 from "jsonwebtoken";
42144
+ import jwt12 from "jsonwebtoken";
41715
42145
  function sessionTokenDebug(...args) {
41716
42146
  if (DEBUG_SESSION_TOKENS) {
41717
42147
  console.debug(...args);
@@ -41721,6 +42151,7 @@ var DEBUG_SESSION_TOKENS, SessionTokenService;
41721
42151
  var init_session_token_service = __esm({
41722
42152
  "src/services/session-token-service.ts"() {
41723
42153
  "use strict";
42154
+ init_executor_session_token();
41724
42155
  DEBUG_SESSION_TOKENS = process.env.AGOR_DEBUG_SESSION_TOKENS === "1" || process.env.DEBUG?.includes("session-token");
41725
42156
  SessionTokenService = class {
41726
42157
  constructor(config2) {
@@ -41750,10 +42181,8 @@ var init_session_token_service = __esm({
41750
42181
  const payload = {
41751
42182
  sub: userId,
41752
42183
  // Standard JWT subject claim (used by Feathers for user lookup)
41753
- type: "executor-session",
41754
- purpose: "executor-task",
41755
- sessionId,
41756
- // Custom claim for session tracking
42184
+ type: EXECUTOR_SESSION_TOKEN_TYPE,
42185
+ purpose: EXECUTOR_SESSION_TOKEN_PURPOSE,
41757
42186
  session_id: sessionId,
41758
42187
  task_id: scope.taskId,
41759
42188
  branch_id: scope.branchId,
@@ -41767,7 +42196,7 @@ var init_session_token_service = __esm({
41767
42196
  iss: "agor"
41768
42197
  // Must match Feathers jwtOptions.issuer
41769
42198
  };
41770
- const token = jwt11.sign(payload, this.jwtSecret, {
42199
+ const token = jwt12.sign(payload, this.jwtSecret, {
41771
42200
  algorithm: "HS256"
41772
42201
  // Must match Feathers jwtOptions.algorithm
41773
42202
  // No expiresIn here - we set exp directly in payload
@@ -42012,16 +42441,16 @@ import {
42012
42441
  loadConfigFromFile,
42013
42442
  renderGitConfigParametersForLog,
42014
42443
  resolveGitConfigParameters,
42015
- resolveMultiTenancyConfig as resolveMultiTenancyConfig7,
42444
+ resolveMultiTenancyConfig as resolveMultiTenancyConfig8,
42016
42445
  resolveSecurity,
42017
42446
  resolveTenantContext as resolveTenantContext6,
42018
42447
  saveConfig as saveConfig4,
42019
42448
  TenantResolutionError as TenantResolutionError5
42020
42449
  } from "@agor/core/config";
42021
- import { getDatabaseUrl, runWithTenantDatabaseScope as runWithTenantDatabaseScope11 } from "@agor/core/db";
42450
+ import { getDatabaseUrl, runWithTenantDatabaseScope as runWithTenantDatabaseScope13 } from "@agor/core/db";
42022
42451
  import {
42023
42452
  authenticate,
42024
- Forbidden as Forbidden26,
42453
+ Forbidden as Forbidden27,
42025
42454
  feathers,
42026
42455
  feathersExpress,
42027
42456
  NotAuthenticated as NotAuthenticated22,
@@ -42036,20 +42465,21 @@ import express from "express";
42036
42465
  import expressStaticGzip from "express-static-gzip";
42037
42466
 
42038
42467
  // src/auth/executor-runtime-scope.ts
42468
+ init_executor_session_token();
42039
42469
  import { Forbidden } from "@agor/core/feathers";
42040
42470
  function scopedPayload(context) {
42041
42471
  const params = context.params;
42042
42472
  const payload = params.authentication?.payload;
42043
- if (payload?.type === "executor-session") {
42044
- if (payload.purpose !== "executor-task") {
42473
+ if (payload?.type === EXECUTOR_SESSION_TOKEN_TYPE) {
42474
+ if (!isExecutorSessionTokenPayload(payload)) {
42045
42475
  throw new Forbidden("Executor token is not valid for this request");
42046
42476
  }
42047
42477
  return payload;
42048
42478
  }
42049
42479
  if (params.authentication?.strategy === "jwt" && params.task_id) {
42050
42480
  return {
42051
- type: "executor-session",
42052
- purpose: "executor-task",
42481
+ type: EXECUTOR_SESSION_TOKEN_TYPE,
42482
+ purpose: EXECUTOR_SESSION_TOKEN_PURPOSE,
42053
42483
  task_id: params.task_id,
42054
42484
  session_id: params.session_id,
42055
42485
  sessionId: params.sessionId,
@@ -42165,7 +42595,7 @@ function executorRuntimeScopeGuard() {
42165
42595
  const payload = scopedPayload(context);
42166
42596
  if (!payload) return context;
42167
42597
  const scope = {
42168
- sessionId: payload.session_id ?? payload.sessionId,
42598
+ sessionId: getExecutorSessionTokenSessionId(payload),
42169
42599
  taskId: payload.task_id,
42170
42600
  branchId: payload.branch_id
42171
42601
  };
@@ -42304,7 +42734,7 @@ import {
42304
42734
  isUnixImpersonationEnabled,
42305
42735
  loadConfig,
42306
42736
  resolveExecutionSecurityMode,
42307
- resolveMultiTenancyConfig as resolveMultiTenancyConfig3,
42737
+ resolveMultiTenancyConfig as resolveMultiTenancyConfig4,
42308
42738
  resolveMultiTenancyDatabaseDialect,
42309
42739
  resolveTenantContext as resolveTenantContext3,
42310
42740
  TenantResolutionError as TenantResolutionError3,
@@ -42461,6 +42891,17 @@ var gatewayRouteHook = async (context) => {
42461
42891
  return context;
42462
42892
  };
42463
42893
 
42894
+ // src/oauth-auth-helpers.ts
42895
+ import { runWithTenantDatabaseScope } from "@agor/core/db";
42896
+ async function runInOAuthTenantScope(db, tenantId, work) {
42897
+ if (!tenantId) return work();
42898
+ return runWithTenantDatabaseScope(db, tenantId, work);
42899
+ }
42900
+ function resolveForUserIdWithGate(opts) {
42901
+ const canUseExplicitUser = opts.isServiceAccount === true || opts.authPayloadType === "executor-session" && opts.queryForUserId === opts.callerUserId;
42902
+ return opts.queryForUserId && canUseExplicitUser ? opts.queryForUserId : opts.callerUserId;
42903
+ }
42904
+
42464
42905
  // src/services/groups.ts
42465
42906
  init_branch_authorization();
42466
42907
  import { BoardRepository, GroupRepository } from "@agor/core/db";
@@ -43676,13 +44117,29 @@ function injectCreatedBy() {
43676
44117
  }
43677
44118
 
43678
44119
  // src/utils/mcp-header-secrets.ts
44120
+ init_executor_session_token();
43679
44121
  import { redactMCPAuthSecrets } from "@agor/core/tools/mcp/auth-secrets";
43680
44122
  import { redactMCPCustomHeaders } from "@agor/core/tools/mcp/http-headers";
44123
+ import jwt2 from "jsonwebtoken";
43681
44124
  function shouldExposeMCPServerSecretsForSessionToken(params, options = {}) {
43682
- const payload = params?.authentication?.payload;
43683
- const sessionId = params?.session_id ?? payload?.session_id ?? payload?.sessionId;
44125
+ const payload = params?.authentication?.payload ?? decodeExecutorSessionPayload(params);
44126
+ const sessionId = params?.session_id ?? getPayloadSessionId(payload);
43684
44127
  return !!params?.provider && (params.authentication?.strategy === "session-token" || payload?.type === "executor-session") && !!sessionId && (!options.sessionId || sessionId === options.sessionId);
43685
44128
  }
44129
+ function decodeExecutorSessionPayload(params) {
44130
+ if (params?.authentication?.strategy !== "jwt" || !params.authentication.accessToken) {
44131
+ return void 0;
44132
+ }
44133
+ const decoded = jwt2.decode(params.authentication.accessToken);
44134
+ if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) {
44135
+ return void 0;
44136
+ }
44137
+ if (!isExecutorSessionTokenPayload(decoded)) return void 0;
44138
+ return decoded;
44139
+ }
44140
+ function getPayloadSessionId(payload) {
44141
+ return payload ? getExecutorSessionTokenSessionId(payload) : void 0;
44142
+ }
43686
44143
  function shouldExposeMCPServerSecrets(params, options = {}) {
43687
44144
  if (!params?.provider) return true;
43688
44145
  if (params.user?._isServiceAccount || params.user?.role === "service") return true;
@@ -43778,6 +44235,7 @@ var RealtimeAccessCache = class {
43778
44235
  }
43779
44236
  branchVisibility = /* @__PURE__ */ new Map();
43780
44237
  sessionBranches = /* @__PURE__ */ new Map();
44238
+ sessionOwners = /* @__PURE__ */ new Map();
43781
44239
  branchVisibilityTtlMs;
43782
44240
  sessionBranchTtlMs;
43783
44241
  now;
@@ -43794,6 +44252,27 @@ var RealtimeAccessCache = class {
43794
44252
  });
43795
44253
  return branchId;
43796
44254
  }
44255
+ /**
44256
+ * Owning user id for a session, cached on the same cadence as the
44257
+ * session→branch map. Used only to offer streaming events to the session
44258
+ * creator's own connections as a fallback when they haven't subscribed to
44259
+ * the per-session stream channel yet.
44260
+ */
44261
+ async getSessionOwnerId(sessionId) {
44262
+ const cached2 = this.sessionOwners.get(sessionId);
44263
+ const now = this.now();
44264
+ if (cached2 && cached2.expiresAt > now) {
44265
+ return cached2.ownerId;
44266
+ }
44267
+ const ownerId = await this.options.sessionsRepository.findCreatedByBySessionId(
44268
+ sessionId
44269
+ ) ?? null;
44270
+ this.sessionOwners.set(sessionId, {
44271
+ ownerId,
44272
+ expiresAt: now + this.sessionBranchTtlMs
44273
+ });
44274
+ return ownerId;
44275
+ }
43797
44276
  async getBranchVisibility(branchId) {
43798
44277
  const cached2 = this.branchVisibility.get(branchId);
43799
44278
  const now = this.now();
@@ -43829,6 +44308,7 @@ var RealtimeAccessCache = class {
43829
44308
  }
43830
44309
  invalidateSession(sessionId) {
43831
44310
  this.sessionBranches.delete(sessionId);
44311
+ this.sessionOwners.delete(sessionId);
43832
44312
  }
43833
44313
  clearVisibility() {
43834
44314
  this.branchVisibility.clear();
@@ -43836,6 +44316,7 @@ var RealtimeAccessCache = class {
43836
44316
  clearAll() {
43837
44317
  this.branchVisibility.clear();
43838
44318
  this.sessionBranches.clear();
44319
+ this.sessionOwners.clear();
43839
44320
  }
43840
44321
  visibilityFromEntry(entry) {
43841
44322
  return entry.mode === "allAuthenticated" ? { mode: "allAuthenticated" } : { mode: "explicitUsers", userIds: entry.userIds };
@@ -43853,6 +44334,26 @@ import { hasMinimumRole as hasMinimumRole7, ROLES as ROLES8 } from "@agor/core/t
43853
44334
  function tenantChannelName(tenantId) {
43854
44335
  return `tenant:${tenantId}`;
43855
44336
  }
44337
+ var SESSION_STREAM_CHANNEL_PREFIX = "session-stream:";
44338
+ function sessionStreamChannelName(sessionId) {
44339
+ return `${SESSION_STREAM_CHANNEL_PREFIX}${sessionId}`;
44340
+ }
44341
+ function leaveAllSessionStreamChannels(app, connection) {
44342
+ for (const name of app.channels ?? []) {
44343
+ if (name.startsWith(SESSION_STREAM_CHANNEL_PREFIX)) {
44344
+ app.channel(name).leave(connection);
44345
+ }
44346
+ }
44347
+ }
44348
+ function existingChannel(app, name) {
44349
+ return (app.channels ?? []).includes(name) ? app.channel(name) : null;
44350
+ }
44351
+ function joinSessionStreamChannel(app, sessionId, connection) {
44352
+ app.channel(sessionStreamChannelName(sessionId)).join(connection);
44353
+ }
44354
+ function leaveSessionStreamChannel(app, sessionId, connection) {
44355
+ existingChannel(app, sessionStreamChannelName(sessionId))?.leave(connection);
44356
+ }
43856
44357
  var DEBUG_REALTIME_PUBLISH = process.env.AGOR_DEBUG_REALTIME_PUBLISH === "1" || process.env.DEBUG?.includes("realtime-publish");
43857
44358
  function realtimePublishDebug(...args) {
43858
44359
  if (DEBUG_REALTIME_PUBLISH) {
@@ -43868,8 +44369,27 @@ var SESSION_ID_SCOPED_PATHS = /* @__PURE__ */ new Set([
43868
44369
  "session-env-selections"
43869
44370
  ]);
43870
44371
  var OPTIONAL_BRANCH_OR_SESSION_SCOPED_PATHS = /* @__PURE__ */ new Set(["board-objects", "board-comments"]);
44372
+ var MESSAGE_STREAMING_EVENTS = /* @__PURE__ */ new Set([
44373
+ "streaming:start",
44374
+ "streaming:chunk",
44375
+ "streaming:end",
44376
+ "streaming:error",
44377
+ "thinking:start",
44378
+ "thinking:chunk",
44379
+ "thinking:end"
44380
+ ]);
44381
+ var TASK_STREAMING_EVENTS = /* @__PURE__ */ new Set(["thinking:chunk", "tool:start", "tool:complete"]);
43871
44382
  function isStreamingEvent(context) {
43872
- return context.path === "messages/streaming" || context.path === "messages" && context.event?.startsWith("streaming:") === true;
44383
+ if (context.path === "messages/streaming") return true;
44384
+ const event = context.event;
44385
+ if (!event) return false;
44386
+ if (context.path === "messages") {
44387
+ return event.startsWith("streaming:") || MESSAGE_STREAMING_EVENTS.has(event);
44388
+ }
44389
+ if (context.path === "tasks") {
44390
+ return TASK_STREAMING_EVENTS.has(event);
44391
+ }
44392
+ return false;
43873
44393
  }
43874
44394
  function asRecord2(value) {
43875
44395
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -44005,6 +44525,47 @@ async function resolvePublishScope(data, context, accessCache) {
44005
44525
  function filterToServiceConnections(authenticated) {
44006
44526
  return authenticated.filter((connection) => isServiceConnection(connection));
44007
44527
  }
44528
+ async function resolveStreamingDelivery(app, data, tenantScoped, accessCache, branchRbacEnabled, allowSuperadmin) {
44529
+ const serviceConnections = filterToServiceConnections(tenantScoped);
44530
+ const sessionId = extractSessionId(data);
44531
+ if (!sessionId) return serviceConnections;
44532
+ const tenantConnections = new Set(
44533
+ tenantScoped.connections
44534
+ );
44535
+ const existingRoom = existingChannel(app, sessionStreamChannelName(sessionId));
44536
+ const room = existingRoom ? existingRoom.filter((connection) => tenantConnections.has(connection)) : null;
44537
+ let ownerId = null;
44538
+ try {
44539
+ ownerId = await accessCache.getSessionOwnerId(sessionId);
44540
+ } catch {
44541
+ }
44542
+ const ownerChannel = () => tenantScoped.filter(
44543
+ (connection) => userFromConnection(connection)?.user_id === ownerId
44544
+ );
44545
+ if (!branchRbacEnabled) {
44546
+ const channels2 = [serviceConnections];
44547
+ if (room) channels2.push(room);
44548
+ if (ownerId) channels2.push(ownerChannel());
44549
+ return channels2;
44550
+ }
44551
+ const branchId = await accessCache.getBranchIdForSession(sessionId);
44552
+ const visibility = branchId ? await accessCache.getBranchVisibility(branchId) : null;
44553
+ if (!visibility) return serviceConnections;
44554
+ if (visibility.mode === "allAuthenticated") {
44555
+ const channels2 = [serviceConnections];
44556
+ if (room) channels2.push(room);
44557
+ if (ownerId) channels2.push(ownerChannel());
44558
+ return channels2;
44559
+ }
44560
+ const channels = [serviceConnections];
44561
+ if (room) {
44562
+ channels.push(filterToUserIdsOrSuperadmins(room, visibility.userIds, allowSuperadmin));
44563
+ }
44564
+ if (ownerId && visibility.userIds.has(ownerId)) {
44565
+ channels.push(ownerChannel());
44566
+ }
44567
+ return channels;
44568
+ }
44008
44569
  function filterToUserIdsOrAdmins(authenticated, userIds, allowSuperadmin) {
44009
44570
  return authenticated.filter((connection) => {
44010
44571
  if (isServiceConnection(connection) || isAdminConnection(connection, allowSuperadmin)) {
@@ -44078,6 +44639,16 @@ function configureRealtimePublish(options) {
44078
44639
  throw error51;
44079
44640
  }
44080
44641
  }
44642
+ if (isStreamingEvent(context)) {
44643
+ return resolveStreamingDelivery(
44644
+ app,
44645
+ data,
44646
+ tenantScoped,
44647
+ accessCache,
44648
+ branchRbacEnabled,
44649
+ allowSuperadmin
44650
+ );
44651
+ }
44081
44652
  if (!branchRbacEnabled) return tenantScoped;
44082
44653
  const scope = await resolvePublishScope(data, context, accessCache);
44083
44654
  if (scope.kind === "global") return tenantScoped;
@@ -44210,28 +44781,9 @@ function recomputeNextRunAt() {
44210
44781
  };
44211
44782
  }
44212
44783
 
44213
- // src/utils/session-task-state.ts
44214
- import {
44215
- isTerminalTaskStatus,
44216
- SessionStatus as SessionStatus2,
44217
- sessionCanStartTask,
44218
- TaskStatus
44219
- } from "@agor/core/types";
44220
- function isTaskBlockingPrompt(task) {
44221
- return task.status !== TaskStatus.QUEUED && !isTerminalTaskStatus(task.status);
44222
- }
44223
- function isTerminalQueueProcessingSuppressed(params) {
44224
- return params?.suppressTerminalQueueProcessing === true;
44225
- }
44226
- function shouldReconcileSessionPromptState(session, tasks3, options = {}) {
44227
- if (session.status !== SessionStatus2.FAILED) return false;
44228
- if (session.ready_for_prompt === true) return false;
44229
- const ignoredTaskIds = new Set(options.ignoredTaskIds ?? []);
44230
- return !tasks3.some((task) => !ignoredTaskIds.has(task.task_id) && isTaskBlockingPrompt(task));
44231
- }
44232
-
44233
- // src/register-hooks.ts
44234
- init_spawn_executor();
44784
+ // src/utils/session-queue-tenant-scope.ts
44785
+ import { resolveMultiTenancyConfig as resolveMultiTenancyConfig2 } from "@agor/core/config";
44786
+ import { runWithTenantDatabaseScope as runWithTenantDatabaseScope3 } from "@agor/core/db";
44235
44787
 
44236
44788
  // src/utils/tenant-db-scope.ts
44237
44789
  init_runtime_tokens();
@@ -44244,10 +44796,10 @@ import {
44244
44796
  enqueueTenantDatabasePostCommitCallback,
44245
44797
  getCurrentTenantId as getCurrentTenantId2,
44246
44798
  runWithoutTenantDatabaseScope,
44247
- runWithTenantDatabaseScope
44799
+ runWithTenantDatabaseScope as runWithTenantDatabaseScope2
44248
44800
  } from "@agor/core/db";
44249
44801
  import { NotAuthenticated as NotAuthenticated7 } from "@agor/core/feathers";
44250
- import jwt2 from "jsonwebtoken";
44802
+ import jwt3 from "jsonwebtoken";
44251
44803
  function readHeaderValue(headers, name) {
44252
44804
  if (!headers) return null;
44253
44805
  const wanted = name.toLowerCase();
@@ -44276,7 +44828,7 @@ function deferWithTenantDatabaseScope(db, params, work, onError) {
44276
44828
  const schedule = () => {
44277
44829
  runWithoutTenantDatabaseScope(() => {
44278
44830
  setImmediate(() => {
44279
- void runWithTenantDatabaseScope(db, tenantId, work).catch((error51) => {
44831
+ void runWithTenantDatabaseScope2(db, tenantId, work).catch((error51) => {
44280
44832
  if (onError) {
44281
44833
  onError(error51);
44282
44834
  return;
@@ -44298,7 +44850,7 @@ function createTenantDatabaseScopeAroundHook(options) {
44298
44850
  const match = authorization?.match(/^Bearer\s+(.+)$/i);
44299
44851
  if (!match || !options.jwtSecret) return void 0;
44300
44852
  try {
44301
- return jwt2.verify(match[1], options.jwtSecret, {
44853
+ return jwt3.verify(match[1], options.jwtSecret, {
44302
44854
  issuer: RUNTIME_JWT_ISSUER,
44303
44855
  audience: RUNTIME_JWT_AUDIENCE
44304
44856
  });
@@ -44333,11 +44885,104 @@ function createTenantDatabaseScopeAroundHook(options) {
44333
44885
  }
44334
44886
  throw error51;
44335
44887
  }
44336
- await runWithTenantDatabaseScope(options.db, context.params.tenant?.tenant_id, next);
44888
+ await runWithTenantDatabaseScope2(options.db, context.params.tenant?.tenant_id, next);
44337
44889
  };
44338
44890
  }
44339
44891
 
44892
+ // src/utils/session-queue-tenant-scope.ts
44893
+ function staticTenantId(config2) {
44894
+ const multiTenancy = resolveMultiTenancyConfig2(config2);
44895
+ return multiTenancy.mode === "static" ? multiTenancy.static_tenant_id : void 0;
44896
+ }
44897
+ function trustedTenantIdHint(options) {
44898
+ const hint = options.tenantIdHint?.trim();
44899
+ return hint || void 0;
44900
+ }
44901
+ function queueTenantParams(params, tenantId, source = "explicit") {
44902
+ const currentTenant = params?.tenant;
44903
+ return {
44904
+ ...params ?? {},
44905
+ tenant: {
44906
+ ...currentTenant,
44907
+ tenant_id: tenantId,
44908
+ source: currentTenant?.source ?? source
44909
+ }
44910
+ };
44911
+ }
44912
+ async function runWithSessionQueueTenantScope(options, work) {
44913
+ const capturedTenantId = resolveTenantIdForDeferredScope(options.params);
44914
+ if (capturedTenantId) {
44915
+ const scopedParams2 = queueTenantParams(options.params, capturedTenantId, "explicit");
44916
+ return runWithTenantDatabaseScope3(options.db, capturedTenantId, () => work(scopedParams2));
44917
+ }
44918
+ const tenantIdHint = trustedTenantIdHint(options);
44919
+ if (tenantIdHint) {
44920
+ const scopedParams2 = queueTenantParams(options.params, tenantIdHint, "explicit");
44921
+ return runWithTenantDatabaseScope3(options.db, tenantIdHint, () => work(scopedParams2));
44922
+ }
44923
+ const configuredStaticTenantId = staticTenantId(options.config);
44924
+ if (!configuredStaticTenantId) {
44925
+ console.error(
44926
+ `\u274C [Queue] ${options.label} skipped for session ${options.sessionId}: missing tenant context`
44927
+ );
44928
+ return void 0;
44929
+ }
44930
+ const scopedParams = queueTenantParams(options.params, configuredStaticTenantId, "static");
44931
+ return runWithTenantDatabaseScope3(options.db, configuredStaticTenantId, () => work(scopedParams));
44932
+ }
44933
+ function deferWithSessionQueueTenantScope(options, work, onError) {
44934
+ const handleError = (error51) => {
44935
+ if (onError) {
44936
+ onError(error51);
44937
+ return;
44938
+ }
44939
+ console.error(`\u274C [Queue] ${options.label} failed:`, error51);
44940
+ };
44941
+ const capturedTenantId = resolveTenantIdForDeferredScope(options.params);
44942
+ if (capturedTenantId) {
44943
+ const scopedParams2 = queueTenantParams(options.params, capturedTenantId, "explicit");
44944
+ deferWithTenantDatabaseScope(options.db, scopedParams2, () => work(scopedParams2), handleError);
44945
+ return;
44946
+ }
44947
+ const tenantIdHint = trustedTenantIdHint(options);
44948
+ if (tenantIdHint) {
44949
+ const scopedParams2 = queueTenantParams(options.params, tenantIdHint, "explicit");
44950
+ deferWithTenantDatabaseScope(options.db, scopedParams2, () => work(scopedParams2), handleError);
44951
+ return;
44952
+ }
44953
+ const configuredStaticTenantId = staticTenantId(options.config);
44954
+ if (!configuredStaticTenantId) {
44955
+ handleError(
44956
+ new Error(`${options.label} skipped for session ${options.sessionId}: missing tenant context`)
44957
+ );
44958
+ return;
44959
+ }
44960
+ const scopedParams = queueTenantParams(options.params, configuredStaticTenantId, "static");
44961
+ deferWithTenantDatabaseScope(options.db, scopedParams, () => work(scopedParams), handleError);
44962
+ }
44963
+
44964
+ // src/utils/session-task-state.ts
44965
+ import {
44966
+ isTerminalTaskStatus,
44967
+ SessionStatus as SessionStatus2,
44968
+ sessionCanStartTask,
44969
+ TaskStatus
44970
+ } from "@agor/core/types";
44971
+ function isTaskBlockingPrompt(task) {
44972
+ return task.status !== TaskStatus.QUEUED && !isTerminalTaskStatus(task.status);
44973
+ }
44974
+ function isTerminalQueueProcessingSuppressed(params) {
44975
+ return params?.suppressTerminalQueueProcessing === true;
44976
+ }
44977
+ function shouldReconcileSessionPromptState(session, tasks3, options = {}) {
44978
+ if (session.status !== SessionStatus2.FAILED) return false;
44979
+ if (session.ready_for_prompt === true) return false;
44980
+ const ignoredTaskIds = new Set(options.ignoredTaskIds ?? []);
44981
+ return !tasks3.some((task) => !ignoredTaskIds.has(task.task_id) && isTaskBlockingPrompt(task));
44982
+ }
44983
+
44340
44984
  // src/register-hooks.ts
44985
+ init_spawn_executor();
44341
44986
  var DEBUG_MCP_TOKENS2 = process.env.AGOR_DEBUG_MCP_TOKENS === "1" || process.env.DEBUG?.includes("mcp-tokens");
44342
44987
  function mcpTokenDebug2(...args) {
44343
44988
  if (DEBUG_MCP_TOKENS2) {
@@ -44449,6 +45094,10 @@ function shouldRunSessionPostTurnHooks(session) {
44449
45094
  function shouldDrainQueueAfterSessionPostTurnPatch(session, params) {
44450
45095
  return shouldRunSessionPostTurnHooks(session) && session.ready_for_prompt === true && !isTerminalQueueProcessingSuppressed(params);
44451
45096
  }
45097
+ function getTrustedSessionTenantId(session) {
45098
+ const tenantId = session?.tenant_id;
45099
+ return typeof tenantId === "string" && tenantId.length > 0 ? tenantId : void 0;
45100
+ }
44452
45101
  async function enrichSessionFindResultWithRemoteRelationships(result, sessionsService) {
44453
45102
  if (isRemoteRelationshipsEnrichedResult(result)) return result;
44454
45103
  if (Array.isArray(result)) {
@@ -44463,6 +45112,7 @@ async function enrichSessionFindResultWithRemoteRelationships(result, sessionsSe
44463
45112
  }
44464
45113
  var TENANT_OWNED_SERVICE_PATHS = [
44465
45114
  "sessions",
45115
+ "sessions/:id/mcp-servers",
44466
45116
  "session-relationships",
44467
45117
  "tasks",
44468
45118
  "messages",
@@ -44479,6 +45129,14 @@ var TENANT_OWNED_SERVICE_PATHS = [
44479
45129
  "boards/:id/group-grants",
44480
45130
  "app-variables",
44481
45131
  "mcp-servers",
45132
+ "mcp-servers/discover",
45133
+ "mcp-servers/oauth-auth-headers",
45134
+ "mcp-servers/oauth-complete",
45135
+ "mcp-servers/oauth-disconnect",
45136
+ "mcp-servers/oauth-refresh",
45137
+ "mcp-servers/oauth-start",
45138
+ "mcp-servers/oauth-status",
45139
+ "mcp-servers/test-oauth",
44482
45140
  "card-types",
44483
45141
  "cards",
44484
45142
  "artifacts",
@@ -44525,7 +45183,7 @@ function registerHooks(ctx) {
44525
45183
  return void 0;
44526
45184
  }
44527
45185
  };
44528
- const multiTenancy = resolveMultiTenancyConfig3(config2);
45186
+ const multiTenancy = resolveMultiTenancyConfig4(config2);
44529
45187
  const tenantColumnsEnabled = resolveMultiTenancyDatabaseDialect(config2) === "postgresql";
44530
45188
  const executionMode = resolveExecutionSecurityMode(config2);
44531
45189
  const tenantOwnedServicePaths = TENANT_OWNED_SERVICE_PATHS;
@@ -44683,16 +45341,36 @@ function registerHooks(ctx) {
44683
45341
  );
44684
45342
  return context;
44685
45343
  };
44686
- const syncUnixAccessForAllBranches = async (context, logPrefix) => {
45344
+ const membershipGroupIdFromContext = (context) => {
45345
+ const resultGroupId = context.result?.group_id;
45346
+ if (typeof resultGroupId === "string" && resultGroupId.length > 0) {
45347
+ return resultGroupId;
45348
+ }
45349
+ const dataGroupId = context.data?.group_id;
45350
+ if (typeof dataGroupId === "string" && dataGroupId.length > 0) return dataGroupId;
45351
+ const queryGroupId = context.params.query?.group_id;
45352
+ if (typeof queryGroupId === "string" && queryGroupId.length > 0) return queryGroupId;
45353
+ const routeGroupId = context.params.route?.groupId;
45354
+ if (typeof routeGroupId === "string" && routeGroupId.length > 0) return routeGroupId;
45355
+ return void 0;
45356
+ };
45357
+ const syncUnixAccessForGroupGrantedBranches = async (context, logPrefix) => {
44687
45358
  if (!executionMode.unixFsIsolationEnabled) return context;
44688
- const branches3 = await branchRepository.findAll({ includeArchived: false });
44689
- for (const branch of branches3) {
44690
- syncBranchUnixAccess(
44691
- branch.branch_id,
44692
- logPrefix,
44693
- context.params
45359
+ const groupId = membershipGroupIdFromContext(context);
45360
+ if (!groupId) {
45361
+ console.warn(
45362
+ `[Unix Integration] Could not resolve group_id for ${context.path}.${context.method}; skipping group membership permission sync`
44694
45363
  );
44695
- await invalidateRealtimeBranchAccess(branch.branch_id);
45364
+ return context;
45365
+ }
45366
+ const branchIds = await branchRepository.findExplicitFsAccessBranchIdsForGroup(groupId);
45367
+ if (branchIds.length === 0) return context;
45368
+ console.log(
45369
+ `[Unix Integration] Queueing group membership permission sync for ${branchIds.length} branch(es) granted to group ${shortId5(groupId)}`
45370
+ );
45371
+ for (const branchId of branchIds) {
45372
+ syncBranchUnixAccess(branchId, logPrefix, context.params);
45373
+ await invalidateRealtimeBranchAccess(branchId);
44696
45374
  }
44697
45375
  return context;
44698
45376
  };
@@ -45236,7 +45914,13 @@ function registerHooks(ctx) {
45236
45914
  });
45237
45915
  const injectPerUserOAuthTokens = async (context) => {
45238
45916
  const queryForUserId = context.params?.query?.forUserId;
45239
- const userId = context.params?.user?.user_id || queryForUserId;
45917
+ const authPayloadType = context.params?.authentication?.payload?.type;
45918
+ const userId = resolveForUserIdWithGate({
45919
+ queryForUserId,
45920
+ isServiceAccount: context.params?.user?._isServiceAccount,
45921
+ authPayloadType,
45922
+ callerUserId: context.params?.user?.user_id
45923
+ });
45240
45924
  if (!userId) {
45241
45925
  return context;
45242
45926
  }
@@ -45593,11 +46277,11 @@ function registerHooks(ctx) {
45593
46277
  after: {
45594
46278
  create: [
45595
46279
  clearRealtimeBranchVisibility,
45596
- (context) => syncUnixAccessForAllBranches(context, "[Executor/group-memberships.create]")
46280
+ (context) => syncUnixAccessForGroupGrantedBranches(context, "[Executor/group-memberships.create]")
45597
46281
  ],
45598
46282
  remove: [
45599
46283
  clearRealtimeBranchVisibility,
45600
- (context) => syncUnixAccessForAllBranches(context, "[Executor/group-memberships.remove]")
46284
+ (context) => syncUnixAccessForGroupGrantedBranches(context, "[Executor/group-memberships.remove]")
45601
46285
  ]
45602
46286
  }
45603
46287
  });
@@ -46243,19 +46927,29 @@ function registerHooks(ctx) {
46243
46927
  }
46244
46928
  });
46245
46929
  if (shouldDrainQueueAfterSessionPostTurnPatch(session, context.params)) {
46246
- deferWithTenantDatabaseScope(db, context.params, async () => {
46247
- try {
46930
+ const sessionTenantId = getTrustedSessionTenantId(session);
46931
+ deferWithSessionQueueTenantScope(
46932
+ {
46933
+ db,
46934
+ config: config2,
46935
+ sessionId: session.session_id,
46936
+ params: context.params,
46937
+ tenantIdHint: sessionTenantId,
46938
+ label: "SessionsService.after.patch queue drain"
46939
+ },
46940
+ async (queueParams) => {
46248
46941
  console.log(
46249
46942
  `\u{1F504} [SessionsService.after.patch] Session ${shortId5(session.session_id)} became promptable (${session.status}), checking for queued tasks...`
46250
46943
  );
46251
- await sessionsService.triggerQueueProcessing(session.session_id, context.params);
46252
- } catch (error51) {
46944
+ await sessionsService.triggerQueueProcessing(session.session_id, queueParams);
46945
+ },
46946
+ (error51) => {
46253
46947
  console.error(
46254
46948
  `\u274C [SessionsService.after.patch] Failed to process queue for session ${shortId5(session.session_id)}:`,
46255
46949
  error51
46256
46950
  );
46257
46951
  }
46258
- });
46952
+ );
46259
46953
  } else {
46260
46954
  console.log(
46261
46955
  `\u23ED\uFE0F [SessionsService.after.patch] Queue drain suppressed for session ${shortId5(session.session_id)} (suppressTerminalQueueProcessing or not ready)`
@@ -46464,6 +47158,17 @@ function registerHooks(ctx) {
46464
47158
  context.dispatch = result;
46465
47159
  return context;
46466
47160
  }
47161
+ if (_action === "mergeObjectFields" && objects) {
47162
+ if (!context.id) throw new Error("Board ID required");
47163
+ const result = await boardsService.mergeBoardObjectFields(
47164
+ context.id,
47165
+ objects
47166
+ );
47167
+ context.result = result;
47168
+ app.service("boards").emit("patched", result);
47169
+ context.dispatch = result;
47170
+ return context;
47171
+ }
46467
47172
  if (_action === "deleteZone" && objectId) {
46468
47173
  if (!context.id) throw new Error("Board ID required");
46469
47174
  const result = await boardsService.deleteZone(
@@ -46676,7 +47381,7 @@ function registerHooks(ctx) {
46676
47381
  import {
46677
47382
  loadConfig as loadConfig4,
46678
47383
  resolveBranchStorageConfig,
46679
- resolveMultiTenancyConfig as resolveMultiTenancyConfig5
47384
+ resolveMultiTenancyConfig as resolveMultiTenancyConfig6
46680
47385
  } from "@agor/core/config";
46681
47386
  import {
46682
47387
  BranchRepository as BranchRepository9,
@@ -46697,7 +47402,7 @@ import {
46697
47402
  BadRequest as BadRequest9,
46698
47403
  Conflict as Conflict2,
46699
47404
  errorHandler,
46700
- Forbidden as Forbidden13,
47405
+ Forbidden as Forbidden14,
46701
47406
  LocalStrategy,
46702
47407
  NotAuthenticated as NotAuthenticated15,
46703
47408
  NotFound as NotFound5
@@ -46705,9 +47410,9 @@ import {
46705
47410
  import { PermissionService } from "@agor/core/permissions";
46706
47411
  import {
46707
47412
  AGENTIC_TOOL_CAPABILITIES as AGENTIC_TOOL_CAPABILITIES2,
46708
- hasMinimumRole as hasMinimumRole12,
47413
+ hasMinimumRole as hasMinimumRole14,
46709
47414
  MessageRole as MessageRole4,
46710
- ROLES as ROLES15,
47415
+ ROLES as ROLES17,
46711
47416
  SERVICE_GROUP_NAMES as SERVICE_GROUP_NAMES2,
46712
47417
  SessionStatus as SessionStatus6,
46713
47418
  TaskStatus as TaskStatus5
@@ -46715,12 +47420,64 @@ import {
46715
47420
  import { NotFoundError as NotFoundError4 } from "@agor/core/utils/errors";
46716
47421
  import { rateLimit as rateLimit2 } from "express-rate-limit";
46717
47422
 
47423
+ // src/auth/issue-browser-tokens-hook.ts
47424
+ init_runtime_tokens();
47425
+ init_token_invalidation();
47426
+
47427
+ // src/auth/user-redaction.ts
47428
+ function redactUserAuthMetadata(user) {
47429
+ const {
47430
+ tokens_valid_after: _tokensValidAfter,
47431
+ password: _password,
47432
+ tenant_id: _tenantId,
47433
+ ...publicUser
47434
+ } = user;
47435
+ return publicUser;
47436
+ }
47437
+
47438
+ // src/auth/issue-browser-tokens-hook.ts
47439
+ var MACHINE_TOKEN_TYPES = /* @__PURE__ */ new Set(["executor-session", "service"]);
47440
+ function createIssueBrowserTokensHook(options) {
47441
+ const { jwtSecret, accessTokenTtl, refreshTokenTtl, tenantClaim, debug } = options;
47442
+ return async (context) => {
47443
+ debug?.("\u2705 Authentication succeeded:", {
47444
+ strategy: context.result?.authentication?.strategy,
47445
+ hasUser: !!context.result?.user,
47446
+ user_id: context.result?.user?.user_id,
47447
+ hasAccessToken: !!context.result?.accessToken
47448
+ });
47449
+ if (!context.result?.user) {
47450
+ return context;
47451
+ }
47452
+ const payloadType = context.result.authentication?.payload?.type;
47453
+ if (typeof payloadType === "string" && MACHINE_TOKEN_TYPES.has(payloadType)) {
47454
+ context.result.user = redactUserAuthMetadata(context.result.user);
47455
+ return context;
47456
+ }
47457
+ const tenantId = context.params?.tenant?.tenant_id ?? context.result.user.tenant_id;
47458
+ const tokens = issueRuntimeTokenPair(
47459
+ context.result.user,
47460
+ jwtSecret,
47461
+ accessTokenTtl,
47462
+ refreshTokenTtl,
47463
+ {
47464
+ ...authTokenIssuedAtClaim(Date.now(), context.result.user),
47465
+ ...runtimeTenantClaims(tenantId, tenantClaim)
47466
+ }
47467
+ );
47468
+ context.result.accessToken = tokens.accessToken;
47469
+ context.result.refreshToken = tokens.refreshToken;
47470
+ context.result.user = redactUserAuthMetadata(context.result.user);
47471
+ return context;
47472
+ };
47473
+ }
47474
+
46718
47475
  // src/auth/launch-auth.ts
46719
47476
  init_runtime_tokens();
46720
47477
  init_token_invalidation();
46721
47478
  import { createHash, createPublicKey, randomBytes } from "crypto";
46722
47479
  import {
46723
- resolveMultiTenancyConfig as resolveMultiTenancyConfig4,
47480
+ resolveMultiTenancyConfig as resolveMultiTenancyConfig5,
46724
47481
  resolveTenantContext as resolveTenantContext4,
46725
47482
  TenantResolutionError as TenantResolutionError4
46726
47483
  } from "@agor/core/config";
@@ -46730,27 +47487,14 @@ import {
46730
47487
  hash as hash2,
46731
47488
  insert as insert2,
46732
47489
  reattributeLegacyAnonymousRows,
46733
- runWithTenantDatabaseScope as runWithTenantDatabaseScope3,
47490
+ runWithTenantDatabaseScope as runWithTenantDatabaseScope5,
46734
47491
  select as select2,
46735
47492
  update as update2,
46736
47493
  users as users2
46737
47494
  } from "@agor/core/db";
46738
47495
  import { BadRequest as BadRequest6, NotAuthenticated as NotAuthenticated10 } from "@agor/core/feathers";
46739
47496
  import { normalizeRole as normalizeRole2, ROLES as ROLES11 } from "@agor/core/types";
46740
- import jwt4 from "jsonwebtoken";
46741
-
46742
- // src/auth/user-redaction.ts
46743
- function redactUserAuthMetadata(user) {
46744
- const {
46745
- tokens_valid_after: _tokensValidAfter,
46746
- password: _password,
46747
- tenant_id: _tenantId,
46748
- ...publicUser
46749
- } = user;
46750
- return publicUser;
46751
- }
46752
-
46753
- // src/auth/launch-auth.ts
47497
+ import jwt5 from "jsonwebtoken";
46754
47498
  var DEFAULT_TIMEOUT_MS = 1e4;
46755
47499
  var DEFAULT_SERVICE_TOKEN_ENV = "AGOR_EXTERNAL_LAUNCH_SERVICE_TOKEN";
46756
47500
  var DEFAULT_SHARED_SECRET_ENV = "AGOR_EXTERNAL_LAUNCH_SHARED_SECRET";
@@ -47015,13 +47759,13 @@ async function resolveVerificationKey(header, settings) {
47015
47759
  return createPublicKey({ key: jwk, format: "jwk" });
47016
47760
  }
47017
47761
  async function verifyLaunchAssertion(assertion, settings) {
47018
- const decoded = jwt4.decode(assertion, { complete: true });
47762
+ const decoded = jwt5.decode(assertion, { complete: true });
47019
47763
  if (!decoded || typeof decoded !== "object") {
47020
47764
  throw new NotAuthenticated10("Invalid one-time launch assertion");
47021
47765
  }
47022
47766
  const key = await resolveVerificationKey(decoded.header, settings);
47023
47767
  const algorithms = settings.algorithms ?? (settings.devSharedSecret ? ["HS256"] : void 0);
47024
- const claims = jwt4.verify(assertion, key, {
47768
+ const claims = jwt5.verify(assertion, key, {
47025
47769
  issuer: settings.issuer,
47026
47770
  audience: settings.audience,
47027
47771
  algorithms
@@ -47064,7 +47808,7 @@ function issueRuntimeTokens(user, jwtSecret, accessTokenTtl, refreshTokenTtl, te
47064
47808
  };
47065
47809
  }
47066
47810
  function createLaunchAuthService(options) {
47067
- const multiTenancy = resolveMultiTenancyConfig4(options.config);
47811
+ const multiTenancy = resolveMultiTenancyConfig5(options.config);
47068
47812
  const tenantClaim = multiTenancy.auth_claim ?? "tenant_id";
47069
47813
  return {
47070
47814
  async create(data, params) {
@@ -47088,7 +47832,7 @@ function createLaunchAuthService(options) {
47088
47832
  authPayload: claims,
47089
47833
  headers: params?.headers
47090
47834
  });
47091
- return await runWithTenantDatabaseScope3(options.db, tenant.tenant_id, async () => {
47835
+ return await runWithTenantDatabaseScope5(options.db, tenant.tenant_id, async () => {
47092
47836
  const user = await upsertLaunchUser(options, claims, tenant);
47093
47837
  return issueRuntimeTokens(
47094
47838
  user,
@@ -47116,12 +47860,12 @@ function createLaunchAuthService(options) {
47116
47860
  init_runtime_tokens();
47117
47861
  init_token_invalidation();
47118
47862
  import { NotAuthenticated as NotAuthenticated11 } from "@agor/core/feathers";
47119
- import jwt5 from "jsonwebtoken";
47863
+ import jwt6 from "jsonwebtoken";
47120
47864
  function createRefreshTokenService(options) {
47121
47865
  return {
47122
47866
  async create(data, _params) {
47123
47867
  try {
47124
- const decoded = jwt5.verify(data.refreshToken, options.jwtSecret, {
47868
+ const decoded = jwt6.verify(data.refreshToken, options.jwtSecret, {
47125
47869
  issuer: RUNTIME_JWT_ISSUER,
47126
47870
  audience: RUNTIME_JWT_AUDIENCE
47127
47871
  });
@@ -47210,7 +47954,7 @@ import {
47210
47954
  getCurrentTenantId as getCurrentTenantId4,
47211
47955
  isPostgresDatabase,
47212
47956
  runWithSystemDatabaseScope,
47213
- runWithTenantDatabaseScope as runWithTenantDatabaseScope4,
47957
+ runWithTenantDatabaseScope as runWithTenantDatabaseScope6,
47214
47958
  ScheduleRepository as ScheduleRepository2,
47215
47959
  SessionMCPServerRepository as SessionMCPServerRepository2,
47216
47960
  SessionRepository as SessionRepository4,
@@ -47382,7 +48126,7 @@ var SchedulerService = class {
47382
48126
  continue;
47383
48127
  }
47384
48128
  try {
47385
- await runWithTenantDatabaseScope4(this.db, ref.tenantId, async () => {
48129
+ await runWithTenantDatabaseScope6(this.db, ref.tenantId, async () => {
47386
48130
  const schedule = await this.scheduleRepo.findById(ref.scheduleId);
47387
48131
  if (!schedule) return;
47388
48132
  await this.processSchedule(schedule, now);
@@ -47405,7 +48149,7 @@ var SchedulerService = class {
47405
48149
  }));
47406
48150
  };
47407
48151
  if (this.config.tenantId) {
47408
- return runWithTenantDatabaseScope4(
48152
+ return runWithTenantDatabaseScope6(
47409
48153
  this.db,
47410
48154
  this.config.tenantId,
47411
48155
  () => findDue(this.config.tenantId)
@@ -47875,7 +48619,7 @@ init_users();
47875
48619
  init_runtime_tokens();
47876
48620
  import { resolveProxies } from "@agor/core/config";
47877
48621
  import { rateLimit } from "express-rate-limit";
47878
- import jwt6 from "jsonwebtoken";
48622
+ import jwt7 from "jsonwebtoken";
47879
48623
  var RATE_LIMIT_PER_MINUTE = 600;
47880
48624
  var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
47881
48625
  var UPSTREAM_TIMEOUT_MS = 3e4;
@@ -47900,7 +48644,7 @@ async function authenticateRequest(req, app, jwtSecret) {
47900
48644
  if (!match) return null;
47901
48645
  let decoded;
47902
48646
  try {
47903
- decoded = jwt6.verify(match[1], jwtSecret, {
48647
+ decoded = jwt7.verify(match[1], jwtSecret, {
47904
48648
  issuer: RUNTIME_JWT_ISSUER,
47905
48649
  audience: [RUNTIME_JWT_AUDIENCE, ARTIFACT_RUNTIME_JWT_AUDIENCE]
47906
48650
  });
@@ -48612,6 +49356,14 @@ async function doResolveWidget(widgetId, action, caller, deps) {
48612
49356
  const entry = getWidget(widget.widget_type);
48613
49357
  let resultMeta;
48614
49358
  let autoResumePrompt;
49359
+ const ctx = {
49360
+ // biome-ignore lint/suspicious/noExplicitAny: Pass-through Feathers Application
49361
+ app: deps.app,
49362
+ sessionId: message.session_id,
49363
+ submitterUserId: caller.user_id,
49364
+ submitterRole: caller.role,
49365
+ sessionCreatorUserId: session.created_by
49366
+ };
48615
49367
  if (action.kind === "submit") {
48616
49368
  if (!entry) {
48617
49369
  throw new NotFound3(
@@ -48623,18 +49375,13 @@ async function doResolveWidget(widgetId, action, caller, deps) {
48623
49375
  throw new Forbidden12(`Invalid submit payload: ${parsed.error.message}`);
48624
49376
  }
48625
49377
  const submit = parsed.data;
48626
- const submitCtx = {
48627
- // biome-ignore lint/suspicious/noExplicitAny: Pass-through Feathers Application
48628
- app: deps.app,
48629
- sessionId: message.session_id,
48630
- submitterUserId: caller.user_id,
48631
- submitterRole: caller.role,
48632
- sessionCreatorUserId: session.created_by
48633
- };
48634
- await entry.applySubmit(submitCtx, submit, widget.params);
49378
+ await entry.applySubmit(ctx, submit, widget.params);
48635
49379
  resultMeta = entry.buildResultMeta(submit);
48636
49380
  autoResumePrompt = entry.buildAutoResumePrompt(resultMeta, widget.params);
48637
49381
  } else {
49382
+ if (entry?.authorizeDismiss) {
49383
+ await entry.authorizeDismiss(ctx, widget.params);
49384
+ }
48638
49385
  autoResumePrompt = entry ? entry.buildDismissedPrompt(widget.params) : `[Agor] User dismissed a widget request. Do not re-request immediately \u2014 ask whether to proceed without, or move on to other work.`;
48639
49386
  }
48640
49387
  const newStatus = action.kind === "submit" ? "submitted" : "dismissed";
@@ -48767,7 +49514,7 @@ async function registerRoutes(ctx) {
48767
49514
  const appRecord = app;
48768
49515
  const sessionTokenService = appRecord.sessionTokenService;
48769
49516
  const authStrategiesArray = ["api-key", "jwt", "local"];
48770
- const multiTenancy = resolveMultiTenancyConfig5(config2);
49517
+ const multiTenancy = resolveMultiTenancyConfig6(config2);
48771
49518
  const tenantTokenClaim = multiTenancy.auth_claim ?? "tenant_id";
48772
49519
  if (sessionTokenService) {
48773
49520
  authStrategiesArray.push("session-token");
@@ -48832,32 +49579,13 @@ async function registerRoutes(ctx) {
48832
49579
  authService.hooks({
48833
49580
  after: {
48834
49581
  create: [
48835
- // biome-ignore lint/suspicious/noExplicitAny: FeathersJS context type not fully typed
48836
- async (context) => {
48837
- authEventDebug("\u2705 Authentication succeeded:", {
48838
- strategy: context.result?.authentication?.strategy,
48839
- hasUser: !!context.result?.user,
48840
- user_id: context.result?.user?.user_id,
48841
- hasAccessToken: !!context.result?.accessToken
48842
- });
48843
- if (context.result?.user) {
48844
- const tenantId = context.params?.tenant?.tenant_id ?? context.result.user.tenant_id;
48845
- const tokens = issueRuntimeTokenPair(
48846
- context.result.user,
48847
- jwtSecret,
48848
- ACCESS_TOKEN_TTL,
48849
- REFRESH_TOKEN_TTL,
48850
- {
48851
- ...authTokenIssuedAtClaim(Date.now(), context.result.user),
48852
- ...runtimeTenantClaims(tenantId, tenantTokenClaim)
48853
- }
48854
- );
48855
- context.result.accessToken = tokens.accessToken;
48856
- context.result.refreshToken = tokens.refreshToken;
48857
- context.result.user = redactUserAuthMetadata(context.result.user);
48858
- }
48859
- return context;
48860
- }
49582
+ createIssueBrowserTokensHook({
49583
+ jwtSecret,
49584
+ accessTokenTtl: ACCESS_TOKEN_TTL,
49585
+ refreshTokenTtl: REFRESH_TOKEN_TTL,
49586
+ tenantClaim: tenantTokenClaim,
49587
+ debug: authEventDebug
49588
+ })
48861
49589
  ]
48862
49590
  }
48863
49591
  });
@@ -48901,12 +49629,12 @@ async function registerRoutes(ctx) {
48901
49629
  throw new NotAuthenticated15("Authentication required");
48902
49630
  }
48903
49631
  const caller = authParams.user;
48904
- if (!hasMinimumRole12(caller.role, ROLES15.SUPERADMIN)) {
48905
- throw new Forbidden13("Superadmin role required for impersonation");
49632
+ if (!hasMinimumRole14(caller.role, ROLES17.SUPERADMIN)) {
49633
+ throw new Forbidden14("Superadmin role required for impersonation");
48906
49634
  }
48907
49635
  const authPayload = authParams.authentication?.payload;
48908
49636
  if (authPayload?.is_impersonated === true) {
48909
- throw new Forbidden13("Cannot impersonate from an already-impersonated token");
49637
+ throw new Forbidden14("Cannot impersonate from an already-impersonated token");
48910
49638
  }
48911
49639
  if (!data?.user_id) {
48912
49640
  throw new BadRequest9("user_id is required");
@@ -48987,7 +49715,7 @@ async function registerRoutes(ctx) {
48987
49715
  }
48988
49716
  },
48989
49717
  {
48990
- create: { role: ROLES15.MEMBER, action: "create messages" }
49718
+ create: { role: ROLES17.MEMBER, action: "create messages" }
48991
49719
  },
48992
49720
  requireAuth
48993
49721
  );
@@ -49009,7 +49737,7 @@ async function registerRoutes(ctx) {
49009
49737
  }
49010
49738
  },
49011
49739
  {
49012
- create: { role: ROLES15.MEMBER, action: "broadcast streaming events" }
49740
+ create: { role: ROLES17.MEMBER, action: "broadcast streaming events" }
49013
49741
  },
49014
49742
  requireAuth
49015
49743
  );
@@ -49037,10 +49765,12 @@ async function registerRoutes(ctx) {
49037
49765
  }
49038
49766
  },
49039
49767
  {
49040
- create: { role: ROLES15.MEMBER, action: "broadcast task streaming events" }
49768
+ create: { role: ROLES17.MEMBER, action: "broadcast task streaming events" }
49041
49769
  },
49042
49770
  requireAuth
49043
49771
  );
49772
+ app.service("/messages/streaming").publish(() => []);
49773
+ app.service("/tasks/streaming").publish(() => []);
49044
49774
  registerAuthenticatedRoute2(
49045
49775
  app,
49046
49776
  "/sessions/:id/fork",
@@ -49059,7 +49789,7 @@ async function registerRoutes(ctx) {
49059
49789
  }
49060
49790
  },
49061
49791
  {
49062
- create: { role: ROLES15.MEMBER, action: "fork sessions" }
49792
+ create: { role: ROLES17.MEMBER, action: "fork sessions" }
49063
49793
  },
49064
49794
  requireAuth
49065
49795
  );
@@ -49081,7 +49811,7 @@ async function registerRoutes(ctx) {
49081
49811
  }
49082
49812
  },
49083
49813
  {
49084
- create: { role: ROLES15.MEMBER, action: "spawn sessions" }
49814
+ create: { role: ROLES17.MEMBER, action: "spawn sessions" }
49085
49815
  },
49086
49816
  requireAuth
49087
49817
  );
@@ -49097,7 +49827,7 @@ async function registerRoutes(ctx) {
49097
49827
  // biome-ignore lint/suspicious/noExplicitAny: FeathersJS route handler type mismatch with Express RouteParams
49098
49828
  },
49099
49829
  {
49100
- find: { role: ROLES15.MEMBER, action: "view session genealogy" }
49830
+ find: { role: ROLES17.MEMBER, action: "view session genealogy" }
49101
49831
  },
49102
49832
  requireAuth
49103
49833
  );
@@ -49121,7 +49851,7 @@ async function registerRoutes(ctx) {
49121
49851
  callerRole: params.user?.role,
49122
49852
  sessionCreatedBy: session.created_by
49123
49853
  })) {
49124
- throw new Forbidden13("You can only restart Claude CLI sessions you created.");
49854
+ throw new Forbidden14("You can only restart Claude CLI sessions you created.");
49125
49855
  }
49126
49856
  const tabName = `cli-${shortId16(session.session_id)}`;
49127
49857
  const channel = `user/${targetUserId}/terminal`;
@@ -49172,7 +49902,7 @@ async function registerRoutes(ctx) {
49172
49902
  // biome-ignore lint/suspicious/noExplicitAny: FeathersJS route handler type mismatch
49173
49903
  },
49174
49904
  {
49175
- create: { role: ROLES15.MEMBER, action: "restart claude CLI session" }
49905
+ create: { role: ROLES17.MEMBER, action: "restart claude CLI session" }
49176
49906
  },
49177
49907
  requireAuth
49178
49908
  );
@@ -49518,7 +50248,7 @@ ${errorMessage}`;
49518
50248
  }
49519
50249
  },
49520
50250
  {
49521
- create: { role: ROLES15.MEMBER, action: "execute prompts" }
50251
+ create: { role: ROLES17.MEMBER, action: "execute prompts" }
49522
50252
  },
49523
50253
  requireAuth
49524
50254
  );
@@ -49548,7 +50278,7 @@ ${errorMessage}`;
49548
50278
  } else {
49549
50279
  const userId = params.user?.user_id;
49550
50280
  if (!userId) {
49551
- throw new Forbidden13("Authentication required to run tasks");
50281
+ throw new Forbidden14("Authentication required to run tasks");
49552
50282
  }
49553
50283
  const wt = await branchRepository.findById(session.branch_id);
49554
50284
  if (!wt) {
@@ -49566,7 +50296,7 @@ ${errorMessage}`;
49566
50296
  );
49567
50297
  const canRun = PERMISSION_RANK[effectiveLevel] >= PERMISSION_RANK.prompt || effectiveLevel === "session" && session.created_by === userId;
49568
50298
  if (!canRun) {
49569
- throw new Forbidden13(
50299
+ throw new Forbidden14(
49570
50300
  `You have '${effectiveLevel}' permission on this branch, which does not allow running tasks. Need 'prompt' or 'all' (or 'session' for own sessions).`
49571
50301
  );
49572
50302
  }
@@ -49609,7 +50339,7 @@ ${errorMessage}`;
49609
50339
  }
49610
50340
  },
49611
50341
  {
49612
- create: { role: ROLES15.MEMBER, action: "execute prompts" }
50342
+ create: { role: ROLES17.MEMBER, action: "execute prompts" }
49613
50343
  },
49614
50344
  requireAuth
49615
50345
  );
@@ -49636,7 +50366,7 @@ ${errorMessage}`;
49636
50366
  }
49637
50367
  },
49638
50368
  {
49639
- create: { role: ROLES15.MEMBER, action: "send spawn-subsession prompts" }
50369
+ create: { role: ROLES17.MEMBER, action: "send spawn-subsession prompts" }
49640
50370
  },
49641
50371
  requireAuth
49642
50372
  );
@@ -49685,7 +50415,7 @@ ${errorMessage}`;
49685
50415
  }
49686
50416
  },
49687
50417
  {
49688
- create: { role: ROLES15.MEMBER, action: "fire zone triggers" }
50418
+ create: { role: ROLES17.MEMBER, action: "fire zone triggers" }
49689
50419
  },
49690
50420
  requireAuth
49691
50421
  );
@@ -49696,7 +50426,7 @@ ${errorMessage}`;
49696
50426
  try {
49697
50427
  const { sessionId } = req.params;
49698
50428
  const params = req.feathers;
49699
- ensureMinimumRole(params, ROLES15.MEMBER, "upload files");
50429
+ ensureMinimumRole(params, ROLES17.MEMBER, "upload files");
49700
50430
  const session = await sessionsService.get(sessionId, params);
49701
50431
  if (!session) {
49702
50432
  console.error(`\u274C [Upload Authz] Session not found: ${shortId16(sessionId)}`);
@@ -49940,7 +50670,7 @@ ${errorMessage}`;
49940
50670
  }
49941
50671
  },
49942
50672
  {
49943
- create: { role: ROLES15.MEMBER, action: "stop sessions" }
50673
+ create: { role: ROLES17.MEMBER, action: "stop sessions" }
49944
50674
  },
49945
50675
  requireAuth
49946
50676
  );
@@ -49961,12 +50691,24 @@ ${errorMessage}`;
49961
50691
  // biome-ignore lint/suspicious/noExplicitAny: Service type not compatible with Express
49962
50692
  },
49963
50693
  {
49964
- find: { role: ROLES15.MEMBER, action: "view queue" }
50694
+ find: { role: ROLES17.MEMBER, action: "view queue" }
49965
50695
  },
49966
50696
  requireAuth
49967
50697
  );
49968
50698
  const queueRetryScheduled = /* @__PURE__ */ new Set();
49969
50699
  async function processNextQueuedTask(sessionId, params) {
50700
+ await runWithSessionQueueTenantScope(
50701
+ {
50702
+ db,
50703
+ config: config2,
50704
+ sessionId,
50705
+ params,
50706
+ label: "processNextQueuedTask"
50707
+ },
50708
+ async (scopedParams) => processNextQueuedTaskInTenantScope(sessionId, scopedParams)
50709
+ );
50710
+ }
50711
+ async function processNextQueuedTaskInTenantScope(sessionId, params) {
49970
50712
  const existingLock = sessionTurnLocks.get(sessionId);
49971
50713
  if (existingLock) {
49972
50714
  console.log(`\u23F3 [Queue] Session turn in progress for ${shortId16(sessionId)}, waiting...`);
@@ -49985,14 +50727,27 @@ ${errorMessage}`;
49985
50727
  }
49986
50728
  if (!queueRetryScheduled.has(sessionId)) {
49987
50729
  queueRetryScheduled.add(sessionId);
49988
- deferInFreshTenantScope(params, async () => {
49989
- queueRetryScheduled.delete(sessionId);
49990
- try {
49991
- await processNextQueuedTask(sessionId, params);
49992
- } catch (error51) {
50730
+ deferWithSessionQueueTenantScope(
50731
+ {
50732
+ db,
50733
+ config: config2,
50734
+ sessionId,
50735
+ params,
50736
+ label: "processNextQueuedTask retry"
50737
+ },
50738
+ async (retryParams) => {
50739
+ queueRetryScheduled.delete(sessionId);
50740
+ try {
50741
+ await processNextQueuedTask(sessionId, retryParams);
50742
+ } catch (error51) {
50743
+ console.error(`\u274C [Queue] Retry failed for session ${shortId16(sessionId)}:`, error51);
50744
+ }
50745
+ },
50746
+ (error51) => {
50747
+ queueRetryScheduled.delete(sessionId);
49993
50748
  console.error(`\u274C [Queue] Retry failed for session ${shortId16(sessionId)}:`, error51);
49994
50749
  }
49995
- });
50750
+ );
49996
50751
  } else {
49997
50752
  console.log(
49998
50753
  `\u23ED\uFE0F [Queue] Retry already scheduled for session ${shortId16(sessionId)}, not queueing another`
@@ -50147,7 +50902,7 @@ ${errorMessage}`;
50147
50902
  }
50148
50903
  },
50149
50904
  {
50150
- create: { role: ROLES15.MEMBER, action: "respond to permission requests" }
50905
+ create: { role: ROLES17.MEMBER, action: "respond to permission requests" }
50151
50906
  },
50152
50907
  requireAuth
50153
50908
  );
@@ -50176,7 +50931,7 @@ ${errorMessage}`;
50176
50931
  }
50177
50932
  },
50178
50933
  {
50179
- create: { role: ROLES15.MEMBER, action: "submit widgets" }
50934
+ create: { role: ROLES17.MEMBER, action: "submit widgets" }
50180
50935
  },
50181
50936
  requireAuth
50182
50937
  );
@@ -50199,7 +50954,7 @@ ${errorMessage}`;
50199
50954
  }
50200
50955
  },
50201
50956
  {
50202
- create: { role: ROLES15.MEMBER, action: "dismiss widgets" }
50957
+ create: { role: ROLES17.MEMBER, action: "dismiss widgets" }
50203
50958
  },
50204
50959
  requireAuth
50205
50960
  );
@@ -50212,7 +50967,7 @@ ${errorMessage}`;
50212
50967
  }
50213
50968
  },
50214
50969
  {
50215
- create: { role: ROLES15.MEMBER, action: "create tasks" }
50970
+ create: { role: ROLES17.MEMBER, action: "create tasks" }
50216
50971
  },
50217
50972
  requireAuth
50218
50973
  );
@@ -50227,7 +50982,7 @@ ${errorMessage}`;
50227
50982
  }
50228
50983
  },
50229
50984
  {
50230
- create: { role: ROLES15.MEMBER, action: "complete tasks" }
50985
+ create: { role: ROLES17.MEMBER, action: "complete tasks" }
50231
50986
  },
50232
50987
  requireAuth
50233
50988
  );
@@ -50242,7 +50997,7 @@ ${errorMessage}`;
50242
50997
  }
50243
50998
  },
50244
50999
  {
50245
- create: { role: ROLES15.MEMBER, action: "fail tasks" }
51000
+ create: { role: ROLES17.MEMBER, action: "fail tasks" }
50246
51001
  },
50247
51002
  requireAuth
50248
51003
  );
@@ -50255,7 +51010,7 @@ ${errorMessage}`;
50255
51010
  }
50256
51011
  },
50257
51012
  {
50258
- create: { role: ROLES15.MEMBER, action: "add local repositories" }
51013
+ create: { role: ROLES17.MEMBER, action: "add local repositories" }
50259
51014
  },
50260
51015
  requireAuth
50261
51016
  );
@@ -50268,7 +51023,7 @@ ${errorMessage}`;
50268
51023
  }
50269
51024
  },
50270
51025
  {
50271
- create: { role: ROLES15.MEMBER, action: "clone repositories" }
51026
+ create: { role: ROLES17.MEMBER, action: "clone repositories" }
50272
51027
  },
50273
51028
  requireAuth
50274
51029
  );
@@ -50287,7 +51042,7 @@ ${errorMessage}`;
50287
51042
  }
50288
51043
  },
50289
51044
  {
50290
- create: { role: ROLES15.MEMBER, action: "create branches" }
51045
+ create: { role: ROLES17.MEMBER, action: "create branches" }
50291
51046
  },
50292
51047
  requireAuth
50293
51048
  );
@@ -50304,7 +51059,7 @@ ${errorMessage}`;
50304
51059
  }
50305
51060
  },
50306
51061
  {
50307
- remove: { role: ROLES15.MEMBER, action: "remove branches" }
51062
+ remove: { role: ROLES17.MEMBER, action: "remove branches" }
50308
51063
  },
50309
51064
  requireAuth
50310
51065
  );
@@ -50320,7 +51075,7 @@ ${errorMessage}`;
50320
51075
  }
50321
51076
  },
50322
51077
  {
50323
- create: { role: ROLES15.ADMIN, action: "import environment config from .agor.yml" }
51078
+ create: { role: ROLES17.ADMIN, action: "import environment config from .agor.yml" }
50324
51079
  },
50325
51080
  requireAuth
50326
51081
  );
@@ -50339,7 +51094,7 @@ ${errorMessage}`;
50339
51094
  // Admin-only, matching Import and repo.environment edit. Export writes a
50340
51095
  // file to the branch working tree, so even though the content is
50341
51096
  // derivable, the side effect warrants the same permission bar as import.
50342
- create: { role: ROLES15.ADMIN, action: "export .agor.yml" }
51097
+ create: { role: ROLES17.ADMIN, action: "export .agor.yml" }
50343
51098
  },
50344
51099
  requireAuth
50345
51100
  );
@@ -50364,10 +51119,10 @@ ${errorMessage}`;
50364
51119
  }
50365
51120
  },
50366
51121
  {
50367
- find: { role: ROLES15.MEMBER, action: "list API keys" },
50368
- create: { role: ROLES15.MEMBER, action: "create API keys" },
50369
- patch: { role: ROLES15.MEMBER, action: "update API keys" },
50370
- remove: { role: ROLES15.MEMBER, action: "delete API keys" }
51122
+ find: { role: ROLES17.MEMBER, action: "list API keys" },
51123
+ create: { role: ROLES17.MEMBER, action: "create API keys" },
51124
+ patch: { role: ROLES17.MEMBER, action: "update API keys" },
51125
+ remove: { role: ROLES17.MEMBER, action: "delete API keys" }
50371
51126
  },
50372
51127
  requireAuth
50373
51128
  );
@@ -50388,7 +51143,7 @@ ${errorMessage}`;
50388
51143
  }
50389
51144
  },
50390
51145
  {
50391
- create: { role: ROLES15.MEMBER, action: "react to board comments" }
51146
+ create: { role: ROLES17.MEMBER, action: "react to board comments" }
50392
51147
  },
50393
51148
  requireAuth
50394
51149
  );
@@ -50410,7 +51165,7 @@ ${errorMessage}`;
50410
51165
  }
50411
51166
  },
50412
51167
  {
50413
- create: { role: ROLES15.MEMBER, action: "reply to board comments" }
51168
+ create: { role: ROLES17.MEMBER, action: "reply to board comments" }
50414
51169
  },
50415
51170
  requireAuth
50416
51171
  );
@@ -50429,7 +51184,7 @@ ${errorMessage}`;
50429
51184
  // Branch `all`/admin control is enforced at the service layer. This
50430
51185
  // route-level gate is just "authenticated" so the service remains
50431
51186
  // the single source of truth across REST, WebSocket, and MCP.
50432
- create: { role: ROLES15.VIEWER, action: "start branch environments" }
51187
+ create: { role: ROLES17.VIEWER, action: "start branch environments" }
50433
51188
  },
50434
51189
  requireAuth
50435
51190
  );
@@ -50445,7 +51200,7 @@ ${errorMessage}`;
50445
51200
  },
50446
51201
  {
50447
51202
  // Branch `all`/admin control is enforced at the service layer.
50448
- create: { role: ROLES15.VIEWER, action: "stop branch environments" }
51203
+ create: { role: ROLES17.VIEWER, action: "stop branch environments" }
50449
51204
  },
50450
51205
  requireAuth
50451
51206
  );
@@ -50464,7 +51219,7 @@ ${errorMessage}`;
50464
51219
  },
50465
51220
  {
50466
51221
  // Branch `all`/admin control is enforced at the service layer.
50467
- create: { role: ROLES15.VIEWER, action: "restart branch environments" }
51222
+ create: { role: ROLES17.VIEWER, action: "restart branch environments" }
50468
51223
  },
50469
51224
  requireAuth
50470
51225
  );
@@ -50480,7 +51235,7 @@ ${errorMessage}`;
50480
51235
  },
50481
51236
  {
50482
51237
  // Branch `all`/admin control is enforced at the service layer.
50483
- create: { role: ROLES15.VIEWER, action: "nuke branch environments" }
51238
+ create: { role: ROLES17.VIEWER, action: "nuke branch environments" }
50484
51239
  },
50485
51240
  requireAuth
50486
51241
  );
@@ -50500,7 +51255,7 @@ ${errorMessage}`;
50500
51255
  },
50501
51256
  {
50502
51257
  // Branch `all`/admin control is enforced at the service layer.
50503
- create: { role: ROLES15.VIEWER, action: "render branch environment" }
51258
+ create: { role: ROLES17.VIEWER, action: "render branch environment" }
50504
51259
  },
50505
51260
  requireAuth
50506
51261
  );
@@ -50516,7 +51271,7 @@ ${errorMessage}`;
50516
51271
  // biome-ignore lint/suspicious/noExplicitAny: Service type not compatible with Express
50517
51272
  },
50518
51273
  {
50519
- find: { role: ROLES15.VIEWER, action: "check branch health" }
51274
+ find: { role: ROLES17.VIEWER, action: "check branch health" }
50520
51275
  },
50521
51276
  requireAuth
50522
51277
  );
@@ -50536,13 +51291,13 @@ ${errorMessage}`;
50536
51291
  before: {
50537
51292
  create: [
50538
51293
  requireAuth,
50539
- requireMinimumRole(ROLES15.MEMBER, "archive or delete branches"),
51294
+ requireMinimumRole(ROLES17.MEMBER, "archive or delete branches"),
50540
51295
  async (context) => {
50541
51296
  const id = context.params.route?.id;
50542
51297
  if (!id) throw new Error("Branch ID required");
50543
51298
  const branch = await branchRepository.findById(id);
50544
51299
  if (!branch) {
50545
- throw new Forbidden13(`Branch not found: ${id}`);
51300
+ throw new Forbidden14(`Branch not found: ${id}`);
50546
51301
  }
50547
51302
  await cacheBranchAccess(context.params, branchRepository, branch);
50548
51303
  return context;
@@ -50550,8 +51305,8 @@ ${errorMessage}`;
50550
51305
  branchRbacEnabled ? ensureBranchPermission("all", "archive or delete branches", superadminOpts) : (context) => {
50551
51306
  const isOwner = context.params.isBranchOwner;
50552
51307
  const userRole = context.params.user?.role;
50553
- if (!isOwner && !hasMinimumRole12(userRole, ROLES15.ADMIN)) {
50554
- throw new Forbidden13(
51308
+ if (!isOwner && !hasMinimumRole14(userRole, ROLES17.ADMIN)) {
51309
+ throw new Forbidden14(
50555
51310
  "You must be the branch owner or a global admin to archive/delete branches"
50556
51311
  );
50557
51312
  }
@@ -50572,13 +51327,13 @@ ${errorMessage}`;
50572
51327
  before: {
50573
51328
  create: [
50574
51329
  requireAuth,
50575
- requireMinimumRole(ROLES15.MEMBER, "unarchive branches"),
51330
+ requireMinimumRole(ROLES17.MEMBER, "unarchive branches"),
50576
51331
  async (context) => {
50577
51332
  const id = context.params.route?.id;
50578
51333
  if (!id) throw new Error("Branch ID required");
50579
51334
  const branch = await branchRepository.findById(id);
50580
51335
  if (!branch) {
50581
- throw new Forbidden13(`Branch not found: ${id}`);
51336
+ throw new Forbidden14(`Branch not found: ${id}`);
50582
51337
  }
50583
51338
  await cacheBranchAccess(context.params, branchRepository, branch);
50584
51339
  return context;
@@ -50586,8 +51341,8 @@ ${errorMessage}`;
50586
51341
  branchRbacEnabled ? ensureBranchPermission("all", "unarchive branches", superadminOpts) : (context) => {
50587
51342
  const isOwner = context.params.isBranchOwner;
50588
51343
  const userRole = context.params.user?.role;
50589
- if (!isOwner && !hasMinimumRole12(userRole, ROLES15.ADMIN)) {
50590
- throw new Forbidden13(
51344
+ if (!isOwner && !hasMinimumRole14(userRole, ROLES17.ADMIN)) {
51345
+ throw new Forbidden14(
50591
51346
  "You must be the branch owner or a global admin to unarchive branches"
50592
51347
  );
50593
51348
  }
@@ -50637,7 +51392,7 @@ ${errorMessage}`;
50637
51392
  before: {
50638
51393
  create: [
50639
51394
  requireAuth,
50640
- requireMinimumRole(ROLES15.MEMBER, "run schedule"),
51395
+ requireMinimumRole(ROLES17.MEMBER, "run schedule"),
50641
51396
  // Reuse the canonical hook so caching semantics (params.schedule
50642
51397
  // / params.branch / params.isBranchOwner) match every other
50643
51398
  // schedule-touching path.
@@ -50646,8 +51401,8 @@ ${errorMessage}`;
50646
51401
  branchRbacEnabled ? ensureBranchPermission("all", "run schedule", superadminOpts) : (context) => {
50647
51402
  const isOwner = context.params.isBranchOwner;
50648
51403
  const userRole = context.params.user?.role;
50649
- if (!isOwner && !hasMinimumRole12(userRole, ROLES15.ADMIN)) {
50650
- throw new Forbidden13(
51404
+ if (!isOwner && !hasMinimumRole14(userRole, ROLES17.ADMIN)) {
51405
+ throw new Forbidden14(
50651
51406
  "You must be the branch owner or a global admin to run schedules"
50652
51407
  );
50653
51408
  }
@@ -50711,7 +51466,7 @@ ${errorMessage}`;
50711
51466
  before: {
50712
51467
  create: [
50713
51468
  requireAuth,
50714
- requireMinimumRole(ROLES15.MEMBER, "execute scheduled runs"),
51469
+ requireMinimumRole(ROLES17.MEMBER, "execute scheduled runs"),
50715
51470
  async (context) => {
50716
51471
  const id = context.params.route?.id;
50717
51472
  if (!id) throw new BadRequest9("Branch ID required");
@@ -50725,8 +51480,8 @@ ${errorMessage}`;
50725
51480
  branchRbacEnabled ? ensureBranchPermission("all", "execute scheduled runs", superadminOpts) : (context) => {
50726
51481
  const isOwner = context.params.isBranchOwner;
50727
51482
  const userRole = context.params.user?.role;
50728
- if (!isOwner && !hasMinimumRole12(userRole, ROLES15.ADMIN)) {
50729
- throw new Forbidden13(
51483
+ if (!isOwner && !hasMinimumRole14(userRole, ROLES17.ADMIN)) {
51484
+ throw new Forbidden14(
50730
51485
  "You must be the branch owner or a global admin to execute scheduled runs"
50731
51486
  );
50732
51487
  }
@@ -50750,7 +51505,7 @@ ${errorMessage}`;
50750
51505
  },
50751
51506
  {
50752
51507
  // Branch `all`/admin control is enforced at the service layer.
50753
- find: { role: ROLES15.VIEWER, action: "view branch logs" }
51508
+ find: { role: ROLES17.VIEWER, action: "view branch logs" }
50754
51509
  },
50755
51510
  requireAuth
50756
51511
  );
@@ -50767,7 +51522,7 @@ ${errorMessage}`;
50767
51522
  }
50768
51523
  },
50769
51524
  {
50770
- create: { role: ROLES15.MEMBER, action: "modify board sessions" }
51525
+ create: { role: ROLES17.MEMBER, action: "modify board sessions" }
50771
51526
  },
50772
51527
  requireAuth
50773
51528
  );
@@ -50797,7 +51552,15 @@ ${errorMessage}`;
50797
51552
  const includeGlobal = params.query?.includeGlobal === "true" || params.query?.includeGlobal === true;
50798
51553
  const includeMetadata = params.query?.includeMetadata === "true" || params.query?.includeMetadata === true;
50799
51554
  const mcpService = app.service("mcp-servers");
50800
- const userId = params.user?.user_id;
51555
+ const queryForUserId = typeof params.query?.forUserId === "string" ? params.query.forUserId : void 0;
51556
+ const authPayloadType = params.authentication?.payload?.type;
51557
+ const routeUser = params.user;
51558
+ const userId = resolveForUserIdWithGate({
51559
+ queryForUserId,
51560
+ isServiceAccount: routeUser?._isServiceAccount,
51561
+ authPayloadType,
51562
+ callerUserId: params.user?.user_id
51563
+ });
50801
51564
  const rawLookupParams = {
50802
51565
  ...params,
50803
51566
  provider: void 0,
@@ -50932,10 +51695,10 @@ ${errorMessage}`;
50932
51695
  // biome-ignore lint/suspicious/noExplicitAny: Service type not compatible with Express
50933
51696
  },
50934
51697
  {
50935
- find: { role: ROLES15.MEMBER, action: "view session MCP servers" },
50936
- create: { role: ROLES15.MEMBER, action: "modify session MCP servers" },
50937
- remove: { role: ROLES15.MEMBER, action: "modify session MCP servers" },
50938
- patch: { role: ROLES15.MEMBER, action: "modify session MCP servers" }
51698
+ find: { role: ROLES17.MEMBER, action: "view session MCP servers" },
51699
+ create: { role: ROLES17.MEMBER, action: "modify session MCP servers" },
51700
+ remove: { role: ROLES17.MEMBER, action: "modify session MCP servers" },
51701
+ patch: { role: ROLES17.MEMBER, action: "modify session MCP servers" }
50939
51702
  },
50940
51703
  requireAuth
50941
51704
  );
@@ -51024,10 +51787,10 @@ ${errorMessage}`;
51024
51787
  // biome-ignore lint/suspicious/noExplicitAny: Service type not compatible with Express
51025
51788
  },
51026
51789
  {
51027
- find: { role: ROLES15.MEMBER, action: "view session env selections" },
51028
- create: { role: ROLES15.MEMBER, action: "modify session env selections" },
51029
- remove: { role: ROLES15.MEMBER, action: "modify session env selections" },
51030
- patch: { role: ROLES15.MEMBER, action: "modify session env selections" }
51790
+ find: { role: ROLES17.MEMBER, action: "view session env selections" },
51791
+ create: { role: ROLES17.MEMBER, action: "modify session env selections" },
51792
+ remove: { role: ROLES17.MEMBER, action: "modify session env selections" },
51793
+ patch: { role: ROLES17.MEMBER, action: "modify session env selections" }
51031
51794
  },
51032
51795
  requireAuth
51033
51796
  );
@@ -51308,7 +52071,9 @@ import {
51308
52071
  BoardRepository as BoardRepository6,
51309
52072
  BranchRepository as BranchRepository16,
51310
52073
  eq as eq6,
52074
+ getCurrentTenantId as getCurrentTenantId7,
51311
52075
  inArray as inArray2,
52076
+ isPostgresDatabase as isPostgresDatabase8,
51312
52077
  MCPServerRepository as MCPServerRepository5,
51313
52078
  SessionMCPServerRepository as SessionMCPServerRepository5,
51314
52079
  select as select6,
@@ -51317,12 +52082,12 @@ import {
51317
52082
  UserMCPOAuthTokenRepository as UserMCPOAuthTokenRepository4,
51318
52083
  visibleSessionReferenceAccessExists as visibleSessionReferenceAccessExists2
51319
52084
  } from "@agor/core/db";
51320
- import { Forbidden as Forbidden25, NotAuthenticated as NotAuthenticated21 } from "@agor/core/feathers";
52085
+ import { Forbidden as Forbidden26, NotAuthenticated as NotAuthenticated21 } from "@agor/core/feathers";
51321
52086
  import {
51322
52087
  AGENTIC_TOOL_CAPABILITIES as AGENTIC_TOOL_CAPABILITIES3,
51323
52088
  isSessionExecuting as isSessionExecuting2,
51324
52089
  isTaskExecuting as isTaskExecuting2,
51325
- ROLES as ROLES23,
52090
+ ROLES as ROLES25,
51326
52091
  SessionStatus as SessionStatus9,
51327
52092
  TaskStatus as TaskStatus7
51328
52093
  } from "@agor/core/types";
@@ -51441,10 +52206,10 @@ import {
51441
52206
  import {
51442
52207
  ARTIFACT_SCOPED_ONLY_GRANT_KEYS,
51443
52208
  GRANT_ENV_VAR_NAMES,
51444
- hasMinimumRole as hasMinimumRole13,
52209
+ hasMinimumRole as hasMinimumRole15,
51445
52210
  NO_CONSENT_GRANT_KEYS,
51446
52211
  proxyGrantEnvName,
51447
- ROLES as ROLES16
52212
+ ROLES as ROLES18
51448
52213
  } from "@agor/core/types";
51449
52214
 
51450
52215
  // src/utils/agor-runtime-source.ts
@@ -52197,7 +52962,7 @@ var ArtifactsService = class extends DrizzleService {
52197
52962
  const existing = await this.artifactRepo.findById(artifactId);
52198
52963
  if (!existing) throw new Error(`Artifact ${artifactId} not found`);
52199
52964
  const isOwner = !!userId && existing.created_by === userId;
52200
- const isAdmin4 = !!userRole && hasMinimumRole13(userRole, ROLES16.ADMIN);
52965
+ const isAdmin4 = !!userRole && hasMinimumRole15(userRole, ROLES18.ADMIN);
52201
52966
  if (userId && !isOwner && !isAdmin4) {
52202
52967
  throw new Error("Forbidden: only the artifact's creator or an admin may update it");
52203
52968
  }
@@ -53263,7 +54028,7 @@ var ArtifactsService = class extends DrizzleService {
53263
54028
  const artifact = await this.artifactRepo.findById(artifactId);
53264
54029
  if (!artifact) throw new Error(`Artifact ${artifactId} not found`);
53265
54030
  const isOwner = !!userId && artifact.created_by === userId;
53266
- const isAdmin4 = !!userRole && hasMinimumRole13(userRole, ROLES16.ADMIN);
54031
+ const isAdmin4 = !!userRole && hasMinimumRole15(userRole, ROLES18.ADMIN);
53267
54032
  if (!isOwner && !isAdmin4) {
53268
54033
  throw new Error("Forbidden: only the artifact's creator or an admin may delete it");
53269
54034
  }
@@ -53874,20 +54639,20 @@ function createBoardObjectsService(db) {
53874
54639
  }
53875
54640
 
53876
54641
  // src/services/board-owners.ts
53877
- import { Forbidden as Forbidden14, NotAuthenticated as NotAuthenticated16 } from "@agor/core/feathers";
53878
- import { hasMinimumRole as hasMinimumRole14, ROLES as ROLES17 } from "@agor/core/types";
54642
+ import { Forbidden as Forbidden15, NotAuthenticated as NotAuthenticated16 } from "@agor/core/feathers";
54643
+ import { hasMinimumRole as hasMinimumRole16, ROLES as ROLES19 } from "@agor/core/types";
53879
54644
  function requireBoardAccess(boardRepo, mode) {
53880
54645
  return async (context) => {
53881
54646
  if (!context.params.provider) return context;
53882
54647
  if (context.params.user?._isServiceAccount) return context;
53883
54648
  const user = context.params.user;
53884
54649
  if (!user?.user_id) throw new NotAuthenticated16("Authentication required");
53885
- if (hasMinimumRole14(user.role, ROLES17.ADMIN)) return context;
54650
+ if (hasMinimumRole16(user.role, ROLES19.ADMIN)) return context;
53886
54651
  const boardId = context.params.route?.id;
53887
54652
  if (!boardId) throw new Error("Board ID is required");
53888
54653
  const allowed = mode === "view" ? await boardRepo.canView(boardId, user.user_id) : await boardRepo.canMutate(boardId, user.user_id);
53889
54654
  if (allowed) return context;
53890
- throw new Forbidden14(
54655
+ throw new Forbidden15(
53891
54656
  mode === "view" ? "You need board access to view board owners" : "You need board owner or board group 'all' access to manage board owners"
53892
54657
  );
53893
54658
  };
@@ -54121,6 +54886,13 @@ var BoardsService = class extends DrizzleService {
54121
54886
  async batchUpsertBoardObjects(boardId, objects, _params) {
54122
54887
  return this.boardRepo.batchUpsertBoardObjects(boardId, objects);
54123
54888
  }
54889
+ /**
54890
+ * Custom method: Atomically shallow-merge field patches into existing board
54891
+ * objects (used by z-order reorder to persist only the changed zIndex).
54892
+ */
54893
+ async mergeBoardObjectFields(boardId, patches, _params) {
54894
+ return this.boardRepo.mergeBoardObjectFields(boardId, patches);
54895
+ }
54124
54896
  /**
54125
54897
  * Custom method: Delete a zone and handle associated sessions
54126
54898
  */
@@ -54273,7 +55045,7 @@ function createBoardsService(db, emitBoardObjectPatched) {
54273
55045
  init_branch_authorization();
54274
55046
  init_spawn_executor();
54275
55047
  import { shortId as shortId18 } from "@agor/core/db";
54276
- import { Forbidden as Forbidden15, NotAuthenticated as NotAuthenticated17 } from "@agor/core/feathers";
55048
+ import { Forbidden as Forbidden16, NotAuthenticated as NotAuthenticated17 } from "@agor/core/feathers";
54277
55049
  function requireViewPermission(branchRepo, allowSuperadmin = true) {
54278
55050
  return async (context) => {
54279
55051
  if (!context.params.provider) {
@@ -54296,12 +55068,12 @@ function requireViewPermission(branchRepo, allowSuperadmin = true) {
54296
55068
  }
54297
55069
  const branch = await branchRepo.findById(branchId);
54298
55070
  if (!branch) {
54299
- throw new Forbidden15(`Branch not found: ${branchId}`);
55071
+ throw new Forbidden16(`Branch not found: ${branchId}`);
54300
55072
  }
54301
55073
  const isOwner = await branchRepo.isOwner(branch.branch_id, userId);
54302
55074
  const effectivePermission = isOwner ? "all" : await branchRepo.resolveUserPermission(branch, userId);
54303
55075
  if (PERMISSION_RANK[effectivePermission] < PERMISSION_RANK.view) {
54304
- throw new Forbidden15("You do not have permission to view this branch");
55076
+ throw new Forbidden16("You do not have permission to view this branch");
54305
55077
  }
54306
55078
  return context;
54307
55079
  };
@@ -54328,7 +55100,7 @@ function requireBranchOwner(branchRepo, allowSuperadmin = true) {
54328
55100
  }
54329
55101
  const isOwner = await branchRepo.isOwner(branchId, userId);
54330
55102
  if (!isOwner) {
54331
- throw new Forbidden15("Only branch owners can manage owners");
55103
+ throw new Forbidden16("Only branch owners can manage owners");
54332
55104
  }
54333
55105
  return context;
54334
55106
  };
@@ -54484,7 +55256,7 @@ import {
54484
55256
  validateManagedEnvLifecyclePolicy as validateManagedEnvLifecyclePolicy2,
54485
55257
  validateRenderedManagedEnvUrlFields as validateRenderedManagedEnvUrlFields2
54486
55258
  } from "@agor/core/environment/webhook";
54487
- import { BadRequest as BadRequest10, Forbidden as Forbidden16, NotAuthenticated as NotAuthenticated18 } from "@agor/core/feathers";
55259
+ import { BadRequest as BadRequest10, Forbidden as Forbidden17, NotAuthenticated as NotAuthenticated18 } from "@agor/core/feathers";
54488
55260
  import { stripGitUrlCredentials } from "@agor/core/git/pure";
54489
55261
  import {
54490
55262
  BRANCH_ENVIRONMENT_CLEARABLE_FIELDS,
@@ -54507,6 +55279,27 @@ function shouldUseCloneReferencePath() {
54507
55279
  return (config2.execution?.unix_user_mode ?? "simple") !== "strict";
54508
55280
  }
54509
55281
 
55282
+ // src/utils/emit-service-event.ts
55283
+ var DEFAULT_EVENT_METHOD = {
55284
+ created: "create",
55285
+ updated: "update",
55286
+ patched: "patch",
55287
+ removed: "remove"
55288
+ };
55289
+ function emitServiceEvent(app, event) {
55290
+ const service = app.service(event.path);
55291
+ service.emit?.(event.event, event.data, {
55292
+ app,
55293
+ service,
55294
+ path: event.path,
55295
+ method: event.method ?? DEFAULT_EVENT_METHOD[event.event] ?? "patch",
55296
+ event: event.event,
55297
+ id: event.id,
55298
+ result: event.data,
55299
+ params: event.params ?? {}
55300
+ });
55301
+ }
55302
+
54510
55303
  // src/utils/git-impersonation.ts
54511
55304
  async function resolveDaemonUserForGroupRefresh() {
54512
55305
  const { getDaemonUser: getDaemonUser2, isUnixGroupRefreshNeeded } = await import("@agor/core/config");
@@ -54928,7 +55721,7 @@ var BranchesService = class extends DrizzleService {
54928
55721
  }
54929
55722
  const isService = !!caller._isServiceAccount;
54930
55723
  if (!isService) {
54931
- throw new Forbidden16("Only the executor service account may initialize Unix groups");
55724
+ throw new Forbidden17("Only the executor service account may initialize Unix groups");
54932
55725
  }
54933
55726
  }
54934
55727
  const { initializeBranchUnixGroup: initializeBranchUnixGroup2 } = await Promise.resolve().then(() => (init_unix_group_init(), unix_group_init_exports));
@@ -55013,7 +55806,7 @@ var BranchesService = class extends DrizzleService {
55013
55806
  if (await this.branchRepo.isOwner(branch.branch_id, userId)) {
55014
55807
  return;
55015
55808
  }
55016
- throw new Forbidden16("Only branch owners or admins can manage assistant knowledge");
55809
+ throw new Forbidden17("Only branch owners or admins can manage assistant knowledge");
55017
55810
  }
55018
55811
  containsAssistantKnowledgeConfigMutation(data) {
55019
55812
  if (!Object.hasOwn(data, "custom_context")) return false;
@@ -55074,7 +55867,7 @@ var BranchesService = class extends DrizzleService {
55074
55867
  if (!userId) throw new NotAuthenticated18("Authentication required");
55075
55868
  const permission = await namespaces.resolveNamespacePermission(namespace.namespace_id, userId);
55076
55869
  if (permission !== "write" && permission !== "own") {
55077
- throw new Forbidden16(
55870
+ throw new Forbidden17(
55078
55871
  "You need write access to use this Knowledge namespace as assistant home"
55079
55872
  );
55080
55873
  }
@@ -55751,7 +56544,13 @@ var BranchesService = class extends DrizzleService {
55751
56544
  },
55752
56545
  resolvedParams
55753
56546
  );
55754
- this.app.service("branches").emit?.("patched", branch);
56547
+ emitServiceEvent(this.app, {
56548
+ path: "branches",
56549
+ event: "patched",
56550
+ data: branch,
56551
+ params: resolvedParams,
56552
+ id
56553
+ });
55755
56554
  return branch;
55756
56555
  }
55757
56556
  /**
@@ -56186,7 +56985,9 @@ var BranchesService = class extends DrizzleService {
56186
56985
  path: branch.path,
56187
56986
  custom_context: branch.custom_context,
56188
56987
  unix_gid: unixGid,
56189
- host_ip_address: hostIpAddress
56988
+ host_ip_address: hostIpAddress,
56989
+ base_ref: branch.base_ref,
56990
+ ref_type: branch.ref_type
56190
56991
  },
56191
56992
  requestedVariant
56192
56993
  );
@@ -56643,6 +57444,9 @@ function isAlias(id) {
56643
57444
  }
56644
57445
  var CONTEXT_1M_BETA = "context-1m-2025-08-07";
56645
57446
  var ONE_MILLION_TOKENS = 9e5;
57447
+ function hasNativeMillionContext(id) {
57448
+ return id.startsWith("claude-fable");
57449
+ }
56646
57450
  function toModelOptions(models) {
56647
57451
  const aliases = models.filter((m) => isAlias(m.id));
56648
57452
  const options = [];
@@ -56653,7 +57457,7 @@ function toModelOptions(models) {
56653
57457
  description: void 0,
56654
57458
  source: "dynamic"
56655
57459
  });
56656
- if (m.max_input_tokens && m.max_input_tokens >= ONE_MILLION_TOKENS) {
57460
+ if (m.max_input_tokens && m.max_input_tokens >= ONE_MILLION_TOKENS && !hasNativeMillionContext(m.id)) {
56657
57461
  options.push({
56658
57462
  id: `${m.id}[1m]`,
56659
57463
  displayName: `${m.display_name} (1M context)`,
@@ -56737,11 +57541,11 @@ import {
56737
57541
  resolveApiKey as resolveApiKey3,
56738
57542
  saveConfig
56739
57543
  } from "@agor/core/config";
56740
- import { BadRequest as BadRequest11, Forbidden as Forbidden17, NotAuthenticated as NotAuthenticated19 } from "@agor/core/feathers";
57544
+ import { BadRequest as BadRequest11, Forbidden as Forbidden18, NotAuthenticated as NotAuthenticated19 } from "@agor/core/feathers";
56741
57545
  import {
56742
57546
  TOOL_API_KEY_NAMES as TOOL_API_KEY_NAMES2
56743
57547
  } from "@agor/core/types";
56744
- import jwt9 from "jsonwebtoken";
57548
+ import jwt10 from "jsonwebtoken";
56745
57549
  var RESOLVABLE_API_KEY_NAMES = {
56746
57550
  ANTHROPIC_API_KEY: true,
56747
57551
  ANTHROPIC_AUTH_TOKEN: true,
@@ -56762,7 +57566,7 @@ function getExecutorTokenPayload(params) {
56762
57566
  }
56763
57567
  const accessToken = params?.authentication?.accessToken;
56764
57568
  if (typeof accessToken === "string") {
56765
- const decoded = jwt9.decode(accessToken);
57569
+ const decoded = jwt10.decode(accessToken);
56766
57570
  if (decoded?.type === "executor-session" && decoded.purpose === "executor-task") {
56767
57571
  return decoded;
56768
57572
  }
@@ -56863,10 +57667,10 @@ var ConfigService = class {
56863
57667
  if (!caller) {
56864
57668
  throw new NotAuthenticated19("Authentication required");
56865
57669
  }
56866
- throw new Forbidden17("Only executor runtime credentials may resolve API keys");
57670
+ throw new Forbidden18("Only executor runtime credentials may resolve API keys");
56867
57671
  }
56868
57672
  if (executorPayload?.task_id && executorPayload.task_id !== taskId) {
56869
- throw new Forbidden17("Executor token task scope does not match this request");
57673
+ throw new Forbidden18("Executor token task scope does not match this request");
56870
57674
  }
56871
57675
  }
56872
57676
  let userId;
@@ -56881,31 +57685,31 @@ var ConfigService = class {
56881
57685
  } catch (err) {
56882
57686
  console.warn(`[Config.resolveApiKey] Failed to fetch task ${taskId}:`, err);
56883
57687
  if (executorPayload) {
56884
- throw new Forbidden17("Executor token task scope could not be verified");
57688
+ throw new Forbidden18("Executor token task scope could not be verified");
56885
57689
  }
56886
57690
  }
56887
57691
  if (executorPayload && (!userId || !sessionId)) {
56888
- throw new Forbidden17("Executor token task scope could not be verified");
57692
+ throw new Forbidden18("Executor token task scope could not be verified");
56889
57693
  }
56890
57694
  if (executorPayload) {
56891
57695
  const verifiedSessionId = sessionId;
56892
57696
  if (!verifiedSessionId) {
56893
- throw new Forbidden17("Executor token task scope could not be verified");
57697
+ throw new Forbidden18("Executor token task scope could not be verified");
56894
57698
  }
56895
57699
  if (!tool) {
56896
57700
  throw new BadRequest11("Tool is required for executor API key resolution");
56897
57701
  }
56898
57702
  const expectedKeyName = TOOL_API_KEY_NAMES2[tool];
56899
57703
  if (!expectedKeyName || expectedKeyName !== keyName) {
56900
- throw new Forbidden17("Executor token is not valid for this API key");
57704
+ throw new Forbidden18("Executor token is not valid for this API key");
56901
57705
  }
56902
57706
  const sessionsService = this.app?.service("sessions");
56903
57707
  if (!sessionsService) {
56904
- throw new Forbidden17("Executor token tool scope could not be verified");
57708
+ throw new Forbidden18("Executor token tool scope could not be verified");
56905
57709
  }
56906
57710
  const session = await sessionsService.get(verifiedSessionId, { provider: void 0 });
56907
57711
  if (session?.agentic_tool !== tool) {
56908
- throw new Forbidden17("Executor token tool scope does not match this session");
57712
+ throw new Forbidden18("Executor token tool scope does not match this session");
56909
57713
  }
56910
57714
  }
56911
57715
  const result = await resolveApiKey3(keyName, {
@@ -56990,7 +57794,7 @@ function createConfigService(db) {
56990
57794
  // src/services/context.ts
56991
57795
  import { readdir as readdir3, readFile as readFile3, stat as stat2 } from "fs/promises";
56992
57796
  import { isAbsolute as isAbsolute4, join as join6, relative as relative3, resolve as resolve5 } from "path";
56993
- import { ROLES as ROLES18 } from "@agor/core/types";
57797
+ import { ROLES as ROLES20 } from "@agor/core/types";
56994
57798
  var ContextService = class {
56995
57799
  branchRepo;
56996
57800
  constructor(branchRepo) {
@@ -57006,7 +57810,7 @@ var ContextService = class {
57006
57810
  console.warn(
57007
57811
  "[Context Service] DEPRECATED: This service is deprecated. Use /file endpoint instead for browsing all files in a branch."
57008
57812
  );
57009
- ensureMinimumRole(params, ROLES18.MEMBER, "list context files");
57813
+ ensureMinimumRole(params, ROLES20.MEMBER, "list context files");
57010
57814
  const branchId = params?.query?.branch_id;
57011
57815
  if (!branchId) {
57012
57816
  throw new Error("branch_id query parameter is required");
@@ -57032,7 +57836,7 @@ var ContextService = class {
57032
57836
  * @param id - Relative path from branch root (e.g., "context/concepts/core.md", "CLAUDE.md")
57033
57837
  */
57034
57838
  async get(id, params) {
57035
- ensureMinimumRole(params, ROLES18.MEMBER, "read context file");
57839
+ ensureMinimumRole(params, ROLES20.MEMBER, "read context file");
57036
57840
  const branchId = params?.query?.branch_id;
57037
57841
  if (!branchId) {
57038
57842
  throw new Error("branch_id query parameter is required");
@@ -57294,7 +58098,7 @@ function createCursorModelsService(db) {
57294
58098
  // src/services/file.ts
57295
58099
  import { lstat as lstat2, readdir as readdir4, readFile as readFile4, realpath as realpath5 } from "fs/promises";
57296
58100
  import { extname, isAbsolute as isAbsolute5, join as join7, relative as relative4, resolve as resolve6, sep as sep5 } from "path";
57297
- import { ROLES as ROLES19 } from "@agor/core/types";
58101
+ import { ROLES as ROLES21 } from "@agor/core/types";
57298
58102
  var MAX_FILES = 5e4;
57299
58103
  var MAX_PREVIEW_SIZE = 1024 * 1024;
57300
58104
  var MAX_TITLE_READ_BYTES = 4096;
@@ -57388,7 +58192,7 @@ var FileService = class {
57388
58192
  * Returns lightweight list items without content
57389
58193
  */
57390
58194
  async find(params) {
57391
- ensureMinimumRole(params, ROLES19.MEMBER, "list files");
58195
+ ensureMinimumRole(params, ROLES21.MEMBER, "list files");
57392
58196
  const branchId = params?.query?.branch_id;
57393
58197
  if (!branchId) {
57394
58198
  throw new Error("branch_id query parameter is required");
@@ -57421,7 +58225,7 @@ var FileService = class {
57421
58225
  * @param id - Relative path from branch root (e.g., "src/index.ts", "README.md")
57422
58226
  */
57423
58227
  async get(id, params) {
57424
- ensureMinimumRole(params, ROLES19.MEMBER, "read file");
58228
+ ensureMinimumRole(params, ROLES21.MEMBER, "read file");
57425
58229
  const branchId = params?.query?.branch_id;
57426
58230
  if (!branchId) {
57427
58231
  throw new Error("branch_id query parameter is required");
@@ -57682,7 +58486,8 @@ import {
57682
58486
  getHiddenTenantId,
57683
58487
  MCPServerRepository as MCPServerRepository3,
57684
58488
  runWithoutTenantDatabaseScope as runWithoutTenantDatabaseScope3,
57685
- runWithTenantDatabaseScope as runWithTenantDatabaseScope5,
58489
+ runWithTenantDatabaseScope as runWithTenantDatabaseScope7,
58490
+ SessionRepository as SessionRepository9,
57686
58491
  shortId as shortId23,
57687
58492
  ThreadSessionMapRepository as ThreadSessionMapRepository2,
57688
58493
  UserMCPOAuthTokenRepository as UserMCPOAuthTokenRepository3,
@@ -57699,7 +58504,7 @@ import {
57699
58504
  parseGitHubThreadId
57700
58505
  } from "@agor/core/gateway";
57701
58506
  import { resolveSessionDefaults as resolveSessionDefaults5 } from "@agor/core/sessions";
57702
- import { hasMinimumRole as hasMinimumRole15, ROLES as ROLES20, SessionStatus as SessionStatus7 } from "@agor/core/types";
58507
+ import { hasMinimumRole as hasMinimumRole17, ROLES as ROLES22, SessionStatus as SessionStatus7 } from "@agor/core/types";
57703
58508
  import { getSessionUrl } from "@agor/core/utils/url";
57704
58509
  function hasListeningConfig(channel) {
57705
58510
  const config2 = channel.config;
@@ -57777,6 +58582,13 @@ function oneLineForPrompt(text, maxChars = 900) {
57777
58582
  if (normalized.length <= maxChars) return normalized;
57778
58583
  return `${normalized.slice(0, Math.max(0, maxChars - 1))}\u2026`;
57779
58584
  }
58585
+ var SLACK_GATEWAY_REPLY_NOTE = "Note: Any assistant message you send in this current Agor session is streamed back directly to the Slack conversation. Only use outbound gateway tools when you intentionally need to start a separate thread, DM, or message.";
58586
+ function prependSlackGatewayReplyNote(prompt) {
58587
+ if (prompt.includes(SLACK_GATEWAY_REPLY_NOTE)) return prompt;
58588
+ return `${SLACK_GATEWAY_REPLY_NOTE}
58589
+
58590
+ ${prompt}`;
58591
+ }
57780
58592
  function formatSlackCatchUpPrompt(args) {
57781
58593
  const slackChannelName = typeof args.metadata?.slack_channel_name === "string" ? args.metadata.slack_channel_name : null;
57782
58594
  const slackChannelId = typeof args.metadata?.channel === "string" ? args.metadata.channel : args.threadId.split("-")[0];
@@ -57963,6 +58775,7 @@ var GatewayService = class _GatewayService {
57963
58775
  threadMapRepo;
57964
58776
  outboundRepo;
57965
58777
  branchRepo;
58778
+ sessionRepo;
57966
58779
  usersRepo;
57967
58780
  mcpServerRepo;
57968
58781
  userTokenRepo;
@@ -58005,6 +58818,7 @@ var GatewayService = class _GatewayService {
58005
58818
  this.threadMapRepo = new ThreadSessionMapRepository2(db);
58006
58819
  this.outboundRepo = new GatewayOutboundMessageRepository(db);
58007
58820
  this.branchRepo = new BranchRepository13(db);
58821
+ this.sessionRepo = new SessionRepository9(db);
58008
58822
  this.usersRepo = new UsersRepository9(db);
58009
58823
  this.mcpServerRepo = new MCPServerRepository3(db);
58010
58824
  this.userTokenRepo = new UserMCPOAuthTokenRepository3(db);
@@ -58500,8 +59314,26 @@ ${chunk}` : chunk;
58500
59314
  console.warn("[gateway] Failed to mirror message stream to Slack:", error51);
58501
59315
  }
58502
59316
  }
59317
+ /**
59318
+ * Session-context emits are hard-bound to the session's branch, with no
59319
+ * admin-role bypass — agent sessions run as admin users, so a role bypass
59320
+ * would let any session impersonate another assistant's channel. The error
59321
+ * deliberately never echoes the channel's target branch, so a denied emit
59322
+ * cannot be used to enumerate which branch a foreign channel serves.
59323
+ */
59324
+ async ensureSessionBranchBoundToChannel(channel, sessionId) {
59325
+ const session = await this.sessionRepo.findById(sessionId);
59326
+ if (!session) {
59327
+ throw new Error("Gateway outbound denied: emitting session not found");
59328
+ }
59329
+ if (session.branch_id !== channel.target_branch_id) {
59330
+ throw new Error(
59331
+ `Gateway outbound denied: session ${shortId23(sessionId)} runs on branch ${shortId23(session.branch_id)}, but this channel targets a different branch. Sessions can emit only through gateway channels whose target branch matches their own. Call agor_gateway_outbound_targets_list to see usable channels.`
59332
+ );
59333
+ }
59334
+ }
58503
59335
  async ensureCanEmitFromChannel(channel, userId, userRole) {
58504
- if (hasMinimumRole15(userRole, ROLES20.ADMIN)) return;
59336
+ if (hasMinimumRole17(userRole, ROLES22.ADMIN)) return;
58505
59337
  const branch = await this.branchRepo.findById(channel.target_branch_id);
58506
59338
  if (!branch) {
58507
59339
  throw new Error(`Branch not found for gateway channel ${shortId23(channel.id)}`);
@@ -58533,6 +59365,9 @@ ${chunk}` : chunk;
58533
59365
  if (config2.outbound_enabled !== true) {
58534
59366
  throw new Error("Gateway outbound is disabled for this channel");
58535
59367
  }
59368
+ if (data.emittedBySessionId) {
59369
+ await this.ensureSessionBranchBoundToChannel(channel, data.emittedBySessionId);
59370
+ }
58536
59371
  await this.ensureCanEmitFromChannel(channel, data.emittedByUserId, data.userRole);
58537
59372
  const target = data.target ?? (typeof config2.default_outbound_target === "string" ? config2.default_outbound_target : void 0);
58538
59373
  if (!target) throw new Error("No usable default outbound target configured");
@@ -59107,6 +59942,9 @@ ${chunk}` : chunk;
59107
59942
  promptText = contextPrefix + promptText;
59108
59943
  }
59109
59944
  }
59945
+ if (channel.channel_type === "slack") {
59946
+ promptText = prependSlackGatewayReplyNote(promptText);
59947
+ }
59110
59948
  if (created && mcpAuthWarning) {
59111
59949
  promptText = `${mcpAuthWarning}
59112
59950
 
@@ -59414,7 +60252,7 @@ ${promptText}`;
59414
60252
  if (!tenantId) {
59415
60253
  throw new Error(`Missing tenant context for gateway listener channel ${channel.id}`);
59416
60254
  }
59417
- await runWithTenantDatabaseScope5(this.db, tenantId, async () => {
60255
+ await runWithTenantDatabaseScope7(this.db, tenantId, async () => {
59418
60256
  await this.create({
59419
60257
  channel_key: channel.channel_key,
59420
60258
  thread_id: msg.threadId,
@@ -59470,11 +60308,11 @@ function createGatewayChannelsService(db) {
59470
60308
  import { GatewayChannelRepository as GatewayChannelRepository5 } from "@agor/core/db";
59471
60309
  import { NotFound as NotFound6 } from "@agor/core/feathers";
59472
60310
  import { getConnector as getConnector3 } from "@agor/core/gateway";
59473
- import { GATEWAY_REDACTED_SENTINEL as GATEWAY_REDACTED_SENTINEL3, GATEWAY_SENSITIVE_CONFIG_FIELDS as GATEWAY_SENSITIVE_CONFIG_FIELDS3 } from "@agor/core/types";
60311
+ import { GATEWAY_REDACTED_SENTINEL as GATEWAY_REDACTED_SENTINEL3, GATEWAY_SENSITIVE_CONFIG_FIELDS as GATEWAY_SENSITIVE_CONFIG_FIELDS4 } from "@agor/core/types";
59474
60312
  function resolveEffectiveConfig(stored, overrides) {
59475
60313
  const resolved = { ...stored };
59476
60314
  if (!overrides) return resolved;
59477
- const sensitive = new Set(GATEWAY_SENSITIVE_CONFIG_FIELDS3);
60315
+ const sensitive = new Set(GATEWAY_SENSITIVE_CONFIG_FIELDS4);
59478
60316
  for (const [key, value] of Object.entries(overrides)) {
59479
60317
  if (value === void 0 || value === null) continue;
59480
60318
  if (value === GATEWAY_REDACTED_SENTINEL3) continue;
@@ -59532,7 +60370,7 @@ function createGatewayChannelsTestService(db) {
59532
60370
 
59533
60371
  // src/services/github-app-setup.ts
59534
60372
  import { shortId as shortId24 } from "@agor/core/db";
59535
- import { hasMinimumRole as hasMinimumRole16, ROLES as ROLES21 } from "@agor/core/types";
60373
+ import { hasMinimumRole as hasMinimumRole18, ROLES as ROLES23 } from "@agor/core/types";
59536
60374
 
59537
60375
  // src/utils/html.ts
59538
60376
  function escapeHtml(value) {
@@ -59638,7 +60476,7 @@ function handleIssueState(app) {
59638
60476
  res.status(401).json({ error: "Authentication required to initiate GitHub App install" });
59639
60477
  return;
59640
60478
  }
59641
- if (!hasMinimumRole16(authed.role, ROLES21.ADMIN)) {
60479
+ if (!hasMinimumRole18(authed.role, ROLES23.ADMIN)) {
59642
60480
  res.status(403).json({ error: "Admin role required to initiate GitHub App install" });
59643
60481
  return;
59644
60482
  }
@@ -59850,7 +60688,7 @@ function registerGitHubAppSetupRoutes(app, opts) {
59850
60688
  // src/services/knowledge-document-edits.ts
59851
60689
  init_knowledge_access();
59852
60690
  import { KnowledgeDocumentVersionRepository, KnowledgeNamespaceRepository as KnowledgeNamespaceRepository4 } from "@agor/core/db";
59853
- import { BadRequest as BadRequest12, Forbidden as Forbidden18, NotFound as NotFound7 } from "@agor/core/feathers";
60691
+ import { BadRequest as BadRequest12, Forbidden as Forbidden19, NotFound as NotFound7 } from "@agor/core/feathers";
59854
60692
  import { applyKnowledgeEditOps, KnowledgeEditError } from "@agor/core/knowledge";
59855
60693
  import { createTwoFilesPatch as createTwoFilesPatch2, structuredPatch } from "diff";
59856
60694
  function versionToToken(version2) {
@@ -59880,7 +60718,7 @@ var KnowledgeDocumentEditsService = class {
59880
60718
  }
59881
60719
  async ensureCanEdit(document, user) {
59882
60720
  if (await canWriteKnowledgeDocument(this.namespaceRepo, document, user)) return;
59883
- throw new Forbidden18("You do not have permission to edit this knowledge document");
60721
+ throw new Forbidden19("You do not have permission to edit this knowledge document");
59884
60722
  }
59885
60723
  async create(data, params) {
59886
60724
  if (!data || !Array.isArray(data.ops) || data.ops.length === 0) {
@@ -60007,7 +60845,7 @@ import {
60007
60845
  KnowledgeGraphRepository,
60008
60846
  KnowledgeNamespaceRepository as KnowledgeNamespaceRepository5
60009
60847
  } from "@agor/core/db";
60010
- import { BadRequest as BadRequest13, Forbidden as Forbidden19, NotFound as NotFound8 } from "@agor/core/feathers";
60848
+ import { BadRequest as BadRequest13, Forbidden as Forbidden20, NotFound as NotFound8 } from "@agor/core/feathers";
60011
60849
  import {
60012
60850
  buildKnowledgeDocumentUri as buildKnowledgeDocumentUri2,
60013
60851
  extractKnowledgeLinks,
@@ -60637,7 +61475,7 @@ var KnowledgeDocumentsService = class extends DrizzleService {
60637
61475
  const statusChanged = data.status !== void 0 && data.status !== existing.status;
60638
61476
  if (!visibilityChanged && !editPolicyChanged && !statusChanged) return;
60639
61477
  if (!this.canManageDocument(existing, user)) {
60640
- throw new Forbidden19(
61478
+ throw new Forbidden20(
60641
61479
  "Only the owner or an admin can change knowledge document visibility, lifecycle status, or edit policy"
60642
61480
  );
60643
61481
  }
@@ -60663,7 +61501,7 @@ var KnowledgeDocumentsService = class extends DrizzleService {
60663
61501
  user
60664
61502
  );
60665
61503
  if (!hasKnowledgeNamespacePermission(permission, "write")) {
60666
- throw new Forbidden19("You do not have permission to write to this knowledge namespace");
61504
+ throw new Forbidden20("You do not have permission to write to this knowledge namespace");
60667
61505
  }
60668
61506
  }
60669
61507
  prepareWriteData(data, existing) {
@@ -60851,7 +61689,7 @@ var KnowledgeDocumentsService = class extends DrizzleService {
60851
61689
  if (!doc) throw new NotFound8(`Knowledge document not found: ${id}`);
60852
61690
  await this.assertActiveDocument(doc);
60853
61691
  if (!await this.canRead(doc, params?.user)) {
60854
- throw new Forbidden19("You do not have permission to view this knowledge document");
61692
+ throw new Forbidden20("You do not have permission to view this knowledge document");
60855
61693
  }
60856
61694
  return this.hydrateDocument(doc, params?.query);
60857
61695
  }
@@ -60860,7 +61698,7 @@ var KnowledgeDocumentsService = class extends DrizzleService {
60860
61698
  if (!doc) throw new NotFound8("Knowledge document not found");
60861
61699
  await this.assertActiveDocument(doc);
60862
61700
  if (!await this.canRead(doc, params?.user)) {
60863
- throw new Forbidden19("You do not have permission to view this knowledge document");
61701
+ throw new Forbidden20("You do not have permission to view this knowledge document");
60864
61702
  }
60865
61703
  return this.hydrateDocument(doc, data);
60866
61704
  }
@@ -60879,7 +61717,7 @@ var KnowledgeDocumentsService = class extends DrizzleService {
60879
61717
  await this.assertActiveDocument(existing);
60880
61718
  this.assertCanChangeGovernance(existing, data, params?.user);
60881
61719
  if (!await this.canEdit(existing, params?.user)) {
60882
- throw new Forbidden19("You do not have permission to update this knowledge document");
61720
+ throw new Forbidden20("You do not have permission to update this knowledge document");
60883
61721
  }
60884
61722
  await this.assertExpectedVersion(existing, data.expected_version);
60885
61723
  const result2 = await this.repo.update(
@@ -60979,7 +61817,7 @@ var KnowledgeDocumentsService = class extends DrizzleService {
60979
61817
  await this.assertActiveDocument(existing);
60980
61818
  this.assertCanChangeGovernance(existing, data, params?.user);
60981
61819
  if (!await this.canEdit(existing, params?.user)) {
60982
- throw new Forbidden19("You do not have permission to update this knowledge document");
61820
+ throw new Forbidden20("You do not have permission to update this knowledge document");
60983
61821
  }
60984
61822
  const result = await this.repo.update(String(id), {
60985
61823
  ...this.prepareWriteData(data, existing),
@@ -61004,7 +61842,7 @@ var KnowledgeDocumentsService = class extends DrizzleService {
61004
61842
  await this.assertActiveDocument(existing);
61005
61843
  this.assertCanChangeGovernance(existing, data, params?.user);
61006
61844
  if (!await this.canEdit(existing, params?.user)) {
61007
- throw new Forbidden19("You do not have permission to update this knowledge document");
61845
+ throw new Forbidden20("You do not have permission to update this knowledge document");
61008
61846
  }
61009
61847
  const result = await this.repo.update(String(id), {
61010
61848
  ...this.prepareWriteData(data, existing),
@@ -61030,7 +61868,7 @@ var KnowledgeDocumentsService = class extends DrizzleService {
61030
61868
  await this.assertActiveDocument(existing);
61031
61869
  await this.assertCanWriteNamespace(existing.namespace_id, params?.user);
61032
61870
  if (!this.canManageDocument(existing, params?.user)) {
61033
- throw new Forbidden19("You do not have permission to delete this knowledge document");
61871
+ throw new Forbidden20("You do not have permission to delete this knowledge document");
61034
61872
  }
61035
61873
  await this.repo.delete(String(id));
61036
61874
  this.emit?.("removed", existing, params);
@@ -61048,7 +61886,7 @@ import {
61048
61886
  KnowledgeGraphRepository as KnowledgeGraphRepository2,
61049
61887
  KnowledgeNamespaceRepository as KnowledgeNamespaceRepository6
61050
61888
  } from "@agor/core/db";
61051
- import { Forbidden as Forbidden20, NotFound as NotFound9 } from "@agor/core/feathers";
61889
+ import { Forbidden as Forbidden21, NotFound as NotFound9 } from "@agor/core/feathers";
61052
61890
  import {
61053
61891
  KNOWLEDGE_DOCUMENT_URI_PREFIX as KNOWLEDGE_DOCUMENT_URI_PREFIX2,
61054
61892
  KNOWLEDGE_UNIT_URI_PREFIX,
@@ -61195,7 +62033,7 @@ var KnowledgeGraphService = class {
61195
62033
  }
61196
62034
  if (!document) return;
61197
62035
  if (!await this.canWrite(document, user)) {
61198
- throw new Forbidden20("You do not have permission to link this knowledge document");
62036
+ throw new Forbidden21("You do not have permission to link this knowledge document");
61199
62037
  }
61200
62038
  }
61201
62039
  async documentForNode(node) {
@@ -61337,7 +62175,7 @@ import {
61337
62175
  GroupRepository as GroupRepository2,
61338
62176
  KnowledgeNamespaceRepository as KnowledgeNamespaceRepository7
61339
62177
  } from "@agor/core/db";
61340
- import { BadRequest as BadRequest14, Forbidden as Forbidden21, NotFound as NotFound10 } from "@agor/core/feathers";
62178
+ import { BadRequest as BadRequest14, Forbidden as Forbidden22, NotFound as NotFound10 } from "@agor/core/feathers";
61341
62179
  var KnowledgeNamespacesService = class extends DrizzleService {
61342
62180
  repo;
61343
62181
  groups;
@@ -61398,13 +62236,13 @@ var KnowledgeNamespacesService = class extends DrizzleService {
61398
62236
  async assertCanReadNamespace(namespace, params) {
61399
62237
  const permission = await this.namespacePermission(namespace, params);
61400
62238
  if (permission === "none") {
61401
- throw new Forbidden21("You do not have permission to view this knowledge namespace");
62239
+ throw new Forbidden22("You do not have permission to view this knowledge namespace");
61402
62240
  }
61403
62241
  }
61404
62242
  async assertCanManage(namespace, params) {
61405
62243
  const permission = await this.namespacePermission(namespace, params);
61406
62244
  if (permission !== "own") {
61407
- throw new Forbidden21("You do not have permission to manage this knowledge namespace");
62245
+ throw new Forbidden22("You do not have permission to manage this knowledge namespace");
61408
62246
  }
61409
62247
  }
61410
62248
  attributionUserId(params, requestedUserId) {
@@ -62230,7 +63068,7 @@ import {
62230
63068
  KnowledgeDocumentVersionRepository as KnowledgeDocumentVersionRepository3,
62231
63069
  KnowledgeNamespaceRepository as KnowledgeNamespaceRepository9
62232
63070
  } from "@agor/core/db";
62233
- import { BadRequest as BadRequest17, Forbidden as Forbidden22 } from "@agor/core/feathers";
63071
+ import { BadRequest as BadRequest17, Forbidden as Forbidden23 } from "@agor/core/feathers";
62234
63072
  import {
62235
63073
  parseKnowledgeUri as parseKnowledgeUri4
62236
63074
  } from "@agor/core/types";
@@ -62274,7 +63112,7 @@ var KnowledgeVersionsService = class extends DrizzleService {
62274
63112
  const namespace = await this.namespaces.findById(document.namespace_id);
62275
63113
  if (!namespace || namespace.archived) return [];
62276
63114
  if (!await this.canRead(document, params?.user)) {
62277
- throw new Forbidden22("You do not have permission to view this knowledge document history");
63115
+ throw new Forbidden23("You do not have permission to view this knowledge document history");
62278
63116
  }
62279
63117
  const versions = await this.versions.findAll({
62280
63118
  document_id: documentId
@@ -62963,7 +63801,7 @@ import {
62963
63801
  shortId as shortId26
62964
63802
  } from "@agor/core/db";
62965
63803
  import { autoAssignBranchUniqueId } from "@agor/core/environment/variable-resolver";
62966
- import { BadRequest as BadRequest19, Forbidden as Forbidden23, NotAuthenticated as NotAuthenticated20 } from "@agor/core/feathers";
63804
+ import { BadRequest as BadRequest19, Forbidden as Forbidden24, NotAuthenticated as NotAuthenticated20 } from "@agor/core/feathers";
62967
63805
  import {
62968
63806
  assertRemoteRefVisibleForClone,
62969
63807
  getDefaultBranch,
@@ -63183,7 +64021,7 @@ var ReposService = class extends DrizzleService {
63183
64021
  }
63184
64022
  const isService = !!caller._isServiceAccount;
63185
64023
  if (!isService) {
63186
- throw new Forbidden23("Only the executor service account may initialize Unix groups");
64024
+ throw new Forbidden24("Only the executor service account may initialize Unix groups");
63187
64025
  }
63188
64026
  }
63189
64027
  const { initializeRepoUnixGroup: initializeRepoUnixGroup2 } = await Promise.resolve().then(() => (init_unix_group_init(), unix_group_init_exports));
@@ -63857,6 +64695,50 @@ function createSessionMCPServersService(db) {
63857
64695
  return new SessionMCPServersService(db);
63858
64696
  }
63859
64697
 
64698
+ // src/services/session-streams.ts
64699
+ import { BadRequest as BadRequest20 } from "@agor/core/feathers";
64700
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
64701
+ function isCanonicalSessionId(id) {
64702
+ return UUID_RE.test(id);
64703
+ }
64704
+ function createSessionStreamsService(app) {
64705
+ const resolveAccessibleSessionId = async (sessionId, params) => {
64706
+ const session = await app.service("sessions").get(sessionId, { ...params ?? {}, query: {} });
64707
+ return session?.session_id ?? null;
64708
+ };
64709
+ return {
64710
+ async create(data, params) {
64711
+ const connection = params?.connection;
64712
+ if (!connection) {
64713
+ throw new BadRequest20("session stream subscription requires a realtime connection");
64714
+ }
64715
+ const sessionId = data?.session_id ?? data?.sessionId;
64716
+ if (!sessionId || typeof sessionId !== "string") {
64717
+ throw new BadRequest20("session_id is required");
64718
+ }
64719
+ const canonicalId = await resolveAccessibleSessionId(sessionId, params) ?? sessionId;
64720
+ joinSessionStreamChannel(app, canonicalId, connection);
64721
+ return { session_id: canonicalId, subscribed: true };
64722
+ },
64723
+ async remove(id, params) {
64724
+ const connection = params?.connection;
64725
+ const sessionId = typeof id === "string" ? id : "";
64726
+ if (!connection || !sessionId) {
64727
+ return { session_id: sessionId, subscribed: false };
64728
+ }
64729
+ let canonicalId = sessionId;
64730
+ if (!isCanonicalSessionId(sessionId)) {
64731
+ try {
64732
+ canonicalId = await resolveAccessibleSessionId(sessionId, params) ?? sessionId;
64733
+ } catch {
64734
+ }
64735
+ }
64736
+ leaveSessionStreamChannel(app, canonicalId, connection);
64737
+ return { session_id: canonicalId, subscribed: false };
64738
+ }
64739
+ };
64740
+ }
64741
+
63860
64742
  // src/services/tasks.ts
63861
64743
  init_drizzle();
63862
64744
  init_append_system_message();
@@ -64761,14 +65643,14 @@ function createTasksService(db, app) {
64761
65643
  }
64762
65644
 
64763
65645
  // src/services/templates.ts
64764
- import { BadRequest as BadRequest20 } from "@agor/core/feathers";
65646
+ import { BadRequest as BadRequest21 } from "@agor/core/feathers";
64765
65647
  import { renderTemplate as renderTemplate2 } from "@agor/core/templates/handlebars-helpers";
64766
- import { ROLES as ROLES22 } from "@agor/core/types";
65648
+ import { ROLES as ROLES24 } from "@agor/core/types";
64767
65649
  var TemplatesService = class {
64768
65650
  async create(data, params) {
64769
- ensureMinimumRole(params, ROLES22.MEMBER, "render templates");
65651
+ ensureMinimumRole(params, ROLES24.MEMBER, "render templates");
64770
65652
  if (typeof data?.template !== "string") {
64771
- throw new BadRequest20("template (string) is required");
65653
+ throw new BadRequest21("template (string) is required");
64772
65654
  }
64773
65655
  const context = data.context && typeof data.context === "object" ? data.context : {};
64774
65656
  const rendered = renderTemplate2(data.template, context, { onError: data.onError ?? "empty" });
@@ -64796,11 +65678,11 @@ import {
64796
65678
  } from "@agor/core/config";
64797
65679
  import {
64798
65680
  BranchRepository as BranchRepository15,
64799
- SessionRepository as SessionRepository9,
65681
+ SessionRepository as SessionRepository10,
64800
65682
  shortId as shortId29,
64801
65683
  UsersRepository as UsersRepository10
64802
65684
  } from "@agor/core/db";
64803
- import { Forbidden as Forbidden24 } from "@agor/core/feathers";
65685
+ import { Forbidden as Forbidden25 } from "@agor/core/feathers";
64804
65686
  import {
64805
65687
  getBranchSymlinkPath,
64806
65688
  resolveUnixUserForImpersonation as resolveUnixUserForImpersonation3,
@@ -64904,12 +65786,12 @@ var TerminalsService = class {
64904
65786
  if (rbacEnabled) {
64905
65787
  const userId = params?.user?.user_id;
64906
65788
  if (!userId) {
64907
- throw new Forbidden24("Authentication required to open terminals");
65789
+ throw new Forbidden25("Authentication required to open terminals");
64908
65790
  }
64909
65791
  const branchRepo = new BranchRepository15(this.db);
64910
65792
  const branch = await branchRepo.findById(data.branchId);
64911
65793
  if (!branch) {
64912
- throw new Forbidden24(`Branch not found: ${data.branchId}`);
65794
+ throw new Forbidden25(`Branch not found: ${data.branchId}`);
64913
65795
  }
64914
65796
  const isOwner = await branchRepo.isOwner(branch.branch_id, userId);
64915
65797
  const effectivePermission = await branchRepo.resolveUserPermission(branch, userId);
@@ -64924,7 +65806,7 @@ var TerminalsService = class {
64924
65806
  allowSuperadmin,
64925
65807
  effectivePermission
64926
65808
  )) {
64927
- throw new Forbidden24(
65809
+ throw new Forbidden25(
64928
65810
  `You need 'session' permission on branch ${branch.name} to open a terminal there.`
64929
65811
  );
64930
65812
  }
@@ -65025,11 +65907,11 @@ var TerminalsService = class {
65025
65907
  async resolveEnsureCliTab(sessionId, claimedBranchId, params) {
65026
65908
  if (!sessionId) return null;
65027
65909
  try {
65028
- const sessionRepo = new SessionRepository9(this.db);
65910
+ const sessionRepo = new SessionRepository10(this.db);
65029
65911
  const session = await sessionRepo.findById(sessionId).catch(() => null);
65030
65912
  if (session?.agentic_tool !== "claude-code-cli") return null;
65031
65913
  if (claimedBranchId && claimedBranchId !== session.branch_id) {
65032
- throw new Forbidden24(
65914
+ throw new Forbidden25(
65033
65915
  `ensureCliSessionId session belongs to a different branch than the one provided.`
65034
65916
  );
65035
65917
  }
@@ -65039,12 +65921,12 @@ var TerminalsService = class {
65039
65921
  if (rbacEnabled) {
65040
65922
  const callerUserId = params?.user?.user_id;
65041
65923
  if (!callerUserId) {
65042
- throw new Forbidden24("Authentication required to ensure a CLI tab");
65924
+ throw new Forbidden25("Authentication required to ensure a CLI tab");
65043
65925
  }
65044
65926
  const branchRepo2 = new BranchRepository15(this.db);
65045
65927
  const wt = await branchRepo2.findById(session.branch_id);
65046
65928
  if (!wt) {
65047
- throw new Forbidden24(`Session's branch not found: ${session.branch_id}`);
65929
+ throw new Forbidden25(`Session's branch not found: ${session.branch_id}`);
65048
65930
  }
65049
65931
  const isOwner = await branchRepo2.isOwner(wt.branch_id, callerUserId);
65050
65932
  const effectivePermission = await branchRepo2.resolveUserPermission(wt, callerUserId);
@@ -65059,7 +65941,7 @@ var TerminalsService = class {
65059
65941
  allowSuperadmin,
65060
65942
  effectivePermission
65061
65943
  )) {
65062
- throw new Forbidden24(
65944
+ throw new Forbidden25(
65063
65945
  `You need 'session' permission on the session's branch to ensure its CLI tab.`
65064
65946
  );
65065
65947
  }
@@ -65070,7 +65952,7 @@ var TerminalsService = class {
65070
65952
  callerRole: params.user?.role,
65071
65953
  sessionCreatedBy: session.created_by
65072
65954
  })) {
65073
- throw new Forbidden24("You can only ensure CLI tabs for Claude CLI sessions you created.");
65955
+ throw new Forbidden25("You can only ensure CLI tabs for Claude CLI sessions you created.");
65074
65956
  }
65075
65957
  const branchRepo = new BranchRepository15(this.db);
65076
65958
  const branch = await branchRepo.findById(session.branch_id);
@@ -65089,7 +65971,7 @@ var TerminalsService = class {
65089
65971
  sessionId: session.session_id
65090
65972
  };
65091
65973
  } catch (err) {
65092
- if (err instanceof Forbidden24) throw err;
65974
+ if (err instanceof Forbidden25) throw err;
65093
65975
  console.warn("[TerminalsService] resolveEnsureCliTab failed", err);
65094
65976
  return null;
65095
65977
  }
@@ -65403,7 +66285,7 @@ init_users();
65403
66285
  init_runtime_tokens();
65404
66286
  import { resolveTenantContext as resolveTenantContext5 } from "@agor/core/config";
65405
66287
  import { shortId as shortId30 } from "@agor/core/db";
65406
- import jwt10 from "jsonwebtoken";
66288
+ import jwt11 from "jsonwebtoken";
65407
66289
  function socketAuthState(userId, isService, tenant) {
65408
66290
  return tenant ? { userId, isService, tenant } : { userId, isService };
65409
66291
  }
@@ -65494,7 +66376,7 @@ function createSocketIOConfig(app, options) {
65494
66376
  console.log(`\u{1F513} WebSocket connection without auth (for login flow): ${socket.id}`);
65495
66377
  return next();
65496
66378
  }
65497
- const decoded = jwt10.verify(token, jwtSecret, {
66379
+ const decoded = jwt11.verify(token, jwtSecret, {
65498
66380
  issuer: RUNTIME_JWT_ISSUER,
65499
66381
  audience: RUNTIME_JWT_AUDIENCE
65500
66382
  });
@@ -65849,6 +66731,7 @@ function configureChannels(app, options = {}) {
65849
66731
  app.channel(tenantUserChannelName(tenant.tenant_id, userId)).leave(context.connection);
65850
66732
  }
65851
66733
  }
66734
+ leaveAllSessionStreamChannels(app, context.connection);
65852
66735
  }
65853
66736
  });
65854
66737
  }
@@ -66073,6 +66956,13 @@ async function registerServices(ctx) {
66073
66956
  sessionsService.setExecuteHandler(
66074
66957
  createExecuteHandler(ctx, sessionsService, sessionTokenService)
66075
66958
  );
66959
+ app.use("/session-streams", createSessionStreamsService(app), {
66960
+ methods: ["create", "remove"]
66961
+ });
66962
+ app.service("/session-streams").hooks({
66963
+ before: { all: [ctx.requireAuth] }
66964
+ });
66965
+ app.service("/session-streams").publish(() => []);
66076
66966
  app.use("/tasks", createTasksService(db, app), {
66077
66967
  // Custom events not in this list are dropped at the FeathersJS transport
66078
66968
  // boundary — they fire on the local EventEmitter but never reach socket
@@ -66275,7 +67165,7 @@ async function registerServices(ctx) {
66275
67165
  app.use("/gateway-channels/test", createGatewayChannelsTestService(db));
66276
67166
  app.service("gateway-channels/test").hooks({
66277
67167
  before: {
66278
- create: [ctx.requireAuth, requireMinimumRole(ROLES23.ADMIN, "test gateway channels")]
67168
+ create: [ctx.requireAuth, requireMinimumRole(ROLES25.ADMIN, "test gateway channels")]
66279
67169
  }
66280
67170
  });
66281
67171
  app.use("/thread-session-map", createThreadSessionMapService(db));
@@ -66313,9 +67203,9 @@ async function registerServices(ctx) {
66313
67203
  app.use("/cursor-models", createCursorModelsService(db));
66314
67204
  app.service("/cursor-models").hooks({ before: { find: [ctx.requireAuth] } });
66315
67205
  const branchRepository = new BranchRepository16(db);
66316
- const { UsersRepository: UsersRepository11, SessionRepository: SessionRepository11 } = await import("@agor/core/db");
67206
+ const { UsersRepository: UsersRepository11, SessionRepository: SessionRepository12 } = await import("@agor/core/db");
66317
67207
  const usersRepository = new UsersRepository11(db);
66318
- const sessionsRepository = new SessionRepository11(db);
67208
+ const sessionsRepository = new SessionRepository12(db);
66319
67209
  if (svcEnabled("file_browser")) {
66320
67210
  app.use("/context", createContextService(branchRepository));
66321
67211
  app.use("/file", createFileService(branchRepository));
@@ -66792,6 +67682,7 @@ async function registerMCPServices(ctx, sessionsService) {
66792
67682
  mcpServerId: opts.mcpServerId,
66793
67683
  userId: opts.userId,
66794
67684
  oauthMode: opts.oauthMode,
67685
+ tenantId: opts.tenantId ?? getCurrentTenantId7(),
66795
67686
  socketId: opts.socketId,
66796
67687
  createdAt: Date.now(),
66797
67688
  tokenResolve,
@@ -66815,6 +67706,31 @@ async function registerMCPServices(ctx, sessionsService) {
66815
67706
  }
66816
67707
  return base;
66817
67708
  }
67709
+ const tenantIdFromParams = (params) => params?.tenant?.tenant_id ?? getCurrentTenantId7();
67710
+ const persistOAuthTokenForPendingFlow = async (tokenResponse, pendingFlow, logPrefix) => {
67711
+ const work = () => persistOAuthToken(
67712
+ db,
67713
+ tokenResponse,
67714
+ pendingFlow.mcpUrl,
67715
+ {
67716
+ ...pendingFlow,
67717
+ clientId: pendingFlow.context.clientId,
67718
+ clientSecret: pendingFlow.context.clientSecret,
67719
+ tokenEndpoint: pendingFlow.context.tokenEndpoint
67720
+ },
67721
+ logPrefix
67722
+ );
67723
+ if (pendingFlow.tenantId) {
67724
+ await runInOAuthTenantScope(db, pendingFlow.tenantId, work);
67725
+ return;
67726
+ }
67727
+ if (isPostgresDatabase8(db) && pendingFlow.mcpServerId) {
67728
+ throw new Error(
67729
+ "Missing tenant context for MCP OAuth callback. Please restart the OAuth flow."
67730
+ );
67731
+ }
67732
+ await work();
67733
+ };
66818
67734
  const oauthCallbackHandler = async (req, res) => {
66819
67735
  res.setHeader("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'");
66820
67736
  res.setHeader("X-Content-Type-Options", "nosniff");
@@ -66857,18 +67773,7 @@ async function registerMCPServices(ctx, sessionsService) {
66857
67773
  const { completeMCPOAuthFlow } = await import("@agor/core/tools/mcp/oauth-mcp-transport");
66858
67774
  const tokenResponse = await completeMCPOAuthFlow(pendingFlow.context, code, state);
66859
67775
  pendingOAuthFlows.delete(state);
66860
- await persistOAuthToken(
66861
- db,
66862
- tokenResponse,
66863
- pendingFlow.mcpUrl,
66864
- {
66865
- ...pendingFlow,
66866
- clientId: pendingFlow.context.clientId,
66867
- clientSecret: pendingFlow.context.clientSecret,
66868
- tokenEndpoint: pendingFlow.context.tokenEndpoint
66869
- },
66870
- "OAuth Callback"
66871
- );
67776
+ await persistOAuthTokenForPendingFlow(tokenResponse, pendingFlow, "OAuth Callback");
66872
67777
  if (app.io) {
66873
67778
  const oauthEvent = {
66874
67779
  state,
@@ -66997,6 +67902,7 @@ async function registerMCPServices(ctx, sessionsService) {
66997
67902
  // call (writes to the shared MCP server row, not per-user).
66998
67903
  oauthMode: "shared",
66999
67904
  clientId: data.client_id,
67905
+ tenantId: tenantIdFromParams(params),
67000
67906
  socketId: connection?.id
67001
67907
  });
67002
67908
  } catch (err) {
@@ -67199,6 +68105,7 @@ async function registerMCPServices(ctx, sessionsService) {
67199
68105
  try {
67200
68106
  console.log("[OAuth Start] Starting two-phase OAuth flow for:", data.mcp_url);
67201
68107
  const userId = params?.user?.user_id;
68108
+ const tenantId = tenantIdFromParams(params);
67202
68109
  let oauthMode;
67203
68110
  let authorizationUrlOverride;
67204
68111
  let tokenUrlOverride;
@@ -67206,8 +68113,10 @@ async function registerMCPServices(ctx, sessionsService) {
67206
68113
  let clientIdFromConfig;
67207
68114
  let scopeOverride;
67208
68115
  if (data.mcp_server_id) {
67209
- const mcpServerRepo = new MCPServerRepository5(db);
67210
- const server = await mcpServerRepo.findById(data.mcp_server_id);
68116
+ const server = await runInOAuthTenantScope(db, tenantId, () => {
68117
+ const mcpServerRepo = new MCPServerRepository5(db);
68118
+ return mcpServerRepo.findById(data.mcp_server_id);
68119
+ });
67211
68120
  if (server?.auth?.type === "oauth") {
67212
68121
  oauthMode = server.auth.oauth_mode || "per_user";
67213
68122
  authorizationUrlOverride = server.auth.oauth_authorization_url;
@@ -67255,6 +68164,7 @@ async function registerMCPServices(ctx, sessionsService) {
67255
68164
  authorizationUrlOverride,
67256
68165
  tokenUrlOverride,
67257
68166
  scope: scopeOverride,
68167
+ tenantId,
67258
68168
  socketId
67259
68169
  });
67260
68170
  } catch (err) {
@@ -67299,18 +68209,13 @@ async function registerMCPServices(ctx, sessionsService) {
67299
68209
  };
67300
68210
  const tokenResponse = await completeMCPOAuthFlow(pendingFlow.context, code, state);
67301
68211
  pendingOAuthFlows.delete(state);
67302
- await persistOAuthToken(
67303
- db,
67304
- tokenResponse,
67305
- pendingFlow.mcpUrl,
67306
- {
67307
- ...pendingFlow,
67308
- clientId: pendingFlow.context.clientId,
67309
- clientSecret: pendingFlow.context.clientSecret,
67310
- tokenEndpoint: pendingFlow.context.tokenEndpoint
67311
- },
67312
- "OAuth Complete"
67313
- );
68212
+ const activeTenantId = getCurrentTenantId7();
68213
+ if (pendingFlow.tenantId && activeTenantId && pendingFlow.tenantId !== activeTenantId) {
68214
+ throw new Error(
68215
+ "OAuth flow belongs to a different tenant. Please restart the OAuth flow."
68216
+ );
68217
+ }
68218
+ await persistOAuthTokenForPendingFlow(tokenResponse, pendingFlow, "OAuth Complete");
67314
68219
  return { success: true, message: "OAuth authentication successful!", tokenObtained: true };
67315
68220
  } catch (error51) {
67316
68221
  console.error("[OAuth Complete] Error:", error51);
@@ -67367,17 +68272,35 @@ async function registerMCPServices(ctx, sessionsService) {
67367
68272
  }
67368
68273
  const sessionId = params?.session_id;
67369
68274
  const trustedInternalOrService = shouldExposeMCPServerSecrets(params);
67370
- const trustedSessionExecutor = shouldExposeMCPServerSecretsForSessionToken(params, {
68275
+ let trustedSessionExecutor = shouldExposeMCPServerSecretsForSessionToken(params, {
67371
68276
  sessionId
67372
68277
  });
68278
+ let executorSessionId = sessionId;
68279
+ if (!trustedSessionExecutor && params?.provider && data.executorSessionToken) {
68280
+ const executorTokenService = app.sessionTokenService;
68281
+ const sessionInfo = await executorTokenService?.validateToken(
68282
+ data.executorSessionToken,
68283
+ {}
68284
+ );
68285
+ if (sessionInfo?.session_id) {
68286
+ executorSessionId = sessionInfo.session_id;
68287
+ trustedSessionExecutor = true;
68288
+ }
68289
+ }
67373
68290
  if (!trustedInternalOrService && !trustedSessionExecutor) {
67374
- throw new Forbidden25("oauth-auth-headers is only available to trusted executor paths");
68291
+ throw new Forbidden26("oauth-auth-headers is only available to trusted executor paths");
67375
68292
  }
67376
68293
  const userTokenRepo = new UserMCPOAuthTokenRepository4(db);
67377
68294
  const mcpServerRepo = new MCPServerRepository5(db);
67378
68295
  if (trustedSessionExecutor) {
68296
+ if (!executorSessionId) {
68297
+ throw new Forbidden26("oauth-auth-headers requires executor session scope");
68298
+ }
67379
68299
  const sessionMcpRepo = new SessionMCPServerRepository5(db);
67380
- const attachedServers = await sessionMcpRepo.listServers(sessionId, true);
68300
+ const attachedServers = await sessionMcpRepo.listServers(
68301
+ executorSessionId,
68302
+ true
68303
+ );
67381
68304
  const globalServers = await mcpServerRepo.findAll({ scope: "global", enabled: true });
67382
68305
  const allowedServerIds = /* @__PURE__ */ new Set([
67383
68306
  ...globalServers.map((server) => server.mcp_server_id),
@@ -67517,7 +68440,7 @@ async function registerMCPServices(ctx, sessionsService) {
67517
68440
  const { restoreRedactedMCPAuthSecrets } = await import("@agor/core/tools/mcp/auth-secrets");
67518
68441
  const { resolveMCPAuthHeaders } = await import("@agor/core/tools/mcp/jwt-auth");
67519
68442
  const { mergeMCPRemoteHeaders, restoreRedactedMCPCustomHeaders } = await import("@agor/core/tools/mcp/http-headers");
67520
- const { hasMinimumRole: hasMinimumRole17, ROLES: ROLES24 } = await import("@agor/core/types");
68443
+ const { hasMinimumRole: hasMinimumRole19, ROLES: ROLES26 } = await import("@agor/core/types");
67521
68444
  const mcpServerRepo = new MCPServerRepository5(db);
67522
68445
  const validateUrl = (url2) => {
67523
68446
  try {
@@ -67552,7 +68475,7 @@ async function registerMCPServices(ctx, sessionsService) {
67552
68475
  if (params?.provider && params.user) {
67553
68476
  const userId2 = params.user.user_id;
67554
68477
  const userRole = params.user.role?.toLowerCase();
67555
- const isAdmin4 = hasMinimumRole17(userRole, ROLES24.ADMIN);
68478
+ const isAdmin4 = hasMinimumRole19(userRole, ROLES26.ADMIN);
67556
68479
  const isOwner = server.owner_user_id === userId2;
67557
68480
  if (server.scope === "global" && !isOwner && !isAdmin4)
67558
68481
  return {
@@ -67581,7 +68504,7 @@ async function registerMCPServices(ctx, sessionsService) {
67581
68504
  if (params?.provider && params.user) {
67582
68505
  const userId2 = params.user.user_id;
67583
68506
  const userRole = params.user.role?.toLowerCase();
67584
- const isAdmin4 = hasMinimumRole17(userRole, ROLES24.ADMIN);
68507
+ const isAdmin4 = hasMinimumRole19(userRole, ROLES26.ADMIN);
67585
68508
  const isOwner = server.owner_user_id === userId2;
67586
68509
  if (server.scope === "global" && !isOwner && !isAdmin4)
67587
68510
  return {
@@ -67674,6 +68597,7 @@ async function registerMCPServices(ctx, sessionsService) {
67674
68597
  // call). Without a serverId nothing is persisted to the DB; the
67675
68598
  // daemon-level cache below carries the token for this request.
67676
68599
  oauthMode: "shared",
68600
+ tenantId: tenantIdFromParams(params),
67677
68601
  socketId: connection?.id
67678
68602
  });
67679
68603
  const tokenResponse = await started.awaitToken();
@@ -67848,7 +68772,7 @@ async function registerMCPServices(ctx, sessionsService) {
67848
68772
  return { oauthCallbackHandler };
67849
68773
  }
67850
68774
  async function bootstrapSuperadminUsers(config2, usersService, allowSuperadmin) {
67851
- const { ROLES: ROLES24 } = await import("@agor/core/types");
68775
+ const { ROLES: ROLES26 } = await import("@agor/core/types");
67852
68776
  const bootstrapUsers = config2.execution?.bootstrap_superadmin_users ?? [];
67853
68777
  if (bootstrapUsers.length === 0) return;
67854
68778
  if (!allowSuperadmin) {
@@ -67863,8 +68787,8 @@ async function bootstrapSuperadminUsers(config2, usersService, allowSuperadmin)
67863
68787
  if (!userId) continue;
67864
68788
  try {
67865
68789
  const user = await usersService.get(userId);
67866
- if (user.role === ROLES24.SUPERADMIN) continue;
67867
- await usersService.patch(userId, { role: ROLES24.SUPERADMIN });
68790
+ if (user.role === ROLES26.SUPERADMIN) continue;
68791
+ await usersService.patch(userId, { role: ROLES26.SUPERADMIN });
67868
68792
  promotedCount++;
67869
68793
  console.log(
67870
68794
  `[RBAC] Bootstrap promoted user ${shortId32(userId)} (${user.email}) to superadmin`
@@ -68091,7 +69015,7 @@ import {
68091
69015
  createTenantScopedDatabaseProxy,
68092
69016
  formatPendingMigrationsMessage,
68093
69017
  runWithSystemDatabaseScope as runWithSystemDatabaseScope2,
68094
- runWithTenantDatabaseScope as runWithTenantDatabaseScope6,
69018
+ runWithTenantDatabaseScope as runWithTenantDatabaseScope8,
68095
69019
  seedInitialData
68096
69020
  } from "@agor/core/db";
68097
69021
  import { extractDbFilePath } from "@agor/core/utils/path";
@@ -68348,7 +69272,7 @@ async function initializeDatabase(dbPath, options = {}) {
68348
69272
  }
68349
69273
  };
68350
69274
  if (options.tenantId) {
68351
- await runWithTenantDatabaseScope6(scopedDb, options.tenantId, runInitialDataSetup);
69275
+ await runWithTenantDatabaseScope8(scopedDb, options.tenantId, runInitialDataSetup);
68352
69276
  } else {
68353
69277
  await runWithSystemDatabaseScope2(scopedDb, "database initialization", runInitialDataSetup);
68354
69278
  }
@@ -68484,15 +69408,15 @@ import path17 from "path";
68484
69408
  import {
68485
69409
  getAgorHome as getAgorHome2,
68486
69410
  resolveExecutorHeartbeatConfig as resolveExecutorHeartbeatConfig3,
68487
- resolveMultiTenancyConfig as resolveMultiTenancyConfig6
69411
+ resolveMultiTenancyConfig as resolveMultiTenancyConfig7
68488
69412
  } from "@agor/core/config";
68489
69413
  import {
68490
69414
  MessagesRepository as MessagesRepository3,
68491
- runWithTenantDatabaseScope as runWithTenantDatabaseScope9,
68492
- SessionRepository as SessionRepository10,
69415
+ runWithTenantDatabaseScope as runWithTenantDatabaseScope11,
69416
+ SessionRepository as SessionRepository11,
68493
69417
  shortId as shortId37
68494
69418
  } from "@agor/core/db";
68495
- import { SessionStatus as SessionStatus10, TaskStatus as TaskStatus8 } from "@agor/core/types";
69419
+ import { isTerminalTaskStatus as isTerminalTaskStatus3, SessionStatus as SessionStatus10, TaskStatus as TaskStatus8 } from "@agor/core/types";
68496
69420
 
68497
69421
  // src/services/executor-heartbeat-supervisor.ts
68498
69422
  import { shortId as shortId33 } from "@agor/core/db";
@@ -68566,8 +69490,9 @@ import { ENVIRONMENT as ENVIRONMENT2 } from "@agor/core/config";
68566
69490
  import {
68567
69491
  BranchRepository as BranchRepository17,
68568
69492
  getHiddenTenantId as getHiddenTenantId2,
69493
+ runWithoutTenantDatabaseScope as runWithoutTenantDatabaseScope4,
68569
69494
  runWithSystemDatabaseScope as runWithSystemDatabaseScope3,
68570
- runWithTenantDatabaseScope as runWithTenantDatabaseScope7,
69495
+ runWithTenantDatabaseScope as runWithTenantDatabaseScope9,
68571
69496
  shortId as shortId34
68572
69497
  } from "@agor/core/db";
68573
69498
  import { NotFoundError as NotFoundError7 } from "@agor/core/utils/errors";
@@ -68655,15 +69580,17 @@ var HealthMonitor = class {
68655
69580
  startMonitoring(branchId, params = this.branchParams.get(branchId)) {
68656
69581
  this.stopMonitoring(branchId);
68657
69582
  if (params) this.branchParams.set(branchId, params);
68658
- setTimeout(() => {
68659
- if (this.isShuttingDown) return;
68660
- this.checkHealth(branchId);
68661
- const interval = setInterval(() => {
69583
+ runWithoutTenantDatabaseScope4(() => {
69584
+ setTimeout(() => {
68662
69585
  if (this.isShuttingDown) return;
68663
69586
  this.checkHealth(branchId);
68664
- }, ENVIRONMENT2.HEALTH_CHECK_INTERVAL_MS);
68665
- this.intervals.set(branchId, interval);
68666
- }, ENVIRONMENT2.STARTUP_GRACE_PERIOD_MS);
69587
+ const interval = setInterval(() => {
69588
+ if (this.isShuttingDown) return;
69589
+ this.checkHealth(branchId);
69590
+ }, ENVIRONMENT2.HEALTH_CHECK_INTERVAL_MS);
69591
+ this.intervals.set(branchId, interval);
69592
+ }, ENVIRONMENT2.STARTUP_GRACE_PERIOD_MS);
69593
+ });
68667
69594
  }
68668
69595
  /**
68669
69596
  * Stop monitoring a branch's health
@@ -68776,7 +69703,7 @@ var HealthMonitor = class {
68776
69703
  activeCount += 1;
68777
69704
  };
68778
69705
  if (this.db) {
68779
- await runWithTenantDatabaseScope7(this.db, ref.tenantId, loadAndStart);
69706
+ await runWithTenantDatabaseScope9(this.db, ref.tenantId, loadAndStart);
68780
69707
  } else {
68781
69708
  await loadAndStart();
68782
69709
  }
@@ -68802,7 +69729,7 @@ var HealthMonitor = class {
68802
69729
  };
68803
69730
  if (this.tenantId) {
68804
69731
  if (!this.db) return findRefs(this.tenantId);
68805
- return runWithTenantDatabaseScope7(this.db, this.tenantId, () => findRefs(this.tenantId));
69732
+ return runWithTenantDatabaseScope9(this.db, this.tenantId, () => findRefs(this.tenantId));
68806
69733
  }
68807
69734
  if (!this.db) return findRefs();
68808
69735
  return runWithSystemDatabaseScope3(
@@ -68846,11 +69773,11 @@ import {
68846
69773
  generateId as generateId9,
68847
69774
  inArray as inArray3,
68848
69775
  insert as insert3,
68849
- isPostgresDatabase as isPostgresDatabase8,
69776
+ isPostgresDatabase as isPostgresDatabase9,
68850
69777
  kbDocumentUnits as kbDocumentUnits3,
68851
69778
  kbDocumentVersions as kbDocumentVersions2,
68852
69779
  kbEmbeddingSpaces,
68853
- runWithTenantDatabaseScope as runWithTenantDatabaseScope8,
69780
+ runWithTenantDatabaseScope as runWithTenantDatabaseScope10,
68854
69781
  select as select7,
68855
69782
  shortId as shortId35,
68856
69783
  sql as sql7,
@@ -69102,7 +70029,7 @@ var KnowledgeEmbeddingIndexer = class {
69102
70029
  }
69103
70030
  async tick() {
69104
70031
  if (this.options.tenantId) {
69105
- return runWithTenantDatabaseScope8(this.db, this.options.tenantId, () => this.tickInScope());
70032
+ return runWithTenantDatabaseScope10(this.db, this.options.tenantId, () => this.tickInScope());
69106
70033
  }
69107
70034
  return this.tickInScope();
69108
70035
  }
@@ -69124,7 +70051,7 @@ var KnowledgeEmbeddingIndexer = class {
69124
70051
  const semantic = config2.knowledge?.semantic_search;
69125
70052
  if (semantic?.enabled !== true) return this.idle();
69126
70053
  if (semantic.indexing?.paused === true) return this.idle();
69127
- if (!isPostgresDatabase8(this.db)) return this.idle();
70054
+ if (!isPostgresDatabase9(this.db)) return this.idle();
69128
70055
  const provider = semantic.provider ?? "openai";
69129
70056
  if (provider !== "openai") return this.idle();
69130
70057
  const apiKey = await this.variables.getPlain(
@@ -69357,7 +70284,7 @@ async function readAndClearSentinel() {
69357
70284
  }
69358
70285
  }
69359
70286
  function startupTenantParams(config2) {
69360
- const multiTenancy = resolveMultiTenancyConfig6(config2);
70287
+ const multiTenancy = resolveMultiTenancyConfig7(config2);
69361
70288
  return {
69362
70289
  tenant: {
69363
70290
  tenant_id: multiTenancy.static_tenant_id,
@@ -69366,7 +70293,7 @@ function startupTenantParams(config2) {
69366
70293
  };
69367
70294
  }
69368
70295
  async function runStartupTenantDatabaseScope(ctx, work) {
69369
- return runWithTenantDatabaseScope9(ctx.db, startupTenantParams(ctx.config).tenant.tenant_id, work);
70296
+ return runWithTenantDatabaseScope11(ctx.db, startupTenantParams(ctx.config).tenant.tenant_id, work);
69370
70297
  }
69371
70298
  async function cleanupOrphanStatuses(ctx) {
69372
70299
  return runStartupTenantDatabaseScope(ctx, () => cleanupOrphanStatusesInTenantScope(ctx));
@@ -69459,18 +70386,36 @@ async function cleanupOrphanStatusesInTenantScope(ctx) {
69459
70386
  }
69460
70387
  }
69461
70388
  }
69462
- const stuckIdleResult = await sessionsService.find({
70389
+ const bootInterruptedTaskIds = /* @__PURE__ */ new Set([
70390
+ ...orphanedTasks.map((t) => t.task_id),
70391
+ ...queuedTasks.map((t) => t.task_id)
70392
+ ]);
70393
+ const idleNotReadyResult = await sessionsService.find({
69463
70394
  query: { status: SessionStatus10.IDLE, ready_for_prompt: false, $limit: 1e3 },
69464
70395
  ...startupParams
69465
70396
  });
69466
- const stuckIdleSessions = stuckIdleResult.data;
69467
- if (stuckIdleSessions.length > 0) {
69468
- for (const session of stuckIdleSessions) {
69469
- await app.service("sessions").patch(session.session_id, { ready_for_prompt: true }, startupParams);
69470
- startupDebug(
69471
- ` \u2713 Unblocked stuck-idle session ${shortId37(session.session_id)} (ready_for_prompt was false)`
69472
- );
70397
+ const stuckIdleSessions = [];
70398
+ for (const session of idleNotReadyResult.data) {
70399
+ const latestTaskId = session.tasks?.at(-1);
70400
+ if (!latestTaskId) {
70401
+ continue;
69473
70402
  }
70403
+ let wasInterrupted = bootInterruptedTaskIds.has(latestTaskId);
70404
+ if (!wasInterrupted) {
70405
+ try {
70406
+ const latestTask = await tasksService.get(latestTaskId, startupParams);
70407
+ wasInterrupted = !isTerminalTaskStatus3(latestTask.status);
70408
+ } catch {
70409
+ }
70410
+ }
70411
+ if (!wasInterrupted) {
70412
+ continue;
70413
+ }
70414
+ stuckIdleSessions.push(session);
70415
+ await app.service("sessions").patch(session.session_id, { ready_for_prompt: true }, startupParams);
70416
+ startupDebug(
70417
+ ` \u2713 Unblocked stuck-idle session ${shortId37(session.session_id)} (ready_for_prompt was false, latest task interrupted)`
70418
+ );
69474
70419
  }
69475
70420
  const cleanupParts = [
69476
70421
  `${orphanedTasks.length} orphaned task(s) stopped`,
@@ -69522,7 +70467,7 @@ async function injectRestartNoticesInTenantScope(ctx, cleanupResult) {
69522
70467
  lastOrphanedTaskBySession.set(sid, task);
69523
70468
  }
69524
70469
  }
69525
- const sessionRepo = new SessionRepository10(db);
70470
+ const sessionRepo = new SessionRepository11(db);
69526
70471
  const messageRepo = new MessagesRepository3(db);
69527
70472
  for (const sessionId of affectedSessionIds) {
69528
70473
  try {
@@ -69620,7 +70565,7 @@ async function startup(ctx) {
69620
70565
  terminalsService
69621
70566
  } = ctx;
69622
70567
  const orphanCleanupResult = await cleanupOrphanStatuses(ctx);
69623
- const startupMultiTenancy = resolveMultiTenancyConfig6(config2);
70568
+ const startupMultiTenancy = resolveMultiTenancyConfig7(config2);
69624
70569
  const healthMonitor = new HealthMonitor(app, {
69625
70570
  defaultParams: startupTenantParams(config2),
69626
70571
  db,
@@ -69670,7 +70615,7 @@ async function startup(ctx) {
69670
70615
  }
69671
70616
  let schedulerService = null;
69672
70617
  if (svcEnabled("scheduler")) {
69673
- const multiTenancy = resolveMultiTenancyConfig6(config2);
70618
+ const multiTenancy = resolveMultiTenancyConfig7(config2);
69674
70619
  schedulerService = new SchedulerService(db, app, {
69675
70620
  tickInterval: 3e4,
69676
70621
  // 30 seconds
@@ -69806,7 +70751,7 @@ import {
69806
70751
  eq as eq7,
69807
70752
  gte as gte3,
69808
70753
  lt,
69809
- runWithTenantDatabaseScope as runWithTenantDatabaseScope10,
70754
+ runWithTenantDatabaseScope as runWithTenantDatabaseScope12,
69810
70755
  select as select8,
69811
70756
  sessions as sessions2,
69812
70757
  tasks as tasks2
@@ -69897,7 +70842,7 @@ async function flushOpenSourceTelemetryUsageSummary(db) {
69897
70842
  }
69898
70843
  function startOpenSourceTelemetryUsageSummaryInterval(db, options) {
69899
70844
  const run = () => {
69900
- runWithTenantDatabaseScope10(
70845
+ runWithTenantDatabaseScope12(
69901
70846
  db,
69902
70847
  options.tenantId,
69903
70848
  () => flushOpenSourceTelemetryUsageSummary(db)
@@ -69920,9 +70865,11 @@ init_spawn_executor();
69920
70865
 
69921
70866
  // src/widgets/index.ts
69922
70867
  init_env_vars();
70868
+ init_gateway_token();
69923
70869
  init_registry();
69924
70870
  function registerAllWidgets() {
69925
70871
  registerEnvVarsWidget();
70872
+ registerGatewayTokenWidget();
69926
70873
  }
69927
70874
 
69928
70875
  // src/index.ts
@@ -69960,7 +70907,7 @@ async function startDaemon(options) {
69960
70907
  process.env.GIT_TERMINAL_PROMPT = "0";
69961
70908
  process.env.GIT_ASKPASS = "echo";
69962
70909
  let config2 = options?.config ? options.config : options?.configPath ? await loadConfigFromFile(options.configPath) : await loadConfig16();
69963
- const multiTenancy = resolveMultiTenancyConfig7(config2);
70910
+ const multiTenancy = resolveMultiTenancyConfig8(config2);
69964
70911
  console.log(
69965
70912
  `\u{1F3E2} Multi-tenancy: mode=${multiTenancy.mode} tenant=${multiTenancy.mode === "static" ? multiTenancy.static_tenant_id : "auth-resolved"}`
69966
70913
  );
@@ -70027,14 +70974,14 @@ async function startDaemon(options) {
70027
70974
  if (context.method === "patch") {
70028
70975
  const data = context.data;
70029
70976
  if (data?.password) return context;
70030
- throw new Forbidden26("Password change required. Please update your password.", {
70977
+ throw new Forbidden27("Password change required. Please update your password.", {
70031
70978
  code: "PASSWORD_CHANGE_REQUIRED",
70032
70979
  user_id: freshUser.user_id
70033
70980
  });
70034
70981
  }
70035
70982
  }
70036
70983
  }
70037
- throw new Forbidden26("Password change required. Please update your password.", {
70984
+ throw new Forbidden27("Password change required. Please update your password.", {
70038
70985
  code: "PASSWORD_CHANGE_REQUIRED",
70039
70986
  user_id: freshUser.user_id
70040
70987
  });
@@ -70381,14 +71328,14 @@ async function startDaemon(options) {
70381
71328
  });
70382
71329
  runPostStartJob("cli-watcher-rehydrate", async () => {
70383
71330
  const { rehydrateCliWatchers: rehydrateCliWatchers2, scanCliWatcherRehydrateSessions: scanCliWatcherRehydrateSessions2 } = await Promise.resolve().then(() => (init_claude_cli_integration(), claude_cli_integration_exports));
70384
- const cliSessions = await runWithTenantDatabaseScope11(
71331
+ const cliSessions = await runWithTenantDatabaseScope13(
70385
71332
  db,
70386
71333
  multiTenancy.static_tenant_id,
70387
71334
  () => scanCliWatcherRehydrateSessions2(app)
70388
71335
  );
70389
71336
  await rehydrateCliWatchers2(
70390
71337
  app,
70391
- (branchId) => runWithTenantDatabaseScope11(db, multiTenancy.static_tenant_id, async () => {
71338
+ (branchId) => runWithTenantDatabaseScope13(db, multiTenancy.static_tenant_id, async () => {
70392
71339
  try {
70393
71340
  const branch = await app.service("branches").get(branchId);
70394
71341
  return branch?.path ?? null;