@desplega.ai/agent-swarm 1.119.6 → 1.121.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 (207) hide show
  1. package/README.md +2 -2
  2. package/dist/{actions-qe5zjcbr.js → actions-mwshtkf7.js} +6 -6
  3. package/dist/{anthropic-messages-qjxzsabf.js → anthropic-messages-4gn944cn.js} +29 -13
  4. package/dist/{app-31x7k3vj.js → app-skbty91x.js} +4 -4
  5. package/dist/{assistant-4crt9dve.js → assistant-tg7dhfj8.js} +13 -13
  6. package/dist/{azure-openai-responses-ntfd05zd.js → azure-openai-responses-79zc0wyy.js} +28 -13
  7. package/dist/{boot-reembed-8g69ttz4.js → boot-reembed-ntjmeyh2.js} +4 -4
  8. package/dist/{boot-reembed-q648j733.js → boot-reembed-q5x0xpcp.js} +5 -5
  9. package/dist/{boot-scrub-logs-sfmzd2hj.js → boot-scrub-logs-5gznsfbe.js} +4 -4
  10. package/dist/{claude-adapter-9n3nzt65.js → claude-adapter-vxvcym3r.js} +3 -3
  11. package/dist/{claude-managed-adapter-pv2nbqy4.js → claude-managed-adapter-g807894s.js} +2 -2
  12. package/dist/{cli-bpvgs2y6.js → cli-072t49hf.js} +2123 -1533
  13. package/dist/{cli-g6qz2kts.js → cli-0df2g86x.js} +336 -18
  14. package/dist/cli-1728qh70.js +791 -0
  15. package/dist/{cli-vg1jxfqz.js → cli-3b1ayz83.js} +5 -5
  16. package/dist/{cli-r93nz80j.js → cli-3fxqwfh3.js} +1 -1
  17. package/dist/{cli-w6rhc30s.js → cli-3jrdwnn9.js} +20 -5
  18. package/dist/{cli-pasyk3vc.js → cli-46etyprn.js} +10 -5
  19. package/dist/{cli-433c0f4b.js → cli-4pfweq4x.js} +8 -8
  20. package/dist/{cli-2r7s8jhp.js → cli-5c4327dx.js} +7 -1
  21. package/dist/{cli-4s84mw72.js → cli-5hpvd15x.js} +2 -2
  22. package/dist/{cli-branks1w.js → cli-6s3ytc5x.js} +1 -1
  23. package/dist/{cli-da3x3btq.js → cli-7pt9sc83.js} +2 -2
  24. package/dist/{cli-2gv8ygwr.js → cli-7pzae66m.js} +2 -2
  25. package/dist/{cli-qs7nb3zn.js → cli-8wrympd7.js} +27 -1
  26. package/dist/{cli-qw1ccvxm.js → cli-95rbe11h.js} +70 -51
  27. package/dist/{cli-6rvm3jkk.js → cli-agdrxyex.js} +1 -1
  28. package/dist/{cli-wr706gez.js → cli-av2s7my4.js} +167 -66
  29. package/dist/{cli-59jy217p.js → cli-avw9ev3x.js} +55 -21
  30. package/dist/{cli-fkxxh5d4.js → cli-b2mqkg1h.js} +2 -2
  31. package/dist/{cli-1arw00dp.js → cli-ccjwgarc.js} +12 -3
  32. package/dist/{cli-pscrc6tw.js → cli-ch7t5v7n.js} +5 -0
  33. package/dist/{cli-tghre7g6.js → cli-cw70hymf.js} +1 -0
  34. package/dist/{cli-n61x2aph.js → cli-dmzv0986.js} +816 -1335
  35. package/dist/{cli-d1reabed.js → cli-g9vva2qh.js} +2 -2
  36. package/dist/{cli-ner6af7m.js → cli-gdpx70nc.js} +24048 -23715
  37. package/dist/{cli-96feg24n.js → cli-ghq3v4c9.js} +2 -2
  38. package/dist/{cli-wxbnjgce.js → cli-ky8esb8t.js} +3 -3
  39. package/dist/{cli-gdqdkr3j.js → cli-nf7sadev.js} +2 -2
  40. package/dist/{cli-h6cf69x5.js → cli-p9w7kb3h.js} +1 -1
  41. package/dist/{cli-xeatwfbe.js → cli-ry656xem.js} +24 -17
  42. package/dist/{cli-q8dway4w.js → cli-s8bdcke2.js} +1 -1
  43. package/dist/cli-s8s499xy.js +81 -0
  44. package/dist/{cli-sbkwvn5e.js → cli-srjqn7fd.js} +1 -1
  45. package/dist/cli-tv7vekye.js +367 -0
  46. package/dist/{cli-gg5bd32n.js → cli-vg3zrw6h.js} +4 -4
  47. package/dist/{cli-vx0y68jv.js → cli-vp9r4ap4.js} +5 -4
  48. package/dist/{cli-mg280wsv.js → cli-we156bmz.js} +1 -1
  49. package/dist/{cli-86fdnerz.js → cli-wwsaj97q.js} +9 -4
  50. package/dist/{cli-e0y603nn.js → cli-x0e0yvnq.js} +101 -1
  51. package/dist/{cli-mw3xpgwb.js → cli-x4apc9t3.js} +4 -4
  52. package/dist/cli.js +26 -21
  53. package/dist/{codex-adapter-6j2b38tg.js → codex-adapter-mmy741zd.js} +8 -9
  54. package/dist/{codex-session-runner-vz215wgw.js → codex-session-runner-sazmx9zt.js} +8 -9
  55. package/dist/{commands-6xr20jbe.js → commands-cxxye5e4.js} +3 -3
  56. package/dist/{db-1mmvk1z4.js → db-ab1954b7.js} +5 -3
  57. package/dist/{google-generative-ai-n1d3xt4n.js → google-generative-ai-wwa2ked5.js} +14 -18
  58. package/dist/{google-vertex-mxst23gy.js → google-vertex-71xmdd7n.js} +14 -18
  59. package/dist/{handlers-xvrknk8w.js → handlers-fmfe2343.js} +13 -13
  60. package/dist/{hook-qm7he8wn.js → hook-ygnw3cvt.js} +7 -8
  61. package/dist/{http-hj08zq67.js → http-kactcc0z.js} +1128 -536
  62. package/dist/{index-688jph6a.js → index-6erq0wvk.js} +11 -11
  63. package/dist/{index-gze4fg2w.js → index-g86j2yt5.js} +9 -9
  64. package/dist/{index-etz7d8md.js → index-gmzs6a2q.js} +10 -10
  65. package/dist/{index-wj8dfpwh.js → index-hmp82wn0.js} +12 -12
  66. package/dist/{keepalive-94wn9yxw.js → keepalive-3cvt26dw.js} +18 -14
  67. package/dist/{lead-rvcqkp11.js → lead-ysf26cjr.js} +31 -32
  68. package/dist/{maintenance-qhfc4dnp.js → maintenance-9npwptbb.js} +5 -5
  69. package/dist/{mistral-conversations-ykcg66za.js → mistral-conversations-26ya4yah.js} +17 -13
  70. package/dist/{oauth-refresh-sweep-rj8wgvsw.js → oauth-refresh-sweep-0r2v76vw.js} +23 -17
  71. package/dist/{onboard-8bv8q1wc.js → onboard-ddb1674a.js} +2 -2
  72. package/dist/{openai-codex-responses-zy0907j9.js → openai-codex-responses-6rr2ye0s.js} +78 -38
  73. package/dist/{openai-completions-xk4p56n9.js → openai-completions-zfa9nkvt.js} +130 -33
  74. package/dist/{openai-responses-4gv3ca5d.js → openai-responses-9wmdks0g.js} +38 -14
  75. package/dist/{opencode-adapter-5bwzvbwj.js → opencode-adapter-h6rkxwrk.js} +2 -2
  76. package/dist/{openrouter-images-4fsrrzes.js → openrouter-images-dgq98729.js} +10 -3
  77. package/dist/{otel-impl-1kf6fr2m.js → otel-impl-b43bkq8e.js} +4 -4
  78. package/dist/{pi-messages-zb8v76wx.js → pi-messages-nzjbbz47.js} +4 -6
  79. package/dist/{pi-mono-adapter-9sg69kg3.js → pi-mono-adapter-ke789738.js} +2328 -1281
  80. package/dist/{pricing-refresh-e84tar2s.js → pricing-refresh-2rqf114z.js} +5 -5
  81. package/dist/{rbac-roles-atytq5ch.js → rbac-roles-5wkhc8xv.js} +5 -5
  82. package/dist/{rbac-roles-pg0zvnwn.js → rbac-roles-6vtafvzb.js} +4 -4
  83. package/dist/{seed-pricing-xd6nxq7k.js → seed-pricing-23wrb873.js} +4 -4
  84. package/dist/{setup-4z389yk0.js → setup-6rny4fk6.js} +2 -2
  85. package/dist/{worker-m61r0t7m.js → worker-yapewe14.js} +31 -32
  86. package/openapi.json +2671 -1811
  87. package/package.json +10 -5
  88. package/plugin/commands/swarm-chat.md +2 -0
  89. package/plugin/pi-skills/swarm-chat/SKILL.md +2 -0
  90. package/src/be/connection-bindings-blob-migration.ts +123 -0
  91. package/src/be/db-queries/mcp-oauth.ts +409 -247
  92. package/src/be/db-queries/oauth.ts +813 -131
  93. package/src/be/db.ts +97 -6
  94. package/src/be/migrations/117_unified_oauth.sql +624 -0
  95. package/src/be/migrations/119_agent_avatar.sql +7 -0
  96. package/src/be/migrations/120_task_title.sql +3 -0
  97. package/src/be/oauth-credential-bindings.ts +48 -14
  98. package/src/be/oauth-encryption-backfill.ts +84 -0
  99. package/src/be/oauth-refresh-sweep.ts +38 -23
  100. package/src/be/script-connections.ts +1130 -163
  101. package/src/be/script-credential-broker.ts +37 -27
  102. package/src/be/scripts/typecheck.ts +3 -2
  103. package/src/be/seed-capabilities.ts +62 -0
  104. package/src/be/seed-pricing.ts +1 -1
  105. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  106. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  107. package/src/be/vendored-openapi.ts +98 -0
  108. package/src/commands/runner.ts +69 -6
  109. package/src/http/agents.ts +23 -5
  110. package/src/http/all-routes.ts +1 -0
  111. package/src/http/core.ts +8 -5
  112. package/src/http/index.ts +18 -5
  113. package/src/http/mcp-bridge.ts +5 -2
  114. package/src/http/mcp-oauth.ts +103 -101
  115. package/src/http/oauth-callback.ts +332 -0
  116. package/src/http/oauth-generic.ts +14 -54
  117. package/src/http/poll.ts +5 -1
  118. package/src/http/script-connections.ts +651 -170
  119. package/src/http/scripts.ts +4 -2
  120. package/src/http/sessions.ts +1 -1
  121. package/src/http/tasks.ts +35 -0
  122. package/src/http/trackers/jira.ts +22 -34
  123. package/src/http/trackers/linear.ts +21 -36
  124. package/src/http/x.ts +6 -2
  125. package/src/jira/app.ts +12 -6
  126. package/src/jira/oauth.ts +48 -48
  127. package/src/linear/app.ts +44 -2
  128. package/src/linear/oauth.ts +12 -37
  129. package/src/oauth/app-validation.ts +27 -9
  130. package/src/oauth/ensure-mcp-token.ts +1 -0
  131. package/src/oauth/ensure-token.ts +331 -108
  132. package/src/oauth/identity-capture.ts +103 -0
  133. package/src/oauth/keepalive.ts +14 -9
  134. package/src/oauth/presets.ts +242 -0
  135. package/src/oauth/wrapper.ts +197 -76
  136. package/src/prompts/base-prompt.ts +48 -4
  137. package/src/prompts/session-templates.ts +13 -1
  138. package/src/providers/claude-managed-models.ts +8 -0
  139. package/src/rbac/legacy-policy.ts +2 -1
  140. package/src/rbac/permissions.ts +7 -3
  141. package/src/scripts-runtime/credential-broker/broker.ts +51 -8
  142. package/src/scripts-runtime/credential-broker/fetch-patch.ts +30 -3
  143. package/src/scripts-runtime/credential-broker/index.ts +1 -1
  144. package/src/scripts-runtime/credential-broker/types.ts +55 -8
  145. package/src/scripts-runtime/egress-secrets.ts +6 -2
  146. package/src/scripts-runtime/eval-harness.ts +2 -2
  147. package/src/scripts-runtime/executors/types.ts +2 -1
  148. package/src/scripts-runtime/loader.ts +3 -0
  149. package/src/scripts-runtime/types/stdlib.d.ts +11 -0
  150. package/src/server.ts +230 -126
  151. package/src/tests/agent-avatar-resolution.test.ts +123 -0
  152. package/src/tests/agents-list-model-display.test.ts +10 -0
  153. package/src/tests/base-prompt.test.ts +72 -0
  154. package/src/tests/claude-managed-adapter.test.ts +6 -0
  155. package/src/tests/connection-embedded-auth.test.ts +709 -0
  156. package/src/tests/context-window.test.ts +1 -0
  157. package/src/tests/credential-broker.test.ts +123 -125
  158. package/src/tests/db-queries-oauth.test.ts +126 -1
  159. package/src/tests/ensure-token.test.ts +44 -2
  160. package/src/tests/list-endpoint-slimming.test.ts +45 -0
  161. package/src/tests/mcp-oauth-manual-client.test.ts +20 -1
  162. package/src/tests/mcp-oauth-queries.test.ts +154 -8
  163. package/src/tests/oauth-access-token-tool.test.ts +37 -1
  164. package/src/tests/oauth-callback-flow.test.ts +534 -0
  165. package/src/tests/oauth-credential-bindings.test.ts +50 -22
  166. package/src/tests/oauth-presets.test.ts +275 -0
  167. package/src/tests/oauth-refresh-failure.test.ts +263 -0
  168. package/src/tests/oauth-refresh-sweep.test.ts +83 -3
  169. package/src/tests/oauth-wrapper.test.ts +77 -55
  170. package/src/tests/prompt-template-session.test.ts +6 -5
  171. package/src/tests/rbac-charact-misc-tools.test.ts +60 -0
  172. package/src/tests/rbac-engine.test.ts +2 -1
  173. package/src/tests/script-connections-http.test.ts +356 -11
  174. package/src/tests/script-connections.test.ts +102 -0
  175. package/src/tests/sdk-allowlist.test.ts +4 -1
  176. package/src/tests/seed-capabilities.test.ts +92 -0
  177. package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
  178. package/src/tests/sessions.test.ts +46 -0
  179. package/src/tests/spec-base-url.test.ts +266 -0
  180. package/src/tests/status.test.ts +3 -4
  181. package/src/tests/swarm-config-reserved-keys.test.ts +4 -2
  182. package/src/tests/task-title-route.test.ts +106 -0
  183. package/src/tests/tool-annotations.test.ts +6 -3
  184. package/src/tests/tracker-fold.test.ts +412 -0
  185. package/src/tests/unified-oauth-migration.test.ts +455 -0
  186. package/src/tests/vendored-openapi.test.ts +263 -0
  187. package/src/tools/credential-bindings/tool.ts +232 -77
  188. package/src/tools/oauth-access-token.ts +55 -6
  189. package/src/tools/script-connections/tool.ts +73 -53
  190. package/src/tools/swarm-config/set-config.ts +24 -50
  191. package/src/tools/update-profile.ts +15 -5
  192. package/src/tracker/types.ts +12 -0
  193. package/src/types.ts +29 -0
  194. package/src/utils/context-window.ts +1 -0
  195. package/src/workflows/executors/swarm-script.ts +6 -2
  196. package/vendored-openapi/README.md +22 -0
  197. package/vendored-openapi/github.json +7759 -0
  198. package/vendored-openapi/gmail.json +393 -0
  199. package/vendored-openapi/jira.json +3396 -0
  200. package/vendored-openapi/linear.json +252 -0
  201. package/vendored-openapi/manifest.json +128 -0
  202. package/vendored-openapi/slack.json +2895 -0
  203. package/dist/cli-0pygq9r1.js +0 -429
  204. package/dist/cli-8hhms4hp.js +0 -393
  205. package/dist/cli-z9gzq07x.js +0 -27
  206. package/src/scripts-runtime/credential-broker/store.ts +0 -72
  207. package/dist/{otel-g299k41b.js → otel-63jr5v38.js} +1 -1
