@desplega.ai/agent-swarm 1.129.0 → 1.131.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 (226) hide show
  1. package/README.md +2 -2
  2. package/dist/{actions-kfv2dawe.js → actions-w53mbns9.js} +11 -11
  3. package/dist/{anthropic-messages-3xgme8my.js → anthropic-messages-rjmd2e7n.js} +5 -5
  4. package/dist/{app-q8vjk6e8.js → app-m9gwqsqv.js} +8 -8
  5. package/dist/{artifact-hg4t1nx7.js → artifact-jysj3wrg.js} +2 -2
  6. package/dist/{assistant-g037r345.js → assistant-bp65m6f3.js} +13 -13
  7. package/dist/{azure-openai-responses-xa4sh9f6.js → azure-openai-responses-c0yjmbfd.js} +8 -5
  8. package/dist/{boot-reembed-18pzsrxa.js → boot-reembed-62p0at2k.js} +6 -6
  9. package/dist/{boot-reembed-wpks2cj8.js → boot-reembed-xmfp6wdh.js} +7 -7
  10. package/dist/{boot-scrub-logs-n7szd57r.js → boot-scrub-logs-rr8q2dms.js} +5 -5
  11. package/dist/{claude-adapter-qz4r0ajt.js → claude-adapter-2y47mrkd.js} +9 -9
  12. package/dist/{cli-mhjnbgb6.js → cli-0rcyq6d9.js} +450 -186
  13. package/dist/{cli-ttkmehy0.js → cli-2q72qz2s.js} +274 -25
  14. package/dist/{cli-gztgc3hz.js → cli-2wadx7p2.js} +2 -2
  15. package/dist/cli-3fj60etf.js +153 -0
  16. package/dist/{cli-7ecgm5v0.js → cli-3kf51j15.js} +1 -1
  17. package/dist/{cli-vgexpfsr.js → cli-57khth04.js} +6 -3
  18. package/dist/{cli-2mbe7vk0.js → cli-5dsbcjjw.js} +1 -1
  19. package/dist/{cli-etr62zy6.js → cli-5kx36evn.js} +2 -2
  20. package/dist/{cli-v2tr6amf.js → cli-5n6e5yfn.js} +30 -8
  21. package/dist/{cli-hx02s9zq.js → cli-5v4rw6yd.js} +2 -2
  22. package/dist/{cli-v37yr6mf.js → cli-5ze9rv97.js} +4 -4
  23. package/dist/{cli-ch7t5v7n.js → cli-6map995v.js} +9 -4
  24. package/dist/{cli-h0457a67.js → cli-70xqj0z3.js} +3 -3
  25. package/dist/{cli-kk4f9r17.js → cli-72q8m8gk.js} +8 -6
  26. package/dist/{cli-7padk69a.js → cli-78xwvkhj.js} +2 -2
  27. package/dist/{cli-dxtxacy7.js → cli-9dr5qzc0.js} +45 -8
  28. package/dist/{cli-17zxdgsq.js → cli-a40rnf71.js} +6 -6
  29. package/dist/{cli-t53vnnxz.js → cli-a4jzdpa3.js} +2514 -1055
  30. package/dist/{cli-w10fkrf1.js → cli-akdvwc9v.js} +354 -115
  31. package/dist/{cli-1qkk5er0.js → cli-bmze986d.js} +1 -1
  32. package/dist/{cli-ga12dc1d.js → cli-bqw0mt1n.js} +3 -3
  33. package/dist/{cli-0sf92efp.js → cli-cdeg08wx.js} +1 -1
  34. package/dist/{cli-a8ynhkyj.js → cli-cvgjwnh1.js} +17 -15
  35. package/dist/{cli-9fxg3v5m.js → cli-d2tfkcga.js} +4 -4
  36. package/dist/{cli-nhcd7x43.js → cli-d3x3x1c9.js} +2 -2
  37. package/dist/{cli-6nh2qxvh.js → cli-darcwax3.js} +1 -1
  38. package/dist/{cli-yjbgmysx.js → cli-e7ckcg4v.js} +12 -12
  39. package/dist/{cli-4n2gcjpd.js → cli-efxfnq6q.js} +4 -3
  40. package/dist/{cli-z9xqqhfk.js → cli-er3a4bb0.js} +1 -1
  41. package/dist/{cli-xg2eezyv.js → cli-f1am9szv.js} +1 -1
  42. package/dist/{cli-xy4cwsm9.js → cli-fywms69x.js} +1 -1
  43. package/dist/{cli-33jj76y8.js → cli-g6b8x1h8.js} +1 -1
  44. package/dist/{cli-epsvdckx.js → cli-gm9cbhbq.js} +20 -10
  45. package/dist/{cli-rkndnn90.js → cli-k9qkf1hw.js} +6 -4
  46. package/dist/{cli-zejvt395.js → cli-kh3ba2zf.js} +1 -1
  47. package/dist/{cli-gb25p1h2.js → cli-nm5gtp1x.js} +2 -2
  48. package/dist/{cli-akxnpx17.js → cli-pm6xbh84.js} +1829 -1629
  49. package/dist/{cli-2k0ztbfp.js → cli-psh3c4va.js} +1 -1
  50. package/dist/{cli-39vgrsep.js → cli-qv61hp37.js} +2 -2
  51. package/dist/{cli-hv2fndhm.js → cli-r6kqqbj0.js} +6 -4
  52. package/dist/{cli-1y1cm570.js → cli-rzbpmxdr.js} +1 -1
  53. package/dist/{cli-mmrk00vp.js → cli-t1xze5qh.js} +1 -1
  54. package/dist/{cli-g252a9ky.js → cli-t55yw3a1.js} +2 -2
  55. package/dist/{cli-7bz8dzqa.js → cli-t76vw5db.js} +1184 -1522
  56. package/dist/{cli-4n4mdv21.js → cli-xpxde7rm.js} +7 -3
  57. package/dist/{cli-fpaa5k2q.js → cli-yw8vghx2.js} +54 -18
  58. package/dist/cli-zsspvvm2.js +490 -0
  59. package/dist/cli.js +27 -25
  60. package/dist/{codex-adapter-7v6t7ff6.js → codex-adapter-d24q75wk.js} +7 -7
  61. package/dist/{codex-hook-amndawyb.js → codex-hook-hq54t9ft.js} +2 -2
  62. package/dist/{codex-session-runner-e7j4khe2.js → codex-session-runner-g50vcsex.js} +7 -7
  63. package/dist/{commands-ex0k5tbf.js → commands-c3nrc5hg.js} +5 -5
  64. package/dist/{db-df7jatfc.js → db-nt7fg3tw.js} +5 -5
  65. package/dist/{google-generative-ai-y42hnw9p.js → google-generative-ai-hysrd8qx.js} +7 -5
  66. package/dist/{google-vertex-4sg66gb6.js → google-vertex-p5arrga0.js} +7 -5
  67. package/dist/{handlers-1j2cfqt2.js → handlers-fkr6swat.js} +13 -13
  68. package/dist/{hook-nx9a0mj3.js → hook-77wpvnjc.js} +7 -7
  69. package/dist/{hook-w6yaf6sy.js → hook-g019yehy.js} +8 -8
  70. package/dist/{http-4tz5h8wg.js → http-jewj0syk.js} +2993 -716
  71. package/dist/{index-0bpkzb09.js → index-070v4eyb.js} +3 -3
  72. package/dist/{index-0s67qv7h.js → index-6gm5he8h.js} +11 -11
  73. package/dist/{index-35gtv6gj.js → index-7n2xpbd4.js} +310 -40
  74. package/dist/{index-ker3gccb.js → index-9znhar3x.js} +4 -4
  75. package/dist/{index-5n2c8yay.js → index-hase6zbe.js} +2 -2
  76. package/dist/{index-kcf3tm9h.js → index-hfvggd0z.js} +12 -12
  77. package/dist/{index-7c0jqna0.js → index-hp1kdbjj.js} +13 -13
  78. package/dist/{keepalive-3j4a9168.js → keepalive-j0fjvqfd.js} +7 -7
  79. package/dist/{lead-6kpevymb.js → lead-1n8x30xe.js} +33 -33
  80. package/dist/{maintenance-m1k82ean.js → maintenance-gvev11sa.js} +7 -7
  81. package/dist/{mistral-conversations-bjkdfb6z.js → mistral-conversations-81fwav0j.js} +6 -6
  82. package/dist/{oauth-refresh-sweep-881k8e4q.js → oauth-refresh-sweep-s9ecsvh0.js} +6 -6
  83. package/dist/{onboard-c0ykffjn.js → onboard-pvm5b38w.js} +2 -2
  84. package/dist/{openai-codex-responses-xetseqvt.js → openai-codex-responses-5g27pd6f.js} +43 -23
  85. package/dist/{openai-completions-5r2g5axa.js → openai-completions-3vkmfz2a.js} +55 -11
  86. package/dist/{openai-responses-ce67hd23.js → openai-responses-dg4t7j7a.js} +8 -5
  87. package/dist/{openrouter-images-pzfp22jb.js → openrouter-images-g3gm8931.js} +1 -1
  88. package/dist/{otel-impl-sqagjerv.js → otel-impl-p6jvgd9c.js} +6 -4
  89. package/dist/{otel-3mp81ppd.js → otel-nqa12wf3.js} +2 -2
  90. package/dist/{pi-mono-adapter-k584k16g.js → pi-mono-adapter-t091s27g.js} +17117 -7517
  91. package/dist/{pricing-refresh-y82whbf9.js → pricing-refresh-k49rcyed.js} +7 -7
  92. package/dist/{rbac-roles-nvkqmzyd.js → rbac-roles-30pspvy7.js} +7 -7
  93. package/dist/{rbac-roles-4ht8gegc.js → rbac-roles-cr80g96t.js} +6 -6
  94. package/dist/{render-v2-wvqdgvvp.js → render-v2-xm457ng1.js} +8 -8
  95. package/dist/{seed-pricing-fy3rxhde.js → seed-pricing-yhpv35fr.js} +6 -6
  96. package/dist/{setup-ak5cnc6f.js → setup-nytnmwwr.js} +2 -2
  97. package/dist/{worker-d8769q8a.js → worker-msytr2cw.js} +33 -33
  98. package/openapi.json +34039 -11036
  99. package/package.json +8 -6
  100. package/src/apps/definition.ts +368 -26
  101. package/src/apps/row-store.ts +66 -10
  102. package/src/apps/schema-migrate.ts +95 -1
  103. package/src/apps/store.ts +13 -4
  104. package/src/apps/sync-run-as.ts +18 -0
  105. package/src/apps/sync.ts +934 -0
  106. package/src/apps/version.ts +16 -2
  107. package/src/assets/key.ts +8 -1
  108. package/src/be/asset-key-audit.ts +3 -1
  109. package/src/be/db-queries/mcp-oauth.ts +297 -24
  110. package/src/be/db.ts +42 -1
  111. package/src/be/migrations/129_asset_keys_apps_scripts.sql +161 -0
  112. package/src/be/scripts/db.ts +5 -2
  113. package/src/be/scripts/typecheck.ts +1 -0
  114. package/src/be/seed-scripts/catalog/app-sync-run.ts +32 -0
  115. package/src/be/seed-scripts/catalog/github-issues-pull.ts +102 -0
  116. package/src/be/seed-scripts/index.ts +18 -0
  117. package/src/be/seed-skills/index.ts +7 -0
  118. package/src/http/active-sessions.ts +26 -18
  119. package/src/http/agents.ts +94 -27
  120. package/src/http/api-keys.ts +91 -17
  121. package/src/http/approval-requests.ts +87 -10
  122. package/src/http/apps.ts +361 -53
  123. package/src/http/assets.ts +165 -31
  124. package/src/http/budgets.ts +7 -5
  125. package/src/http/codex-oauth-keep-warm.ts +21 -3
  126. package/src/http/config.ts +46 -16
  127. package/src/http/context.ts +33 -6
  128. package/src/http/ecosystem.ts +20 -6
  129. package/src/http/events.ts +24 -9
  130. package/src/http/favorites.ts +20 -6
  131. package/src/http/fs.ts +60 -17
  132. package/src/http/heartbeat.ts +18 -4
  133. package/src/http/inbox-state.ts +12 -6
  134. package/src/http/integrations.ts +25 -5
  135. package/src/http/kv.ts +36 -15
  136. package/src/http/mcp-bridge.ts +5 -1
  137. package/src/http/mcp-oauth.ts +332 -17
  138. package/src/http/mcp-servers.ts +56 -18
  139. package/src/http/memory.ts +282 -29
  140. package/src/http/metrics.ts +80 -17
  141. package/src/http/oauth-callback.ts +10 -4
  142. package/src/http/oauth-generic.ts +5 -1
  143. package/src/http/oauth-locks.ts +6 -3
  144. package/src/http/openapi.ts +57 -9
  145. package/src/http/page-proxy.ts +25 -5
  146. package/src/http/pages-public.ts +37 -20
  147. package/src/http/pages.ts +93 -28
  148. package/src/http/poll.ts +89 -5
  149. package/src/http/pricing.ts +9 -6
  150. package/src/http/prompt-templates.ts +85 -21
  151. package/src/http/route-def.ts +67 -4
  152. package/src/http/schedules.ts +63 -14
  153. package/src/http/script-connection-proxy.ts +30 -4
  154. package/src/http/script-connections.ts +393 -43
  155. package/src/http/script-runs.ts +73 -19
  156. package/src/http/scripts.ts +257 -39
  157. package/src/http/session-data.ts +87 -14
  158. package/src/http/sessions.ts +80 -5
  159. package/src/http/skills.ts +115 -35
  160. package/src/http/stats.ts +94 -13
  161. package/src/http/task-templates.ts +6 -4
  162. package/src/http/tasks.ts +230 -61
  163. package/src/http/trackers/jira.ts +90 -30
  164. package/src/http/trackers/linear.ts +46 -12
  165. package/src/http/users.ts +162 -25
  166. package/src/http/utils.ts +12 -2
  167. package/src/http/webhooks.ts +30 -16
  168. package/src/http/workflow-events.ts +18 -5
  169. package/src/http/workflows.ts +128 -39
  170. package/src/http/x.ts +37 -6
  171. package/src/oauth/ensure-mcp-token.ts +16 -1
  172. package/src/oauth/mcp-wrapper.ts +281 -26
  173. package/src/providers/pi-mono-adapter.ts +1 -1
  174. package/src/scripts-runtime/credential-broker/README.md +1 -1
  175. package/src/scripts-runtime/credential-broker/fetch-patch.ts +12 -2
  176. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  177. package/src/scripts-runtime/stdlib/fetch.ts +6 -0
  178. package/src/scripts-runtime/types/stdlib.d.ts +1 -0
  179. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
  180. package/src/server.ts +2 -0
  181. package/src/tests/apps-rbac.test.ts +110 -0
  182. package/src/tests/apps-spike2.test.ts +4 -1
  183. package/src/tests/apps-sync-engine.test.ts +2540 -0
  184. package/src/tests/apps-sync.test.ts +1627 -0
  185. package/src/tests/asset-key-api.test.ts +48 -2
  186. package/src/tests/asset-key-invariants.test.ts +92 -0
  187. package/src/tests/asset-key-migration.test.ts +174 -8
  188. package/src/tests/asset-key-normalization.test.ts +2 -0
  189. package/src/tests/credential-broker.test.ts +27 -2
  190. package/src/tests/mcp-oauth-dcr-reuse.test.ts +743 -0
  191. package/src/tests/mcp-oauth-ensure-token.test.ts +45 -0
  192. package/src/tests/mcp-oauth-manual-client.test.ts +135 -2
  193. package/src/tests/mcp-oauth-queries.test.ts +153 -0
  194. package/src/tests/mcp-oauth-wrapper.test.ts +432 -0
  195. package/src/tests/memory-http-recall-gating.test.ts +2 -0
  196. package/src/tests/openapi-response-contract.test.ts +138 -0
  197. package/src/tests/rbac-admission-e2e.test.ts +68 -0
  198. package/src/tests/script-connections.test.ts +3 -0
  199. package/src/tests/scripts-runtime-fetch.test.ts +121 -0
  200. package/src/tests/scripts-runtime-secret-egress.test.ts +4 -2
  201. package/src/tests/seed-scripts.test.ts +2 -2
  202. package/src/tests/swarm-tool-result-gate.test.ts +32 -0
  203. package/src/tests/task-working-dir.test.ts +8 -4
  204. package/src/tests/whoami-route.test.ts +127 -0
  205. package/src/tests/workflow-engine-v2.test.ts +80 -7
  206. package/src/tests/workflow-swarm-script.test.ts +104 -0
  207. package/src/tools/app-get.ts +10 -2
  208. package/src/tools/app-rollback.ts +1 -0
  209. package/src/tools/app-sync.ts +112 -0
  210. package/src/tools/script-common.ts +20 -0
  211. package/src/tools/tool-config.ts +2 -1
  212. package/src/tools/utils.ts +11 -0
  213. package/src/tools/workflows/create-workflow.ts +4 -1
  214. package/src/types.ts +1394 -1206
  215. package/src/utils/template.ts +8 -1
  216. package/src/utils/zod-openapi.ts +11 -0
  217. package/src/workflows/engine.ts +27 -4
  218. package/src/workflows/executors/swarm-script.ts +28 -0
  219. package/src/workflows/retry-poller.ts +19 -2
  220. package/templates/skills/apps/content.md +94 -5
  221. package/templates/skills/asset-namespaces/content.md +2 -2
  222. package/templates/skills/scheduled-task-resilience/SKILL.md +102 -0
  223. package/templates/skills/scheduled-task-resilience/config.json +14 -0
  224. package/templates/skills/scheduled-task-resilience/content.md +97 -0
  225. package/dist/cli-7wmybrt5.js +0 -115
  226. package/dist/cli-d91pvmq1.js +0 -14
