@desplega.ai/agent-swarm 1.113.0 → 1.115.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +2 -2
  2. package/dist/{actions-q4n7cz6e.js → actions-17ya5wde.js} +8 -9
  3. package/dist/{app-es4nzc71.js → app-4f718yk0.js} +6 -6
  4. package/dist/{assistant-0x8ey0vs.js → assistant-ah7csnjz.js} +19 -18
  5. package/dist/{boot-reembed-69agkhv6.js → boot-reembed-4jpxhb25.js} +6 -6
  6. package/dist/{boot-reembed-p0ny05t8.js → boot-reembed-qata34xa.js} +5 -5
  7. package/dist/{boot-scrub-logs-yybrkmvr.js → boot-scrub-logs-tv48nn3z.js} +4 -4
  8. package/dist/{claude-adapter-mma9a1yr.js → claude-adapter-3csn5bks.js} +3 -3
  9. package/dist/{claude-managed-adapter-pv2nbqy4.js → claude-managed-adapter-kysmkz89.js} +2 -2
  10. package/dist/cli-0pygq9r1.js +429 -0
  11. package/dist/{cli-hsetkkd3.js → cli-12vb0xg6.js} +33 -9
  12. package/dist/{cli-ncfgsqbd.js → cli-15y7qhzp.js} +262 -33
  13. package/dist/{cli-7ga43spr.js → cli-1yjrbg1f.js} +1 -1
  14. package/dist/{cli-36ymmpmp.js → cli-3samkyap.js} +2 -2
  15. package/dist/cli-52fg7s7x.js +269 -0
  16. package/dist/{cli-hjhvekf4.js → cli-65ekje01.js} +11 -8
  17. package/dist/{cli-h3k622d0.js → cli-6aq8xhvm.js} +90 -3
  18. package/dist/{cli-f3qa069v.js → cli-7xje5amz.js} +1 -1
  19. package/dist/cli-86fdnerz.js +405 -0
  20. package/dist/{cli-9cy6nc5m.js → cli-8fjwym53.js} +1 -1
  21. package/dist/{cli-c3frqf65.js → cli-b23axgz4.js} +3 -3
  22. package/dist/{cli-a0t7vapf.js → cli-bc89my0z.js} +1 -1
  23. package/dist/{cli-bnzbn8j8.js → cli-beea4a3y.js} +73 -24
  24. package/dist/{cli-q21d49ac.js → cli-bw80ck94.js} +1 -1
  25. package/dist/{cli-pwc5e83c.js → cli-cchksf5q.js} +12 -7
  26. package/dist/{cli-30bbaveh.js → cli-cmw9rzy5.js} +2 -2
  27. package/dist/{cli-y6mrptcn.js → cli-cnwssmsr.js} +1 -1
  28. package/dist/{cli-r5g6ngtn.js → cli-dcca6j8m.js} +46 -5
  29. package/dist/{cli-tkmm5nf9.js → cli-e61fs2hp.js} +59 -2
  30. package/dist/{cli-6wscj9z0.js → cli-e7306swv.js} +14 -14
  31. package/dist/{cli-dcfyyh3t.js → cli-ftyf58xj.js} +2 -2
  32. package/dist/{cli-4xyj9jtq.js → cli-hrhye2rm.js} +1062 -478
  33. package/dist/cli-jkhwmmgg.js +136 -0
  34. package/dist/{cli-y4ycrnjc.js → cli-k3y4z3h5.js} +1 -1
  35. package/dist/{cli-6xd0bvf8.js → cli-ma04faw5.js} +1 -1
  36. package/dist/{cli-2fc97xm5.js → cli-mnbgv3w7.js} +4 -4
  37. package/dist/{cli-trmapn9k.js → cli-pet3aev6.js} +95 -10
  38. package/dist/{cli-z46pnksz.js → cli-q1rx7g0d.js} +175 -28
  39. package/dist/{cli-qwgtacd6.js → cli-r9f6ngrf.js} +1244 -336
  40. package/dist/{cli-gtkqc144.js → cli-shvaxw27.js} +13 -7
  41. package/dist/{cli-ywez6yps.js → cli-sq7f72d6.js} +1 -1
  42. package/dist/cli-t0vqevnm.js +393 -0
  43. package/dist/{cli-tbw13sx8.js → cli-tece6zgm.js} +1 -1
  44. package/dist/{cli-bgvskydy.js → cli-tffvrh6j.js} +1 -1
  45. package/dist/cli-wh91zby6.js +187222 -0
  46. package/dist/{cli-anrj584m.js → cli-xz9aq0rf.js} +1 -1
  47. package/dist/{cli-mq580e06.js → cli-zsrdty74.js} +18 -18
  48. package/dist/cli.js +32 -15
  49. package/dist/{codex-adapter-5cmy858w.js → codex-adapter-de3w0nch.js} +10 -10
  50. package/dist/{codex-session-runner-46q8mrba.js → codex-session-runner-bnw8cysk.js} +10 -10
  51. package/dist/{commands-z5dbwta3.js → commands-hch4e2k1.js} +4 -4
  52. package/dist/{db-bbgahh4z.js → db-p3h2j06x.js} +14 -4
  53. package/dist/{handlers-6b44317z.js → handlers-9er4zx9q.js} +17 -31
  54. package/dist/{hook-sbp5fmps.js → hook-4axs756z.js} +7 -7
  55. package/dist/{http-2xz43gz3.js → http-809fvtz2.js} +1934 -886
  56. package/dist/{index-wz1ea15p.js → index-2dgfj9av.js} +3 -3
  57. package/dist/{index-nhat35em.js → index-47em3d2n.js} +14 -11
  58. package/dist/{index-5ghxn8s6.js → index-9fqaktjr.js} +12 -9
  59. package/dist/{index-0gzmqj4k.js → index-9ht8gr56.js} +13 -10
  60. package/dist/{index-vfedgdw6.js → index-rm85cfdx.js} +368 -40
  61. package/dist/{index-ykvs43z4.js → index-t2snazez.js} +4 -4
  62. package/dist/{keepalive-62gfj91d.js → keepalive-3cps6e3e.js} +7 -6
  63. package/dist/{lead-ag0akn0v.js → lead-fkk95wgq.js} +36 -32
  64. package/dist/{maintenance-xxrrzk5x.js → maintenance-fweqh0yj.js} +6 -6
  65. package/dist/{mistral-conversations-26yrnbf1.js → mistral-conversations-7gqnz2wt.js} +7 -7
  66. package/dist/oauth-refresh-sweep-sycqtbph.js +114 -0
  67. package/dist/{onboard-64zyfcbs.js → onboard-cy0rkgvx.js} +2 -2
  68. package/dist/{opencode-adapter-mqsxr170.js → opencode-adapter-p6bxnbm4.js} +2 -2
  69. package/dist/{otel-zjd9zaxs.js → otel-g299k41b.js} +2 -2
  70. package/dist/{otel-impl-b86qseaa.js → otel-impl-wbns8hf7.js} +1 -1
  71. package/dist/{pi-mono-adapter-kxjxpfgy.js → pi-mono-adapter-05sck1ds.js} +17 -109
  72. package/dist/{pricing-refresh-cgtkffpa.js → pricing-refresh-0hvazmkf.js} +6 -6
  73. package/dist/rbac-roles-kfesjy9y.js +28 -0
  74. package/dist/rbac-roles-mwcwhbxc.js +283 -0
  75. package/dist/{seed-pricing-1r1y1xkq.js → seed-pricing-h2kdq4cq.js} +5 -5
  76. package/dist/{setup-mkcgkjf7.js → setup-8506q97w.js} +2 -2
  77. package/dist/{worker-351sze63.js → worker-4r5c6zyb.js} +36 -32
  78. package/openapi.json +1327 -20
  79. package/package.json +3 -3
  80. package/src/agentmail/handlers.ts +16 -6
  81. package/src/agentmail/templates.ts +25 -5
  82. package/src/be/audit-user.ts +28 -4
  83. package/src/be/db-queries/oauth.ts +42 -0
  84. package/src/be/db.ts +314 -28
  85. package/src/be/identity.ts +79 -0
  86. package/src/be/mcp-proxy.ts +132 -0
  87. package/src/be/migrations/109_rbac_roles.sql +79 -0
  88. package/src/be/migrations/111_oauth_credential_bindings.sql +6 -0
  89. package/src/be/migrations/112_script_connections_graphql.sql +96 -0
  90. package/src/be/migrations/113_task_routing_affinity.sql +9 -0
  91. package/src/be/migrations/114_backfill_gpt_5_6_pricing.sql +15 -0
  92. package/src/be/modelsdev-cache.json +135 -0
  93. package/src/be/oauth-credential-bindings.ts +35 -0
  94. package/src/be/oauth-refresh-sweep.ts +159 -0
  95. package/src/be/rbac-audit.ts +52 -13
  96. package/src/be/rbac-roles.ts +424 -0
  97. package/src/be/script-connections.ts +459 -40
  98. package/src/be/script-credential-broker.ts +29 -3
  99. package/src/be/scripts/typecheck.ts +51 -7
  100. package/src/be/seed-scripts/catalog/catalog-report.inline.ts +325 -24
  101. package/src/be/seed-scripts/catalog/catalog-report.ts +325 -24
  102. package/src/be/users.ts +24 -0
  103. package/src/cli.tsx +17 -0
  104. package/src/github/handlers.ts +10 -9
  105. package/src/github/templates.ts +45 -9
  106. package/src/gitlab/handlers.ts +20 -8
  107. package/src/gitlab/templates.ts +18 -6
  108. package/src/heartbeat/heartbeat.ts +143 -21
  109. package/src/http/all-routes.ts +3 -0
  110. package/src/http/approval-requests.ts +12 -0
  111. package/src/http/config.ts +44 -10
  112. package/src/http/core.ts +30 -3
  113. package/src/http/favorites.ts +1 -5
  114. package/src/http/index.ts +25 -1
  115. package/src/http/mcp-oauth.ts +2 -0
  116. package/src/http/mcp-servers.ts +44 -3
  117. package/src/http/oauth-generic.ts +105 -0
  118. package/src/http/poll.ts +20 -3
  119. package/src/http/script-connection-proxy.ts +116 -0
  120. package/src/http/script-connections.ts +1657 -0
  121. package/src/http/scripts.ts +27 -7
  122. package/src/http/skills.ts +11 -0
  123. package/src/http/tasks.ts +27 -13
  124. package/src/http/x.ts +6 -2
  125. package/src/integrations/kapso/inbound.ts +23 -6
  126. package/src/jira/sync.ts +111 -17
  127. package/src/jira/templates.ts +10 -2
  128. package/src/mcp-client/http-client.ts +194 -0
  129. package/src/oauth/app-validation.ts +16 -0
  130. package/src/oauth/ensure-token.ts +69 -14
  131. package/src/oauth/mcp-wrapper.ts +15 -0
  132. package/src/oauth/wrapper.ts +48 -20
  133. package/src/prompts/session-templates.ts +3 -3
  134. package/src/providers/codex-models.ts +100 -30
  135. package/src/providers/modelsdev-reasoning.json +51 -0
  136. package/src/providers/pi-mono-mcp-client.ts +2 -133
  137. package/src/providers/reasoning-effort.ts +11 -5
  138. package/src/rbac/admission.ts +68 -0
  139. package/src/rbac/index.ts +2 -0
  140. package/src/rbac/legacy-policy.ts +12 -0
  141. package/src/rbac/permissions.ts +48 -0
  142. package/src/scheduler/scheduler.ts +9 -1
  143. package/src/scripts-runtime/api-client.ts +151 -19
  144. package/src/scripts-runtime/api-types.ts +38 -6
  145. package/src/scripts-runtime/credential-broker/default-bindings.ts +1 -0
  146. package/src/scripts-runtime/credential-broker/types.ts +8 -0
  147. package/src/scripts-runtime/ctx.ts +11 -1
  148. package/src/scripts-runtime/eval-harness.ts +5 -1
  149. package/src/scripts-runtime/executors/types.ts +2 -1
  150. package/src/scripts-runtime/loader.ts +3 -1
  151. package/src/scripts-runtime/mcp-client.ts +87 -0
  152. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  153. package/src/scripts-runtime/types/stdlib.d.ts +6 -0
  154. package/src/scripts-runtime/types/swarm-sdk.d.ts +19 -0
  155. package/src/server-user.ts +114 -59
  156. package/src/server.ts +12 -0
  157. package/src/slack/assistant.ts +14 -6
  158. package/src/slack/enrich.ts +17 -0
  159. package/src/slack/handlers.ts +8 -22
  160. package/src/slack/responses.ts +91 -1
  161. package/src/slack/thread-buffer.ts +6 -3
  162. package/src/slack/watcher.ts +9 -6
  163. package/src/tasks/worker-follow-up.ts +100 -3
  164. package/src/tests/agentmail-handlers.test.ts +32 -3
  165. package/src/tests/approval-requests.test.ts +61 -0
  166. package/src/tests/audit-user.test.ts +47 -1
  167. package/src/tests/codex-adapter.test.ts +46 -23
  168. package/src/tests/credential-broker.test.ts +5 -2
  169. package/src/tests/delete-page-tool.test.ts +197 -0
  170. package/src/tests/github-handlers.test.ts +28 -2
  171. package/src/tests/gitlab-handlers.test.ts +125 -0
  172. package/src/tests/heartbeat.test.ts +48 -3
  173. package/src/tests/identity.test.ts +117 -0
  174. package/src/tests/jira-sync.test.ts +99 -1
  175. package/src/tests/kapso-inbound.test.ts +7 -0
  176. package/src/tests/mcp-tools-user.test.ts +60 -6
  177. package/src/tests/model-control.test.ts +8 -2
  178. package/src/tests/oauth-credential-bindings.test.ts +490 -0
  179. package/src/tests/oauth-refresh-sweep.test.ts +160 -0
  180. package/src/tests/pool-affinity.test.ts +401 -0
  181. package/src/tests/prompt-template-remaining.test.ts +2 -2
  182. package/src/tests/rbac-admission-e2e.test.ts +386 -0
  183. package/src/tests/rbac-admission.test.ts +492 -0
  184. package/src/tests/rbac-e2e-helpers.ts +3 -0
  185. package/src/tests/rbac-engine.test.ts +50 -0
  186. package/src/tests/rbac-mcp-admission-e2e.test.ts +362 -0
  187. package/src/tests/rbac-roles.test.ts +328 -0
  188. package/src/tests/reasoning-effort.test.ts +15 -2
  189. package/src/tests/schedule-target-type.test.ts +50 -0
  190. package/src/tests/scheduled-tasks.test.ts +40 -0
  191. package/src/tests/script-apis-mcp.test.ts +37 -13
  192. package/src/tests/script-connections-http.test.ts +1158 -0
  193. package/src/tests/script-connections-mcp.test.ts +894 -0
  194. package/src/tests/script-connections.test.ts +754 -6
  195. package/src/tests/scripts-external-api.test.ts +49 -1
  196. package/src/tests/scripts-runtime-secret-egress.test.ts +1 -0
  197. package/src/tests/scripts-runtime.test.ts +291 -0
  198. package/src/tests/seed-scripts.test.ts +36 -0
  199. package/src/tests/slack-identity-resolution.test.ts +37 -3
  200. package/src/tests/slack-inline-output.test.ts +65 -0
  201. package/src/tests/slack-thread-buffer.test.ts +28 -0
  202. package/src/tests/tool-annotations.test.ts +1 -0
  203. package/src/tests/user-token-rest-auth.test.ts +140 -1
  204. package/src/tests/workflow-swarm-script.test.ts +44 -0
  205. package/src/tests/workflow-triggers-v2.test.ts +300 -1
  206. package/src/tools/credential-bindings/tool.ts +277 -18
  207. package/src/tools/delete-page.ts +145 -0
  208. package/src/tools/mcp-servers/mcp-server-delete.ts +7 -4
  209. package/src/tools/request-human-input.ts +2 -0
  210. package/src/tools/resolve-user.ts +77 -27
  211. package/src/tools/script-apis.ts +62 -3
  212. package/src/tools/script-connections/tool.ts +275 -31
  213. package/src/tools/send-task.ts +15 -1
  214. package/src/tools/task-action.ts +34 -2
  215. package/src/tools/templates.ts +53 -1
  216. package/src/tools/tool-config.ts +2 -1
  217. package/src/tools/utils.ts +2 -0
  218. package/src/tools/workflows/create-workflow.ts +8 -1
  219. package/src/tools/workflows/update-workflow.ts +11 -2
  220. package/src/types.ts +90 -11
  221. package/src/utils/scoped-resource.ts +25 -0
  222. package/src/workflows/executors/human-in-the-loop.ts +1 -0
  223. package/src/workflows/executors/swarm-script.ts +7 -1
  224. package/src/workflows/triggers.ts +146 -18
  225. package/templates/skills/swarm-scripts/content.md +33 -8
  226. package/dist/cli-bgef578c.js +0 -771
  227. package/dist/cli-c9mtm09b.js +0 -43
  228. package/dist/cli-ceewqajw.js +0 -1698
  229. package/dist/{anthropic-messages-8nc6r2dw.js → anthropic-messages-kq3pjhbp.js} +6 -6
  230. package/dist/{azure-openai-responses-6yfzw3qw.js → azure-openai-responses-gm1ejtmn.js} +7 -7
  231. package/dist/{cli-vbraamxr.js → cli-6b4wwmm8.js} +9 -9
  232. package/dist/{cli-2r7s8jhp.js → cli-sznberje.js} +3 -3
  233. package/dist/{google-generative-ai-vfq6ymz9.js → google-generative-ai-hyrra2jm.js} +3 -3
  234. package/dist/{google-vertex-mj5y5rfj.js → google-vertex-8bs2pzgn.js} +3 -3
  235. package/dist/{openai-codex-responses-8h6kvfv8.js → openai-codex-responses-3nkvje79.js} +4 -4
  236. package/dist/{openai-completions-x6kjnqjd.js → openai-completions-0asftkwg.js} +11 -11
  237. package/dist/{openai-responses-7jwyz3ds.js → openai-responses-pmatek45.js} +10 -10
