@desplega.ai/agent-swarm 1.123.0 → 1.124.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 (234) hide show
  1. package/README.md +2 -1
  2. package/dist/{actions-e4c9f0xy.js → actions-z3d0fk7z.js} +6 -6
  3. package/dist/{app-vmvmj2v3.js → app-q2rc9tdr.js} +4 -4
  4. package/dist/{assistant-yn7a792a.js → assistant-3stjq7fz.js} +9 -9
  5. package/dist/{boot-reembed-xg1r86h2.js → boot-reembed-5867whnb.js} +3 -3
  6. package/dist/{boot-reembed-chxty50a.js → boot-reembed-v6r1df32.js} +4 -4
  7. package/dist/{boot-scrub-logs-exwddvp5.js → boot-scrub-logs-8b8jegk3.js} +2 -2
  8. package/dist/{cli-1592nfd6.js → cli-0925phzv.js} +1 -1
  9. package/dist/{cli-hbbxmhec.js → cli-2yr6edr7.js} +4 -4
  10. package/dist/{cli-8cq4z4x7.js → cli-3z4q09tn.js} +182 -19
  11. package/dist/{cli-vz9drmce.js → cli-5jn6wnd9.js} +17 -8
  12. package/dist/{cli-5arfywtm.js → cli-5ncdb7ff.js} +19 -9
  13. package/dist/{cli-3tv9z4wx.js → cli-92bqgbgy.js} +1 -1
  14. package/dist/{cli-fjf04hpa.js → cli-dq2ssyf0.js} +48 -56
  15. package/dist/{cli-mngsj9fx.js → cli-gkvf8d83.js} +1 -1
  16. package/dist/{cli-mepk1j0t.js → cli-gzjsamwc.js} +2 -2
  17. package/dist/{cli-c9m59f83.js → cli-h89g0qrd.js} +1 -1
  18. package/dist/{cli-n8mf7x4b.js → cli-jp4mdj9d.js} +2 -2
  19. package/dist/{cli-822d4bjk.js → cli-kk58tg33.js} +7 -7
  20. package/dist/{cli-9hj1adgp.js → cli-mcez7z4p.js} +1 -1
  21. package/dist/{cli-byv3720g.js → cli-mmk5vagg.js} +2 -2
  22. package/dist/{cli-zdp11t8m.js → cli-p0ec4vv6.js} +1 -1
  23. package/dist/{cli-h2say6xk.js → cli-p0zgv8pj.js} +3 -1
  24. package/dist/{cli-8ef5ky75.js → cli-perb20ma.js} +1 -1
  25. package/dist/{cli-zkj6f2sk.js → cli-qf7acvyr.js} +90 -4
  26. package/dist/{cli-kmm1rwmp.js → cli-qwsagsmg.js} +1 -1
  27. package/dist/{cli-we91ywvy.js → cli-sqv6c9b8.js} +6 -6
  28. package/dist/{cli-np0yhmmk.js → cli-vm20sh8d.js} +3 -3
  29. package/dist/{cli-p11rf59d.js → cli-wn3m6q54.js} +3 -3
  30. package/dist/{cli-mkpf4ndf.js → cli-x268say8.js} +2976 -5621
  31. package/dist/{cli-ngbsk4yx.js → cli-y43n987v.js} +1 -1
  32. package/dist/{cli-4509mwm5.js → cli-ykthjw29.js} +8 -8
  33. package/dist/{cli-9eka6jwv.js → cli-zxb72ctk.js} +2 -2
  34. package/dist/cli.js +12 -10
  35. package/dist/{commands-sjk3zfyr.js → commands-sda76yn8.js} +2 -2
  36. package/dist/{db-2je5agjb.js → db-vn9e4rqj.js} +6 -2
  37. package/dist/{e2b-2s4psnbf.js → e2b-q345y8s4.js} +0 -4
  38. package/dist/{handlers-yzv369hf.js → handlers-xfja18ac.js} +9 -9
  39. package/dist/{hook-rfsj2r8x.js → hook-2medrp47.js} +1 -40
  40. package/dist/{http-v9rr144m.js → http-937pyjfc.js} +99 -44
  41. package/dist/{index-nr2199m6.js → index-2bqd2q9v.js} +11 -11
  42. package/dist/{index-3xtk777g.js → index-gw4en7jg.js} +9 -9
  43. package/dist/{index-c3tan44f.js → index-knqczmnm.js} +10 -10
  44. package/dist/{index-smjdc3h9.js → index-vah60vxp.js} +8 -8
  45. package/dist/{keepalive-5ds53bjd.js → keepalive-20rd6hxh.js} +5 -5
  46. package/dist/{lead-5xhez25b.js → lead-7w2k3a49.js} +21 -21
  47. package/dist/{maintenance-b43d24q6.js → maintenance-p3x918yw.js} +4 -4
  48. package/dist/{oauth-refresh-sweep-qe286vn2.js → oauth-refresh-sweep-shm0nzm4.js} +4 -4
  49. package/dist/{onboard-c4r3ds3z.js → onboard-py9dnf54.js} +6 -2
  50. package/dist/{otel-impl-nv3rjw61.js → otel-impl-7yjkfg68.js} +1 -1
  51. package/dist/{pi-mono-adapter-2syx1x4d.js → pi-mono-adapter-z6h98fva.js} +4 -38
  52. package/dist/{pricing-refresh-5ct2wp4k.js → pricing-refresh-3snb6370.js} +4 -4
  53. package/dist/{rbac-roles-m502w0qv.js → rbac-roles-5e72bbpk.js} +3 -3
  54. package/dist/{rbac-roles-b82cxpxn.js → rbac-roles-gqxdp8st.js} +4 -4
  55. package/dist/{seed-pricing-jhjphbf0.js → seed-pricing-bg42d7h2.js} +3 -3
  56. package/dist/{setup-zbaspydq.js → setup-nzk3kbbq.js} +2 -2
  57. package/dist/{worker-snvhpax5.js → worker-0p1jdzkm.js} +21 -21
  58. package/openapi.json +146 -1
  59. package/package.json +3 -1
  60. package/src/be/db.ts +81 -4
  61. package/src/be/models-catalog.ts +156 -0
  62. package/src/be/modelsdev-cache.ts +14 -1
  63. package/src/be/pricing-refresh.ts +2 -0
  64. package/src/be/scripts/typecheck.ts +17 -4
  65. package/src/be/seed-scripts/catalog/task-context-gathering.ts +1 -1
  66. package/src/commands/onboard/env-generator.ts +12 -0
  67. package/src/e2b/env.ts +0 -4
  68. package/src/hooks/hook.ts +0 -67
  69. package/src/http/all-routes.ts +1 -0
  70. package/src/http/index.ts +2 -0
  71. package/src/http/models-catalog.ts +65 -0
  72. package/src/http/workflows.ts +16 -4
  73. package/src/prompts/base-prompt.ts +8 -7
  74. package/src/prompts/session-templates.ts +106 -12
  75. package/src/providers/pi-mono-adapter.ts +12 -2
  76. package/src/providers/pi-mono-extension.ts +0 -64
  77. package/src/providers/pricing-sources.md +16 -1
  78. package/src/scripts-runtime/eval-harness.ts +18 -1
  79. package/src/scripts-runtime/swarm-sdk.ts +10 -0
  80. package/src/scripts-runtime/types/stdlib.d.ts +13 -0
  81. package/src/scripts-runtime/types/swarm-sdk.d.ts +13 -0
  82. package/src/server-user.ts +4 -7
  83. package/src/slack/blocks.ts +21 -8
  84. package/src/slack/responses.ts +27 -51
  85. package/src/slack/watcher.ts +22 -5
  86. package/src/telemetry.ts +169 -3
  87. package/src/tests/base-prompt.test.ts +79 -0
  88. package/src/tests/mcp-tools-user.test.ts +62 -16
  89. package/src/tests/mcp-tools.test.ts +6 -3
  90. package/src/tests/memory-edit.test.ts +21 -4
  91. package/src/tests/model-groups-live-catalog.test.ts +65 -0
  92. package/src/tests/models-catalog.test.ts +119 -0
  93. package/src/tests/oauth-access-token-tool.test.ts +71 -1
  94. package/src/tests/pricing-refresh.test.ts +25 -0
  95. package/src/tests/prompt-template-session.test.ts +142 -5
  96. package/src/tests/rbac-charact-skills.test.ts +81 -14
  97. package/src/tests/script-apis-mcp.test.ts +56 -8
  98. package/src/tests/scripts-mcp-e2e.test.ts +157 -8
  99. package/src/tests/scripts-typecheck.test.ts +17 -0
  100. package/src/tests/sdk-allowlist.test.ts +34 -1
  101. package/src/tests/seed-scripts.test.ts +43 -0
  102. package/src/tests/slack-blocks.test.ts +2 -1
  103. package/src/tests/slack-inline-output.test.ts +24 -30
  104. package/src/tests/slack-watcher.test.ts +42 -0
  105. package/src/tests/steer-task-tool.test.ts +37 -8
  106. package/src/tests/steering-transport.test.ts +18 -3
  107. package/src/tests/swarm-tool-result-gate.test.ts +399 -0
  108. package/src/tests/task-tools-ctx.test.ts +49 -17
  109. package/src/tests/task-tools-ownership.test.ts +41 -13
  110. package/src/tests/telemetry-init.test.ts +376 -2
  111. package/src/tests/tool-output-agent-id.test.ts +14 -2
  112. package/src/tests/workflow-http-v2.test.ts +116 -0
  113. package/src/tools/accept-steer.ts +45 -53
  114. package/src/tools/cancel-task.ts +31 -41
  115. package/src/tools/context-diff.ts +19 -63
  116. package/src/tools/context-history.ts +22 -51
  117. package/src/tools/create-channel.ts +24 -39
  118. package/src/tools/create-metric.ts +19 -82
  119. package/src/tools/create-page.ts +20 -81
  120. package/src/tools/credential-bindings/tool.ts +125 -246
  121. package/src/tools/db-query.ts +13 -24
  122. package/src/tools/delete-channel.ts +25 -71
  123. package/src/tools/delete-page.ts +27 -75
  124. package/src/tools/get-metrics.ts +29 -22
  125. package/src/tools/get-swarm.ts +44 -15
  126. package/src/tools/get-task-details.ts +150 -32
  127. package/src/tools/get-tasks.ts +63 -29
  128. package/src/tools/inject-learning.ts +8 -38
  129. package/src/tools/join-swarm.ts +45 -47
  130. package/src/tools/kv/kv-delete.ts +13 -37
  131. package/src/tools/kv/kv-get.ts +38 -29
  132. package/src/tools/kv/kv-incr.ts +28 -56
  133. package/src/tools/kv/kv-list.ts +40 -31
  134. package/src/tools/kv/kv-set.ts +34 -76
  135. package/src/tools/list-channels.ts +24 -17
  136. package/src/tools/list-services.ts +37 -45
  137. package/src/tools/manage-user.ts +53 -66
  138. package/src/tools/mcp-servers/mcp-server-create.ts +19 -55
  139. package/src/tools/mcp-servers/mcp-server-delete.ts +9 -39
  140. package/src/tools/mcp-servers/mcp-server-get.ts +9 -35
  141. package/src/tools/mcp-servers/mcp-server-install.ts +13 -60
  142. package/src/tools/mcp-servers/mcp-server-list.ts +28 -26
  143. package/src/tools/mcp-servers/mcp-server-uninstall.ts +10 -36
  144. package/src/tools/mcp-servers/mcp-server-update.ts +13 -57
  145. package/src/tools/memory-delete.ts +16 -46
  146. package/src/tools/memory-edit.ts +48 -49
  147. package/src/tools/memory-get.ts +40 -36
  148. package/src/tools/memory-rate.ts +14 -44
  149. package/src/tools/memory-search.ts +46 -52
  150. package/src/tools/my-agent-info.ts +39 -33
  151. package/src/tools/oauth-access-token.ts +9 -24
  152. package/src/tools/poll-task.ts +50 -77
  153. package/src/tools/post-message.ts +24 -38
  154. package/src/tools/prompt-templates/delete.ts +16 -48
  155. package/src/tools/prompt-templates/get.ts +45 -42
  156. package/src/tools/prompt-templates/list.ts +29 -44
  157. package/src/tools/prompt-templates/preview.ts +14 -40
  158. package/src/tools/prompt-templates/set.ts +30 -52
  159. package/src/tools/read-messages.ts +45 -54
  160. package/src/tools/register-agentmail-inbox.ts +28 -84
  161. package/src/tools/register-kapso-number.ts +20 -50
  162. package/src/tools/register-service.ts +36 -44
  163. package/src/tools/repos/get-repos.ts +29 -21
  164. package/src/tools/repos/update-repo.ts +30 -27
  165. package/src/tools/request-human-input.ts +10 -33
  166. package/src/tools/resolve-user.ts +59 -16
  167. package/src/tools/schedules/create-schedule.ts +58 -173
  168. package/src/tools/schedules/delete-schedule.ts +16 -56
  169. package/src/tools/schedules/list-schedules.ts +41 -67
  170. package/src/tools/schedules/patch-schedule.ts +57 -153
  171. package/src/tools/schedules/run-schedule-now.ts +18 -59
  172. package/src/tools/schedules/update-schedule.ts +57 -153
  173. package/src/tools/script-apis.ts +46 -28
  174. package/src/tools/script-common.ts +129 -34
  175. package/src/tools/script-connections/tool.ts +59 -154
  176. package/src/tools/script-query-types.ts +43 -3
  177. package/src/tools/script-run.ts +41 -2
  178. package/src/tools/script-runs.ts +69 -1
  179. package/src/tools/script-search.ts +24 -1
  180. package/src/tools/script-upsert.ts +18 -3
  181. package/src/tools/send-task.ts +32 -81
  182. package/src/tools/skills/skill-create.ts +14 -39
  183. package/src/tools/skills/skill-delete.ts +14 -42
  184. package/src/tools/skills/skill-get-file.ts +10 -43
  185. package/src/tools/skills/skill-get.ts +9 -32
  186. package/src/tools/skills/skill-install-remote.ts +16 -52
  187. package/src/tools/skills/skill-install.ts +14 -52
  188. package/src/tools/skills/skill-list.ts +24 -26
  189. package/src/tools/skills/skill-publish.ts +18 -54
  190. package/src/tools/skills/skill-search.ts +21 -18
  191. package/src/tools/skills/skill-sync-remote.ts +12 -24
  192. package/src/tools/skills/skill-uninstall.ts +10 -26
  193. package/src/tools/skills/skill-update.ts +24 -81
  194. package/src/tools/slack-delete.ts +8 -32
  195. package/src/tools/slack-download-file.ts +17 -57
  196. package/src/tools/slack-list-channels.ts +16 -43
  197. package/src/tools/slack-post.ts +11 -40
  198. package/src/tools/slack-read.ts +33 -103
  199. package/src/tools/slack-reply.ts +16 -53
  200. package/src/tools/slack-start-thread.ts +14 -55
  201. package/src/tools/slack-update.ts +8 -35
  202. package/src/tools/slack-upload-file.ts +22 -122
  203. package/src/tools/steer-task.ts +22 -35
  204. package/src/tools/store-progress.ts +11 -26
  205. package/src/tools/swarm-config/delete-config.ts +19 -57
  206. package/src/tools/swarm-config/get-config.ts +36 -44
  207. package/src/tools/swarm-config/list-config.ts +36 -44
  208. package/src/tools/swarm-config/set-config.ts +35 -73
  209. package/src/tools/swarm-x.ts +20 -26
  210. package/src/tools/task-action.ts +26 -46
  211. package/src/tools/task-tool-ctx.ts +5 -13
  212. package/src/tools/tracker/tracker-link-task.ts +8 -22
  213. package/src/tools/tracker/tracker-map-agent.ts +8 -22
  214. package/src/tools/tracker/tracker-status.ts +27 -19
  215. package/src/tools/tracker/tracker-sync-status.ts +8 -18
  216. package/src/tools/tracker/tracker-unlink.ts +4 -16
  217. package/src/tools/unregister-service.ts +19 -59
  218. package/src/tools/update-profile.ts +60 -100
  219. package/src/tools/update-service-status.ts +42 -67
  220. package/src/tools/utils.ts +189 -8
  221. package/src/tools/whatsapp-message.ts +19 -32
  222. package/src/tools/workflows/cancel-workflow-run.ts +4 -16
  223. package/src/tools/workflows/create-workflow.ts +9 -38
  224. package/src/tools/workflows/delete-workflow.ts +5 -17
  225. package/src/tools/workflows/get-workflow-run.ts +41 -31
  226. package/src/tools/workflows/get-workflow.ts +10 -21
  227. package/src/tools/workflows/list-workflow-runs.ts +107 -34
  228. package/src/tools/workflows/list-workflows.ts +5 -17
  229. package/src/tools/workflows/patch-workflow-node.ts +11 -39
  230. package/src/tools/workflows/patch-workflow.ts +11 -36
  231. package/src/tools/workflows/retry-workflow-run.ts +4 -16
  232. package/src/tools/workflows/trigger-workflow.ts +22 -58
  233. package/src/tools/workflows/update-workflow.ts +10 -42
  234. package/templates/skills/swarm-scripts/SKILL.md +1 -1