@@ -2,15 +2,15 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import * as z from "zod";
3
3
  import { getAgentById } from "@/be/db";
4
4
  import {
5
+ ConnectionAuthInputSchema,
6
+ connectionAuthInputFromFlat,
5
7
  getScriptConnectionById,
6
8
  listScriptConnections,
7
9
  refreshScriptConnection,
8
10
  setScriptConnectionEnabled,
9
- upsertCredentialBinding,
10
11
  upsertScriptConnection,
11
12
  } from "@/be/script-connections";
12
13
  import { can } from "@/rbac";
13
- import { placeholderForConfigKey } from "@/scripts-runtime/credential-broker";
14
14
  import { createToolRegistrar } from "@/tools/utils";
15
15
  import { resolveScopedResourceId, scopedResourceScopeIdSchema } from "@/utils/scoped-resource";
16
16
 
@@ -51,24 +51,29 @@ const scriptConnectionsInputSchema = z.object({
51
51
  .nullable()
52
52
  .optional()
53
53
  .describe("Existing credential binding ID to attach to the connection."),
54
+ auth: ConnectionAuthInputSchema.optional().describe(
55
+ "Inline connection auth. type=bearer|header|query with an inline `secret` (stored encrypted under a derived key) or a shared `configKey`; type=oauth with an `authorizationId`; type=none clears auth. Auto-manages the connection's credential binding.",
56
+ ),
54
57
  configKey: z
55
58
  .string()
56
59
  .min(1)
57
60
  .max(255)
58
61
  .optional()
59
- .describe(
60
- "Config key used to create a credential binding when credentialBindingId is omitted.",
61
- ),
62
+ .describe("Deprecated flat alias for auth: config key for a derived credential binding."),
62
63
  headerTemplate: z
63
64
  .string()
64
65
  .min(1)
65
66
  .optional()
66
- .describe("Header template containing the config-key placeholder."),
67
+ .describe(
68
+ "Deprecated flat alias for auth: header template containing the config-key placeholder.",
69
+ ),
67
70
  queryTemplate: z
68
71
  .string()
69
72
  .min(1)
70
73
  .optional()
71
- .describe("Query parameter template containing the config-key placeholder."),
74
+ .describe(
75
+ "Deprecated flat alias for auth: query parameter template containing the config-key placeholder.",
76
+ ),
72
77
  openapiSpecUrl: z
73
78
  .string()
74
79
  .url()
@@ -78,6 +83,12 @@ const scriptConnectionsInputSchema = z.object({
78
83
  .string()
79
84
  .optional()
80
85
  .describe("Inline OpenAPI JSON for upsert-openapi. Mutually exclusive with openapiSpecUrl."),
86
+ specSource: z
87
+ .object({ kind: z.literal("vendored"), slug: z.string().regex(/^[a-z0-9][a-z0-9-]*$/) })
88
+ .optional()
89
+ .describe(
90
+ "Vendored OpenAPI source. Mutually exclusive with openapiSpecUrl and openapiSpecJson.",
91
+ ),
81
92
  enabled: z.boolean().optional().describe("Whether the connection is enabled."),
82
93
  });
83
94
 
@@ -91,6 +102,14 @@ const scriptConnectionsOutputSchema = z.object({
91
102
  type ScriptConnectionsArgs = z.infer<typeof scriptConnectionsInputSchema>;
92
103
  type ExistingConnection = NonNullable<ReturnType<typeof getScriptConnectionById>>;
93
104
 
105
+ function baseUrlProvenanceText(connection: {
106
+ slug: string;
107
+ baseUrlSource: string;
108
+ baseUrlMismatch?: { specUrl: string; effectiveUrl: string };
109
+ }): string {
110
+ return `${connection.slug}: baseUrlSource=${connection.baseUrlSource}${connection.baseUrlMismatch ? `, baseUrlMismatch=${JSON.stringify(connection.baseUrlMismatch)}` : ""}`;
111
+ }
112
+
94
113
  function resolveConnectionScope(
95
114
  args: ScriptConnectionsArgs,
96
115
  existing: ExistingConnection | null,
@@ -164,8 +183,16 @@ export const registerScriptConnectionsTool = (server: McpServer) => {
164
183
 
165
184
  if (args.action === "list") {
166
185
  const connections = listScriptConnections({ includeDisabled: true, allScopes: true });
186
+ const provenanceLines = connections
187
+ .filter((connection) => connection.kind === "openapi" || connection.baseUrl !== null)
188
+ .map(baseUrlProvenanceText);
167
189
  return {
168
- content: [{ type: "text", text: `Found ${connections.length} script connection(s).` }],
190
+ content: [
191
+ {
192
+ type: "text",
193
+ text: `Found ${connections.length} script connection(s).${provenanceLines.length ? `\n${provenanceLines.join("\n")}` : ""}`,
194
+ },
195
+ ],
169
196
  structuredContent: {
170
197
  yourAgentId: requestInfo.agentId,
171
198
  success: true,
@@ -226,7 +253,12 @@ export const registerScriptConnectionsTool = (server: McpServer) => {
226
253
  };
227
254
  }
228
255
  return {
229
- content: [{ type: "text", text: `Script connection ${refreshed.slug} refreshed.` }],
256
+ content: [
257
+ {
258
+ type: "text",
259
+ text: `Script connection ${refreshed.slug} refreshed (${baseUrlProvenanceText(refreshed)}).`,
260
+ },
261
+ ],
230
262
  structuredContent: {
231
263
  yourAgentId: requestInfo.agentId,
232
264
  success: !refreshed.generationError,
@@ -292,23 +324,6 @@ export const registerScriptConnectionsTool = (server: McpServer) => {
292
324
  };
293
325
  }
294
326
 
295
- let credentialBindingId = args.credentialBindingId ?? null;
296
- if (!credentialBindingId && args.configKey) {
297
- const placeholder = placeholderForConfigKey(args.configKey);
298
- const bindingScope = args.scope ?? "global";
299
- const binding = upsertCredentialBinding({
300
- configKey: args.configKey,
301
- allowedHosts: args.allowedHosts,
302
- headerTemplate:
303
- args.headerTemplate ??
304
- (args.queryTemplate ? undefined : `Authorization: Bearer ${placeholder}`),
305
- queryTemplate: args.queryTemplate,
306
- scope: bindingScope,
307
- scopeId: resolveScopedResourceId(bindingScope, args.scopeId, "bindings"),
308
- });
309
- credentialBindingId = binding.id;
310
- }
311
-
312
327
  const existing = args.id ? getScriptConnectionById(args.id) : null;
313
328
  const { scope, scopeId } = resolveConnectionScope(args, existing);
314
329
  const connection = await upsertScriptConnection({
@@ -320,7 +335,14 @@ export const registerScriptConnectionsTool = (server: McpServer) => {
320
335
  scopeId,
321
336
  baseUrl: args.baseUrl,
322
337
  allowedHosts: args.allowedHosts,
323
- credentialBindingId,
338
+ auth: connectionAuthInputFromFlat({
339
+ auth: args.auth,
340
+ configKey: args.configKey,
341
+ headerTemplate: args.headerTemplate,
342
+ queryTemplate: args.queryTemplate,
343
+ allowedHosts: args.allowedHosts,
344
+ }),
345
+ credentialBindingId: args.credentialBindingId ?? undefined,
324
346
  enabled: resolveConnectionEnabled(args, existing),
325
347
  });
326
348
 
@@ -338,56 +360,40 @@ export const registerScriptConnectionsTool = (server: McpServer) => {
338
360
  };
339
361
  }
340
362
 
341
- if (!args.slug || !args.baseUrl || (!args.openapiSpecJson && !args.openapiSpecUrl)) {
363
+ if (!args.slug || (!args.openapiSpecJson && !args.openapiSpecUrl && !args.specSource)) {
342
364
  return {
343
365
  content: [
344
366
  {
345
367
  type: "text",
346
- text: "slug, baseUrl, and either openapiSpecJson or openapiSpecUrl are required.",
368
+ text: "slug and exactly one OpenAPI spec source (openapiSpecJson, openapiSpecUrl, or specSource) are required.",
347
369
  },
348
370
  ],
349
371
  structuredContent: {
350
372
  yourAgentId: requestInfo.agentId,
351
373
  success: false,
352
- message: "slug, baseUrl, and either openapiSpecJson or openapiSpecUrl are required.",
374
+ message:
375
+ "slug and exactly one OpenAPI spec source (openapiSpecJson, openapiSpecUrl, or specSource) are required.",
353
376
  connections: listScriptConnections({ includeDisabled: true, allScopes: true }),
354
377
  },
355
378
  };
356
379
  }
357
- if (args.openapiSpecJson && args.openapiSpecUrl) {
380
+ if ([args.openapiSpecJson, args.openapiSpecUrl, args.specSource].filter(Boolean).length > 1) {
358
381
  return {
359
382
  content: [
360
383
  {
361
384
  type: "text",
362
- text: "Provide either openapiSpecJson or openapiSpecUrl, not both.",
385
+ text: "Provide exactly one OpenAPI spec source.",
363
386
  },
364
387
  ],
365
388
  structuredContent: {
366
389
  yourAgentId: requestInfo.agentId,
367
390
  success: false,
368
- message: "Provide either openapiSpecJson or openapiSpecUrl, not both.",
391
+ message: "Provide exactly one OpenAPI spec source.",
369
392
  connections: listScriptConnections({ includeDisabled: true, allScopes: true }),
370
393
  },
371
394
  };
372
395
  }
373
396
 
374
- let credentialBindingId = args.credentialBindingId ?? null;
375
- if (!credentialBindingId && args.configKey) {
376
- const placeholder = placeholderForConfigKey(args.configKey);
377
- const bindingScope = args.scope ?? "global";
378
- const binding = upsertCredentialBinding({
379
- configKey: args.configKey,
380
- allowedHosts: args.allowedHosts ?? [new URL(args.baseUrl).hostname],
381
- headerTemplate:
382
- args.headerTemplate ??
383
- (args.queryTemplate ? undefined : `Authorization: Bearer ${placeholder}`),
384
- queryTemplate: args.queryTemplate,
385
- scope: bindingScope,
386
- scopeId: resolveScopedResourceId(bindingScope, args.scopeId, "bindings"),
387
- });
388
- credentialBindingId = binding.id;
389
- }
390
-
391
397
  const existing = args.id ? getScriptConnectionById(args.id) : null;
392
398
  const { scope, scopeId } = resolveConnectionScope(args, existing);
393
399
  const connection = await upsertScriptConnection({
@@ -398,8 +404,17 @@ export const registerScriptConnectionsTool = (server: McpServer) => {
398
404
  scope,
399
405
  scopeId,
400
406
  baseUrl: args.baseUrl,
401
- allowedHosts: args.allowedHosts ?? [new URL(args.baseUrl).hostname],
402
- credentialBindingId,
407
+ allowedHosts: args.allowedHosts,
408
+ auth: connectionAuthInputFromFlat({
409
+ auth: args.auth,
410
+ configKey: args.configKey,
411
+ headerTemplate: args.headerTemplate,
412
+ queryTemplate: args.queryTemplate,
413
+ allowedHosts: args.allowedHosts,
414
+ }),
415
+ credentialBindingId: args.credentialBindingId ?? undefined,
416
+ openapiSpecSourceKind: args.specSource ? "vendored" : undefined,
417
+ openapiSpecSource: args.specSource?.slug,
403
418
  openapiSpecUrl: args.openapiSpecUrl,
404
419
  openapiSpecJson: args.openapiSpecJson,
405
420
  enabled: resolveConnectionEnabled(args, existing),
@@ -407,7 +422,12 @@ export const registerScriptConnectionsTool = (server: McpServer) => {
407
422
 
408
423
  const connections = listScriptConnections({ includeDisabled: true, allScopes: true });
409
424
  return {
410
- content: [{ type: "text", text: `Script connection ${connection.slug} saved.` }],
425
+ content: [
426
+ {
427
+ type: "text",
428
+ text: `Script connection ${connection.slug} saved (${baseUrlProvenanceText(connection)}).`,
429
+ },
430
+ ],
411
431
  structuredContent: {
412
432
  yourAgentId: requestInfo.agentId,
413
433
  success: !connection.generationError,
@@ -8,7 +8,6 @@ import {
8
8
  } from "@/be/swarm-config-guard";
9
9
  import { scheduleIntegrationsReload } from "@/http/core";
10
10
  import { can } from "@/rbac";
11
- import { CREDENTIAL_BINDINGS_CONFIG_KEY } from "@/scripts-runtime/credential-broker";
12
11
  import { createToolRegistrar } from "@/tools/utils";
13
12
  import { SwarmConfigSchema, SwarmConfigScopeSchema } from "@/types";
14
13
 
@@ -96,56 +95,31 @@ export const registerSetConfigTool = (server: McpServer) => {
96
95
  };
97
96
  }
98
97
 
99
- if (key.toUpperCase() === CREDENTIAL_BINDINGS_CONFIG_KEY) {
100
- const agent = getAgentById(requestInfo.agentId);
101
- const decision = can({
102
- principal: {
103
- kind: "agent",
104
- agentId: requestInfo.agentId,
105
- isLead: agent?.isLead ?? false,
106
- },
107
- verb: "config.credential-bindings.write",
108
- resource: { kind: "none" },
109
- source: "mcp",
110
- });
111
- if (!decision.allow) {
112
- const message =
113
- "Only the lead can manage SCRIPT_CREDENTIAL_BINDINGS. Use the credential-bindings tool.";
114
- return {
115
- content: [{ type: "text", text: message }],
116
- structuredContent: {
117
- yourAgentId: requestInfo.agentId,
118
- success: false,
119
- message,
120
- },
121
- };
122
- }
123
- } else {
124
- // Every other swarm-config key is lead-gated (DES-445 follow-up):
125
- // previously ANY agent could write arbitrary config (incl. secret
126
- // values), routing around the credential-bindings gate above.
127
- const agent = getAgentById(requestInfo.agentId);
128
- const decision = can({
129
- principal: {
130
- kind: "agent",
131
- agentId: requestInfo.agentId,
132
- isLead: agent?.isLead ?? false,
98
+ // Every swarm-config write is lead-gated (DES-445 follow-up): previously
99
+ // ANY agent could write arbitrary config (incl. secret values). The
100
+ // legacy SCRIPT_CREDENTIAL_BINDINGS blob is retired, so there is no
101
+ // special-cased key here anymore.
102
+ const agent = getAgentById(requestInfo.agentId);
103
+ const decision = can({
104
+ principal: {
105
+ kind: "agent",
106
+ agentId: requestInfo.agentId,
107
+ isLead: agent?.isLead ?? false,
108
+ },
109
+ verb: "config.write.any",
110
+ resource: { kind: "none" },
111
+ source: "mcp",
112
+ });
113
+ if (!decision.allow) {
114
+ const message = "Writing swarm config requires the lead agent.";
115
+ return {
116
+ content: [{ type: "text", text: message }],
117
+ structuredContent: {
118
+ yourAgentId: requestInfo.agentId,
119
+ success: false,
120
+ message,
133
121
  },
134
- verb: "config.write.any",
135
- resource: { kind: "none" },
136
- source: "mcp",
137
- });
138
- if (!decision.allow) {
139
- const message = "Writing swarm config requires the lead agent.";
140
- return {
141
- content: [{ type: "text", text: message }],
142
- structuredContent: {
143
- yourAgentId: requestInfo.agentId,
144
- success: false,
145
- message,
146
- },
147
- };
148
- }
122
+ };
149
123
  }
150
124
 
151
125
  const validationError = validateConfigValue(key, value);
@@ -6,7 +6,7 @@ import * as z from "zod";
6
6
  import { getAgentById, updateAgentName, updateAgentProfile } from "@/be/db";
7
7
  import { can } from "@/rbac";
8
8
  import { createToolRegistrar } from "@/tools/utils";
9
- import { type Agent, AgentSchema } from "@/types";
9
+ import { type Agent, AgentAvatarSchema, AgentSchema } from "@/types";
10
10
 
11
11
  async function validateSetupScriptSyntax(setupScript: string): Promise<string | null> {
12
12
  const proc = Bun.spawn(["bash", "-n", "-c", setupScript], {
@@ -121,6 +121,11 @@ export const registerUpdateProfileTool = (server: McpServer) => {
121
121
  .describe(
122
122
  "Heartbeat checklist content (HEARTBEAT.md). Checked periodically — add standing orders for the lead to review. Synced to /workspace/HEARTBEAT.md.",
123
123
  ),
124
+ avatar: AgentAvatarSchema.nullable()
125
+ .optional()
126
+ .describe(
127
+ "Custom avatar: { type: 'lucide', icon: '<kebab-case-lucide-name>', color?: '#RRGGBB' }. Pass null to reset to the default deterministic icon/color.",
128
+ ),
124
129
  }),
125
130
  outputSchema: z.object({
126
131
  yourAgentId: z.string().uuid().optional(),
@@ -142,6 +147,7 @@ export const registerUpdateProfileTool = (server: McpServer) => {
142
147
  setupScript,
143
148
  toolsMd,
144
149
  heartbeatMd,
150
+ avatar,
145
151
  },
146
152
  requestInfo,
147
153
  _meta,
@@ -221,20 +227,21 @@ export const registerUpdateProfileTool = (server: McpServer) => {
221
227
  identityMd === undefined &&
222
228
  setupScript === undefined &&
223
229
  toolsMd === undefined &&
224
- heartbeatMd === undefined
230
+ heartbeatMd === undefined &&
231
+ avatar === undefined
225
232
  ) {
226
233
  return {
227
234
  content: [
228
235
  {
229
236
  type: "text",
230
- text: "At least one field (name, description, role, capabilities, claudeMd, soulMd, identityMd, setupScript, toolsMd, or heartbeatMd) must be provided.",
237
+ text: "At least one field (name, description, role, capabilities, claudeMd, soulMd, identityMd, setupScript, toolsMd, heartbeatMd, or avatar) must be provided.",
231
238
  },
232
239
  ],
233
240
  structuredContent: {
234
241
  yourAgentId: requestInfo.agentId,
235
242
  success: false,
236
243
  message:
237
- "At least one field (name, description, role, capabilities, claudeMd, soulMd, identityMd, setupScript, toolsMd, or heartbeatMd) must be provided.",
244
+ "At least one field (name, description, role, capabilities, claudeMd, soulMd, identityMd, setupScript, toolsMd, heartbeatMd, or avatar) must be provided.",
238
245
  },
239
246
  };
240
247
  }
@@ -273,7 +280,8 @@ export const registerUpdateProfileTool = (server: McpServer) => {
273
280
  }
274
281
  }
275
282
 
276
- // Update profile fields if provided
283
+ // Update profile fields if provided. `avatar` is spread in only when
284
+ // present so `null` (reset) stays distinguishable from "not provided".
277
285
  agent = updateAgentProfile(
278
286
  targetAgentId,
279
287
  {
@@ -286,6 +294,7 @@ export const registerUpdateProfileTool = (server: McpServer) => {
286
294
  setupScript,
287
295
  toolsMd,
288
296
  heartbeatMd,
297
+ ...(avatar !== undefined ? { avatar } : {}),
289
298
  },
290
299
  {
291
300
  changeSource: isUpdatingSelf ? "self_edit" : "lead_coaching",
@@ -381,6 +390,7 @@ export const registerUpdateProfileTool = (server: McpServer) => {
381
390
  if (setupScript !== undefined) updatedFields.push("setupScript");
382
391
  if (toolsMd !== undefined) updatedFields.push("toolsMd");
383
392
  if (heartbeatMd !== undefined) updatedFields.push("heartbeatMd");
393
+ if (avatar !== undefined) updatedFields.push("avatar");
384
394
 
385
395
  const targetLabel = isUpdatingSelf ? "own" : `agent ${targetAgentId}`;
386
396
  return {
@@ -3,12 +3,23 @@ export type TrackerProvider = "linear" | "jira"; // extend as providers are adde
3
3
  export interface OAuthApp {
4
4
  id: string;
5
5
  provider: string;
6
+ displayName: string | null;
6
7
  clientId: string;
7
8
  clientSecret: string;
9
+ clientSecretEncrypted: boolean;
8
10
  authorizeUrl: string;
9
11
  tokenUrl: string;
12
+ revocationUrl: string | null;
13
+ userinfoUrl: string | null;
10
14
  redirectUri: string;
11
15
  scopes: string;
16
+ scopeSeparator: string;
17
+ tokenAuthStyle: "body" | "basic";
18
+ tokenBodyFormat: "form" | "json";
19
+ requiresRefreshTokenRotation: boolean;
20
+ extraParamsJson: string | null;
21
+ source: "manual" | "dcr" | "curated-prefill";
22
+ mcpServerId: string | null;
12
23
  metadata: string; // JSON string
13
24
  createdAt: string;
14
25
  updatedAt: string;
@@ -21,6 +32,7 @@ export interface OAuthTokens {
21
32
  refreshToken: string | null;
22
33
  expiresAt: string;
23
34
  scope: string | null;
35
+ tokenVersion: number;
24
36
  createdAt: string;
25
37
  updatedAt: string;
26
38
  }
package/src/types.ts CHANGED
@@ -371,6 +371,7 @@ export const AgentTaskSchema = z.object({
371
371
  agentId: z.uuid().nullable(), // Nullable for unassigned tasks
372
372
  creatorAgentId: z.uuid().optional(), // Who created this task (optional for Slack/API)
373
373
  task: z.string().min(1),
374
+ title: z.string().optional(), // Human-facing display title override (e.g. session rename); falls back to `task` when unset
374
375
  status: AgentTaskStatusSchema,
375
376
  source: AgentTaskSourceSchema.default("mcp"),
376
377
 
@@ -725,6 +726,25 @@ export type TaskTemplate = z.infer<typeof TaskTemplateSchema>;
725
726
 
726
727
  export const AgentStatusSchema = z.enum(["idle", "busy", "offline", "waiting_for_credentials"]);
727
728
 
729
+ // Discriminated union so future avatar types (emoji, image, ...) can be added
730
+ // with zero migrations — just a new zod branch + a UI renderer branch. Server
731
+ // validates shape only; the UI owns the curated icon catalog and deterministic
732
+ // fallback, so an unrecognized/hand-set icon name can never break rendering.
733
+ export const AgentAvatarSchema = z.discriminatedUnion("type", [
734
+ z.object({
735
+ type: z.literal("lucide"),
736
+ icon: z
737
+ .string()
738
+ .regex(/^[a-z0-9-]+$/)
739
+ .max(64),
740
+ color: z
741
+ .string()
742
+ .regex(/^#[0-9a-fA-F]{6}$/)
743
+ .optional(),
744
+ }),
745
+ ]);
746
+ export type AgentAvatar = z.infer<typeof AgentAvatarSchema>;
747
+
728
748
  export const AgentSchema = z.object({
729
749
  id: z.uuid(),
730
750
  name: z.string().min(1),
@@ -784,6 +804,14 @@ export const AgentSchema = z.object({
784
804
  .nullable()
785
805
  .optional(),
786
806
 
807
+ // Custom avatar (icon + color). Null/missing = fall back to the
808
+ // deterministic hash-derived icon and color (see apps/ui/src/lib/agent-icon.ts
809
+ // and agent-color.ts). Migration 119 adds the column.
810
+ avatar: z
811
+ .lazy(() => AgentAvatarSchema)
812
+ .nullable()
813
+ .optional(),
814
+
787
815
  createdAt: z.iso.datetime().default(() => new Date().toISOString()),
788
816
  lastUpdatedAt: z.iso.datetime().default(() => new Date().toISOString()),
789
817
  });
@@ -1767,6 +1795,7 @@ export type AgentTaskSummary = Pick<
1767
1795
  | "agentId"
1768
1796
  | "creatorAgentId"
1769
1797
  | "task"
1798
+ | "title"
1770
1799
  | "status"
1771
1800
  | "source"
1772
1801
  | "taskType"
@@ -27,6 +27,7 @@ export const CONTEXT_FORMULA = "input-cache-output" as const;
27
27
 
28
28
  const CONTEXT_WINDOW_DEFAULTS: Record<string, number> = {
29
29
  // Anthropic Fable / Mythos tier
30
+ "claude-opus-5": 1_000_000,
30
31
  "claude-fable-5": 1_000_000,
31
32
  "claude-mythos-5": 1_000_000,
32
33
  "claude-sonnet-5": 1_000_000,
@@ -3,7 +3,7 @@ import {
3
3
  getScriptApiConnectionDescriptors,
4
4
  getScriptMcpConnectionDescriptors,
5
5
  } from "../../be/script-connections";
6
- import { buildScriptCredentialBindings } from "../../be/script-credential-broker";
6
+ import { buildScriptCredentialBindingsWithFailures } from "../../be/script-credential-broker";
7
7
  import { getScript, getScriptVersion } from "../../be/scripts/db";
8
8
  import { DEFAULT_SCRIPT_RESOURCES } from "../../scripts-runtime/executors/types";
9
9
  import { runScript } from "../../scripts-runtime/loader";
@@ -72,12 +72,16 @@ export class SwarmScriptExecutor extends BaseExecutor<
72
72
  return { status: "failed", error: resolved.error };
73
73
  }
74
74
 
75
+ const credentials = await buildScriptCredentialBindingsWithFailures({
76
+ agentId: agentId ?? undefined,
77
+ });
75
78
  const output = await runScript({
76
79
  source: resolved.source,
77
80
  args: config.args,
78
81
  fsMode: "none",
79
82
  agentId: agentId ?? "workflow",
80
- egressSecrets: await buildScriptCredentialBindings({ agentId: agentId ?? undefined }),
83
+ egressSecrets: credentials.egressSecrets,
84
+ failedBindings: credentials.failedBindings,
81
85
  apiConnections: getScriptApiConnectionDescriptors({ agentId: agentId ?? undefined }),
82
86
  mcpConnections: getScriptMcpConnectionDescriptors({ agentId: agentId ?? undefined }),
83
87
  timeoutMs: config.timeoutMs,
@@ -0,0 +1,22 @@
1
+ # Vendored OpenAPI specs
2
+
3
+ This directory is reviewed source, not a runtime download cache. `manifest.json` defines the
4
+ blessed catalog entries and their allowlisted `METHOD /path` operations. Each JSON file contains
5
+ only that operation subset, its real parameters/request bodies/responses, and the transitive
6
+ closure of locally referenced schemas needed by generated script clients.
7
+
8
+ Run `bun run refresh:vendored-openapi` to retrieve directly trim-compatible pinned upstream
9
+ documents, re-trim them, print which files changed, and update manifest checksums. It stages every
10
+ change before writing so a failed fetch cannot leave a partial refresh. Machine sources are pinned
11
+ by both their upstream version and raw-document SHA-256. `operator-review` entries are canonicalized
12
+ and checksummed locally but are never fetched. For those entries, `specSourceUrl` is an
13
+ operator-reference URL documenting provenance; it is not asserted to be a directly fetchable
14
+ OpenAPI document. Run `bun run check:vendored-openapi` offline in CI; it rejects non-canonical
15
+ trims, unknown operations, unsafe filenames, inconsistent provenance metadata, and hash drift.
16
+
17
+ Provenance: GitHub and Slack are pinned to upstream Git commits. Jira is pinned to the raw SHA-256
18
+ of Atlassian's Cloud v3 document because its published URL is mutable. Gmail is an
19
+ operator-reviewed OpenAPI façade based on Google's v1 Discovery model. Linear publishes a GraphQL
20
+ endpoint rather than an OpenAPI document, so `linear.json` is an operator-reviewed façade for the
21
+ documented `POST /graphql` issue query/mutation shapes. Both façades must remain narrow and be
22
+ reviewed when their reference APIs change.