@@ -7,20 +7,22 @@ import {
7
7
  LlmRater,
8
8
  REFERENCES_SOURCE_MAX_LENGTH,
9
9
  sanitizeReferencesSource
10
- } from "./cli-dcfyyh3t.js";
11
- import {
12
- ensureToken,
13
- ensureTokenOrThrow,
14
- getOAuthApp,
15
- getOAuthTokens,
16
- upsertOAuthApp
17
- } from "./cli-bgef578c.js";
10
+ } from "./cli-ftyf58xj.js";
18
11
  import {
19
12
  startPricingRefreshLoop
20
- } from "./cli-36ymmpmp.js";
13
+ } from "./cli-3samkyap.js";
21
14
  import {
22
15
  seedPricingFromModelsDev
23
- } from "./cli-tbw13sx8.js";
16
+ } from "./cli-tece6zgm.js";
17
+ import {
18
+ ensureRbacSeedsSynced,
19
+ getUserGrant
20
+ } from "./cli-52fg7s7x.js";
21
+ import {
22
+ can,
23
+ decideAdmission,
24
+ isRbacEnabled
25
+ } from "./cli-86fdnerz.js";
24
26
  import {
25
27
  createTrackerAgentMapping,
26
28
  createTrackerSync,
@@ -35,7 +37,7 @@ import {
35
37
  repointTrackerSyncBySwarmId,
36
38
  updateTrackerSync,
37
39
  updateTrackerSyncSwarmId
38
- } from "./cli-r5g6ngtn.js";
40
+ } from "./cli-dcca6j8m.js";
39
41
  import {
40
42
  ZodFirstPartyTypeKind,
41
43
  objectType
@@ -45,35 +47,41 @@ import {
45
47
  ensure,
46
48
  require_dist,
47
49
  runScheduleNow
48
- } from "./cli-gtkqc144.js";
50
+ } from "./cli-shvaxw27.js";
49
51
  import {
50
52
  TriggerSchemaError,
51
53
  applyDefinitionPatch,
52
54
  cancelWorkflowRun,
53
55
  generateEdges,
54
56
  getExecutorRegistry,
57
+ getOAuthBindingTokenStatus,
58
+ getOAuthProviderConfig,
55
59
  retryFailedRun,
56
60
  snapshotWorkflow,
57
61
  startWorkflowExecution,
58
62
  validateDefinition,
59
63
  validateJsonSchema
60
- } from "./cli-z46pnksz.js";
64
+ } from "./cli-q1rx7g0d.js";
61
65
  import {
62
66
  getApiKey
63
67
  } from "./cli-f14fvzag.js";