@@ -13,7 +13,7 @@ import {
13
13
  import { mergeScheduleTiming, validateRecurringTiming } from "@/be/schedules/validate";
14
14
  import { getScript } from "@/be/scripts/db";
15
15
  import { calculateNextRun } from "@/scheduler";
16
- import { createToolRegistrar } from "@/tools/utils";
16
+ import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
17
17
  import {
18
18
  AssetKeySchema,
19
19
  ModelTierSchema,
@@ -73,6 +73,34 @@ export const updateScheduleInputSchema = z.object({
73
73
  .describe("Portable model tier for tasks created by this schedule. Set to null to clear."),
74
74
  });
75
75
 
76
+ const scheduleDataShape = {
77
+ id: z.string().optional(),
78
+ key: AssetKeySchema.optional(),
79
+ name: z.string().optional(),
80
+ description: z.string().optional(),
81
+ cronExpression: z.string().optional(),
82
+ intervalMs: z.number().optional(),
83
+ taskTemplate: z.string().optional(),
84
+ taskType: z.string().optional(),
85
+ tags: z.array(z.string()).optional(),
86
+ priority: z.number().optional(),
87
+ targetAgentId: z.string().optional(),
88
+ enabled: z.boolean().optional(),
89
+ lastRunAt: z.string().optional(),
90
+ nextRunAt: z.string().optional(),
91
+ createdByAgentId: z.string().optional(),
92
+ timezone: z.string().optional(),
93
+ model: z.string().optional(),
94
+ modelTier: ModelTierSchema.optional(),
95
+ scheduleType: z.string().optional(),
96
+ targetType: ScheduledTaskTargetTypeSchema.optional(),
97
+ workflowId: z.string().optional(),
98
+ scriptName: z.string().optional(),
99
+ scriptArgs: z.record(z.string(), z.unknown()).optional(),
100
+ createdAt: z.string().optional(),
101
+ lastUpdatedAt: z.string().optional(),
102
+ };
103
+
76
104
  export const registerUpdateScheduleTool = (server: McpServer) => {
77
105
  createToolRegistrar(server)(
78
106
  "update-schedule",
@@ -81,39 +109,9 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
81
109
  annotations: { idempotentHint: true },
82
110
  description: "Update an existing scheduled task. Any registered agent can update schedules.",
83
111
  inputSchema: updateScheduleInputSchema,
84
- outputSchema: z.object({
112
+ outputSchema: swarmToolOutputSchema({
85
113
  yourAgentId: z.string().optional(),
86
- success: z.boolean(),
87
- message: z.string(),
88
- schedule: z
89
- .object({
90
- id: z.string(),
91
- key: AssetKeySchema,
92
- name: z.string(),
93
- description: z.string().optional(),
94
- cronExpression: z.string().optional(),
95
- intervalMs: z.number().optional(),
96
- taskTemplate: z.string().optional(),
97
- taskType: z.string().optional(),
98
- tags: z.array(z.string()),
99
- priority: z.number(),
100
- targetAgentId: z.string().optional(),
101
- enabled: z.boolean(),
102
- lastRunAt: z.string().optional(),
103
- nextRunAt: z.string().optional(),
104
- createdByAgentId: z.string().optional(),
105
- timezone: z.string(),
106
- model: z.string().optional(),
107
- modelTier: ModelTierSchema.optional(),
108
- scheduleType: z.string(),
109
- targetType: ScheduledTaskTargetTypeSchema.optional(),
110
- workflowId: z.string().optional(),
111
- scriptName: z.string().optional(),
112
- scriptArgs: z.record(z.string(), z.unknown()).optional(),
113
- createdAt: z.string(),
114
- lastUpdatedAt: z.string(),
115
- })
116
- .optional(),
114
+ schedule: z.looseObject(scheduleDataShape).optional(),
117
115
  }),
118
116
  },
119
117
  async (
@@ -143,13 +141,7 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
143
141
  _meta,
144
142
  ) => {
145
143
  if (!requestInfo.agentId) {
146
- return {
147
- content: [{ type: "text", text: 'Agent ID not found. Set the "X-Agent-ID" header.' }],
148
- structuredContent: {
149
- success: false,
150
- message: 'Agent ID not found. Set the "X-Agent-ID" header.',
151
- },
152
- };
144
+ return toolErr('Agent ID not found. Set the "X-Agent-ID" header.');
153
145
  }
154
146
 
155
147
  // Find the schedule
@@ -160,40 +152,19 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
160
152
  : null;
161
153
 
162
154
  if (!schedule) {
163
- return {
164
- content: [{ type: "text", text: "Schedule not found." }],
165
- structuredContent: {
166
- success: false,
167
- message: "Schedule not found.",
168
- },
169
- };
155
+ return toolErr("Schedule not found.");
170
156
  }
171
157
 
172
158
  const caller = getAgentById(requestInfo.agentId);
173
159
  if (!caller) {
174
- return {
175
- content: [{ type: "text", text: "Agent not found." }],
176
- structuredContent: {
177
- success: false,
178
- message: "Agent not found.",
179
- },
180
- };
160
+ return toolErr("Agent not found.");
181
161
  }
182
162
 
183
163
  // Reject updates on completed one-time schedules
184
164
  if (schedule.scheduleType === "one_time" && !schedule.enabled && schedule.lastRunAt) {
185
- return {
186
- content: [
187
- {
188
- type: "text",
189
- text: `One-time schedule "${schedule.name}" has already executed. Create a new one instead.`,
190
- },
191
- ],
192
- structuredContent: {
193
- success: false,
194
- message: `One-time schedule "${schedule.name}" has already executed. Create a new one instead.`,
195
- },
196
- };
165
+ return toolErr(
166
+ `One-time schedule "${schedule.name}" has already executed. Create a new one instead.`,
167
+ );
197
168
  }
198
169
 
199
170
  // Validate new cron expression if provided
@@ -204,13 +175,7 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
204
175
  });
205
176
  } catch (err) {
206
177
  const message = err instanceof Error ? err.message : "Invalid cron expression";
207
- return {
208
- content: [{ type: "text", text: `Invalid cron expression: ${message}` }],
209
- structuredContent: {
210
- success: false,
211
- message: `Invalid cron expression: ${message}`,
212
- },
213
- };
178
+ return toolErr(`Invalid cron expression: ${message}`);
214
179
  }
215
180
  }
216
181
 
@@ -218,13 +183,7 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
218
183
  if (targetAgentId && targetAgentId !== null) {
219
184
  const agent = getAgentById(targetAgentId);
220
185
  if (!agent) {
221
- return {
222
- content: [{ type: "text", text: `Target agent not found: ${targetAgentId}` }],
223
- structuredContent: {
224
- success: false,
225
- message: `Target agent not found: ${targetAgentId}`,
226
- },
227
- };
186
+ return toolErr(`Target agent not found: ${targetAgentId}`);
228
187
  }
229
188
  }
230
189
 
@@ -232,13 +191,7 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
232
191
  if (newName && newName !== schedule.name) {
233
192
  const existing = getScheduledTaskByName(newName);
234
193
  if (existing) {
235
- return {
236
- content: [{ type: "text", text: `Schedule with name "${newName}" already exists.` }],
237
- structuredContent: {
238
- success: false,
239
- message: `Schedule with name "${newName}" already exists.`,
240
- },
241
- };
194
+ return toolErr(`Schedule with name "${newName}" already exists.`);
242
195
  }
243
196
  }
244
197
 
@@ -249,42 +202,22 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
249
202
  const mergedScriptName = scriptName !== undefined ? scriptName : schedule.scriptName;
250
203
 
251
204
  if (mergedTargetType === "agent-task" && !mergedTaskTemplate) {
252
- const message = "taskTemplate is required when targetType is 'agent-task'.";
253
- return {
254
- content: [{ type: "text", text: message }],
255
- structuredContent: { success: false, message },
256
- };
205
+ return toolErr("taskTemplate is required when targetType is 'agent-task'.");
257
206
  }
258
207
  if (mergedTargetType === "workflow") {
259
208
  if (!mergedWorkflowId) {
260
- const message = "workflowId is required when targetType is 'workflow'.";
261
- return {
262
- content: [{ type: "text", text: message }],
263
- structuredContent: { success: false, message },
264
- };
209
+ return toolErr("workflowId is required when targetType is 'workflow'.");
265
210
  }
266
211
  if (!getWorkflow(mergedWorkflowId)) {
267
- const message = `Workflow not found: ${mergedWorkflowId}`;
268
- return {
269
- content: [{ type: "text", text: message }],
270
- structuredContent: { success: false, message },
271
- };
212
+ return toolErr(`Workflow not found: ${mergedWorkflowId}`);
272
213
  }
273
214
  }
274
215
  if (mergedTargetType === "script") {
275
216
  if (!mergedScriptName) {
276
- const message = "scriptName is required when targetType is 'script'.";
277
- return {
278
- content: [{ type: "text", text: message }],
279
- structuredContent: { success: false, message },
280
- };
217
+ return toolErr("scriptName is required when targetType is 'script'.");
281
218
  }
282
219
  if (!getScript({ name: mergedScriptName, scope: "global" })) {
283
- const message = `Script not found: ${mergedScriptName}`;
284
- return {
285
- content: [{ type: "text", text: message }],
286
- structuredContent: { success: false, message },
287
- };
220
+ return toolErr(`Script not found: ${mergedScriptName}`);
288
221
  }
289
222
  }
290
223
 
@@ -333,19 +266,9 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
333
266
  );
334
267
  const timingError = validateRecurringTiming(timing);
335
268
  if (timingError) {
336
- return {
337
- content: [
338
- {
339
- type: "text",
340
- text: "At least one of intervalMs or cronExpression must be set for recurring schedules.",
341
- },
342
- ],
343
- structuredContent: {
344
- success: false,
345
- message:
346
- "At least one of intervalMs or cronExpression must be set for recurring schedules.",
347
- },
348
- };
269
+ return toolErr(
270
+ "At least one of intervalMs or cronExpression must be set for recurring schedules.",
271
+ );
349
272
  }
350
273
 
351
274
  const needsNextRunRecalc =
@@ -375,39 +298,20 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
375
298
  const updated = updateScheduledTask(schedule.id, { ...updateData, updatedBy });
376
299
 
377
300
  if (!updated) {
378
- return {
379
- content: [{ type: "text", text: "Failed to update schedule." }],
380
- structuredContent: {
381
- success: false,
382
- message: "Failed to update schedule.",
383
- },
384
- };
301
+ return toolErr("Failed to update schedule.");
385
302
  }
386
303
 
387
- return {
388
- content: [
389
- {
390
- type: "text",
391
- text: `Updated schedule "${updated.name}". Next run: ${updated.nextRunAt || "disabled"}`,
392
- },
393
- ],
394
- structuredContent: {
395
- yourAgentId: requestInfo.agentId,
396
- success: true,
397
- message: `Updated schedule "${updated.name}".`,
398
- schedule: updated,
304
+ return toolOk(
305
+ `Updated schedule "${updated.name}". Next run: ${updated.nextRunAt || "disabled"}`,
306
+ {
307
+ data: { yourAgentId: requestInfo.agentId, schedule: updated },
399
308
  },
400
- };
309
+ );
401
310
  } catch (error) {
402
311
  const message = error instanceof Error ? error.message : "Unknown error";
403
- return {
404
- content: [{ type: "text", text: `Failed to update schedule: ${message}` }],
405
- structuredContent: {
406
- yourAgentId: requestInfo.agentId,
407
- success: false,
408
- message: `Failed to update schedule: ${message}`,
409
- },
410
- };
312
+ return toolErr(`Failed to update schedule: ${message}`, {
313
+ data: { yourAgentId: requestInfo.agentId },
314
+ });
411
315
  }
412
316
  },