@@ -10,12 +10,26 @@ type HttpMethod = "get" | "post" | "put" | "delete" | "patch";
10
10
  interface RouteResponseDef {
11
11
  description: string;
12
12
  schema?: z.ZodType;
13
+ /**
14
+ * Reason this response intentionally carries no JSON schema — SSE stream,
15
+ * binary download, HTML, redirect, proxied upstream body, intentionally
16
+ * dynamic shape, etc. Mutually exclusive with `schema`. Every 2xx response
17
+ * (except bodiless 204/205/304) must declare one of the two — enforced by
18
+ * `bun run check:openapi-response-coverage` (CI).
19
+ */
20
+ unstructured?: string;
13
21
  }
14
22
 
23
+ type ResponsesDef = Record<number, RouteResponseDef>;
24
+
25
+ /** Extracts the payload type respond() accepts for one response def. */
26
+ type ResponseData<R> = R extends { schema: infer S extends z.ZodType } ? z.input<S> : never;
27
+
15
28
  export interface RouteDef<
16
29
  TParams extends z.ZodType = z.ZodType,
17
30
  TQuery extends z.ZodType = z.ZodType,
18
31
  TBody extends z.ZodType = z.ZodType,
32
+ TResponses extends ResponsesDef = ResponsesDef,
19
33
  > {
20
34
  method: HttpMethod;
21
35
  path: string; // OpenAPI-style: "/api/tasks/{id}"
@@ -28,7 +42,7 @@ export interface RouteDef<
28
42
  params?: TParams;
29
43
  query?: TQuery;
30
44
  body?: TBody;
31
- responses: Record<number, RouteResponseDef>;
45
+ responses: TResponses;
32
46
  auth?: {
33
47
  apiKey?: boolean; // default true
34
48
  agentId?: boolean; // requires X-Agent-ID
@@ -49,7 +63,7 @@ interface ParsedRequest<TParams, TQuery, TBody> {
49
63
  body: TBody;
50
64
  }
51
65
 
52
- interface RouteHandle<TParams, TQuery, TBody> {
66
+ interface RouteHandle<TParams, TQuery, TBody, TResponses extends ResponsesDef = ResponsesDef> {
53
67
  /** Check if this route matches the request */
54
68
  match(method: string | undefined, pathSegments: string[]): boolean;
55
69
 
@@ -61,6 +75,24 @@ interface RouteHandle<TParams, TQuery, TBody> {
61
75
  queryParams: URLSearchParams,
62
76
  ): Promise<ParsedRequest<TParams, TQuery, TBody> | null>;
63
77
 
78
+ /**
79
+ * Typed JSON egress: send `data` as the response body for `code`, with `data`
80
+ * compile-time checked against the `schema` declared for that code — the
81
+ * declared OpenAPI shape and the wire shape cannot drift silently. Only
82
+ * callable for codes that declare a `schema`; keep `jsonError` for error
83
+ * paths and raw `res` for bodiless/unstructured responses.
84
+ *
85
+ * Outside production the payload is additionally runtime-validated against
86
+ * the schema (throws → 500 + test failure), catching mismatches the type
87
+ * system can't see through `any`-typed DB rows. Always sends the raw `data`
88
+ * object, never the parsed output — validation must not change the wire.
89
+ */
90
+ respond<C extends keyof TResponses & number>(
91
+ res: ServerResponse,
92
+ code: C,
93
+ data: ResponseData<TResponses[C]>,
94
+ ): void;
95
+
64
96
  /** The raw definition (for OpenAPI generation) */
65
97
  def: RouteDef;
66
98
  }
@@ -154,18 +186,49 @@ export function describeRequestRoute(
154
186
 
155
187
  // ─── Factory ─────────────────────────────────────────────────────────────────
156
188
 
189
+ /**
190
+ * Runtime response validation gate for `respond()`. NODE_ENV is deliberately
191
+ * unset on prod deploys, so this is opt-in outside `bun test` (which sets
192
+ * NODE_ENV=test): response schemas must never introduce a production failure
193
+ * mode — they fail loudly in tests and (opt-in) local dev instead.
194
+ */
195
+ const VALIDATE_RESPONSES =
196
+ process.env.NODE_ENV === "test" ||
197
+ process.env.NODE_ENV === "development" ||
198
+ process.env.VALIDATE_HTTP_RESPONSES === "true";
199
+
157
200
  export function route<
158
201
  TParams extends z.ZodType = z.ZodUndefined,
159
202
  TQuery extends z.ZodType = z.ZodUndefined,
160
203
  TBody extends z.ZodType = z.ZodUndefined,
204
+ TResponses extends ResponsesDef = ResponsesDef,
161
205
  >(
162
- def: RouteDef<TParams, TQuery, TBody>,
163
- ): RouteHandle<z.infer<TParams>, z.infer<TQuery>, z.infer<TBody>> {
206
+ def: RouteDef<TParams, TQuery, TBody, TResponses>,
207
+ ): RouteHandle<z.infer<TParams>, z.infer<TQuery>, z.infer<TBody>, TResponses> {
164
208
  routeRegistry.push(def as RouteDef);
165
209
 
166
210
  return {
167
211
  def: def as RouteDef,
168
212
 
213
+ respond(res, code, data) {
214
+ if (VALIDATE_RESPONSES) {
215
+ const schema = (def.responses as ResponsesDef)[code]?.schema;
216
+ if (schema) {
217
+ const result = schema.safeParse(data);
218
+ if (!result.success) {
219
+ throw new Error(
220
+ `Response schema violation: ${def.method.toUpperCase()} ${def.path} ${code} — ` +
221
+ result.error.issues
222
+ .map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
223
+ .join("; "),
224
+ );
225
+ }
226
+ }
227
+ }
228
+ res.writeHead(code, { "Content-Type": "application/json" });
229
+ res.end(JSON.stringify(data));
230
+ },
231
+
169
232
  match(method, pathSegments) {
170
233
  return matchRoute(
171
234
  method,
@@ -18,14 +18,16 @@ import { mergeScheduleTiming, validateRecurringTiming } from "../be/schedules/va
18
18
  import { getScript } from "../be/scripts/db";
19
19
  import { calculateNextRun, dispatchScheduleTarget } from "../scheduler/scheduler";
20
20
  import {
21
+ AgentTaskSchema,
21
22
  AssetKeySchema,
22
23
  ModelTierSchema,
24
+ ScheduledTaskSchema,
23
25
  ScheduledTaskTargetTypeSchema,
24
26
  splitLegacyModelAlias,
25
27
  } from "../types";
26
28
  import { resolveHttpFavoriteOwner } from "./favorite-owner";
27
29
  import { route } from "./route-def";
28
- import { json, jsonError } from "./utils";
30
+ import { jsonError } from "./utils";
29
31
 
30
32
  // ─── Route Definitions ───────────────────────────────────────────────────────
31
33
 
@@ -51,6 +53,34 @@ const scheduleUpdateBodySchema = z.object({
51
53
  scriptArgs: z.record(z.string(), z.unknown()).nullable().optional(),
52
54
  });
53
55
 
56
+ // `ScheduledTaskSchema` carries `.refine()` checks, so Zod v4 forbids the
57
+ // plain `.extend()` / `.omit()` chain-builders here (they throw at call
58
+ // time: "Object schemas containing refinements cannot be extended/omitted.
59
+ // Use `.safeExtend()` instead."). Use `.safeExtend()` for additive shapes,
60
+ // and build the taskTemplate-dropping summary shape from `.shape` directly.
61
+
62
+ /** Full schedule row decorated with the caller's favorite flag (full-mode `listSchedules`). */
63
+ const scheduleWithFavoriteSchema = ScheduledTaskSchema.safeExtend({ favorite: z.boolean() });
64
+
65
+ /**
66
+ * `getSchedule` response: `favorite` is optional because the handler falls
67
+ * back to the undecorated row (`decorated ?? schedule`) if `withFavoriteFlags`
68
+ * ever returned an empty array — structurally unreachable for a 1-row input,
69
+ * but the schema stays honest about what the code could send.
70
+ */
71
+ const scheduleWithOptionalFavoriteSchema = ScheduledTaskSchema.safeExtend({
72
+ favorite: z.boolean().optional(),
73
+ });
74
+
75
+ /** Slim list row — `taskTemplate` swapped for a bounded preview (see `ScheduledTaskSummary`). */
76
+ const { taskTemplate: _omittedTaskTemplate, ...scheduleShapeWithoutTemplate } =
77
+ ScheduledTaskSchema.shape;
78
+ const scheduleSummaryWithFavoriteSchema = z.object({
79
+ ...scheduleShapeWithoutTemplate,
80
+ taskTemplatePreview: z.string(),
81
+ favorite: z.boolean(),
82
+ });
83
+
54
84
  const createSchedule = route({
55
85
  method: "post",
56
86
  path: "/api/schedules",
@@ -81,7 +111,7 @@ const createSchedule = route({
81
111
  runAt: z.string().optional(),
82
112
  }),
83
113
  responses: {
84
- 201: { description: "Schedule created" },
114
+ 201: { description: "Schedule created", schema: ScheduledTaskSchema },
85
115
  400: { description: "Validation error" },
86
116
  409: { description: "Duplicate name" },
87
117
  },
@@ -95,7 +125,14 @@ const runScheduleNow = route({
95
125
  tags: ["Schedules"],
96
126
  params: z.object({ id: z.string() }),
97
127
  responses: {
98
- 200: { description: "Schedule run triggered" },
128
+ 200: {
129
+ description: "Schedule run triggered",
130
+ schema: z.object({
131
+ schedule: ScheduledTaskSchema.nullable(),
132
+ workflowRunIds: z.array(z.string()).optional(),
133
+ task: AgentTaskSchema.optional(),
134
+ }),
135
+ },
99
136
  400: { description: "Schedule is disabled" },
100
137
  404: { description: "Schedule not found" },
101
138
  },
@@ -131,7 +168,16 @@ const listSchedules = route({
131
168
  keyPrefix: AssetKeySchema.optional(),
132
169
  }),
133
170
  responses: {
134
- 200: { description: "List of schedules" },
171
+ 200: {
172
+ description: "List of schedules",
173
+ schema: z.object({
174
+ schedules: z.union([
175
+ z.array(scheduleSummaryWithFavoriteSchema),
176
+ z.array(scheduleWithFavoriteSchema),
177
+ ]),
178
+ count: z.number().int(),
179
+ }),
180
+ },
135
181
  },
136
182
  });
137
183
 
@@ -143,7 +189,7 @@ const getSchedule = route({
143
189
  tags: ["Schedules"],
144
190
  params: z.object({ id: z.string() }),
145
191
  responses: {
146
- 200: { description: "Schedule details" },
192
+ 200: { description: "Schedule details", schema: scheduleWithOptionalFavoriteSchema },
147
193
  404: { description: "Schedule not found" },
148
194
  },
149
195
  });
@@ -157,7 +203,7 @@ const updateSchedule = route({
157
203
  params: z.object({ id: z.string() }),
158
204
  body: scheduleUpdateBodySchema,
159
205
  responses: {
160
- 200: { description: "Schedule updated" },
206
+ 200: { description: "Schedule updated", schema: ScheduledTaskSchema.nullable() },
161
207
  400: { description: "Validation error" },
162
208
  404: { description: "Schedule not found" },
163
209
  409: { description: "Duplicate name" },
@@ -175,7 +221,7 @@ const patchSchedule = route({
175
221
  params: z.object({ id: z.string() }),
176
222
  body: scheduleUpdateBodySchema,
177
223
  responses: {
178
- 200: { description: "Schedule patched" },
224
+ 200: { description: "Schedule patched", schema: ScheduledTaskSchema.nullable() },
179
225
  400: { description: "Validation error" },
180
226
  404: { description: "Schedule not found" },
181
227
  409: { description: "Duplicate name" },
@@ -194,7 +240,7 @@ const deleteSchedule = route({
194
240
  tags: ["Schedules"],
195
241
  params: z.object({ id: z.string() }),
196
242
  responses: {
197
- 200: { description: "Schedule deleted" },
243
+ 200: { description: "Schedule deleted", schema: z.object({ success: z.literal(true) }) },
198
244
  404: { description: "Schedule not found" },
199
245
  },
200
246
  });
@@ -234,7 +280,10 @@ export async function handleSchedules(
234
280
  favoriteScope,
235
281
  itemType: "schedule",
236
282
  });
237
- json(res, { schedules: decoratedSchedules, count: decoratedSchedules.length });
283
+ listSchedules.respond(res, 200, {
284
+ schedules: decoratedSchedules,
285
+ count: decoratedSchedules.length,
286
+ });
238
287
  return true;
239
288
  }
240
289
 
@@ -379,7 +428,7 @@ export async function handleSchedules(
379
428
  createdBy: resolveHttpAuditUserId(req, myAgentId) ?? undefined,
380
429
  });
381
430
 
382
- json(res, schedule, 201);
431
+ createSchedule.respond(res, 201, schedule);
383
432
  } catch (_error) {
384
433
  jsonError(res, "Failed to create schedule", 500);
385
434
  }
@@ -420,7 +469,7 @@ export async function handleSchedules(
420
469
  }
421
470
 
422
471
  const updatedSchedule = getScheduledTaskById(parsed.params.id);
423
- json(res, { schedule: updatedSchedule, workflowRunIds, task });
472
+ runScheduleNow.respond(res, 200, { schedule: updatedSchedule, workflowRunIds, task });
424
473
  } catch (error) {
425
474
  jsonError(res, error instanceof Error ? error.message : "Failed to run schedule", 500);
426
475
  }
@@ -439,7 +488,7 @@ export async function handleSchedules(
439
488
 
440
489
  const favoriteScope = resolveHttpFavoriteOwner(req, myAgentId)?.scope;
441
490
  const [decorated] = withFavoriteFlags([schedule], { favoriteScope, itemType: "schedule" });
442
- json(res, decorated ?? schedule);
491
+ getSchedule.respond(res, 200, decorated ?? schedule);
443
492
  return true;
444
493
  }
445
494
 
@@ -601,7 +650,7 @@ export async function handleSchedules(
601
650
  const updatedBy = resolveHttpAuditUserId(req, myAgentId);
602
651
  if (updatedBy !== null) body.updatedBy = updatedBy;
603
652
  const schedule = updateScheduledTask(parsed.params.id, body);
604
- json(res, schedule);
653
+ routeHandle.respond(res, 200, schedule);
605
654
  return true;
606
655
  }
607
656
 
@@ -615,7 +664,7 @@ export async function handleSchedules(
615
664
  return true;
616
665
  }
617
666
 
618
- json(res, { success: true });
667
+ deleteSchedule.respond(res, 200, { success: true });
619
668
  return true;
620
669
  }
621
670
 
@@ -5,7 +5,30 @@ import { callMcpServerTool } from "@/be/mcp-proxy";
5
5
  import { getScriptConnectionById } from "@/be/script-connections";
6
6
  import { can } from "@/rbac";
7
7
  import { route } from "./route-def";
8
- import { json, jsonError } from "./utils";
8
+ import { jsonError } from "./utils";
9
+
10
+ // Mirrors `McpToolCallResult` from `src/mcp-client/http-client.ts`.
11
+ const McpToolCallResultSchema = z.object({
12
+ content: z.array(z.object({ type: z.string(), text: z.string().optional() })),
13
+ isError: z.boolean().optional(),
14
+ });
15
+
16
+ // Mirrors `McpJsonRpcError` from `src/mcp-client/http-client.ts`.
17
+ const McpJsonRpcErrorSchema = z.object({
18
+ code: z.number().optional(),
19
+ message: z.string().optional(),
20
+ data: z.unknown().optional(),
21
+ });
22
+
23
+ // Mirrors `McpToolCallEnvelope` from `src/mcp-client/http-client.ts` — the
24
+ // success/failure envelope returned by `callMcpServerTool`, plus the
25
+ // `{ ok: false, error: string }` shape the catch block below constructs
26
+ // directly (a subtype of the `ok: false` branch, since `error` accepts a
27
+ // plain string there too).
28
+ const McpToolCallEnvelopeSchema = z.union([
29
+ z.object({ ok: z.literal(true), result: McpToolCallResultSchema }),
30
+ z.object({ ok: z.literal(false), error: z.union([McpJsonRpcErrorSchema, z.string()]) }),
31
+ ]);
9
32
 
10
33
  const mcpCallRoute = route({
11
34
  method: "post",
@@ -21,7 +44,7 @@ const mcpCallRoute = route({
21
44
  arguments: z.record(z.string(), z.unknown()).optional(),
22
45
  }),
23
46
  responses: {
24
- 200: { description: "MCP call result" },
47
+ 200: { description: "MCP call result", schema: McpToolCallEnvelopeSchema },
25
48
  400: { description: "Invalid MCP connection or request" },
26
49
  403: { description: "Not allowed to invoke this MCP connection" },
27
50
  404: { description: "Script connection or agent not found" },
@@ -108,9 +131,12 @@ export async function handleScriptConnectionProxy(
108
131
  parsed.body.arguments ?? {},
109
132
  { agentId, timeoutMs: 30_000 },
110
133
  );
111
- json(res, result);
134
+ mcpCallRoute.respond(res, 200, result);
112
135
  } catch (err) {
113
- json(res, { ok: false, error: err instanceof Error ? err.message : String(err) });
136
+ mcpCallRoute.respond(res, 200, {
137
+ ok: false,
138
+ error: err instanceof Error ? err.message : String(err),
139
+ });
114
140
  }
115
141
  return true;
116
142
  }