64
68
  import {
65
69
  CREDENTIAL_BINDINGS_CONFIG_KEY,
66
70
  CredentialBindingSchema,
71
+ assertUrlSafe,
67
72
  disableCredentialBinding,
68
73
  getScript,
74
+ getScriptConnectionById,
69
75
  importLegacyCredentialBindings,
70
76
  listRelationalCredentialBindings,
71
77
  listScriptConnections,
72
78
  placeholderForConfigKey,
79
+ publicEndpointSsrfOptions,
80
+ refreshScriptConnection,
73
81
  setScriptConnectionEnabled,
74
82
  upsertCredentialBinding,
75
83
  upsertScriptConnection
76
- } from "./cli-qwgtacd6.js";
84
+ } from "./cli-r9f6ngrf.js";
77
85
  import {
78
86
  CANDIDATE_SET_MULTIPLIER,
79
87
  computeScore,
@@ -85,32 +93,44 @@ import {
85
93
  isGraphExpansionEnabled,
86
94
  rerank,
87
95
  rowToCandidate
88
- } from "./cli-f3qa069v.js";
96
+ } from "./cli-7xje5amz.js";
97
+ import {
98
+ buildAuthorizationUrl,
99
+ ensureToken,
100
+ ensureTokenOrThrow,
101
+ getOAuthApp,
102
+ getOAuthTokens,
103
+ upsertOAuthApp
104
+ } from "./cli-t0vqevnm.js";
89
105
  import {
90
106
  createAdditiveBuffer,
91
107
  extractSlackMessageText,
92
108
  parseSlackTs
93
- } from "./cli-bgvskydy.js";
109
+ } from "./cli-tffvrh6j.js";
94
110
  import {
95
111
  getSlackApp,
96
112
  startSlackApp,
97
113
  stopSlackApp
98
- } from "./cli-trmapn9k.js";
114
+ } from "./cli-pet3aev6.js";
99
115
  import {
100
116
  findOrCreateUserByEmail,
101
- findUserByEmail,
102
117
  findUserByExternalId,
103
118
  findUserById,
119
+ findUsersByName,
104
120
  fingerprintApiKey,
105
121
  getUserIdentities,
106
122
  linkIdentity,
107
123
  recordIdentityEvent,
124
+ recordUnmappedIdentity,
125
+ renderIdentity,
126
+ resolveIdentity,
127
+ resolveIdentityByEmail,
108
128
  resolveUserByToken,
109
129
  unlinkIdentity
110
- } from "./cli-h3k622d0.js";
130
+ } from "./cli-6aq8xhvm.js";
111
131
  import {
112
132
  markdownToSlack
113
- } from "./cli-6xd0bvf8.js";
133
+ } from "./cli-ma04faw5.js";
114
134
  import {
115
135
  agentContextKey,
116
136
  agentmailContextKey,
@@ -118,7 +138,7 @@ import {
118
138
  createTaskWithSiblingAwareness,
119
139
  githubContextKey,
120
140
  linearContextKey
121
- } from "./cli-y4ycrnjc.js";
141
+ } from "./cli-k3y4z3h5.js";
122
142
  import {
123
143
  getAppUrl,
124
144
  getMcpBaseUrl,
@@ -193,6 +213,7 @@ import {
193
213
  deleteChannel,
194
214
  deleteKv,
195
215
  deleteMcpServer,
216
+ deletePage,
196
217
  deletePromptTemplate,
197
218
  deleteScheduledTask,
198
219
  deleteService,
@@ -295,6 +316,7 @@ import {
295
316
  installMcpServer,
296
317
  installSkill,
297
318
  interpolate,
319
+ isAgentEligibleForTask,
298
320
  isReservedConfigKey,
299
321
  isTerminalTaskStatus,
300
322
  listKv,
@@ -347,10 +369,10 @@ import {
347
369
  upsertService,
348
370
  upsertSwarmConfig,
349
371
  validateConfigValue
350
- } from "./cli-ncfgsqbd.js";
372
+ } from "./cli-15y7qhzp.js";
351
373
  import {
352
374
  init_zod
353
- } from "./cli-q21d49ac.js";
375
+ } from "./cli-bw80ck94.js";
354
376
  import {
355
377
  $ZodObject,
356
378
  $ZodType,
@@ -387,11 +409,11 @@ import {
387
409
  union,
388
410
  unknown,
389
411
  uuid
390
- } from "./cli-anrj584m.js";
412
+ } from "./cli-xz9aq0rf.js";
391
413
  import {
392
414
  init_package,
393
415
  package_default
394
- } from "./cli-c3frqf65.js";
416
+ } from "./cli-b23axgz4.js";
395
417
  import {
396
418
  init_secret_scrubber,
397
419
  refreshSecretScrubberCache,
@@ -19613,300 +19635,6 @@ function registerGithubTaskReactions() {
19613
19635
  init_zod();
19614
19636
  init_db();
19615
19637
 
19616
- // src/rbac/legacy-policy.ts
19617
- var leadOnly = {
19618
- name: "lead-only",
19619
- denyReason: "requires lead agent",
19620
- evaluate: (principal) => principal.kind === "agent" && principal.isLead
19621
- };
19622
- var leadOrTaskCreator = {
19623
- name: "lead-or-task-creator",
19624
- denyReason: "requires lead agent or task creator",
19625
- evaluate: (principal, resource) => {
19626
- if (principal.kind !== "agent")
19627
- return false;
19628
- if (principal.isLead)
19629
- return true;
19630
- return resource?.kind === "task" && resource.creatorAgentId != null && resource.creatorAgentId === principal.agentId;
19631
- }
19632
- };
19633
- var leadOrResourceOwner = {
19634
- name: "lead-or-resource-owner",
19635
- denyReason: "requires lead agent or resource owner",
19636
- evaluate: (principal, resource) => {
19637
- if (principal.kind !== "agent")
19638
- return false;
19639
- if (principal.isLead)
19640
- return true;
19641
- return resource?.kind === "owned" && resource.ownerAgentId != null && resource.ownerAgentId === principal.agentId;
19642
- }
19643
- };
19644
- var leadOrOwnNamespace = {
19645
- name: "lead-or-own-namespace",
19646
- denyReason: "requires lead agent or your own task:agent: namespace",
19647
- evaluate: (principal, resource) => {
19648
- if (principal.kind !== "agent")
19649
- return false;
19650
- if (principal.isLead)
19651
- return true;
19652
- return principal.agentId !== "" && resource?.kind === "kv-namespace" && resource.namespace === `task:agent:${principal.agentId}`;
19653
- }
19654
- };
19655
- var requesterOwnsTask = {
19656
- name: "requester-owns-task",
19657
- denyReason: "not the task requester",
19658
- evaluate: (principal, resource) => {
19659
- if (principal.kind !== "user")
19660
- return true;
19661
- return resource?.kind === "task" && resource.requestedByUserId === principal.userId;
19662
- }
19663
- };
19664
- var memoryOwnerOrLeadSwarm = {
19665
- name: "memory-owner-or-lead-swarm",
19666
- denyReason: "requires memory owner, or lead agent for swarm-scoped memories",
19667
- evaluate: (principal, resource) => {
19668
- if (principal.kind !== "agent" || resource?.kind !== "owned")
19669
- return false;
19670
- if (resource.ownerAgentId != null && resource.ownerAgentId === principal.agentId)
19671
- return true;
19672
- return principal.isLead && resource.scope === "swarm";
19673
- }
19674
- };
19675
- var taskFsMutate = {
19676
- name: "operator-or-user-or-lead-or-task-owner",
19677
- denyReason: "requires operator, user, lead agent, task assignee, or task creator",
19678
- evaluate: (principal, resource) => {
19679
- if (principal.kind === "operator" || principal.kind === "user")
19680
- return true;
19681
- if (principal.isLead)
19682
- return true;
19683
- if (resource?.kind !== "task")
19684
- return false;
19685
- return resource.agentId != null && resource.agentId === principal.agentId || resource.creatorAgentId != null && resource.creatorAgentId === principal.agentId;
19686
- }
19687
- };
19688
- var LEGACY_POLICY = {
19689
- "user.manage": leadOnly,
19690
- "agent.profile.update.any": leadOnly,
19691
- "agent.context.read.any": leadOnly,
19692
- "task.cancel.any": leadOrTaskCreator,
19693
- "task.read.own": requesterOwnsTask,
19694
- "task.cancel.own": requesterOwnsTask,
19695
- "task.action.own": requesterOwnsTask,
19696
- "task.fs.mutate": taskFsMutate,
19697
- "memory.learning.inject": leadOnly,
19698
- "memory.delete.any": memoryOwnerOrLeadSwarm,
19699
- "channel.delete": leadOnly,
19700
- "integration.kapso.manage": leadOnly,
19701
- "integration.slack.post": leadOnly,
19702
- "integration.slack.read": leadOnly,
19703
- "integration.slack.thread.start": leadOnly,
19704
- "integration.slack.upload": leadOnly,
19705
- "integration.slack.delete": leadOnly,
19706
- "integration.slack.update": leadOnly,
19707
- "credential-binding.manage": leadOnly,
19708
- "script-connection.manage": leadOnly,
19709
- "config.credential-bindings.write": leadOnly,
19710
- "config.write.any": leadOnly,
19711
- "config.delete.any": leadOnly,
19712
- "config.read.secrets": leadOnly,
19713
- "skill.create.swarm": leadOnly,
19714
- "skill.install.any": leadOnly,
19715
- "skill.install.global": leadOnly,
19716
- "skill.uninstall.any": leadOnly,
19717
- "skill.update.any": leadOrResourceOwner,
19718
- "skill.promote.swarm": leadOnly,
19719
- "skill.delete.any": leadOrResourceOwner,
19720
- "mcp-server.create.swarm": leadOnly,
19721
- "mcp-server.install.any": leadOnly,
19722
- "mcp-server.uninstall.any": leadOnly,
19723
- "mcp-server.delete.any": leadOrResourceOwner,
19724
- "mcp-server.update.any": leadOrResourceOwner,
19725
- "kv.write.any": leadOrOwnNamespace,
19726
- "script.global.write": leadOnly,
19727
- "script.global.delete": leadOnly
19728
- };
19729
-
19730
- // src/rbac/can.ts
19731
- var auditSink = null;
19732
- function setAuditSink(fn) {
19733
- auditSink = fn;
19734
- }
19735
- function clearAuditSink() {
19736
- auditSink = null;
19737
- }
19738
- function can(check) {
19739
- const rule = LEGACY_POLICY[check.verb];
19740
- const decision = rule.evaluate(check.principal, check.resource) ? { allow: true } : { allow: false, reason: rule.denyReason, missing: check.verb };
19741
- if (auditSink) {
19742
- try {
19743
- auditSink(check, decision);
19744
- } catch {}
19745
- }
19746
- return decision;
19747
- }
19748
- // src/rbac/permissions.ts
19749
- init_zod();
19750
- var PERMISSIONS = {
19751
- "user.manage": {
19752
- description: "Create, update, or deactivate user profiles.",
19753
- namespace: "user"
19754
- },
19755
- "agent.profile.update.any": {
19756
- description: "Update another agent's profile.",
19757
- namespace: "agent"
19758
- },
19759
- "agent.context.read.any": {
19760
- description: "View or diff another agent's context history.",
19761
- namespace: "agent"
19762
- },
19763
- "task.cancel.any": {
19764
- description: "Cancel any task (beyond tasks the caller created).",
19765
- namespace: "task"
19766
- },
19767
- "task.read.own": {
19768
- description: "Read details of a task the principal requested.",
19769
- namespace: "task"
19770
- },
19771
- "task.cancel.own": {
19772
- description: "Cancel a task the principal requested.",
19773
- namespace: "task"
19774
- },
19775
- "task.action.own": {
19776
- description: "Run actions (follow-up, retry, ...) on a task the principal requested.",
19777
- namespace: "task"
19778
- },
19779
- "task.fs.mutate": {
19780
- description: "Mutate a task's filesystem artifacts and attachments.",
19781
- namespace: "task"
19782
- },
19783
- "memory.learning.inject": {
19784
- description: "Inject a learning into another agent's memory.",
19785
- namespace: "memory"
19786
- },
19787
- "memory.delete.any": {
19788
- description: "Delete a memory entry (own entries, or swarm-scoped entries as lead).",
19789
- namespace: "memory"
19790
- },
19791
- "channel.delete": {
19792
- description: "Delete a Slack channel.",
19793
- namespace: "channel"
19794
- },
19795
- "integration.kapso.manage": {
19796
- description: "Register or unregister a Kapso inbound number.",
19797
- namespace: "integration"
19798
- },
19799
- "integration.slack.post": {
19800
- description: "Post to a direct Slack channel.",
19801
- namespace: "integration"
19802
- },
19803
- "integration.slack.read": {
19804
- description: "Read a direct Slack channel.",
19805
- namespace: "integration"
19806
- },
19807
- "integration.slack.thread.start": {
19808
- description: "Start a thread in a direct Slack channel.",
19809
- namespace: "integration"
19810
- },
19811
- "integration.slack.upload": {
19812
- description: "Upload a file to a direct Slack channel.",
19813
- namespace: "integration"
19814
- },
19815
- "integration.slack.delete": {
19816
- description: "Delete a Slack message.",
19817
- namespace: "integration"
19818
- },
19819
- "integration.slack.update": {
19820
- description: "Update (edit) a Slack message.",
19821
- namespace: "integration"
19822
- },
19823
- "credential-binding.manage": {
19824
- description: "Manage script credential bindings.",
19825
- namespace: "credential-binding"
19826
- },
19827
- "script-connection.manage": {
19828
- description: "Manage script connections.",
19829
- namespace: "script-connection"
19830
- },
19831
- "config.credential-bindings.write": {
19832
- description: "Write the SCRIPT_CREDENTIAL_BINDINGS swarm-config key.",
19833
- namespace: "config"
19834
- },
19835
- "config.write.any": {
19836
- description: "Write any swarm-config key.",
19837
- namespace: "config"
19838
- },
19839
- "config.delete.any": {
19840
- description: "Delete any swarm-config entry.",
19841
- namespace: "config"
19842
- },
19843
- "config.read.secrets": {
19844
- description: "Read unmasked secret config values.",
19845
- namespace: "config"
19846
- },
19847
- "skill.create.swarm": {
19848
- description: "Create a swarm-scoped skill.",
19849
- namespace: "skill"
19850
- },
19851
- "skill.install.any": {
19852
- description: "Install a skill for another agent.",
19853
- namespace: "skill"
19854
- },
19855
- "skill.install.global": {
19856
- description: "Install a remote/global skill.",
19857
- namespace: "skill"
19858
- },
19859
- "skill.uninstall.any": {
19860
- description: "Uninstall a skill for another agent.",
19861
- namespace: "skill"
19862
- },
19863
- "skill.update.any": {
19864
- description: "Update a skill the caller does not own.",
19865
- namespace: "skill"
19866
- },
19867
- "skill.promote.swarm": {
19868
- description: "Promote a skill to swarm scope (skill-approval path).",
19869
- namespace: "skill"
19870
- },
19871
- "skill.delete.any": {
19872
- description: "Delete a skill the caller does not own.",
19873
- namespace: "skill"
19874
- },
19875
- "mcp-server.create.swarm": {
19876
- description: "Create a swarm- or global-scoped MCP server.",
19877
- namespace: "mcp-server"
19878
- },
19879
- "mcp-server.install.any": {
19880
- description: "Install an MCP server for another agent.",
19881
- namespace: "mcp-server"
19882
- },
19883
- "mcp-server.uninstall.any": {
19884
- description: "Uninstall an MCP server for another agent.",
19885
- namespace: "mcp-server"
19886
- },
19887
- "mcp-server.delete.any": {
19888
- description: "Delete an MCP server the caller does not own.",
19889
- namespace: "mcp-server"
19890
- },
19891
- "mcp-server.update.any": {
19892
- description: "Update an MCP server the caller does not own.",
19893
- namespace: "mcp-server"
19894
- },
19895
- "kv.write.any": {
19896
- description: "Write another agent's task:agent: KV namespace.",
19897
- namespace: "kv"
19898
- },
19899
- "script.global.write": {
19900
- description: "Create or update a global-scope script.",
19901
- namespace: "script"
19902
- },
19903
- "script.global.delete": {
19904
- description: "Delete a global-scope script.",
19905
- namespace: "script"
19906
- }
19907
- };
19908
- var PERMISSION_VERBS = Object.keys(PERMISSIONS);
19909
- var PermissionVerbSchema = _enum(PERMISSION_VERBS);
19910
19638
  // src/tools/task-tool-ctx.ts
19911
19639
  function ownerCtx(info) {
19912
19640
  return {
@@ -19973,7 +19701,7 @@ async function initOtel(serviceRole = process.env.AGENT_ROLE || "api") {
19973
19701
  return;
19974
19702
  initialized2 = true;
19975
19703
  try {
19976
- const impl = await import("./otel-impl-b86qseaa.js");
19704
+ const impl = await import("./otel-impl-wbns8hf7.js");
19977
19705
  await impl.boot(serviceRole);
19978
19706
  realWithSpan = impl.withSpan;
19979
19707
  realStartSpan = impl.startSpan;
@@ -21248,22 +20976,87 @@ var registerCreatePageTool = (server) => {
21248
20976
  // src/tools/credential-bindings/tool.ts
21249
20977
  init_zod();
21250
20978
  init_db();
20979
+
20980
+ // src/oauth/app-validation.ts
20981
+ var RESERVED_OAUTH_PROVIDERS = new Set(["linear", "jira"]);
20982
+ function assertOAuthProviderIsNotReserved(provider) {
20983
+ if (!RESERVED_OAUTH_PROVIDERS.has(provider.toLowerCase()))
20984
+ return;
20985
+ throw new Error(`OAuth provider "${provider}" is reserved for dedicated tracker OAuth flows. Use the Linear/Jira tracker integration flow instead.`);
20986
+ }
20987
+ function assertOAuthAppUrlsSafe(input) {
20988
+ const options = publicEndpointSsrfOptions();
20989
+ assertUrlSafe(input.authorizeUrl, options);
20990
+ assertUrlSafe(input.tokenUrl, options);
20991
+ }
20992
+
20993
+ // src/utils/scoped-resource.ts
20994
+ init_zod();
20995
+ var scopedResourceScopeIdSchema = exports_external.string().min(1).max(255);
20996
+ var agentScopeIdSchema = exports_external.string().uuid();
20997
+ function resolveScopedResourceId(scope, scopeId, subject) {
20998
+ if (!scope || scope === "global")
20999
+ return null;
21000
+ if (!scopeId || scopeId.trim().length === 0) {
21001
+ throw new Error(`scopeId is required for ${scope} ${subject}.`);
21002
+ }
21003
+ if (scopeId.length > 255) {
21004
+ throw new Error(`scopeId must be at most 255 characters for ${scope} ${subject}.`);
21005
+ }
21006
+ if (scope === "agent" && !agentScopeIdSchema.safeParse(scopeId).success) {
21007
+ throw new Error(`scopeId must be an agent UUID for agent ${subject}.`);
21008
+ }
21009
+ return scopeId;
21010
+ }
21011
+
21012
+ // src/tools/credential-bindings/tool.ts
21013
+ var providerSchema = string().min(1).max(255).regex(/^[A-Za-z0-9_-]+$/);
21014
+ var tokenStatusSchema = _enum(["ok", "expiring", "missing"]);
21015
+ var credentialBindingToolBindingSchema = CredentialBindingSchema.and(object({
21016
+ id: string().optional(),
21017
+ source: _enum(["default", "user", "migration"]).optional(),
21018
+ createdAt: string().optional(),
21019
+ updatedAt: string().optional(),
21020
+ createdBy: string().nullable().optional(),
21021
+ updatedBy: string().nullable().optional(),
21022
+ tokenStatus: tokenStatusSchema.optional()
21023
+ }));
21251
21024
  var credentialBindingsOutputSchema = object({
21252
21025
  yourAgentId: string().uuid().optional(),
21253
21026
  success: boolean(),
21254
21027
  message: string(),
21255
- bindings: array(CredentialBindingSchema)
21028
+ provider: string().optional(),
21029
+ authorizeUrl: string().optional(),
21030
+ redirectUri: string().optional(),
21031
+ bindings: array(credentialBindingToolBindingSchema)
21256
21032
  });
21257
21033
  var credentialBindingsInputSchema = object({
21258
- action: _enum(["list", "upsert", "disable", "import-legacy"]).describe("List, add/update, disable, or import legacy JSON bindings."),
21259
- id: string().uuid().optional(),
21260
- configKey: string().min(1).max(255).optional(),
21261
- allowedHosts: array(string().min(1)).min(1).optional(),
21262
- headerTemplate: string().min(1).optional(),
21263
- queryTemplate: string().min(1).optional(),
21264
- scope: _enum(["global", "agent", "repo"]).default("global").optional(),
21265
- scopeId: string().uuid().nullable().optional()
21034
+ action: _enum(["list", "upsert", "disable", "import-legacy", "oauth-app-upsert", "oauth-authorize-url"]).describe("List, add/update, disable, import legacy JSON bindings, or register/authorize OAuth apps."),
21035
+ id: string().uuid().optional().describe("Existing credential binding ID for update or disable."),
21036
+ configKey: string().min(1).max(255).optional().describe("Swarm config key whose secret value is injected through templates."),
21037
+ allowedHosts: array(string().min(1)).min(1).optional().describe("Allowed outbound hostnames for this binding."),
21038
+ headerTemplate: string().min(1).optional().describe("Header template containing the config-key placeholder."),
21039
+ queryTemplate: string().min(1).optional().describe("Query parameter template containing the config-key placeholder."),
21040
+ scope: _enum(["global", "agent", "repo"]).default("global").optional().describe("Binding visibility scope."),
21041
+ scopeId: scopedResourceScopeIdSchema.nullable().optional().describe("Agent UUID for agent scope or repo id (owner/name) for repo scope."),
21042
+ authKind: _enum(["config", "oauth"]).default("config").optional().describe("Use config for stored swarm config secrets or oauth for OAuth token resolution."),
21043
+ oauthProvider: providerSchema.optional().describe("OAuth provider slug required when authKind is oauth."),
21044
+ provider: providerSchema.optional().describe("OAuth provider slug for oauth-app-upsert and oauth-authorize-url."),
21045
+ clientId: string().min(1).optional().describe("OAuth client ID for oauth-app-upsert."),
21046
+ clientSecret: string().min(1).optional().describe("OAuth client secret for oauth-app-upsert."),
21047
+ authorizeUrl: string().url().optional().describe("OAuth authorization URL for oauth-app-upsert."),
21048
+ tokenUrl: string().url().optional().describe("OAuth token URL for oauth-app-upsert."),
21049
+ scopes: array(string().min(1)).optional().describe("OAuth scopes for oauth-app-upsert."),
21050
+ extraParams: record(string(), string()).optional().describe("Extra OAuth authorization parameters stored with the OAuth app."),
21051
+ tokenAuthStyle: _enum(["body", "basic"]).optional().describe("How client credentials reach the token endpoint: body params (default) or HTTP Basic auth (required by e.g. Notion)."),
21052
+ tokenBodyFormat: _enum(["form", "json"]).optional().describe("Token request body encoding: form-urlencoded (default) or JSON (required by e.g. Notion).")
21266
21053
  });
21054
+ function genericOAuthRedirectUri(provider) {
21055
+ return `${getPublicMcpBaseUrl()}/api/oauth/${encodeURIComponent(provider)}/callback`;
21056
+ }
21057
+ function decorateBindings(bindings) {
21058
+ return bindings.map((binding) => binding.authKind === "oauth" && binding.oauthProvider ? { ...binding, tokenStatus: getOAuthBindingTokenStatus(binding.oauthProvider) } : binding);
21059
+ }
21267
21060
  var registerCredentialBindingsTool = (server) => {
21268
21061
  createToolRegistrar(server)("credential-bindings", {
21269
21062
  title: "Credential Bindings",
@@ -21304,7 +21097,115 @@ var registerCredentialBindingsTool = (server) => {
21304
21097
  }
21305
21098
  };
21306
21099
  }
21307
- const bindings = listRelationalCredentialBindings({ includeInactive: true });
21100
+ const currentBindings = () => decorateBindings(listRelationalCredentialBindings({ includeInactive: true }));
21101
+ const bindings = currentBindings();
21102
+ if (args.action === "oauth-app-upsert") {
21103
+ if (!args.provider || !args.clientId || !args.clientSecret || !args.authorizeUrl || !args.tokenUrl || !args.scopes) {
21104
+ return {
21105
+ content: [
21106
+ {
21107
+ type: "text",
21108
+ text: "provider, clientId, clientSecret, authorizeUrl, tokenUrl, and scopes are required for oauth-app-upsert."
21109
+ }
21110
+ ],
21111
+ structuredContent: {
21112
+ yourAgentId: requestInfo.agentId,
21113
+ success: false,
21114
+ message: "provider, clientId, clientSecret, authorizeUrl, tokenUrl, and scopes are required for oauth-app-upsert.",
21115
+ bindings
21116
+ }
21117
+ };
21118
+ }
21119
+ try {
21120
+ assertOAuthProviderIsNotReserved(args.provider);
21121
+ assertOAuthAppUrlsSafe({
21122
+ authorizeUrl: args.authorizeUrl,
21123
+ tokenUrl: args.tokenUrl
21124
+ });
21125
+ } catch (err) {
21126
+ const message = err instanceof Error ? err.message : String(err);
21127
+ return {
21128
+ content: [{ type: "text", text: message }],
21129
+ structuredContent: {
21130
+ yourAgentId: requestInfo.agentId,
21131
+ success: false,
21132
+ message,
21133
+ bindings
21134
+ }
21135
+ };
21136
+ }
21137
+ const redirectUri = genericOAuthRedirectUri(args.provider);
21138
+ upsertOAuthApp(args.provider, {
21139
+ clientId: args.clientId,
21140
+ clientSecret: args.clientSecret,
21141
+ authorizeUrl: args.authorizeUrl,
21142
+ tokenUrl: args.tokenUrl,
21143
+ redirectUri,
21144
+ scopes: args.scopes.join(","),
21145
+ ...args.extraParams || args.tokenAuthStyle || args.tokenBodyFormat ? {
21146
+ metadata: JSON.stringify({
21147
+ ...args.extraParams ? { extraParams: args.extraParams } : {},
21148
+ ...args.tokenAuthStyle ? { tokenAuthStyle: args.tokenAuthStyle } : {},
21149
+ ...args.tokenBodyFormat ? { tokenBodyFormat: args.tokenBodyFormat } : {}
21150
+ })
21151
+ } : {}
21152
+ });
21153
+ return {
21154
+ content: [
21155
+ {
21156
+ type: "text",
21157
+ text: `OAuth app ${args.provider} saved. Redirect URI: ${redirectUri}`
21158
+ }
21159
+ ],
21160
+ structuredContent: {
21161
+ yourAgentId: requestInfo.agentId,
21162
+ success: true,
21163
+ message: `OAuth app ${args.provider} saved.`,
21164
+ provider: args.provider,
21165
+ redirectUri,
21166
+ bindings: currentBindings()
21167
+ }
21168
+ };
21169
+ }
21170
+ if (args.action === "oauth-authorize-url") {
21171
+ if (!args.provider) {
21172
+ return {
21173
+ content: [{ type: "text", text: "provider is required for oauth-authorize-url." }],
21174
+ structuredContent: {
21175
+ yourAgentId: requestInfo.agentId,
21176
+ success: false,
21177
+ message: "provider is required for oauth-authorize-url.",
21178
+ bindings
21179
+ }
21180
+ };
21181
+ }
21182
+ const config = getOAuthProviderConfig(args.provider);
21183
+ if (!config) {
21184
+ return {
21185
+ content: [{ type: "text", text: `OAuth app ${args.provider} is not configured.` }],
21186
+ structuredContent: {
21187
+ yourAgentId: requestInfo.agentId,
21188
+ success: false,
21189
+ message: `OAuth app ${args.provider} is not configured.`,
21190
+ provider: args.provider,
21191
+ bindings
21192
+ }
21193
+ };
21194
+ }
21195
+ const result = await buildAuthorizationUrl(config);
21196
+ return {
21197
+ content: [{ type: "text", text: result.url }],
21198
+ structuredContent: {
21199
+ yourAgentId: requestInfo.agentId,
21200
+ success: true,
21201
+ message: `OAuth authorization URL generated for ${args.provider}.`,
21202
+ provider: args.provider,
21203
+ authorizeUrl: result.url,
21204
+ redirectUri: config.redirectUri,
21205
+ bindings
21206
+ }
21207
+ };
21208
+ }
21308
21209
  if (args.action === "list") {
21309
21210
  return {
21310
21211
  content: [
@@ -21323,7 +21224,7 @@ var registerCredentialBindingsTool = (server) => {
21323
21224
  }
21324
21225
  if (args.action === "import-legacy") {
21325
21226
  const imported = importLegacyCredentialBindings();
21326
- const nextBindings2 = listRelationalCredentialBindings({ includeInactive: true });
21227
+ const nextBindings2 = currentBindings();
21327
21228
  return {
21328
21229
  content: [{ type: "text", text: `Imported ${imported} legacy credential binding(s).` }],
21329
21230
  structuredContent: {
@@ -21347,7 +21248,7 @@ var registerCredentialBindingsTool = (server) => {
21347
21248
  }
21348
21249
  };
21349
21250
  }
21350
- const nextBindings2 = listRelationalCredentialBindings({ includeInactive: true });
21251
+ const nextBindings2 = currentBindings();
21351
21252
  return {
21352
21253
  content: [{ type: "text", text: `Credential binding ${disabled.configKey} disabled.` }],
21353
21254
  structuredContent: {
@@ -21370,14 +21271,17 @@ var registerCredentialBindingsTool = (server) => {
21370
21271
  };
21371
21272
  }
21372
21273
  const scope = args.scope ?? "global";
21373
- const scopeId = scope === "global" ? null : args.scopeId ?? null;
21374
- if (scope !== "global" && !scopeId) {
21274
+ let scopeId;
21275
+ try {
21276
+ scopeId = resolveScopedResourceId(scope, args.scopeId, "bindings");
21277
+ } catch (err) {
21278
+ const message = err instanceof Error ? err.message : String(err);
21375
21279
  return {
21376
- content: [{ type: "text", text: `scopeId is required for ${scope} bindings.` }],
21280
+ content: [{ type: "text", text: message }],
21377
21281
  structuredContent: {
21378
21282
  yourAgentId: requestInfo.agentId,
21379
21283
  success: false,
21380
- message: `scopeId is required for ${scope} bindings.`,
21284
+ message,
21381
21285
  bindings
21382
21286
  }
21383
21287
  };
@@ -21398,6 +21302,17 @@ var registerCredentialBindingsTool = (server) => {
21398
21302
  }
21399
21303
  };
21400
21304
  }
21305
+ if ((args.authKind ?? "config") === "oauth" && !args.oauthProvider) {
21306
+ return {
21307
+ content: [{ type: "text", text: "oauthProvider is required for oauth bindings." }],
21308
+ structuredContent: {
21309
+ yourAgentId: requestInfo.agentId,
21310
+ success: false,
21311
+ message: "oauthProvider is required for oauth bindings.",
21312
+ bindings
21313
+ }
21314
+ };
21315
+ }
21401
21316
  const placeholder = placeholderForConfigKey(args.configKey);
21402
21317
  if (args.headerTemplate && !args.headerTemplate.includes(placeholder)) {
21403
21318
  return {
@@ -21428,7 +21343,9 @@ var registerCredentialBindingsTool = (server) => {
21428
21343
  queryTemplate: args.queryTemplate,
21429
21344
  scope,
21430
21345
  scopeId,
21431
- active: true
21346
+ active: true,
21347
+ authKind: args.authKind ?? "config",
21348
+ oauthProvider: args.oauthProvider
21432
21349
  });
21433
21350
  upsertCredentialBinding({
21434
21351
  id: args.id,
@@ -21438,9 +21355,11 @@ var registerCredentialBindingsTool = (server) => {
21438
21355
  queryTemplate: nextBinding.queryTemplate,
21439
21356
  scope: nextBinding.scope,
21440
21357
  scopeId: nextBinding.scopeId ?? null,
21441
- active: true
21358
+ active: true,
21359
+ authKind: nextBinding.authKind,
21360
+ oauthProvider: nextBinding.oauthProvider ?? null
21442
21361
  });
21443
- const nextBindings = listRelationalCredentialBindings({ includeInactive: true });
21362
+ const nextBindings = currentBindings();
21444
21363
  return {
21445
21364
  content: [{ type: "text", text: `Credential binding ${args.configKey} saved.` }],
21446
21365
  structuredContent: {
@@ -21637,6 +21556,126 @@ var registerDeleteChannelTool = (server) => {
21637
21556
  });
21638
21557
  };
21639
21558
 
21559
+ // src/tools/delete-page.ts
21560
+ init_zod();
21561
+ init_db();
21562
+ var registerDeletePageTool = (server) => {
21563
+ createToolRegistrar(server)("delete-page", {
21564
+ title: "Delete Page",
21565
+ description: "Permanently delete one page by pageId, or by slug in the caller's page namespace. Only the lead or the page owner can delete a page.",
21566
+ annotations: { destructiveHint: true },
21567
+ inputSchema: object({
21568
+ pageId: string().min(1).optional().describe("Page ID to delete."),
21569
+ slug: string().min(1).optional().describe("Page slug to delete from the caller's own (agentId, slug) namespace. Alternative to pageId.")
21570
+ }),
21571
+ outputSchema: object({
21572
+ yourAgentId: string().optional(),
21573
+ success: boolean(),
21574
+ message: string(),
21575
+ deletedPage: object({
21576
+ id: string(),
21577
+ slug: string(),
21578
+ title: string()
21579
+ }).optional()
21580
+ })
21581
+ }, async ({ pageId, slug }, requestInfo, _meta) => {
21582
+ if (!requestInfo.agentId) {
21583
+ return {
21584
+ content: [{ type: "text", text: 'Agent ID not found. Set the "X-Agent-ID" header.' }],
21585
+ structuredContent: {
21586
+ success: false,
21587
+ message: 'Agent ID not found. Set the "X-Agent-ID" header.'
21588
+ }
21589
+ };
21590
+ }
21591
+ if (!pageId && !slug) {
21592
+ return {
21593
+ content: [{ type: "text", text: "Either pageId or slug must be provided." }],
21594
+ structuredContent: {
21595
+ yourAgentId: requestInfo.agentId,
21596
+ success: false,
21597
+ message: "Either pageId or slug must be provided."
21598
+ }
21599
+ };
21600
+ }
21601
+ const caller = getAgentById(requestInfo.agentId);
21602
+ if (!caller) {
21603
+ return {
21604
+ content: [{ type: "text", text: "Agent not found." }],
21605
+ structuredContent: {
21606
+ yourAgentId: requestInfo.agentId,
21607
+ success: false,
21608
+ message: "Agent not found."
21609
+ }
21610
+ };
21611
+ }
21612
+ const page = pageId ? getPage(pageId) : slug ? getPageBySlug(requestInfo.agentId, slug) : null;
21613
+ if (!page) {
21614
+ return {
21615
+ content: [{ type: "text", text: "Page not found." }],
21616
+ structuredContent: {
21617
+ yourAgentId: requestInfo.agentId,
21618
+ success: false,
21619
+ message: "Page not found."
21620
+ }
21621
+ };
21622
+ }
21623
+ const decision = can({
21624
+ principal: { kind: "agent", agentId: caller.id, isLead: caller.isLead },
21625
+ verb: "page.delete.any",
21626
+ resource: { kind: "owned", ownerAgentId: page.agentId },
21627
+ source: "mcp"
21628
+ });
21629
+ if (!decision.allow) {
21630
+ return {
21631
+ content: [{ type: "text", text: "Only the lead or page owner can delete pages." }],
21632
+ structuredContent: {
21633
+ yourAgentId: requestInfo.agentId,
21634
+ success: false,
21635
+ message: "Only the lead or page owner can delete pages."
21636
+ }
21637
+ };
21638
+ }
21639
+ try {
21640
+ const deleted = deletePage(page.id);
21641
+ if (!deleted) {
21642
+ return {
21643
+ content: [{ type: "text", text: "Failed to delete page." }],
21644
+ structuredContent: {
21645
+ yourAgentId: requestInfo.agentId,
21646
+ success: false,
21647
+ message: "Failed to delete page."
21648
+ }
21649
+ };
21650
+ }
21651
+ const deletedPage = {
21652
+ id: page.id,
21653
+ slug: page.slug,
21654
+ title: page.title
21655
+ };
21656
+ return {
21657
+ content: [{ type: "text", text: `Deleted page "${page.title}".` }],
21658
+ structuredContent: {
21659
+ yourAgentId: requestInfo.agentId,
21660
+ success: true,
21661
+ message: `Deleted page "${page.title}".`,
21662
+ deletedPage
21663
+ }
21664
+ };
21665
+ } catch (error) {
21666
+ const message = error instanceof Error ? error.message : "Unknown error";
21667
+ return {
21668
+ content: [{ type: "text", text: `Failed to delete page: ${message}` }],
21669
+ structuredContent: {
21670
+ yourAgentId: requestInfo.agentId,
21671
+ success: false,
21672
+ message: `Failed to delete page: ${message}`
21673
+ }
21674
+ };
21675
+ }
21676
+ });
21677
+ };
21678
+
21640
21679
  // src/tools/get-metrics.ts
21641
21680
  init_zod();
21642
21681
  init_db();
@@ -23095,18 +23134,19 @@ var registerMcpServerDeleteTool = (server) => {
23095
23134
  }
23096
23135
  };
23097
23136
  }
23098
- const deleted = deleteMcpServer(args.id);
23137
+ const result = deleteMcpServer(args.id);
23138
+ const message = result.deleted ? `Deleted MCP server "${existing.name}" and ${result.deletedScriptConnectionCount} script connection(s).` : "Delete failed.";
23099
23139
  return {
23100
23140
  content: [
23101
23141
  {
23102
23142
  type: "text",
23103
- text: deleted ? `Deleted MCP server "${existing.name}".` : "Delete failed."
23143
+ text: message
23104
23144
  }
23105
23145
  ],
23106
23146
  structuredContent: {
23107
23147
  yourAgentId: requestInfo.agentId,
23108
- success: deleted,
23109
- message: deleted ? `Deleted MCP server "${existing.name}".` : "Delete failed."
23148
+ success: result.deleted,
23149
+ message
23110
23150
  }
23111
23151
  };
23112
23152
  });
@@ -26180,6 +26220,45 @@ var registerUpdateRepoTool = (server) => {
26180
26220
  };
26181
26221
  // src/tools/request-human-input.ts
26182
26222
  init_zod();
26223
+
26224
+ // src/be/audit-user.ts
26225
+ init_request_auth_context();
26226
+ init_db();
26227
+ function resolveTaskAuditUserId(sourceTaskId, callerAgentId) {
26228
+ if (!callerAgentId)
26229
+ return null;
26230
+ let resolvedSourceTaskId = sourceTaskId;
26231
+ if (!resolvedSourceTaskId) {
26232
+ const currentTask = getAgentCurrentTask(callerAgentId);
26233
+ if (currentTask)
26234
+ resolvedSourceTaskId = currentTask.id;
26235
+ }
26236
+ if (!resolvedSourceTaskId)
26237
+ return null;
26238
+ const task = getTaskById(resolvedSourceTaskId);
26239
+ if (!task)
26240
+ return null;
26241
+ if (task.agentId !== callerAgentId)
26242
+ return null;
26243
+ if (task.requestedByUserId)
26244
+ return task.requestedByUserId;
26245
+ if (task.slackUserId) {
26246
+ const user = findUserByExternalId("slack", task.slackUserId);
26247
+ if (user)
26248
+ return user.id;
26249
+ }
26250
+ return null;
26251
+ }
26252
+ function resolveHttpAuditUserId(req, callerAgentId) {
26253
+ const auth = getRequestAuth(req);
26254
+ if (auth?.kind === "user")
26255
+ return auth.userId;
26256
+ const header = req.headers["x-source-task-id"];
26257
+ const sourceTaskId = Array.isArray(header) ? header[0] : header;
26258
+ return resolveTaskAuditUserId(sourceTaskId, callerAgentId);
26259
+ }
26260
+
26261
+ // src/tools/request-human-input.ts
26183
26262
  init_db();
26184
26263
  var QuestionSchema = object({
26185
26264
  id: string().describe("Unique ID for the question (used as key in responses)"),
@@ -26245,7 +26324,8 @@ var registerRequestHumanInputTool = (server) => {
26245
26324
  questions,
26246
26325
  approvers: { policy: "any" },
26247
26326
  sourceTaskId: sourceTaskId ?? undefined,
26248
- timeoutSeconds
26327
+ timeoutSeconds,
26328
+ createdBy: resolveTaskAuditUserId(sourceTaskId, requestInfo.agentId) ?? undefined
26249
26329
  });
26250
26330
  const appUrl = getAppUrl();
26251
26331
  const url = `${appUrl}/approval-requests/${request.id}`;
@@ -26270,67 +26350,72 @@ var registerRequestHumanInputTool = (server) => {
26270
26350
  // src/tools/resolve-user.ts
26271
26351
  init_zod();
26272
26352
  var resolveUserInputSchema = object({
26273
- kind: string().optional().describe("Identity kind — e.g. 'slack', 'linear', 'github', 'gitlab', 'jira', or a custom value. Must be paired with externalId."),
26274
- externalId: string().optional().describe("Platform-specific identifier for the given kind (e.g. Slack user ID 'U08NR6QD6CS', Linear user UUID, GitHub login)."),
26353
+ kind: string().optional().describe("Identity kind — e.g. 'slack', 'linear', 'github', 'gitlab', 'jira', 'kapso', 'whatsapp', or a custom value. Must be paired with externalId."),
26354
+ externalId: string().optional().describe("Platform-specific identifier for the given kind (e.g. Slack user ID 'U08NR6QD6CS', Linear user UUID, GitHub login, Jira accountId)."),
26275
26355
  email: string().email().optional().describe("Email address (primary or alias)."),
26276
- userId: string().optional().describe("Canonical swarm user ID. Use this to reverse-look up all external identities for a known user (e.g. find their GitHub handle from a requestedByUserId).")
26277
- }).strict().refine((v) => v.kind !== undefined && v.externalId !== undefined || v.email !== undefined || v.userId !== undefined, { message: "Provide either (kind + externalId), email, or userId" });
26356
+ userId: string().optional().describe("Canonical swarm user ID. Use this to reverse-look up all external identities for a known user (e.g. find their GitHub handle from a requestedByUserId)."),
26357
+ name: string().min(2).optional().describe("Human display name to search for (exact, or first-token prefix). Convenience only ambiguous matches return all candidates rather than picking one.")
26358
+ }).strict().refine((v) => v.kind !== undefined && v.externalId !== undefined || v.email !== undefined || v.userId !== undefined || v.name !== undefined, { message: "Provide either (kind + externalId), email, userId, or name" });
26359
+ function jsonResult(value) {
26360
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
26361
+ }
26362
+ function profileResult(user) {
26363
+ return jsonResult({ ...user, externalIds: getUserIdentities(user.id) });
26364
+ }
26365
+ function unknownResult(kind, externalId) {
26366
+ return jsonResult({ status: "unknown", kind, externalId });
26367
+ }
26368
+ function ambiguousResult(candidates) {
26369
+ return jsonResult({
26370
+ status: "ambiguous",
26371
+ message: "AMBIGUOUS — do not pick by salience. Multiple users match this name; disambiguate with (kind, externalId), email, or userId.",
26372
+ candidates: candidates.map((u) => ({ userId: u.id, name: u.name, email: u.email }))
26373
+ });
26374
+ }
26278
26375
  var registerResolveUserTool = (server) => {
26279
26376
  createToolRegistrar(server)("resolve-user", {
26280
26377
  title: "Resolve user identity",
26281
- description: "Look up a canonical user profile by an `(kind, externalId)` pair (e.g. {kind: 'slack', externalId: 'U_X'}), by email (primary or alias), or by swarm `userId`. Returns the user profile including `externalIds` (all linked platform identities) or 'No user found'.",
26378
+ description: "Provider-agnostic reverse lookup: (kind, externalId) user, e.g. {kind: 'slack', externalId: 'U016H7XKZGS'} or {kind: 'github', externalId: 'octocat'} — the same shape for every provider, no per-provider keys. Also accepts email (primary or alias), userId (reverse lookup of all linked identities), or name (exact/prefix search). A miss returns a structured {status: 'unknown', ...} payload, never prose; an ambiguous name search returns {status: 'ambiguous', candidates: [...]}.",
26282
26379
  annotations: { readOnlyHint: true },
26283
26380
  inputSchema: resolveUserInputSchema
26284
- }, async ({ kind, externalId, email, userId }) => {
26285
- let user = null;
26381
+ }, async ({ kind, externalId, email, userId, name }) => {
26286
26382
  if (kind && externalId) {
26287
- user = findUserByExternalId(kind, externalId);
26288
- } else if (email) {
26289
- user = findUserByEmail(email);
26290
- } else if (userId) {
26291
- user = findUserById(userId);
26292
- }
26293
- if (!user) {
26294
- return {
26295
- content: [{ type: "text", text: "No user found matching the given criteria." }]
26296
- };
26297
- }
26298
- const externalIds = getUserIdentities(user.id);
26299
- return {
26300
- content: [
26301
- { type: "text", text: JSON.stringify({ ...user, externalIds }, null, 2) }
26302
- ]
26303
- };
26383
+ const resolution = resolveIdentity(kind, externalId);
26384
+ if (resolution.status === "unknown")
26385
+ return unknownResult(kind, externalId);
26386
+ const user = findUserById(resolution.userId);
26387
+ if (!user)
26388
+ return unknownResult(kind, externalId);
26389
+ return profileResult(user);
26390
+ }
26391
+ if (email) {
26392
+ const resolution = resolveIdentityByEmail(email);
26393
+ if (resolution.status === "unknown")
26394
+ return unknownResult("email", email);
26395
+ const user = findUserById(resolution.userId);
26396
+ if (!user)
26397
+ return unknownResult("email", email);
26398
+ return profileResult(user);
26399
+ }
26400
+ if (userId) {
26401
+ const user = findUserById(userId);
26402
+ if (!user)
26403
+ return unknownResult("userId", userId);
26404
+ return profileResult(user);
26405
+ }
26406
+ const matches = findUsersByName(name ?? "");
26407
+ if (matches.length === 0)
26408
+ return unknownResult("name", name ?? "");
26409
+ const [only] = matches;
26410
+ if (matches.length === 1 && only)
26411
+ return profileResult(only);
26412
+ return ambiguousResult(matches);
26304
26413
  });
26305
26414
  };
26306
26415
 
26307
26416
  // src/tools/schedules/create-schedule.ts
26308
26417
  init_zod();
26309
26418
  var import_cron_parser = __toESM(require_dist(), 1);
26310
-
26311
- // src/be/audit-user.ts
26312
- init_request_auth_context();
26313
- init_db();
26314
- function resolveTaskAuditUserId(sourceTaskId, callerAgentId) {
26315
- if (!sourceTaskId || !callerAgentId)
26316
- return null;
26317
- const task = getTaskById(sourceTaskId);
26318
- if (!task)
26319
- return null;
26320
- if (task.agentId !== callerAgentId)
26321
- return null;
26322
- return task.requestedByUserId ?? null;
26323
- }
26324
- function resolveHttpAuditUserId(req, callerAgentId) {
26325
- const auth = getRequestAuth(req);
26326
- if (auth?.kind === "user")
26327
- return auth.userId;
26328
- const header = req.headers["x-source-task-id"];
26329
- const sourceTaskId = Array.isArray(header) ? header[0] : header;
26330
- return resolveTaskAuditUserId(sourceTaskId, callerAgentId);
26331
- }
26332
-
26333
- // src/tools/schedules/create-schedule.ts
26334
26419
  init_db();
26335
26420
  init_types();
26336
26421
  var createScheduleInputSchema = object({
@@ -27783,6 +27868,7 @@ var registerUpdateScheduleTool = (server) => {
27783
27868
  };
27784
27869
  // src/tools/script-apis.ts
27785
27870
  init_zod();
27871
+ init_db();
27786
27872
  init_secret_scrubber();
27787
27873
 
27788
27874
  // src/tools/script-common.ts
@@ -27866,13 +27952,29 @@ function maskToken(endpoint) {
27866
27952
  const { token: _drop, ...rest } = endpoint;
27867
27953
  return { ...rest, token: endpoint.authMode === "bearer" ? "********" : null };
27868
27954
  }
27869
- function errorResult(message) {
27955
+ function errorResult(message, status = 400) {
27870
27956
  return {
27871
27957
  isError: true,
27872
27958
  content: [{ type: "text", text: message }],
27873
- structuredContent: { success: false, status: 400, error: message }
27959
+ structuredContent: { success: false, status, error: message }
27874
27960
  };
27875
27961
  }
27962
+ function requireScriptApiPermission(requestInfo, verb, message) {
27963
+ if (!requestInfo.agentId)
27964
+ return errorResult(SCRIPT_TRANSPORT_ERROR);
27965
+ const agent = getAgentById(requestInfo.agentId);
27966
+ const decision = can({
27967
+ principal: {
27968
+ kind: "agent",
27969
+ agentId: requestInfo.agentId,
27970
+ isLead: agent?.isLead ?? false
27971
+ },
27972
+ verb,
27973
+ resource: { kind: "none" },
27974
+ source: "mcp"
27975
+ });
27976
+ return decision.allow ? null : errorResult(message, 403);
27977
+ }
27876
27978
  var registerScriptApisTool = (server) => {
27877
27979
  createToolRegistrar(server)("script-apis", {
27878
27980
  title: "Script APIs",
@@ -27893,6 +27995,9 @@ var registerScriptApisTool = (server) => {
27893
27995
  const raw = result.structuredContent?.data;
27894
27996
  let endpoints = (raw?.apis ?? []).map(maskToken);
27895
27997
  if (args.includeSecrets) {
27998
+ const denied = requireScriptApiPermission(requestInfo, "script.api.read.secrets", "Only lead agents can reveal script API bearer tokens.");
27999
+ if (denied)
28000
+ return denied;
27896
28001
  endpoints = await Promise.all(endpoints.map(async (endpoint) => {
27897
28002
  if (endpoint.authMode !== "bearer")
27898
28003
  return endpoint;
@@ -27916,6 +28021,9 @@ var registerScriptApisTool = (server) => {
27916
28021
  };
27917
28022
  }
27918
28023
  if (args.action === "create") {
28024
+ const denied = requireScriptApiPermission(requestInfo, "script.api.create", "Only lead agents can create script API endpoints.");
28025
+ if (denied)
28026
+ return denied;
27919
28027
  const result = await proxyScriptsApi({
27920
28028
  method: "POST",
27921
28029
  path: `/api/scripts/${args.scriptId}/apis`,
@@ -27937,6 +28045,9 @@ var registerScriptApisTool = (server) => {
27937
28045
  if (args.action === "rotate") {
27938
28046
  if (!args.endpointId)
27939
28047
  return errorResult("endpointId is required for rotate.");
28048
+ const denied = requireScriptApiPermission(requestInfo, "script.api.rotate", "Only lead agents can rotate script API bearer tokens.");
28049
+ if (denied)
28050
+ return denied;
27940
28051
  const result = await proxyScriptsApi({
27941
28052
  method: "POST",
27942
28053
  path: `/api/scripts/${args.scriptId}/apis/${args.endpointId}/rotate`,
@@ -27953,6 +28064,9 @@ var registerScriptApisTool = (server) => {
27953
28064
  if (args.action === "update") {
27954
28065
  if (!args.endpointId)
27955
28066
  return errorResult("endpointId is required for update.");
28067
+ const denied = requireScriptApiPermission(requestInfo, "script.api.update", "Only lead agents can update script API endpoints.");
28068
+ if (denied)
28069
+ return denied;
27956
28070
  return proxyScriptsApi({
27957
28071
  method: "PATCH",
27958
28072
  path: `/api/scripts/${args.scriptId}/apis/${args.endpointId}`,
@@ -27964,6 +28078,9 @@ var registerScriptApisTool = (server) => {
27964
28078
  if (args.action === "delete") {
27965
28079
  if (!args.endpointId)
27966
28080
  return errorResult("endpointId is required for delete.");
28081
+ const denied = requireScriptApiPermission(requestInfo, "script.api.delete", "Only lead agents can delete script API endpoints.");
28082
+ if (denied)
28083
+ return denied;
27967
28084
  return proxyScriptsApi({
27968
28085
  method: "DELETE",
27969
28086
  path: `/api/scripts/${args.scriptId}/apis/${args.endpointId}`,
@@ -27979,20 +28096,22 @@ var registerScriptApisTool = (server) => {
27979
28096
  init_zod();
27980
28097
  init_db();
27981
28098
  var scriptConnectionsInputSchema = object({
27982
- action: _enum(["list", "upsert-openapi", "disable"]).describe("List, create/update, or disable a script connection."),
27983
- id: string().uuid().optional(),
27984
- slug: string().min(1).max(80).optional(),
27985
- displayName: string().max(160).optional(),
27986
- scope: _enum(["global", "agent", "repo"]).default("global").optional(),
27987
- scopeId: string().uuid().nullable().optional(),
27988
- baseUrl: string().url().optional(),
27989
- allowedHosts: array(string().min(1)).optional(),
27990
- credentialBindingId: string().uuid().nullable().optional(),
27991
- configKey: string().min(1).max(255).optional(),
27992
- headerTemplate: string().min(1).optional(),
27993
- queryTemplate: string().min(1).optional(),
27994
- openapiSpecJson: string().optional(),
27995
- enabled: boolean().default(true).optional()
28099
+ action: _enum(["list", "upsert-openapi", "upsert-mcp", "upsert-graphql", "refresh", "disable"]).describe("List, create/update, refresh, or disable a script connection."),
28100
+ id: string().uuid().optional().describe("Existing connection ID for update, refresh, or disable."),
28101
+ slug: string().min(1).max(80).optional().describe("Stable script namespace slug exposed under ctx.api or ctx.mcp."),
28102
+ displayName: string().max(160).optional().describe("Human-readable connection name."),
28103
+ scope: _enum(["global", "agent", "repo"]).optional().describe("Connection visibility scope."),
28104
+ scopeId: scopedResourceScopeIdSchema.nullable().optional().describe("Agent UUID for agent scope or repo id (owner/name) for repo scope."),
28105
+ mcpServerId: string().uuid().optional().describe("Registered MCP server ID for upsert-mcp connections."),
28106
+ baseUrl: string().url().optional().describe("Base URL for OpenAPI or GraphQL connections."),
28107
+ allowedHosts: array(string().min(1)).optional().describe("Allowed outbound hostnames for credential substitution."),
28108
+ credentialBindingId: string().uuid().nullable().optional().describe("Existing credential binding ID to attach to the connection."),
28109
+ configKey: string().min(1).max(255).optional().describe("Config key used to create a credential binding when credentialBindingId is omitted."),
28110
+ headerTemplate: string().min(1).optional().describe("Header template containing the config-key placeholder."),
28111
+ queryTemplate: string().min(1).optional().describe("Query parameter template containing the config-key placeholder."),
28112
+ openapiSpecUrl: string().url().optional().describe("URL to fetch and store an OpenAPI spec for upsert-openapi and refresh."),
28113
+ openapiSpecJson: string().optional().describe("Inline OpenAPI JSON for upsert-openapi. Mutually exclusive with openapiSpecUrl."),
28114
+ enabled: boolean().optional().describe("Whether the connection is enabled.")
27996
28115
  });
27997
28116
  var scriptConnectionsOutputSchema = object({
27998
28117
  yourAgentId: string().uuid().optional(),
@@ -28000,10 +28119,23 @@ var scriptConnectionsOutputSchema = object({
28000
28119
  message: string(),
28001
28120
  connections: array(unknown())
28002
28121
  });
28122
+ function resolveConnectionScope(args, existing) {
28123
+ const scopeWasProvided = Object.hasOwn(args, "scope");
28124
+ const scopeIdWasProvided = Object.hasOwn(args, "scopeId");
28125
+ const scope = (scopeWasProvided ? args.scope : existing?.scope) ?? "global";
28126
+ const scopeIdInput = scopeIdWasProvided ? args.scopeId : existing && scope === existing.scope ? existing.scopeId : null;
28127
+ return {
28128
+ scope,
28129
+ scopeId: resolveScopedResourceId(scope, scopeIdInput, "connections")
28130
+ };
28131
+ }
28132
+ function resolveConnectionEnabled(args, existing) {
28133
+ return Object.hasOwn(args, "enabled") ? args.enabled !== false : existing?.enabled ?? true;
28134
+ }
28003
28135
  var registerScriptConnectionsTool = (server) => {
28004
28136
  createToolRegistrar(server)("script-connections", {
28005
28137
  title: "Script Connections",
28006
- description: "Lead-only registry management for scripts ctx.api/ctx.mcp connections. Phase 1 supports OpenAPI ctx.api connections with generated args and response types.",
28138
+ description: "Lead-only registry management for scripts ctx.api/ctx.mcp connections. Supports OpenAPI, MCP, and GraphQL script connections.",
28007
28139
  annotations: { idempotentHint: true },
28008
28140
  inputSchema: scriptConnectionsInputSchema,
28009
28141
  outputSchema: scriptConnectionsOutputSchema
@@ -28041,7 +28173,7 @@ var registerScriptConnectionsTool = (server) => {
28041
28173
  };
28042
28174
  }
28043
28175
  if (args.action === "list") {
28044
- const connections2 = listScriptConnections({ includeDisabled: true });
28176
+ const connections2 = listScriptConnections({ includeDisabled: true, allScopes: true });
28045
28177
  return {
28046
28178
  content: [{ type: "text", text: `Found ${connections2.length} script connection(s).` }],
28047
28179
  structuredContent: {
@@ -28060,12 +28192,12 @@ var registerScriptConnectionsTool = (server) => {
28060
28192
  yourAgentId: requestInfo.agentId,
28061
28193
  success: false,
28062
28194
  message: "id is required for disable.",
28063
- connections: listScriptConnections({ includeDisabled: true })
28195
+ connections: listScriptConnections({ includeDisabled: true, allScopes: true })
28064
28196
  }
28065
28197
  };
28066
28198
  }
28067
28199
  setScriptConnectionEnabled(args.id, false);
28068
- const connections2 = listScriptConnections({ includeDisabled: true });
28200
+ const connections2 = listScriptConnections({ includeDisabled: true, allScopes: true });
28069
28201
  return {
28070
28202
  content: [{ type: "text", text: "Script connection disabled." }],
28071
28203
  structuredContent: {
@@ -28076,44 +28208,191 @@ var registerScriptConnectionsTool = (server) => {
28076
28208
  }
28077
28209
  };
28078
28210
  }
28079
- if (!args.slug || !args.baseUrl || !args.openapiSpecJson) {
28211
+ if (args.action === "refresh") {
28212
+ if (!args.id) {
28213
+ return {
28214
+ content: [{ type: "text", text: "id is required for refresh." }],
28215
+ structuredContent: {
28216
+ yourAgentId: requestInfo.agentId,
28217
+ success: false,
28218
+ message: "id is required for refresh.",
28219
+ connections: listScriptConnections({ includeDisabled: true, allScopes: true })
28220
+ }
28221
+ };
28222
+ }
28223
+ const refreshed = await refreshScriptConnection(args.id, null, requestInfo.agentId);
28224
+ const connections2 = listScriptConnections({ includeDisabled: true, allScopes: true });
28225
+ if (!refreshed) {
28226
+ return {
28227
+ content: [{ type: "text", text: "Script connection not found." }],
28228
+ structuredContent: {
28229
+ yourAgentId: requestInfo.agentId,
28230
+ success: false,
28231
+ message: "Script connection not found.",
28232
+ connections: connections2
28233
+ }
28234
+ };
28235
+ }
28236
+ return {
28237
+ content: [{ type: "text", text: `Script connection ${refreshed.slug} refreshed.` }],
28238
+ structuredContent: {
28239
+ yourAgentId: requestInfo.agentId,
28240
+ success: !refreshed.generationError,
28241
+ message: refreshed.generationError ? `Refreshed but generation failed: ${refreshed.generationError}` : `Script connection ${refreshed.slug} refreshed.`,
28242
+ connections: connections2
28243
+ }
28244
+ };
28245
+ }
28246
+ if (args.action === "upsert-mcp") {
28247
+ if (!args.slug || !args.mcpServerId) {
28248
+ return {
28249
+ content: [{ type: "text", text: "slug and mcpServerId are required." }],
28250
+ structuredContent: {
28251
+ yourAgentId: requestInfo.agentId,
28252
+ success: false,
28253
+ message: "slug and mcpServerId are required.",
28254
+ connections: listScriptConnections({ includeDisabled: true, allScopes: true })
28255
+ }
28256
+ };
28257
+ }
28258
+ const existing2 = args.id ? getScriptConnectionById(args.id) : null;
28259
+ const { scope: scope2, scopeId: scopeId2 } = resolveConnectionScope(args, existing2);
28260
+ const connection2 = await upsertScriptConnection({
28261
+ id: args.id,
28262
+ slug: args.slug,
28263
+ displayName: args.displayName,
28264
+ kind: "mcp",
28265
+ scope: scope2,
28266
+ scopeId: scopeId2,
28267
+ mcpServerId: args.mcpServerId,
28268
+ enabled: resolveConnectionEnabled(args, existing2),
28269
+ agentId: requestInfo.agentId
28270
+ });
28271
+ const connections2 = listScriptConnections({ includeDisabled: true, allScopes: true });
28272
+ return {
28273
+ content: [{ type: "text", text: `Script MCP connection ${connection2.slug} saved.` }],
28274
+ structuredContent: {
28275
+ yourAgentId: requestInfo.agentId,
28276
+ success: !connection2.generationError,
28277
+ message: connection2.generationError ? `Saved but generation failed: ${connection2.generationError}` : `Script MCP connection ${connection2.slug} saved.`,
28278
+ connections: connections2
28279
+ }
28280
+ };
28281
+ }
28282
+ if (args.action === "upsert-graphql") {
28283
+ if (!args.slug || !args.baseUrl || !args.allowedHosts?.length) {
28284
+ return {
28285
+ content: [{ type: "text", text: "slug, baseUrl, and allowedHosts are required." }],
28286
+ structuredContent: {
28287
+ yourAgentId: requestInfo.agentId,
28288
+ success: false,
28289
+ message: "slug, baseUrl, and allowedHosts are required.",
28290
+ connections: listScriptConnections({ includeDisabled: true, allScopes: true })
28291
+ }
28292
+ };
28293
+ }
28294
+ let credentialBindingId2 = args.credentialBindingId ?? null;
28295
+ if (!credentialBindingId2 && args.configKey) {
28296
+ const placeholder = placeholderForConfigKey(args.configKey);
28297
+ const bindingScope = args.scope ?? "global";
28298
+ const binding = upsertCredentialBinding({
28299
+ configKey: args.configKey,
28300
+ allowedHosts: args.allowedHosts,
28301
+ headerTemplate: args.headerTemplate ?? (args.queryTemplate ? undefined : `Authorization: Bearer ${placeholder}`),
28302
+ queryTemplate: args.queryTemplate,
28303
+ scope: bindingScope,
28304
+ scopeId: resolveScopedResourceId(bindingScope, args.scopeId, "bindings")
28305
+ });
28306
+ credentialBindingId2 = binding.id;
28307
+ }
28308
+ const existing2 = args.id ? getScriptConnectionById(args.id) : null;
28309
+ const { scope: scope2, scopeId: scopeId2 } = resolveConnectionScope(args, existing2);
28310
+ const connection2 = await upsertScriptConnection({
28311
+ id: args.id,
28312
+ slug: args.slug,
28313
+ displayName: args.displayName,
28314
+ kind: "graphql",
28315
+ scope: scope2,
28316
+ scopeId: scopeId2,
28317
+ baseUrl: args.baseUrl,
28318
+ allowedHosts: args.allowedHosts,
28319
+ credentialBindingId: credentialBindingId2,
28320
+ enabled: resolveConnectionEnabled(args, existing2)
28321
+ });
28322
+ const connections2 = listScriptConnections({ includeDisabled: true, allScopes: true });
28323
+ return {
28324
+ content: [{ type: "text", text: `Script GraphQL connection ${connection2.slug} saved.` }],
28325
+ structuredContent: {
28326
+ yourAgentId: requestInfo.agentId,
28327
+ success: !connection2.generationError,
28328
+ message: connection2.generationError ? `Saved but generation failed: ${connection2.generationError}` : `Script GraphQL connection ${connection2.slug} saved.`,
28329
+ connections: connections2
28330
+ }
28331
+ };
28332
+ }
28333
+ if (!args.slug || !args.baseUrl || !args.openapiSpecJson && !args.openapiSpecUrl) {
28334
+ return {
28335
+ content: [
28336
+ {
28337
+ type: "text",
28338
+ text: "slug, baseUrl, and either openapiSpecJson or openapiSpecUrl are required."
28339
+ }
28340
+ ],
28341
+ structuredContent: {
28342
+ yourAgentId: requestInfo.agentId,
28343
+ success: false,
28344
+ message: "slug, baseUrl, and either openapiSpecJson or openapiSpecUrl are required.",
28345
+ connections: listScriptConnections({ includeDisabled: true, allScopes: true })
28346
+ }
28347
+ };
28348
+ }
28349
+ if (args.openapiSpecJson && args.openapiSpecUrl) {
28080
28350
  return {
28081
- content: [{ type: "text", text: "slug, baseUrl, and openapiSpecJson are required." }],
28351
+ content: [
28352
+ {
28353
+ type: "text",
28354
+ text: "Provide either openapiSpecJson or openapiSpecUrl, not both."
28355
+ }
28356
+ ],
28082
28357
  structuredContent: {
28083
28358
  yourAgentId: requestInfo.agentId,
28084
28359
  success: false,
28085
- message: "slug, baseUrl, and openapiSpecJson are required.",
28086
- connections: listScriptConnections({ includeDisabled: true })
28360
+ message: "Provide either openapiSpecJson or openapiSpecUrl, not both.",
28361
+ connections: listScriptConnections({ includeDisabled: true, allScopes: true })
28087
28362
  }
28088
28363
  };
28089
28364
  }
28090
28365
  let credentialBindingId = args.credentialBindingId ?? null;
28091
28366
  if (!credentialBindingId && args.configKey) {
28092
28367
  const placeholder = placeholderForConfigKey(args.configKey);
28368
+ const bindingScope = args.scope ?? "global";
28093
28369
  const binding = upsertCredentialBinding({
28094
28370
  configKey: args.configKey,
28095
28371
  allowedHosts: args.allowedHosts ?? [new URL(args.baseUrl).hostname],
28096
- headerTemplate: args.headerTemplate ?? `Authorization: Bearer ${placeholder}`,
28372
+ headerTemplate: args.headerTemplate ?? (args.queryTemplate ? undefined : `Authorization: Bearer ${placeholder}`),
28097
28373
  queryTemplate: args.queryTemplate,
28098
- scope: args.scope ?? "global",
28099
- scopeId: args.scope === "global" ? null : args.scopeId ?? null
28374
+ scope: bindingScope,
28375
+ scopeId: resolveScopedResourceId(bindingScope, args.scopeId, "bindings")
28100
28376
  });
28101
28377
  credentialBindingId = binding.id;
28102
28378
  }
28103
- const connection = upsertScriptConnection({
28379
+ const existing = args.id ? getScriptConnectionById(args.id) : null;
28380
+ const { scope, scopeId } = resolveConnectionScope(args, existing);
28381
+ const connection = await upsertScriptConnection({
28104
28382
  id: args.id,
28105
28383
  slug: args.slug,
28106
28384
  displayName: args.displayName,
28107
28385
  kind: "openapi",
28108
- scope: args.scope ?? "global",
28109
- scopeId: args.scope === "global" ? null : args.scopeId ?? null,
28386
+ scope,
28387
+ scopeId,
28110
28388
  baseUrl: args.baseUrl,
28111
28389
  allowedHosts: args.allowedHosts ?? [new URL(args.baseUrl).hostname],
28112
28390
  credentialBindingId,
28391
+ openapiSpecUrl: args.openapiSpecUrl,
28113
28392
  openapiSpecJson: args.openapiSpecJson,
28114
- enabled: args.enabled !== false
28393
+ enabled: resolveConnectionEnabled(args, existing)
28115
28394
  });
28116
- const connections = listScriptConnections({ includeDisabled: true });
28395
+ const connections = listScriptConnections({ includeDisabled: true, allScopes: true });
28117
28396
  return {
28118
28397
  content: [{ type: "text", text: `Script connection ${connection.slug} saved.` }],
28119
28398
  structuredContent: {
@@ -28385,6 +28664,7 @@ var sendTaskInputSchema = object({
28385
28664
  offerMode: boolean().default(false).describe("If true, offer the task instead of direct assign (agent must accept/reject)."),
28386
28665
  taskType: string().max(50).optional().describe("Task type (e.g., 'bug', 'feature', 'review')."),
28387
28666
  tags: array(string()).optional().describe("Tags for filtering (e.g., ['urgent', 'frontend'])."),
28667
+ requiredCapabilities: array(string()).optional().describe("Capabilities a claiming agent must have (declared via join-swarm/update-profile) to be pool-eligible for this task. Written into the created task's routingAffinity (role is left unset — only enforced when the pool auto-claim/claim-tool paths check it). Most useful when omitting agentId (unassigned pool task); a no-op for a task with an explicit agentId, which bypasses the pool gate entirely."),
28388
28668
  priority: number().int().min(0).max(100).optional().describe("Priority 0-100 (default: 50)."),
28389
28669
  dependsOn: array(uuid()).optional().describe("Task IDs this task depends on."),
28390
28670
  parentTaskId: uuid().optional().describe("Parent task ID for session continuity. Child task will resume the parent's Claude session. Auto-routes to the same worker unless agentId is explicitly provided."),
@@ -28392,7 +28672,7 @@ var sendTaskInputSchema = object({
28392
28672
  vcsRepo: string().optional().describe("VCS repo identifier (e.g., 'desplega-ai/agent-swarm' for GitHub or 'group/project' for GitLab). Links the task to a registered repo for workspace context."),
28393
28673
  model: string().trim().min(1).optional().describe("Concrete model override for this task, interpreted by the assignee's harness/provider. This does not switch providers. Prefer modelTier for portable intent."),
28394
28674
  modelTier: ModelTierSchema.optional().describe("Portable model tier for this task: 'smol', 'regular', 'smart', or 'ultra'. Resolved at claim/run time using the assignee's harness/provider. Legacy model shortnames map as haiku→smol, sonnet→regular, opus→smart, fable→ultra."),
28395
- effort: ReasoningEffortSchema.optional().describe("Reasoning effort for this task: 'off', 'low', 'medium', 'high', or 'xhigh'. If omitted, the assignee's REASONING_EFFORT_OVERRIDE/default applies."),
28675
+ effort: ReasoningEffortSchema.optional().describe("Reasoning effort for this task: 'off', 'low', 'medium', 'high', 'xhigh', or 'max'. If omitted, the assignee's REASONING_EFFORT_OVERRIDE/default applies."),
28396
28676
  allowDuplicate: boolean().default(false).describe("If true, skip duplicate detection and create the task even if a similar one exists."),
28397
28677
  slackChannelId: string().optional().describe("Slack channel ID to post progress updates to. Use this to propagate Slack context when delegating from a Slack thread."),
28398
28678
  slackThreadTs: string().optional().describe("Slack thread timestamp. Required with slackChannelId for thread-level updates."),
@@ -28429,6 +28709,7 @@ async function sendTaskHandler(ctx, {
28429
28709
  offerMode,
28430
28710
  taskType,
28431
28711
  tags,
28712
+ requiredCapabilities,
28432
28713
  priority,
28433
28714
  dependsOn,
28434
28715
  dir,
@@ -28561,7 +28842,8 @@ async function sendTaskHandler(ctx, {
28561
28842
  slackChannelId,
28562
28843
  slackThreadTs,
28563
28844
  slackUserId,
28564
- followUpConfig
28845
+ followUpConfig,
28846
+ routingAffinity: requiredCapabilities?.length ? { capabilities: requiredCapabilities } : undefined
28565
28847
  });
28566
28848
  transferTrackerSyncToResumeChild({
28567
28849
  parentTaskId: effectiveParentTaskId,
@@ -32013,7 +32295,10 @@ Thread: {{thread_id}}
32013
32295
 
32014
32296
  {{preview}}`,
32015
32297
  variables: [
32016
- { name: "from", description: "Sender address(es)" },
32298
+ {
32299
+ name: "from",
32300
+ description: "Sender identity — resolved canonical name (e.g. 'Luis (email:luis@x.com)') or the UNKNOWN sentinel"
32301
+ },
32017
32302
  { name: "subject", description: "Email subject" },
32018
32303
  { name: "inbox_id", description: "AgentMail inbox ID" },
32019
32304
  { name: "thread_id", description: "AgentMail thread ID" },
@@ -32032,7 +32317,10 @@ Message: {{message_id}}
32032
32317
 
32033
32318
  {{preview}}`,
32034
32319
  variables: [
32035
- { name: "from", description: "Sender address(es)" },
32320
+ {
32321
+ name: "from",
32322
+ description: "Sender identity — resolved canonical name (e.g. 'Luis (email:luis@x.com)') or the UNKNOWN sentinel"
32323
+ },
32036
32324
  { name: "subject", description: "Email subject" },
32037
32325
  { name: "inbox_id", description: "AgentMail inbox ID" },
32038
32326
  { name: "thread_id", description: "AgentMail thread ID" },
@@ -32051,7 +32339,10 @@ Thread: {{thread_id}}
32051
32339
 
32052
32340
  {{preview}}`,
32053
32341
  variables: [
32054
- { name: "from", description: "Sender address(es)" },
32342
+ {
32343
+ name: "from",
32344
+ description: "Sender identity — resolved canonical name (e.g. 'Luis (email:luis@x.com)') or the UNKNOWN sentinel"
32345
+ },
32055
32346
  { name: "subject", description: "Email subject" },
32056
32347
  { name: "inbox_id", description: "AgentMail inbox ID" },
32057
32348
  { name: "thread_id", description: "AgentMail thread ID" },
@@ -32070,7 +32361,10 @@ Message: {{message_id}}
32070
32361
 
32071
32362
  {{preview}}`,
32072
32363
  variables: [
32073
- { name: "from", description: "Sender address(es)" },
32364
+ {
32365
+ name: "from",
32366
+ description: "Sender identity — resolved canonical name (e.g. 'Luis (email:luis@x.com)') or the UNKNOWN sentinel"
32367
+ },
32074
32368
  { name: "subject", description: "Email subject" },
32075
32369
  { name: "inbox_id", description: "AgentMail inbox ID" },
32076
32370
  { name: "thread_id", description: "AgentMail thread ID" },
@@ -32089,7 +32383,10 @@ Thread: {{thread_id}}
32089
32383
 
32090
32384
  {{preview}}`,
32091
32385
  variables: [
32092
- { name: "from", description: "Sender address(es)" },
32386
+ {
32387
+ name: "from",
32388
+ description: "Sender identity — resolved canonical name (e.g. 'Luis (email:luis@x.com)') or the UNKNOWN sentinel"
32389
+ },
32093
32390
  { name: "subject", description: "Email subject" },
32094
32391
  { name: "inbox_id", description: "AgentMail inbox ID" },
32095
32392
  { name: "thread_id", description: "AgentMail thread ID" },
@@ -32211,6 +32508,7 @@ async function handleMessageReceived(payload) {
32211
32508
  const { user } = findOrCreateUserByEmail(senderEmail, { name: senderName ?? undefined }, { kind: "system", id: "webhook:agentmail" });
32212
32509
  requestedByUserId = user.id;
32213
32510
  }
32511
+ const senderDisplay = senderEmail ? renderIdentity(resolveIdentityByEmail(senderEmail)) : renderIdentity({ status: "unknown", kind: "email", externalId: "unknown" });
32214
32512
  const preview = body.length > 500 ? `${body.substring(0, 500)}...` : body;
32215
32513
  workflowEventBus.emit("agentmail.message.received", {
32216
32514
  inboxId: inbox_id,
@@ -32225,7 +32523,7 @@ async function handleMessageReceived(payload) {
32225
32523
  const contextKey = agentmailContextKey({ threadId: thread_id });
32226
32524
  const siblingInFlight = ACTIVE_TASK_STATUSES.has(existingTask.status);
32227
32525
  if (agentmailBuffer.enabled && agentmailBuffer.maybeBuffer(contextKey, siblingInFlight, {
32228
- from,
32526
+ from: senderDisplay,
32229
32527
  subject,
32230
32528
  inboxId: inbox_id,
32231
32529
  threadId: thread_id,
@@ -32239,7 +32537,7 @@ async function handleMessageReceived(payload) {
32239
32537
  return { created: false };
32240
32538
  }
32241
32539
  const followupResult = resolveTemplate("agentmail.email.followup", {
32242
- from,
32540
+ from: senderDisplay,
32243
32541
  subject,
32244
32542
  inbox_id,
32245
32543
  thread_id,
@@ -32268,7 +32566,7 @@ async function handleMessageReceived(payload) {
32268
32566
  if (agent) {
32269
32567
  if (agent.isLead) {
32270
32568
  const leadResult = resolveTemplate("agentmail.email.mapped_lead", {
32271
- from,
32569
+ from: senderDisplay,
32272
32570
  subject,
32273
32571
  inbox_id,
32274
32572
  thread_id,
@@ -32292,7 +32590,7 @@ async function handleMessageReceived(payload) {
32292
32590
  return { created: true, taskId: task3.id };
32293
32591
  }
32294
32592
  const workerResult = resolveTemplate("agentmail.email.mapped_worker", {
32295
- from,
32593
+ from: senderDisplay,
32296
32594
  subject,
32297
32595
  inbox_id,
32298
32596
  thread_id,
@@ -32318,7 +32616,7 @@ async function handleMessageReceived(payload) {
32318
32616
  const lead = findLeadAgent();
32319
32617
  if (lead) {
32320
32618
  const unmappedResult = resolveTemplate("agentmail.email.unmapped", {
32321
- from,
32619
+ from: senderDisplay,
32322
32620
  subject,
32323
32621
  inbox_id,
32324
32622
  thread_id,
@@ -32342,7 +32640,7 @@ async function handleMessageReceived(payload) {
32342
32640
  return { created: true, taskId: task2.id };
32343
32641
  }
32344
32642
  const noAgentResult = resolveTemplate("agentmail.email.no_agent", {
32345
- from,
32643
+ from: senderDisplay,
32346
32644
  subject,
32347
32645
  inbox_id,
32348
32646
  thread_id,
@@ -32365,6 +32663,170 @@ async function handleMessageReceived(payload) {
32365
32663
  }
32366
32664
  // src/http/core.ts
32367
32665
  init_db();
32666
+
32667
+ // src/be/rbac-audit.ts
32668
+ init_db();
32669
+ var FLUSH_INTERVAL_MS = 2000;
32670
+ var FLUSH_MAX_ROWS = 200;
32671
+ var AUDIT_GC_INTERVAL_MS = 24 * 60 * 60 * 1000;
32672
+ var DEFAULT_RETENTION_DAYS = 30;
32673
+ var buffer = [];
32674
+ var flushTimer = null;
32675
+ var auditGcTimer = null;
32676
+ var thresholdFlushScheduled = false;
32677
+ function isAuditDisabled() {
32678
+ return process.env.RBAC_AUDIT_DISABLED === "true";
32679
+ }
32680
+ function principalIdOf(principal) {
32681
+ switch (principal.kind) {
32682
+ case "agent":
32683
+ return principal.agentId;
32684
+ case "user":
32685
+ return principal.userId;
32686
+ case "operator":
32687
+ return null;
32688
+ }
32689
+ }
32690
+ function resourceIdOf(resource) {
32691
+ if (!resource)
32692
+ return null;
32693
+ switch (resource.kind) {
32694
+ case "task":
32695
+ return resource.taskId;
32696
+ case "agent":
32697
+ return resource.agentId;
32698
+ case "kv-namespace":
32699
+ return resource.namespace;
32700
+ case "owned":
32701
+ return resource.ownerAgentId ?? null;
32702
+ case "none":
32703
+ return null;
32704
+ }
32705
+ }
32706
+ function toRow(check, decision) {
32707
+ return {
32708
+ principalType: check.principal.kind,
32709
+ principalId: principalIdOf(check.principal),
32710
+ originatorUserId: null,
32711
+ verb: check.verb,
32712
+ resourceType: check.resource?.kind ?? null,
32713
+ resourceId: resourceIdOf(check.resource),
32714
+ decision: decision.allow ? "allow" : "deny",
32715
+ reason: decision.allow ? null : decision.reason,
32716
+ source: check.source
32717
+ };
32718
+ }
32719
+ function enqueueRow(row) {
32720
+ buffer.push(row);
32721
+ if (buffer.length >= FLUSH_MAX_ROWS && !thresholdFlushScheduled) {
32722
+ thresholdFlushScheduled = true;
32723
+ const t = setTimeout(() => {
32724
+ thresholdFlushScheduled = false;
32725
+ flushAuditBuffer();
32726
+ }, 0);
32727
+ if (typeof t.unref === "function")
32728
+ t.unref();
32729
+ }
32730
+ }
32731
+ function enqueueAuditRow(check, decision) {
32732
+ if (isAuditDisabled())
32733
+ return;
32734
+ try {
32735
+ enqueueRow(toRow(check, decision));
32736
+ } catch (err) {
32737
+ console.warn("[rbac-audit] enqueue failed, dropping row:", err.message);
32738
+ }
32739
+ }
32740
+ function enqueueAdmissionRow(input) {
32741
+ if (isAuditDisabled())
32742
+ return;
32743
+ try {
32744
+ const isMcp = input.source === "mcp";
32745
+ const method = isMcp ? undefined : (input.method ?? "UNKNOWN").toUpperCase();
32746
+ enqueueRow({
32747
+ principalType: "user",
32748
+ principalId: input.userId,
32749
+ originatorUserId: null,
32750
+ verb: input.decision.verb ?? "(admission:no-verb)",
32751
+ resourceType: isMcp ? "mcp-tool" : "http-route",
32752
+ resourceId: isMcp ? input.toolName : `${method} ${input.route}`,
32753
+ decision: input.decision.allow ? "allow" : "deny",
32754
+ reason: input.decision.allow ? null : input.decision.reason,
32755
+ source: isMcp ? "mcp" : "http"
32756
+ });
32757
+ } catch (err) {
32758
+ console.warn("[rbac-audit] enqueue failed, dropping admission row:", err.message);
32759
+ }
32760
+ }
32761
+ function flushAuditBuffer() {
32762
+ if (buffer.length === 0)
32763
+ return;
32764
+ const rows = buffer;
32765
+ buffer = [];
32766
+ try {
32767
+ const db = getDb();
32768
+ const stmt = db.prepare(`INSERT INTO permission_audit
32769
+ (principalType, principalId, originatorUserId, verb, resourceType, resourceId, decision, reason, source)
32770
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
32771
+ const insertAll = db.transaction((batch) => {
32772
+ for (const r of batch) {
32773
+ stmt.run(r.principalType, r.principalId, r.originatorUserId, r.verb, r.resourceType, r.resourceId, r.decision, r.reason, r.source);
32774
+ }
32775
+ });
32776
+ insertAll(rows);
32777
+ } catch (err) {
32778
+ console.warn(`[rbac-audit] flush failed, dropping ${rows.length} row(s):`, err.message);
32779
+ }
32780
+ }
32781
+ function flushIntervalMs() {
32782
+ const v = Number(process.env.RBAC_AUDIT_FLUSH_MS);
32783
+ return Number.isFinite(v) && v > 0 ? v : FLUSH_INTERVAL_MS;
32784
+ }
32785
+ function startAuditWriter(intervalMs = flushIntervalMs()) {
32786
+ if (flushTimer)
32787
+ return;
32788
+ flushTimer = setInterval(() => flushAuditBuffer(), intervalMs);
32789
+ if (typeof flushTimer?.unref === "function")
32790
+ flushTimer.unref();
32791
+ }
32792
+ function stopAuditWriter() {
32793
+ if (flushTimer) {
32794
+ clearInterval(flushTimer);
32795
+ flushTimer = null;
32796
+ }
32797
+ }
32798
+ function purgeExpiredAuditRows() {
32799
+ try {
32800
+ const days = Number(process.env.RBAC_AUDIT_RETENTION_DAYS) || DEFAULT_RETENTION_DAYS;
32801
+ const result = getDb().prepare("DELETE FROM permission_audit WHERE ts < datetime('now', ?)").run(`-${days} days`);
32802
+ return result.changes;
32803
+ } catch (err) {
32804
+ console.warn("[rbac-audit] retention purge failed:", err.message);
32805
+ return 0;
32806
+ }
32807
+ }
32808
+ function startAuditGc(intervalMs = AUDIT_GC_INTERVAL_MS) {
32809
+ if (auditGcTimer)
32810
+ return;
32811
+ const purged = purgeExpiredAuditRows();
32812
+ if (purged > 0) {
32813
+ console.log(`[rbac-audit] Initial retention purge removed ${purged} audit row(s)`);
32814
+ }
32815
+ auditGcTimer = setInterval(() => {
32816
+ const n = purgeExpiredAuditRows();
32817
+ if (n > 0) {
32818
+ console.log(`[rbac-audit] Retention purge removed ${n} audit row(s)`);
32819
+ }
32820
+ }, intervalMs);
32821
+ if (typeof auditGcTimer?.unref === "function")
32822
+ auditGcTimer.unref();
32823
+ }
32824
+ function stopAuditGc() {
32825
+ if (auditGcTimer) {
32826
+ clearInterval(auditGcTimer);
32827
+ auditGcTimer = null;
32828
+ }
32829
+ }
32368
32830
  // src/github/handlers.ts
32369
32831
  init_db();
32370
32832
  init_resolver();
@@ -32458,7 +32920,10 @@ Context:
32458
32920
  { name: "pr_number", description: "Pull request number" },
32459
32921
  { name: "pr_title", description: "Pull request title" },
32460
32922
  { name: "bot_name", description: "GitHub bot username" },
32461
- { name: "sender_login", description: "Event sender login" },
32923
+ {
32924
+ name: "sender_login",
32925
+ description: "Event sender identity — resolved canonical name (e.g. 'Luis (github:login)') or the UNKNOWN sentinel"
32926
+ },
32462
32927
  { name: "repo_full_name", description: "Repository full name (owner/repo)" },
32463
32928
  { name: "head_ref", description: "Head branch name" },
32464
32929
  { name: "base_ref", description: "Base branch name" },
@@ -32486,7 +32951,10 @@ Context:
32486
32951
  { name: "pr_number", description: "Pull request number" },
32487
32952
  { name: "pr_title", description: "Pull request title" },
32488
32953
  { name: "bot_name", description: "GitHub bot username" },
32489
- { name: "sender_login", description: "Event sender login" },
32954
+ {
32955
+ name: "sender_login",
32956
+ description: "Event sender identity — resolved canonical name (e.g. 'Luis (github:login)') or the UNKNOWN sentinel"
32957
+ },
32490
32958
  { name: "repo_full_name", description: "Repository full name (owner/repo)" },
32491
32959
  { name: "head_ref", description: "Head branch name" },
32492
32960
  { name: "base_ref", description: "Base branch name" },
@@ -32563,7 +33031,10 @@ Context:
32563
33031
  variables: [
32564
33032
  { name: "pr_number", description: "Pull request number" },
32565
33033
  { name: "pr_title", description: "Pull request title" },
32566
- { name: "sender_login", description: "Event sender login" },
33034
+ {
33035
+ name: "sender_login",
33036
+ description: "Event sender identity — resolved canonical name (e.g. 'Luis (github:login)') or the UNKNOWN sentinel"
33037
+ },
32567
33038
  { name: "repo_full_name", description: "Repository full name (owner/repo)" },
32568
33039
  { name: "head_ref", description: "Head branch name" },
32569
33040
  { name: "base_ref", description: "Base branch name" },
@@ -32591,7 +33062,10 @@ Context:
32591
33062
  { name: "pr_number", description: "Pull request number" },
32592
33063
  { name: "pr_title", description: "Pull request title" },
32593
33064
  { name: "label_name", description: "Label that was added" },
32594
- { name: "sender_login", description: "Event sender login" },
33065
+ {
33066
+ name: "sender_login",
33067
+ description: "Event sender identity — resolved canonical name (e.g. 'Luis (github:login)') or the UNKNOWN sentinel"
33068
+ },
32595
33069
  { name: "repo_full_name", description: "Repository full name (owner/repo)" },
32596
33070
  { name: "head_ref", description: "Head branch name" },
32597
33071
  { name: "base_ref", description: "Base branch name" },
@@ -32618,7 +33092,10 @@ Context:
32618
33092
  { name: "issue_number", description: "Issue number" },
32619
33093
  { name: "issue_title", description: "Issue title" },
32620
33094
  { name: "bot_name", description: "GitHub bot username" },
32621
- { name: "sender_login", description: "Event sender login" },
33095
+ {
33096
+ name: "sender_login",
33097
+ description: "Event sender identity — resolved canonical name (e.g. 'Luis (github:login)') or the UNKNOWN sentinel"
33098
+ },
32622
33099
  { name: "repo_full_name", description: "Repository full name (owner/repo)" },
32623
33100
  { name: "issue_url", description: "Issue HTML URL" },
32624
33101
  { name: "context", description: "Issue body or title as context" }
@@ -32641,7 +33118,10 @@ Context:
32641
33118
  variables: [
32642
33119
  { name: "issue_number", description: "Issue number" },
32643
33120
  { name: "issue_title", description: "Issue title" },
32644
- { name: "sender_login", description: "Event sender login" },
33121
+ {
33122
+ name: "sender_login",
33123
+ description: "Event sender identity — resolved canonical name (e.g. 'Luis (github:login)') or the UNKNOWN sentinel"
33124
+ },
32645
33125
  { name: "repo_full_name", description: "Repository full name (owner/repo)" },
32646
33126
  { name: "issue_url", description: "Issue HTML URL" },
32647
33127
  { name: "context", description: "Extracted mention context or issue title" }
@@ -32666,7 +33146,10 @@ Context:
32666
33146
  { name: "issue_number", description: "Issue number" },
32667
33147
  { name: "issue_title", description: "Issue title" },
32668
33148
  { name: "label_name", description: "Label that was added" },
32669
- { name: "sender_login", description: "Event sender login" },
33149
+ {
33150
+ name: "sender_login",
33151
+ description: "Event sender identity — resolved canonical name (e.g. 'Luis (github:login)') or the UNKNOWN sentinel"
33152
+ },
32670
33153
  { name: "repo_full_name", description: "Repository full name (owner/repo)" },
32671
33154
  { name: "issue_url", description: "Issue HTML URL" },
32672
33155
  { name: "context", description: "Issue body or title as context" }
@@ -32690,7 +33173,10 @@ Comment:
32690
33173
  { name: "target_type", description: "PR or Issue" },
32691
33174
  { name: "target_number", description: "PR or issue number" },
32692
33175
  { name: "target_title", description: "PR or issue title" },
32693
- { name: "sender_login", description: "Event sender login" },
33176
+ {
33177
+ name: "sender_login",
33178
+ description: "Event sender identity — resolved canonical name (e.g. 'Luis (github:login)') or the UNKNOWN sentinel"
33179
+ },
32694
33180
  { name: "repo_full_name", description: "Repository full name (owner/repo)" },
32695
33181
  { name: "comment_url", description: "Comment HTML URL" },
32696
33182
  { name: "context", description: "Extracted mention context from comment" },
@@ -32718,7 +33204,10 @@ URL: {{review_url}}{{review_body_section}}{{inline_comments_section}}
32718
33204
  { name: "pr_number", description: "Pull request number" },
32719
33205
  { name: "review_label", description: "Review state label (APPROVED, CHANGES REQUESTED, etc.)" },
32720
33206
  { name: "pr_title", description: "Pull request title" },
32721
- { name: "sender_login", description: "Reviewer login" },
33207
+ {
33208
+ name: "sender_login",
33209
+ description: "Reviewer identity — resolved canonical name (e.g. 'Luis (github:login)') or the UNKNOWN sentinel"
33210
+ },
32722
33211
  { name: "repo_full_name", description: "Repository full name (owner/repo)" },
32723
33212
  { name: "review_url", description: "Review HTML URL" },
32724
33213
  { name: "review_body_section", description: "Review overall body section or empty string" },
@@ -32915,7 +33404,7 @@ async function handlePullRequest(event) {
32915
33404
  pr_number: pr.number,
32916
33405
  pr_title: pr.title,
32917
33406
  bot_name: GITHUB_BOT_NAME,
32918
- sender_login: sender.login,
33407
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
32919
33408
  repo_full_name: repository.full_name,
32920
33409
  head_ref: pr.head.ref,
32921
33410
  base_ref: pr.base.ref,
@@ -32987,7 +33476,7 @@ async function handlePullRequest(event) {
32987
33476
  pr_number: pr.number,
32988
33477
  pr_title: pr.title,
32989
33478
  bot_name: GITHUB_BOT_NAME,
32990
- sender_login: sender.login,
33479
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
32991
33480
  repo_full_name: repository.full_name,
32992
33481
  head_ref: pr.head.ref,
32993
33482
  base_ref: pr.base.ref,
@@ -33055,7 +33544,7 @@ async function handlePullRequest(event) {
33055
33544
  pr_number: pr.number,
33056
33545
  pr_title: pr.title,
33057
33546
  label_name: labelName,
33058
- sender_login: sender.login,
33547
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
33059
33548
  repo_full_name: repository.full_name,
33060
33549
  head_ref: pr.head.ref,
33061
33550
  base_ref: pr.base.ref,
@@ -33113,7 +33602,7 @@ async function handlePullRequest(event) {
33113
33602
  const result = resolveTemplate("github.pull_request.mentioned", {
33114
33603
  pr_number: pr.number,
33115
33604
  pr_title: pr.title,
33116
- sender_login: sender.login,
33605
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
33117
33606
  repo_full_name: repository.full_name,
33118
33607
  head_ref: pr.head.ref,
33119
33608
  base_ref: pr.base.ref,
@@ -33163,7 +33652,7 @@ async function handleIssue(event) {
33163
33652
  issue_number: issue.number,
33164
33653
  issue_title: issue.title,
33165
33654
  bot_name: GITHUB_BOT_NAME,
33166
- sender_login: sender.login,
33655
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
33167
33656
  repo_full_name: repository.full_name,
33168
33657
  issue_url: issue.html_url,
33169
33658
  context: issue.body || issue.title
@@ -33229,7 +33718,7 @@ async function handleIssue(event) {
33229
33718
  issue_number: issue.number,
33230
33719
  issue_title: issue.title,
33231
33720
  label_name: labelName,
33232
- sender_login: sender.login,
33721
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
33233
33722
  repo_full_name: repository.full_name,
33234
33723
  issue_url: issue.html_url,
33235
33724
  context: issue.body || issue.title
@@ -33277,7 +33766,7 @@ async function handleIssue(event) {
33277
33766
  const result = resolveTemplate("github.issue.mentioned", {
33278
33767
  issue_number: issue.number,
33279
33768
  issue_title: issue.title,
33280
- sender_login: sender.login,
33769
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
33281
33770
  repo_full_name: repository.full_name,
33282
33771
  issue_url: issue.html_url,
33283
33772
  context
@@ -33338,7 +33827,7 @@ async function handleComment(event, eventType) {
33338
33827
  target_type: targetType,
33339
33828
  target_number: targetNumber,
33340
33829
  target_title: targetTitle,
33341
- sender_login: sender.login,
33830
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
33342
33831
  repo_full_name: repository.full_name,
33343
33832
  comment_url: comment.html_url,
33344
33833
  context,
@@ -33533,7 +34022,7 @@ ${review.body}` : "";
33533
34022
  pr_number: pr.number,
33534
34023
  review_label: label,
33535
34024
  pr_title: pr.title,
33536
- sender_login: sender.login,
34025
+ sender_login: renderIdentity(resolveIdentity("github", sender.login)),
33537
34026
  repo_full_name: repository.full_name,
33538
34027
  review_url: review.html_url,
33539
34028
  review_body_section: reviewBodySection,
@@ -33906,7 +34395,10 @@ Reporter: {{reporter}}
33906
34395
  { name: "issue_key", description: "Jira issue key (e.g. ENG-123)" },
33907
34396
  { name: "issue_summary", description: "Issue summary / title" },
33908
34397
  { name: "issue_url", description: "Issue URL on the Jira site" },
33909
- { name: "reporter", description: "Reporter display name (or empty string)" },
34398
+ {
34399
+ name: "reporter",
34400
+ description: "Reporter identity — resolved canonical name (e.g. 'Luis (jira:<accountId>)') or the UNKNOWN sentinel"
34401
+ },
33910
34402
  {
33911
34403
  name: "description_section",
33912
34404
  description: "Description section (extracted from ADF) or empty string"
@@ -33928,7 +34420,10 @@ Comment:
33928
34420
  { name: "issue_key", description: "Jira issue key (e.g. ENG-123)" },
33929
34421
  { name: "issue_summary", description: "Issue summary / title" },
33930
34422
  { name: "issue_url", description: "Issue URL on the Jira site" },
33931
- { name: "comment_author", description: "Comment author display name" },
34423
+ {
34424
+ name: "comment_author",
34425
+ description: "Comment author identity — resolved canonical name (e.g. 'Luis (jira:<accountId>)') or the UNKNOWN sentinel"
34426
+ },
33932
34427
  {
33933
34428
  name: "description_section",
33934
34429
  description: "Description section (extracted from ADF) or empty string"
@@ -33968,6 +34463,31 @@ Original issue: {{issue_key}} — {{issue_summary}}`,
33968
34463
  init_db();
33969
34464
  init_resolver();
33970
34465
  init_types();
34466
+ var JIRA_WEBHOOK_ACTOR = { kind: "system", id: "webhook:jira" };
34467
+ function resolveJiraActor(accountId, email, name, sampleEventType, sampleContext) {
34468
+ if (!accountId)
34469
+ return;
34470
+ const existing = findUserByExternalId("jira", accountId);
34471
+ if (existing)
34472
+ return existing.id;
34473
+ const trimmedEmail = typeof email === "string" ? email.trim() : "";
34474
+ if (trimmedEmail !== "") {
34475
+ const { user } = findOrCreateUserByEmail(trimmedEmail, { name: name?.trim() || undefined }, JIRA_WEBHOOK_ACTOR);
34476
+ try {
34477
+ linkIdentity(user.id, "jira", accountId, JIRA_WEBHOOK_ACTOR);
34478
+ } catch (error) {
34479
+ console.warn(`[Jira Sync] linkIdentity('jira', ${accountId}) failed — likely a concurrent enroll`, error);
34480
+ }
34481
+ return user.id;
34482
+ }
34483
+ recordUnmappedIdentity("jira", accountId, { sampleEventType, sampleContext });
34484
+ return;
34485
+ }
34486
+ function renderJiraIdentity(accountId) {
34487
+ if (!accountId)
34488
+ return "(unknown user)";
34489
+ return renderIdentity(resolveIdentity("jira", accountId));
34490
+ }
33971
34491
  var BOT_ACCOUNT_ID_SLOT = Symbol.for("agent-swarm.jira.botAccountId");
33972
34492
  function getCachedBotAccountId() {
33973
34493
  return globalThis[BOT_ACCOUNT_ID_SLOT] ?? null;
@@ -34065,7 +34585,8 @@ async function handleIssueEvent(event) {
34065
34585
  const issueId = issue.id;
34066
34586
  const issueKey = issue.key;
34067
34587
  const summary = issue.fields?.summary ?? "(no summary)";
34068
- const reporterName = issue.fields?.reporter?.displayName ?? "";
34588
+ const reporterAccountId = issue.fields?.reporter?.accountId;
34589
+ const requestedByUserId = resolveJiraActor(reporterAccountId, issue.fields?.reporter?.emailAddress, issue.fields?.reporter?.displayName, "issue_updated", `${issueKey}: ${summary}`);
34069
34590
  const descriptionText = extractText(issue.fields?.description);
34070
34591
  const issueUrl = buildIssueUrl(issueKey);
34071
34592
  const claim = createTrackerSyncIfAbsent({
@@ -34083,7 +34604,8 @@ async function handleIssueEvent(event) {
34083
34604
  await createInitialJiraTask({
34084
34605
  issueKey,
34085
34606
  summary,
34086
- reporterName,
34607
+ reporterAccountId,
34608
+ requestedByUserId,
34087
34609
  descriptionText,
34088
34610
  issueUrl,
34089
34611
  syncRowId: claim.sync.id,
@@ -34099,7 +34621,8 @@ async function handleIssueEvent(event) {
34099
34621
  await createInitialJiraTask({
34100
34622
  issueKey,
34101
34623
  summary,
34102
- reporterName,
34624
+ reporterAccountId,
34625
+ requestedByUserId,
34103
34626
  descriptionText,
34104
34627
  issueUrl,
34105
34628
  syncRowId: claim.sync.id,
@@ -34141,7 +34664,8 @@ async function handleCommentEvent(event) {
34141
34664
  const summary = issue.fields?.summary ?? "(no summary)";
34142
34665
  const descriptionText = extractText(issue.fields?.description);
34143
34666
  const commentText = extractText(comment.body);
34144
- const commentAuthor = comment.author?.displayName ?? "";
34667
+ const commentAuthorAccountId = comment.author?.accountId ?? comment.updateAuthor?.accountId;
34668
+ const requestedByUserId = resolveJiraActor(commentAuthorAccountId, comment.author?.emailAddress ?? comment.updateAuthor?.emailAddress, comment.author?.displayName ?? comment.updateAuthor?.displayName, "comment_created", `${issueKey}: ${commentText.slice(0, 80)}`);
34145
34669
  const issueUrl = buildIssueUrl(issueKey);
34146
34670
  if (!existing) {
34147
34671
  const claim = createTrackerSyncIfAbsent({
@@ -34161,7 +34685,8 @@ async function handleCommentEvent(event) {
34161
34685
  summary,
34162
34686
  descriptionText,
34163
34687
  commentText,
34164
- commentAuthor,
34688
+ commentAuthorAccountId,
34689
+ requestedByUserId,
34165
34690
  issueUrl,
34166
34691
  syncRowId: claim.sync.id
34167
34692
  });
@@ -34172,7 +34697,8 @@ async function handleCommentEvent(event) {
34172
34697
  summary,
34173
34698
  issueUrl,
34174
34699
  commentText,
34175
- commentAuthor,
34700
+ commentAuthorAccountId,
34701
+ requestedByUserId,
34176
34702
  syncRow: claim.sync
34177
34703
  });
34178
34704
  }
@@ -34181,7 +34707,8 @@ async function handleCommentEvent(event) {
34181
34707
  summary,
34182
34708
  issueUrl,
34183
34709
  commentText,
34184
- commentAuthor,
34710
+ commentAuthorAccountId,
34711
+ requestedByUserId,
34185
34712
  syncRow: existing
34186
34713
  });
34187
34714
  }
@@ -34191,15 +34718,17 @@ async function routeCommentOnExistingSync(input) {
34191
34718
  console.log(`[Jira Sync] Bot mentioned on issue ${input.issueKey} but task ${priorTask.id} still ${priorTask.status} — ignoring`);
34192
34719
  return;
34193
34720
  }
34721
+ const commentAuthorDisplay = renderJiraIdentity(input.commentAuthorAccountId);
34194
34722
  await createInitialJiraTask({
34195
34723
  issueKey: input.issueKey,
34196
34724
  summary: input.summary,
34197
- reporterName: input.commentAuthor,
34725
+ reporterAccountId: input.commentAuthorAccountId,
34726
+ requestedByUserId: input.requestedByUserId,
34198
34727
  descriptionText: "",
34199
34728
  issueUrl: input.issueUrl,
34200
34729
  syncRowId: input.syncRow.id,
34201
34730
  followup: true,
34202
- followupTrigger: `New comment from ${input.commentAuthor || "user"}`,
34731
+ followupTrigger: `New comment from ${commentAuthorDisplay}`,
34203
34732
  followupMessage: input.commentText
34204
34733
  });
34205
34734
  }
@@ -34233,7 +34762,7 @@ ${input.descriptionText}
34233
34762
  issue_key: input.issueKey,
34234
34763
  issue_summary: input.summary,
34235
34764
  issue_url: input.issueUrl,
34236
- reporter: input.reporterName,
34765
+ reporter: renderJiraIdentity(input.reporterAccountId),
34237
34766
  description_section: descriptionSection
34238
34767
  };
34239
34768
  const result = resolveTemplate(tmplName, variables);
@@ -34245,6 +34774,7 @@ ${input.descriptionText}
34245
34774
  agentId: lead?.id ?? "",
34246
34775
  source: "jira",
34247
34776
  taskType: "jira-issue",
34777
+ requestedByUserId: input.requestedByUserId,
34248
34778
  contextKey: buildJiraContextKey(input.issueKey)
34249
34779
  });
34250
34780
  updateTrackerSyncSwarmId(input.syncRowId, task.id);
@@ -34261,7 +34791,7 @@ ${input.descriptionText}
34261
34791
  issue_key: input.issueKey,
34262
34792
  issue_summary: input.summary,
34263
34793
  issue_url: input.issueUrl,
34264
- comment_author: input.commentAuthor,
34794
+ comment_author: renderJiraIdentity(input.commentAuthorAccountId),
34265
34795
  description_section: descriptionSection,
34266
34796
  comment_text: input.commentText
34267
34797
  });
@@ -34273,6 +34803,7 @@ ${input.descriptionText}
34273
34803
  agentId: lead?.id ?? "",
34274
34804
  source: "jira",
34275
34805
  taskType: "jira-issue",
34806
+ requestedByUserId: input.requestedByUserId,
34276
34807
  contextKey: buildJiraContextKey(input.issueKey)
34277
34808
  });
34278
34809
  updateTrackerSyncSwarmId(input.syncRowId, task.id);
@@ -34307,7 +34838,7 @@ async function notifySlack(text) {
34307
34838
  return;
34308
34839
  }
34309
34840
  try {
34310
- const { getSlackApp: getSlackApp2 } = await import("./app-es4nzc71.js");
34841
+ const { getSlackApp: getSlackApp2 } = await import("./app-4f718yk0.js");
34311
34842
  const app = getSlackApp2();
34312
34843
  if (!app) {
34313
34844
  console.warn("[Jira webhook keepalive] Slack not available, cannot send notification");
@@ -34376,7 +34907,7 @@ async function deleteJiraWebhook(webhookId) {
34376
34907
  console.log(`[Jira webhook keepalive] Deleted webhook id=${webhookId}`);
34377
34908
  }
34378
34909
  async function overwriteWebhookIds(next) {
34379
- const { getDb: getDb2 } = await import("./db-bbgahh4z.js");
34910
+ const { getDb: getDb2 } = await import("./db-p3h2j06x.js");
34380
34911
  const db = getDb2();
34381
34912
  const txn = db.transaction(() => {
34382
34913
  const row = db.query("SELECT metadata FROM oauth_apps WHERE provider = 'jira'").get();
@@ -108655,10 +109186,11 @@ async function handleCore(req, res, myAgentId, apiKey) {
108655
109186
  }
108656
109187
  const pathSegments = getPathSegments(req.url || "");
108657
109188
  const isUserMcpRoute = req.url === "/mcp-user";
109189
+ let auth = null;
108658
109190
  if (isUserMcpRoute || isPublicRoute(req.method, pathSegments)) {
108659
109191
  setRequestAuth(req, null);
108660
109192
  } else {
108661
- const auth = resolveHttpRequestAuth(req, apiKey);
109193
+ auth = resolveHttpRequestAuth(req, apiKey);
108662
109194
  if (!auth) {
108663
109195
  setRequestAuth(req, null);
108664
109196
  res.writeHead(401, { "Content-Type": "application/json" });
@@ -108667,6 +109199,28 @@ async function handleCore(req, res, myAgentId, apiKey) {
108667
109199
  }
108668
109200
  setRequestAuth(req, auth);
108669
109201
  }
109202
+ if (auth?.kind === "user" && isRbacEnabled()) {
109203
+ const grant = getUserGrant(auth.userId);
109204
+ if (!grant.grantsAll) {
109205
+ const def = findRoute(req.method, pathSegments);
109206
+ const decision = decideAdmission({
109207
+ method: req.method ?? "",
109208
+ rbac: def?.rbac,
109209
+ routeKnown: def !== undefined,
109210
+ grant
109211
+ });
109212
+ enqueueAdmissionRow({
109213
+ userId: auth.userId,
109214
+ decision,
109215
+ method: req.method,
109216
+ route: def?.path ?? pathSegments.join("/")
109217
+ });
109218
+ if (!decision.allow) {
109219
+ jsonError(res, `Forbidden: ${decision.reason}`, 403);
109220
+ return true;
109221
+ }
109222
+ }
109223
+ }
108670
109224
  if (req.method === "POST" && req.url === "/internal/reload-config") {
108671
109225
  try {
108672
109226
  const result = await reloadGlobalConfigsAndIntegrations();
@@ -109505,7 +110059,8 @@ var taskActionInputSchema = object({
109505
110059
  dir: string().min(1).startsWith("/").optional().describe("Working directory (absolute path) for the agent to start in. Only used with 'create' action."),
109506
110060
  model: string().trim().min(1).optional().describe("Concrete model override for the created task, interpreted by the claiming worker's harness/provider. This does not switch providers. Only used with 'create' action."),
109507
110061
  modelTier: ModelTierSchema.optional().describe("Portable model tier for the created task: 'smol', 'regular', 'smart', or 'ultra'. Resolved when a worker claims/runs the task. Only used with 'create' action."),
109508
- effort: ReasoningEffortSchema.optional().describe("Reasoning effort for the created task: 'off', 'low', 'medium', 'high', or 'xhigh'. Only used with 'create' action.")
110062
+ effort: ReasoningEffortSchema.optional().describe("Reasoning effort for the created task: 'off', 'low', 'medium', 'high', 'xhigh', or 'max'. Only used with 'create' action."),
110063
+ requiredCapabilities: array(string()).optional().describe("Capabilities a claiming agent must have (declared via join-swarm/update-profile) to be pool-eligible for this task. Written into the created task's routingAffinity (role is left unset). Only used with 'create' action.")
109509
110064
  });
109510
110065
  var taskActionOutputSchema = object({
109511
110066
  yourAgentId: string().uuid().optional(),
@@ -109551,7 +110106,19 @@ function taskActionCallResult(result, agentId) {
109551
110106
  };
109552
110107
  }
109553
110108
  async function taskActionHandler(ctx, input) {
109554
- const { action, task, taskType, tags, priority, dependsOn, taskId, reason, dir, model } = input;
110109
+ const {
110110
+ action,
110111
+ task,
110112
+ taskType,
110113
+ tags,
110114
+ priority,
110115
+ dependsOn,
110116
+ taskId,
110117
+ reason,
110118
+ dir,
110119
+ model,
110120
+ requiredCapabilities
110121
+ } = input;
109555
110122
  const normalizedModel = splitLegacyModelAlias({ model, modelTier: input.modelTier });
109556
110123
  if (ctx.kind === "user") {
109557
110124
  if (action !== "to_backlog" && action !== "from_backlog") {
@@ -109631,7 +110198,8 @@ async function taskActionHandler(ctx, input) {
109631
110198
  dir,
109632
110199
  model: normalizedModel.model,
109633
110200
  modelTier: normalizedModel.modelTier,
109634
- effort: input.effort
110201
+ effort: input.effort,
110202
+ routingAffinity: requiredCapabilities?.length ? { capabilities: requiredCapabilities } : undefined
109635
110203
  });
109636
110204
  return {
109637
110205
  success: true,
@@ -109668,6 +110236,13 @@ async function taskActionHandler(ctx, input) {
109668
110236
  message: `Task "${taskId}" has unmet dependencies: ${blockedBy.join(", ")}. Cannot claim until dependencies are completed.`
109669
110237
  };
109670
110238
  }
110239
+ const claimingAgent = getAgentById(agentId);
110240
+ if (claimingAgent && !isAgentEligibleForTask(claimingAgent, existingTask)) {
110241
+ return {
110242
+ success: false,
110243
+ message: `Task "${taskId}" requires role "${existingTask.routingAffinity?.role ?? "(unspecified)"}"; yours is "${claimingAgent.role ?? "(unspecified)"}". Cannot claim.`
110244
+ };
110245
+ }
109671
110246
  const claimedTask = claimTask(taskId, agentId);
109672
110247
  if (!claimedTask) {
109673
110248
  return {
@@ -110740,12 +111315,13 @@ var registerCreateWorkflowTool = (server) => {
110740
111315
  ` + "- STRUCTURED OUTPUT: For agent-task nodes, put outputSchema inside 'config' to validate the agent's raw JSON output. " + `Node-level outputSchema validates the executor's return ({taskId, taskOutput}), which is different.
110741
111316
  ` + `- Agent-task config: { template, outputSchema?, agentId?, tags?, priority?, dir?, vcsRepo?, model? }.
110742
111317
  ` + "- TRIGGER SCHEMA: Optional 'triggerSchema' is a JSON-Schema object that validates incoming trigger payloads. " + "Supported keywords: type, required, properties, enum, const, items (recursive into arrays). " + `Other JSON-Schema keywords (oneOf/anyOf/$ref/pattern/format/additionalProperties) are silently ignored.
111318
+ ` + "- WEBHOOK VERIFICATION: Webhook triggers use hmacSecret for all verification formats. " + "Omit verification for legacy HMAC-SHA256 over the raw body with fallback header scanning; " + "or set verification to { format: 'hmac-sha256', header }, { format: 'timestamped-hmac-sha256', header, toleranceSeconds? }, " + "or { format: 'token-equality', header }. Example: { type: 'webhook', hmacSecret: 'secret.SUPERAGENT_WEBHOOK_SECRET', " + `verification: { format: 'timestamped-hmac-sha256', header: 'X-Superagent-Signature', toleranceSeconds: 300 } }.
110743
111319
  ` + "- WAIT NODE: type 'wait' pauses a workflow for a duration or until a named workflowEventBus event arrives. " + "See runbooks/workflows.md#wait-nodes for config shapes, ordering caveats, and built-in event names.",
110744
111320
  inputSchema: exports_external.object({
110745
111321
  name: exports_external.string().describe("Unique name for the workflow"),
110746
111322
  description: exports_external.string().optional().describe("Description of what this workflow does"),
110747
111323
  definition: WorkflowDefinitionSchema.describe("The workflow definition with nodes (each node has id, type, config, and optional next/retry/validation)"),
110748
- triggers: exports_external.array(TriggerConfigSchema).optional().describe("Optional trigger configurations (webhook, schedule)"),
111324
+ triggers: exports_external.array(TriggerConfigSchema).optional().describe("Optional trigger configurations (webhook, schedule). Webhook verification formats: legacy omitted verification, hmac-sha256, timestamped-hmac-sha256, token-equality."),
110749
111325
  cooldown: CooldownConfigSchema.optional().describe("Optional cooldown configuration to prevent re-triggering too frequently"),
110750
111326
  input: exports_external.record(exports_external.string(), InputValueSchema).optional().describe("Optional input values resolved at execution time (env vars like VAR_NAME, secrets secret.NAME, or literals)"),
110751
111327
  dir: exports_external.string().min(1).startsWith("/").optional().describe("Default working directory for all agent-task nodes (absolute path, e.g. /tmp/workspace)"),
@@ -111364,13 +111940,13 @@ var registerUpdateWorkflowTool = (server) => {
111364
111940
  createToolRegistrar(server)("update-workflow", {
111365
111941
  title: "Update Workflow",
111366
111942
  annotations: { destructiveHint: false },
111367
- description: "Update an existing workflow's name, description, definition, triggers, cooldown, input, triggerSchema, or enabled state. " + "Creates a version snapshot before applying changes. " + "TRIGGER SCHEMA: pass 'triggerSchema' as a JSON-Schema object to set/replace, or 'null' to clear. " + "Supported JSON-Schema keywords: type, required, properties, enum, const, items (recursive into arrays). " + "Other JSON-Schema keywords (oneOf/anyOf/$ref/pattern/format/additionalProperties) are silently ignored.",
111943
+ description: "Update an existing workflow's name, description, definition, triggers, cooldown, input, triggerSchema, or enabled state. " + "Creates a version snapshot before applying changes. " + "TRIGGER SCHEMA: pass 'triggerSchema' as a JSON-Schema object to set/replace, or 'null' to clear. " + "Supported JSON-Schema keywords: type, required, properties, enum, const, items (recursive into arrays). " + "Other JSON-Schema keywords (oneOf/anyOf/$ref/pattern/format/additionalProperties) are silently ignored. " + "WEBHOOK VERIFICATION: webhook triggers use hmacSecret for all verification formats. " + "Omit verification for legacy HMAC-SHA256 over the raw body with fallback header scanning; " + "or set verification to { format: 'hmac-sha256', header }, { format: 'timestamped-hmac-sha256', header, toleranceSeconds? }, " + "or { format: 'token-equality', header }.",
111368
111944
  inputSchema: exports_external.object({
111369
111945
  id: exports_external.string().uuid().describe("Workflow ID to update"),
111370
111946
  name: exports_external.string().optional().describe("New name for the workflow"),
111371
111947
  description: exports_external.string().optional().describe("New description"),
111372
111948
  definition: WorkflowDefinitionSchema.optional().describe("New workflow definition"),
111373
- triggers: exports_external.array(TriggerConfigSchema).optional().describe("New trigger configurations"),
111949
+ triggers: exports_external.array(TriggerConfigSchema).optional().describe("New trigger configurations. Webhook verification formats: legacy omitted verification, hmac-sha256, timestamped-hmac-sha256, token-equality."),
111374
111950
  cooldown: CooldownConfigSchema.optional().nullable().describe("New cooldown configuration (null to remove)"),
111375
111951
  input: exports_external.record(exports_external.string(), InputValueSchema).optional().nullable().describe("New input values (null to remove)"),
111376
111952
  dir: exports_external.string().min(1).startsWith("/").optional().nullable().describe("Default working directory for all agent-task nodes (null to remove)"),
@@ -111478,6 +112054,13 @@ function createServer() {
111478
112054
  initDb(process.env.DATABASE_PATH);
111479
112055
  seedPricingFromModelsDev();
111480
112056
  startPricingRefreshLoop();
112057
+ try {
112058
+ ensureRbacSeedsSynced();
112059
+ } catch (err) {
112060
+ console.error("[startup] Failed to sync RBAC seed rows:", err);
112061
+ if (isRbacEnabled())
112062
+ throw err;
112063
+ }
111481
112064
  registerGithubTaskReactions();
111482
112065
  const server = new McpServer({
111483
112066
  name: package_default.name,
@@ -111608,6 +112191,7 @@ function createServer() {
111608
112191
  registerSkillPublishTool(server);
111609
112192
  if (hasCapability("pages")) {
111610
112193
  registerCreatePageTool(server);
112194
+ registerDeletePageTool(server);
111611
112195
  }
111612
112196
  if (hasCapability("metrics")) {
111613
112197
  registerCreateMetricTool(server);
@@ -111768,4 +112352,4 @@ function writeSkillsToFilesystem(entries, harnessType = "all", home) {
111768
112352
  return { synced, removed, errors };
111769
112353
  }
111770
112354
 
111771
- export { DEFAULT_NEGOTIATED_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, isJSONRPCRequest, isJSONRPCResultResponse, isJSONRPCErrorResponse, JSONRPCMessageSchema, isInitializeRequest, McpServer, isGitHubEnabled, initGitHub, verifyWebhookSignature, setAuditSink, clearAuditSink, can, userCtx, isPollTracingEnabled, initOtel, withSpan, startSpan, withSpanContext, withRemoteContext, injectTraceContext, recordSessionCost, createToolRegistrar, cancelTaskInputSchema, cancelTaskOutputSchema, cancelTaskHandler, setCorsHeaders, parseQueryParams, getPathSegments, safeRequestUrlForLog, agentWithCapacity, parseBody, BODY_TOO_LARGE, enforceContentLengthCap, json, jsonError, triggerSchemaErrorResponse, deriveApiBaseUrl, httpServerSemconvAttributes, describeRequestRoute, route, assertSelectOnlyQuery, executeReadOnlyQuery, handleDbQuery, snapshotMetric, snapshotPage, getTaskDetailsInputSchema, getTaskDetailsOutputSchema, getTaskDetailsHandler, getTasksInputSchema, getTasksOutputSchema, getTasksHandler, storeLinks, refreshLinks, canReadMemory, getLinksForMemory, recordRetrievals, expandCandidatesWithGraph, markKapsoMessageRead, sendKapsoReaction, getKapsoConfig, getKapsoNumberMapping, markKapsoMessageSeen, resolveHttpAuditUserId, createEvent, createEventsBatch, getEventCountsFiltered, getEventsFiltered, mergeScheduleTiming, validateRecurringTiming, sendTaskOutputSchema, sendTaskHandler, parseSkillContent, ExplicitSelfDuplicateError, applyRating, shouldPersistAutomaticTaskMemory, isAgentMailEnabled, initAgentMail, verifyAgentMailWebhook, isInboxAllowed, isSenderAllowed, handleMessageReceived, handlePullRequest, handleIssue, handleComment, handlePullRequestReview, handleCheckRun, handleCheckSuite, handleWorkflowRun, getJiraMetadata, updateJiraMetadata, clearJiraMetadata, registerJiraWebhook, deleteJiraWebhook, isJiraEnabled, initJira, handleJiraWebhook, handleAgentSessionEvent, handleIssueUpdate, handleIssueDelete, handleAgentSessionPrompted, isLinearEnabled, initLinear, loadGlobalConfigsIntoEnv, reloadGlobalConfigsAndIntegrations, scheduleIntegrationsReload, handleCore, canClaim, emitBudgetRefusalSideEffects, taskActionInputSchema, taskActionOutputSchema, taskActionHandler, hasCapability, getEnabledCapabilities, createServer, writeSkillsToFilesystem };
112355
+ export { DEFAULT_NEGOTIATED_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, isJSONRPCRequest, isJSONRPCResultResponse, isJSONRPCErrorResponse, JSONRPCMessageSchema, isInitializeRequest, McpServer, isGitHubEnabled, initGitHub, verifyWebhookSignature, userCtx, isPollTracingEnabled, initOtel, withSpan, startSpan, withSpanContext, withRemoteContext, injectTraceContext, recordSessionCost, createToolRegistrar, cancelTaskInputSchema, cancelTaskOutputSchema, cancelTaskHandler, setCorsHeaders, parseQueryParams, getPathSegments, safeRequestUrlForLog, agentWithCapacity, parseBody, BODY_TOO_LARGE, enforceContentLengthCap, json, jsonError, triggerSchemaErrorResponse, deriveApiBaseUrl, httpServerSemconvAttributes, describeRequestRoute, route, assertSelectOnlyQuery, executeReadOnlyQuery, handleDbQuery, snapshotMetric, snapshotPage, assertOAuthProviderIsNotReserved, assertOAuthAppUrlsSafe, scopedResourceScopeIdSchema, resolveScopedResourceId, getTaskDetailsInputSchema, getTaskDetailsOutputSchema, getTaskDetailsHandler, getTasksInputSchema, getTasksOutputSchema, getTasksHandler, storeLinks, refreshLinks, canReadMemory, getLinksForMemory, recordRetrievals, expandCandidatesWithGraph, markKapsoMessageRead, sendKapsoReaction, getKapsoConfig, getKapsoNumberMapping, markKapsoMessageSeen, resolveTaskAuditUserId, resolveHttpAuditUserId, createEvent, createEventsBatch, getEventCountsFiltered, getEventsFiltered, mergeScheduleTiming, validateRecurringTiming, sendTaskOutputSchema, sendTaskHandler, parseSkillContent, ExplicitSelfDuplicateError, applyRating, shouldPersistAutomaticTaskMemory, isAgentMailEnabled, initAgentMail, verifyAgentMailWebhook, isInboxAllowed, isSenderAllowed, handleMessageReceived, enqueueAuditRow, enqueueAdmissionRow, flushAuditBuffer, startAuditWriter, stopAuditWriter, startAuditGc, stopAuditGc, handlePullRequest, handleIssue, handleComment, handlePullRequestReview, handleCheckRun, handleCheckSuite, handleWorkflowRun, getJiraMetadata, updateJiraMetadata, clearJiraMetadata, registerJiraWebhook, deleteJiraWebhook, isJiraEnabled, initJira, handleJiraWebhook, handleAgentSessionEvent, handleIssueUpdate, handleIssueDelete, handleAgentSessionPrompted, isLinearEnabled, initLinear, loadGlobalConfigsIntoEnv, reloadGlobalConfigsAndIntegrations, scheduleIntegrationsReload, handleCore, canClaim, emitBudgetRefusalSideEffects, taskActionInputSchema, taskActionOutputSchema, taskActionHandler, hasCapability, getEnabledCapabilities, createServer, writeSkillsToFilesystem };