413
317
  );
@@ -1,10 +1,9 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
2
  import * as z from "zod";
4
3
  import { getAgentById } from "@/be/db";
5
4
  import { can, type PermissionVerb } from "@/rbac";
6
- import type { RequestInfo } from "@/tools/utils";
7
- import { createToolRegistrar } from "@/tools/utils";
5
+ import type { RequestInfo, SwarmToolResult } from "@/tools/utils";
6
+ import { createToolRegistrar, toolErr, toolOk } from "@/tools/utils";
8
7
  import type { ScriptApiRecord, ScriptApiWithSecret } from "@/types";
9
8
  import { registerVolatileSecret } from "@/utils/secret-scrubber";
10
9
  import { proxyScriptsApi, SCRIPT_TRANSPORT_ERROR, scriptToolOutputSchema } from "./script-common";
@@ -44,20 +43,23 @@ function maskToken(endpoint: RawEndpoint): RawEndpoint {
44
43
  return { ...rest, token: endpoint.authMode === "bearer" ? "********" : null };
45
44
  }
46
45
 
47
- function errorResult(message: string, status = 400): CallToolResult {
48
- return {
49
- isError: true,
50
- content: [{ type: "text", text: message }],
51
- structuredContent: { success: false, status, error: message },
52
- };
46
+ function renderEndpointsList(endpoints: RawEndpoint[]): string | undefined {
47
+ if (endpoints.length === 0) return undefined;
48
+ return endpoints
49
+ .map((endpoint) => {
50
+ const label = endpoint.label ? ` "${endpoint.label}"` : "";
51
+ const token = endpoint.token ? `, token=${endpoint.token}` : "";
52
+ return `- ${endpoint.id}${label}: authMode=${endpoint.authMode}, enabled=${endpoint.enabled}${token}`;
53
+ })
54
+ .join("\n");
53
55
  }
54
56
 
55
57
  function requireScriptApiPermission(
56
58
  requestInfo: RequestInfo,
57
59
  verb: PermissionVerb,
58
60
  message: string,
59
- ): CallToolResult | null {
60
- if (!requestInfo.agentId) return errorResult(SCRIPT_TRANSPORT_ERROR);
61
+ ): SwarmToolResult | null {
62
+ if (!requestInfo.agentId) return toolErr(SCRIPT_TRANSPORT_ERROR);
61
63
 
62
64
  const agent = getAgentById(requestInfo.agentId);
63
65
  const decision = can({
@@ -70,7 +72,7 @@ function requireScriptApiPermission(
70
72
  resource: { kind: "none" },
71
73
  source: "mcp",
72
74
  });
73
- return decision.allow ? null : errorResult(message, 403);
75
+ return decision.allow ? null : toolErr(message, { data: { status: 403 } });
74
76
  }
75
77
 
76
78
  export const registerScriptApisTool = (server: McpServer) => {
@@ -92,8 +94,8 @@ export const registerScriptApisTool = (server: McpServer) => {
92
94
  requestInfo,
93
95
  successMessage: () => "",
94
96
  });
95
- if (result.isError) return result;
96
- const raw = (result.structuredContent as { data?: { apis?: RawEndpoint[] } })?.data;
97
+ if (!result.ok) return result;
98
+ const raw = (result.data as { data?: { apis?: RawEndpoint[] } } | undefined)?.data;
97
99
  let endpoints = (raw?.apis ?? []).map(maskToken);
98
100
 
99
101
  if (args.includeSecrets) {
@@ -113,8 +115,8 @@ export const registerScriptApisTool = (server: McpServer) => {
113
115
  requestInfo,
114
116
  successMessage: () => "",
115
117
  });
116
- if (secretResult.isError) return endpoint;
117
- const token = (secretResult.structuredContent as { data?: { token?: string | null } })
118
+ if (!secretResult.ok) return endpoint;
119
+ const token = (secretResult.data as { data?: { token?: string | null } } | undefined)
118
120
  ?.data?.token;
119
121
  if (token) registerVolatileSecret(token, `script-api:${endpoint.id}`);
120
122
  return { ...endpoint, token: token ?? null };
@@ -122,10 +124,12 @@ export const registerScriptApisTool = (server: McpServer) => {
122
124
  );
123
125
  }
124
126
 
125
- return {
126
- content: [{ type: "text", text: `Found ${endpoints.length} endpoint(s).` }],
127
- structuredContent: { success: true, status: 200, data: { apis: endpoints } },
128
- };
127
+ return toolOk(`Found ${endpoints.length} endpoint(s).`, {
128
+ details: renderEndpointsList(endpoints),
129
+ data: { status: 200, data: { apis: endpoints } },
130
+ // Deliberate reveal path — includeSecrets returns real bearer tokens.
131
+ allowSecretEgress: args.includeSecrets === true,
132
+ });
129
133
  }
130
134
 
131
135
  if (args.action === "create") {
@@ -146,16 +150,23 @@ export const registerScriptApisTool = (server: McpServer) => {
146
150
  },
147
151
  requestInfo,
148
152
  successMessage: (data) => `Endpoint ${(data as ScriptApiWithSecret).id} created.`,
153
+ successDetails: (data) => {
154
+ const token = (data as ScriptApiWithSecret).token;
155
+ return token ? `Bearer token (shown once — save it now): ${token}` : undefined;
156
+ },
149
157
  });
150
- if (!result.isError) {
151
- const endpoint = (result.structuredContent as { data?: ScriptApiWithSecret })?.data;
158
+ if (result.ok) {
159
+ const endpoint = (result.data as { data?: ScriptApiWithSecret } | undefined)?.data;
152
160
  if (endpoint?.token) registerVolatileSecret(endpoint.token, `script-api:${endpoint.id}`);
161
+ // Deliberate one-time reveal — the volatile secret registered above
162
+ // would otherwise be redacted by the finalize scrubber.
163
+ return { ...result, allowSecretEgress: true };
153
164
  }
154
165
  return result;
155
166
  }
156
167
 
157
168
  if (args.action === "rotate") {
158
- if (!args.endpointId) return errorResult("endpointId is required for rotate.");
169
+ if (!args.endpointId) return toolErr("endpointId is required for rotate.");
159
170
  const denied = requireScriptApiPermission(
160
171
  requestInfo,
161
172
  "script.api.rotate",
@@ -168,16 +179,23 @@ export const registerScriptApisTool = (server: McpServer) => {
168
179
  path: `/api/scripts/${args.scriptId}/apis/${args.endpointId}/rotate`,
169
180
  requestInfo,
170
181
  successMessage: () => "Token rotated.",
182
+ successDetails: (data) => {
183
+ const token = (data as ScriptApiWithSecret).token;
184
+ return token ? `New bearer token (shown once — save it now): ${token}` : undefined;
185
+ },
171
186
  });
172
- if (!result.isError) {
173
- const endpoint = (result.structuredContent as { data?: ScriptApiWithSecret })?.data;
187
+ if (result.ok) {
188
+ const endpoint = (result.data as { data?: ScriptApiWithSecret } | undefined)?.data;
174
189
  if (endpoint?.token) registerVolatileSecret(endpoint.token, `script-api:${endpoint.id}`);
190
+ // Deliberate one-time reveal — the volatile secret registered above
191
+ // would otherwise be redacted by the finalize scrubber.
192
+ return { ...result, allowSecretEgress: true };
175
193
  }
176
194
  return result;
177
195
  }
178
196
 
179
197
  if (args.action === "update") {
180
- if (!args.endpointId) return errorResult("endpointId is required for update.");
198
+ if (!args.endpointId) return toolErr("endpointId is required for update.");
181
199
  const denied = requireScriptApiPermission(
182
200
  requestInfo,
183
201
  "script.api.update",
@@ -195,7 +213,7 @@ export const registerScriptApisTool = (server: McpServer) => {
195
213
  }
196
214
 
197
215
  if (args.action === "delete") {
198
- if (!args.endpointId) return errorResult("endpointId is required for delete.");
216
+ if (!args.endpointId) return toolErr("endpointId is required for delete.");
199
217
  const denied = requireScriptApiPermission(
200
218
  requestInfo,
201
219
  "script.api.delete",
@@ -211,7 +229,7 @@ export const registerScriptApisTool = (server: McpServer) => {
211
229
  });
212
230
  }
213
231
 
214
- return errorResult(`Unknown action: ${args.action}`);
232
+ return toolErr(`Unknown action: ${args.action}`);
215
233
  },
216
234
  );
217
235
  };
@@ -1,8 +1,13 @@
1
- import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
1
  import * as z from "zod";
3
2
  import { getApiKey } from "@/utils/api-key";
4
3
  import { getMcpBaseUrl } from "@/utils/constants";
5
- import type { RequestInfo } from "./utils";
4
+ import {
5
+ type RequestInfo,
6
+ type SwarmToolResult,
7
+ swarmToolOutputSchema,
8
+ toolErr,
9
+ toolOk,
10
+ } from "./utils";
6
11
 
7
12
  export const SCRIPT_TRANSPORT_ERROR =
8
13
  "script_* tools require HTTP MCP transport — agent identity is not available over stdio in this build. Switch to MCP_BASE_URL=http://... or invoke the HTTP API directly.";
@@ -11,29 +16,108 @@ export const scriptNameSchema = z.string().min(1).max(200);
11
16
  export const scriptScopeSchema = z.enum(["agent", "global"]);
12
17
  export const scriptFsModeSchema = z.enum(["none", "workspace-rw"]);
13
18
 
14
- export const scriptToolOutputSchema = z.object({
15
- success: z.boolean(),
16
- status: z.number(),
19
+ export const scriptToolOutputSchema = swarmToolOutputSchema({
20
+ status: z.number().optional(),
17
21
  data: z.unknown().optional(),
18
- error: z.string().optional(),
19
22
  });
20
23
 
21
- export type ScriptToolStructuredContent = z.infer<typeof scriptToolOutputSchema>;
24
+ const DETAILS_CAP = 16_000;
25
+
26
+ function stripAnsi(text: string): string {
27
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping terminal color codes
28
+ return text.replace(/\x1b\[[0-9;]*m/g, "");
29
+ }
30
+
31
+ export function capDetails(text: string | undefined): string | undefined {
32
+ if (!text) return undefined;
33
+ if (text.length <= DETAILS_CAP) return text;
34
+ return `${text.slice(0, DETAILS_CAP)}\n… [truncated ${text.length - DETAILS_CAP} chars]`;
35
+ }
36
+
37
+ function isRecord(value: unknown): value is Record<string, unknown> {
38
+ return typeof value === "object" && value !== null && !Array.isArray(value);
39
+ }
22
40
 
23
41
  function apiBaseUrl(): string {
24
42
  return getMcpBaseUrl();
25
43
  }
26
44
 
27
- function toolError(message: string, status = 400): CallToolResult {
28
- return {
29
- isError: true,
30
- content: [{ type: "text", text: message }],
31
- structuredContent: {
32
- success: false,
33
- status,
34
- error: message,
35
- } satisfies ScriptToolStructuredContent,
36
- };
45
+ type ScriptFailure = { message: string; details?: string };
46
+
47
+ /**
48
+ * Honest failure detection for the scripts API. The API returns HTTP 200 for
49
+ * script runs that failed at runtime (error / runtimeError / exitCode live in
50
+ * the body) and durable runs carry `run.status` / `run.error` — trusting
51
+ * `res.ok` alone reports failures as successes.
52
+ */
53
+ function describeScriptFailure(
54
+ resOk: boolean,
55
+ status: number,
56
+ data: unknown,
57
+ ): ScriptFailure | undefined {
58
+ const body = isRecord(data) ? data : undefined;
59
+
60
+ if (!resOk) {
61
+ const error = body && "error" in body ? String(body.error) : undefined;
62
+
63
+ // Typecheck rejection: fold real diagnostics instead of the literal "typecheck_failed".
64
+ const diagnostics =
65
+ body && Array.isArray(body.diagnostics)
66
+ ? (body.diagnostics as unknown[]).map((d) => stripAnsi(String(d)))
67
+ : undefined;
68
+ if (error === "typecheck_failed" && diagnostics && diagnostics.length > 0) {
69
+ const more = diagnostics.length > 1 ? ` (+${diagnostics.length - 1} more)` : "";
70
+ const firstLine = diagnostics[0]?.split("\n")[0] ?? "unknown diagnostic";
71
+ return {
72
+ message: `Typecheck failed: ${firstLine}${more}`,
73
+ details: capDetails(diagnostics.join("\n\n")),
74
+ };
75
+ }
76
+
77
+ // Label-lint / policy rejections: fold violation detail.
78
+ const violations =
79
+ body && Array.isArray(body.violations) ? (body.violations as unknown[]) : undefined;
80
+ if (violations && violations.length > 0) {
81
+ return {
82
+ message: `${error ?? `Scripts API request failed with ${status}`} — ${violations.length} violation(s)`,
83
+ details: capDetails(
84
+ violations.map((v) => (typeof v === "string" ? v : JSON.stringify(v))).join("\n"),
85
+ ),
86
+ };
87
+ }
88
+
89
+ return { message: error ?? `Scripts API request failed with ${status}` };
90
+ }
91
+
92
+ // HTTP 200 inline-run body: failure signalled inside the payload.
93
+ if (body && ("exitCode" in body || "runtimeError" in body || "stderr" in body)) {
94
+ const runtimeError = isRecord(body.runtimeError)
95
+ ? `${String(body.runtimeError.name ?? "Error")}: ${String(body.runtimeError.message ?? "")}`
96
+ : undefined;
97
+ const error = typeof body.error === "string" && body.error ? body.error : undefined;
98
+ const exitCode = typeof body.exitCode === "number" ? body.exitCode : 0;
99
+ if (error || runtimeError || exitCode !== 0) {
100
+ const reason =
101
+ [error, runtimeError].filter(Boolean).join(" — ") || `script exited with code ${exitCode}`;
102
+ const stderr =
103
+ typeof body.stderr === "string" && body.stderr.trim() ? body.stderr.trim() : undefined;
104
+ return {
105
+ message: `Script run failed: ${reason}`,
106
+ details: capDetails(stderr ? `stderr:\n${stderr}` : undefined),
107
+ };
108
+ }
109
+ }
110
+
111
+ // Durable run body: { run: { status, error, ... } }.
112
+ if (body && isRecord(body.run) && body.run.status === "failed") {
113
+ const runError =
114
+ typeof body.run.error === "string" && body.run.error ? body.run.error : "unknown error";
115
+ return {
116
+ message: `Script run ${String(body.run.id ?? "")} failed: ${runError}`.replace(" ", " "),
117
+ };
118
+ }
119
+
120
+ return undefined;
37
121
  }
38
122
 
39
123
  export async function proxyScriptsApi(args: {
@@ -42,8 +126,22 @@ export async function proxyScriptsApi(args: {
42
126
  body?: unknown;
43
127
  requestInfo: RequestInfo;
44
128
  successMessage: (data: unknown) => string;
45
- }): Promise<CallToolResult> {
46
- if (!args.requestInfo.agentId) return toolError(SCRIPT_TRANSPORT_ERROR);
129
+ /** Optional model-facing rendering of the success payload (appended to the text channel). */
130
+ successDetails?: (data: unknown) => string | undefined;
131
+ /**
132
+ * Optional model-facing rendering of the payload when the call fails, used
133
+ * when the failure itself carries no details (e.g. a failed durable run whose
134
+ * journal holds the step errors).
135
+ */
136
+ failureDetails?: (data: unknown) => string | undefined;
137
+ /**
138
+ * Skip the generic details cap. Only for payloads that are authoritative in
139
+ * full (e.g. script-query-types' type blobs, which exceed the cap and have
140
+ * no pagination) — everything else stays capped.
141
+ */
142
+ uncappedDetails?: boolean;
143
+ }): Promise<SwarmToolResult> {
144
+ if (!args.requestInfo.agentId) return toolErr(SCRIPT_TRANSPORT_ERROR);
47
145
 
48
146
  const apiKey = getApiKey();
49
147
  const headers: Record<string, string> = {
@@ -70,21 +168,18 @@ export async function proxyScriptsApi(args: {
70
168
  }
71
169
  }
72
170
 
73
- const error =
74
- typeof data === "object" && data !== null && "error" in data
75
- ? String((data as { error: unknown }).error)
76
- : undefined;
77
-
78
- if (!res.ok) {
79
- return toolError(error ?? `Scripts API request failed with ${res.status}`, res.status);
171
+ const toolData = { status: res.status, data };
172
+ const failure = describeScriptFailure(res.ok, res.status, data);
173
+ if (failure) {
174
+ return toolErr(failure.message, {
175
+ details: failure.details ?? capDetails(args.failureDetails?.(data)),
176
+ data: toolData,
177
+ });
80
178
  }
81
179
 
82
- return {
83
- content: [{ type: "text", text: args.successMessage(data) }],
84
- structuredContent: {
85
- success: true,
86
- status: res.status,
87
- data,
88
- } satisfies ScriptToolStructuredContent,
89
- };
180
+ const successDetails = args.successDetails?.(data);
181
+ return toolOk(args.successMessage(data), {
182
+ details: args.uncappedDetails ? successDetails : capDetails(successDetails),
183
+ data: toolData,
184
+ });
90
185
  }