@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
@@ -1,10 +1,22 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import { z } from "zod";
2
3
  import { getServicesByAgentId } from "../be/db";
3
4
  import { route } from "./route-def";
4
- import { json, jsonError } from "./utils";
5
+ import { jsonError } from "./utils";
5
6
 
6
7
  // ─── Route Definitions ───────────────────────────────────────────────────────
7
8
 
9
+ const EcosystemAppSchema = z.object({
10
+ name: z.string(),
11
+ script: z.string(),
12
+ cwd: z.string().optional(),
13
+ interpreter: z.string().optional(),
14
+ args: z.array(z.string()).optional(),
15
+ env: z.record(z.string(), z.string()).optional(),
16
+ });
17
+
18
+ type EcosystemApp = z.infer<typeof EcosystemAppSchema>;
19
+
8
20
  const getEcosystem = route({
9
21
  method: "get",
10
22
  path: "/ecosystem",
@@ -13,7 +25,10 @@ const getEcosystem = route({
13
25
  tags: ["Ecosystem"],
14
26
  auth: { apiKey: true, agentId: true },
15
27
  responses: {
16
- 200: { description: "PM2 ecosystem config" },
28
+ 200: {
29
+ description: "PM2 ecosystem config",
30
+ schema: z.object({ apps: z.array(EcosystemAppSchema) }),
31
+ },
17
32
  400: { description: "Missing X-Agent-ID" },
18
33
  },
19
34
  });
@@ -39,7 +54,7 @@ export async function handleEcosystem(
39
54
  apps: services
40
55
  .filter((s) => s.script) // Only include services with script path
41
56
  .map((s) => {
42
- const app: Record<string, unknown> = {
57
+ const app: EcosystemApp = {
43
58
  name: s.name,
44
59
  script: s.script,
45
60
  };
@@ -48,14 +63,13 @@ export async function handleEcosystem(
48
63
  if (s.interpreter) app.interpreter = s.interpreter;
49
64
  if (s.args && s.args.length > 0) app.args = s.args;
50
65
  if (s.env && Object.keys(s.env).length > 0) app.env = s.env;
51
- if (s.port)
52
- app.env = { ...((app.env as Record<string, string>) || {}), PORT: String(s.port) };
66
+ if (s.port) app.env = { ...(app.env || {}), PORT: String(s.port) };
53
67
 
54
68
  return app;
55
69
  }),
56
70
  };
57
71
 
58
- json(res, ecosystem);
72
+ getEcosystem.respond(res, 200, ecosystem);
59
73
  return true;
60
74
  }
61
75
 
@@ -11,9 +11,10 @@ import {
11
11
  EventNameSchema,
12
12
  EventSourceSchema,
13
13
  EventStatusSchema,
14
+ SwarmEventSchema,
14
15
  } from "../types";
15
16
  import { route } from "./route-def";
16
- import { json, jsonError } from "./utils";
17
+ import { jsonError } from "./utils";
17
18
 
18
19
  // ─── Route Definitions ───────────────────────────────────────────────────────
19
20
 
@@ -39,7 +40,10 @@ const createEventRoute = route({
39
40
  tags: ["Events"],
40
41
  body: eventBodySchema,
41
42
  responses: {
42
- 201: { description: "Event stored" },
43
+ 201: {
44
+ description: "Event stored",
45
+ schema: z.object({ success: z.literal(true), event: SwarmEventSchema }),
46
+ },
43
47
  400: { description: "Validation error" },
44
48
  },
45
49
  auth: { apiKey: true },
@@ -55,7 +59,10 @@ const createEventsBatchRoute = route({
55
59
  events: z.array(eventBodySchema).min(1).max(500),
56
60
  }),
57
61
  responses: {
58
- 201: { description: "Events stored" },
62
+ 201: {
63
+ description: "Events stored",
64
+ schema: z.object({ success: z.literal(true), count: z.number().int() }),
65
+ },
59
66
  400: { description: "Validation error" },
60
67
  },
61
68
  auth: { apiKey: true },
@@ -80,7 +87,10 @@ const getEventsRoute = route({
80
87
  limit: z.coerce.number().int().min(1).max(1000).optional(),
81
88
  }),
82
89
  responses: {
83
- 200: { description: "List of events" },
90
+ 200: {
91
+ description: "List of events",
92
+ schema: z.object({ events: z.array(SwarmEventSchema) }),
93
+ },
84
94
  },
85
95
  auth: { apiKey: true },
86
96
  });
@@ -101,7 +111,12 @@ const getEventCountsRoute = route({
101
111
  until: z.string().optional(),
102
112
  }),
103
113
  responses: {
104
- 200: { description: "Event counts" },
114
+ 200: {
115
+ description: "Event counts",
116
+ schema: z.object({
117
+ counts: z.array(z.object({ event: z.string(), count: z.number().int() })),
118
+ }),
119
+ },
105
120
  },
106
121
  auth: { apiKey: true },
107
122
  });
@@ -122,7 +137,7 @@ export async function handleEvents(
122
137
 
123
138
  try {
124
139
  const count = createEventsBatch(parsed.body.events);
125
- json(res, { success: true, count }, 201);
140
+ createEventsBatchRoute.respond(res, 201, { success: true, count });
126
141
  } catch (error) {
127
142
  console.error("[HTTP] Failed to create events batch:", error);
128
143
  jsonError(res, "Failed to store events batch", 500);
@@ -144,7 +159,7 @@ export async function handleEvents(
144
159
  since: parsed.query.since || undefined,
145
160
  until: parsed.query.until || undefined,
146
161
  });
147
- json(res, { counts });
162
+ getEventCountsRoute.respond(res, 200, { counts });
148
163
  return true;
149
164
  }
150
165
 
@@ -155,7 +170,7 @@ export async function handleEvents(
155
170
 
156
171
  try {
157
172
  const event = createEvent(parsed.body);
158
- json(res, { success: true, event }, 201);
173
+ createEventRoute.respond(res, 201, { success: true, event });
159
174
  } catch (error) {
160
175
  console.error("[HTTP] Failed to create event:", error);
161
176
  jsonError(res, "Failed to store event", 500);
@@ -180,7 +195,7 @@ export async function handleEvents(
180
195
  until: parsed.query.until || undefined,
181
196
  limit: parsed.query.limit ?? 100,
182
197
  });
183
- json(res, { events });
198
+ getEventsRoute.respond(res, 200, { events });
184
199
  return true;
185
200
  }
186
201
 
@@ -1,10 +1,10 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { z } from "zod";
3
3
  import { listFavorites as listFavoriteRows, setFavorite } from "../be/db";
4
- import { FavoriteItemTypeSchema } from "../types";
4
+ import { FavoriteItemTypeSchema, UserFavoriteSchema } from "../types";
5
5
  import { resolveHttpFavoriteOwner } from "./favorite-owner";
6
6
  import { route } from "./route-def";
7
- import { json, jsonError } from "./utils";
7
+ import { jsonError } from "./utils";
8
8
 
9
9
  const listFavorites = route({
10
10
  method: "get",
@@ -17,7 +17,13 @@ const listFavorites = route({
17
17
  itemIds: z.string().optional(),
18
18
  }),
19
19
  responses: {
20
- 200: { description: "Favorite rows and favorite item ids" },
20
+ 200: {
21
+ description: "Favorite rows and favorite item ids",
22
+ schema: z.object({
23
+ favorites: z.array(UserFavoriteSchema),
24
+ favoriteIds: z.array(z.string()),
25
+ }),
26
+ },
21
27
  401: { description: "No authenticated principal context" },
22
28
  },
23
29
  });
@@ -35,7 +41,15 @@ const putFavorite = route({
35
41
  favorite: z.boolean(),
36
42
  }),
37
43
  responses: {
38
- 200: { description: "Favorite state" },
44
+ 200: {
45
+ description: "Favorite state",
46
+ schema: z.object({
47
+ favorite: z.boolean(),
48
+ itemType: FavoriteItemTypeSchema,
49
+ itemId: z.string(),
50
+ row: UserFavoriteSchema.nullable(),
51
+ }),
52
+ },
39
53
  401: { description: "No authenticated principal context" },
40
54
  },
41
55
  });
@@ -64,7 +78,7 @@ export async function handleFavorites(
64
78
  itemType: parsed.query.itemType,
65
79
  itemIds,
66
80
  });
67
- json(res, {
81
+ listFavorites.respond(res, 200, {
68
82
  favorites,
69
83
  favoriteIds: favorites.map((favorite) => favorite.itemId),
70
84
  });
@@ -87,7 +101,7 @@ export async function handleFavorites(
87
101
  favorite: parsed.body.favorite,
88
102
  actorId: owner.actorId,
89
103
  });
90
- json(res, {
104
+ putFavorite.respond(res, 200, {
91
105
  favorite: parsed.body.favorite,
92
106
  itemType: parsed.body.itemType,
93
107
  itemId: parsed.body.itemId,
package/src/http/fs.ts CHANGED
@@ -15,12 +15,12 @@ import {
15
15
  import { type FileObject, type FileScope, FilesError, normalizeFilesError } from "../fs/provider";
16
16
  import { getFileStorageProvider } from "../fs/registry";
17
17
  import { can, type RbacPrincipal, type RbacResource } from "../rbac";
18
- import type { TaskAttachment } from "../types";
18
+ import { type TaskAttachment, TaskAttachmentSchema } from "../types";
19
19
  import { attachmentContentDisposition } from "../utils/content-disposition";
20
20
  import { getCurrentRequestAuth, getRequestAuth } from "../utils/request-auth-context";
21
21
  import { scrubSecrets } from "../utils/secret-scrubber";
22
22
  import { route } from "./route-def";
23
- import { BODY_TOO_LARGE, enforceContentLengthCap, json, jsonError } from "./utils";
23
+ import { BODY_TOO_LARGE, enforceContentLengthCap, jsonError } from "./utils";
24
24
 
25
25
  const MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
26
26
 
@@ -38,6 +38,37 @@ const signedUrlQuery = z.object({
38
38
  expiresIn: z.coerce.number().int().positive().max(3600).optional(),
39
39
  });
40
40
 
41
+ // Mirrors `ProviderCapabilities` from src/fs/capabilities.ts — no zod schema
42
+ // exists there (it's a plain TS type consumed by provider implementations).
43
+ const providerCapabilitiesSchema = z.object({
44
+ signedUrl: z.object({
45
+ supported: z.boolean(),
46
+ maxExpiresIn: z.number().optional(),
47
+ }),
48
+ search: z.boolean().optional(),
49
+ comments: z.boolean().optional(),
50
+ versioning: z.boolean().optional(),
51
+ });
52
+
53
+ const agentFsCredentialStateSchema = z.object({
54
+ enabled: z.boolean(),
55
+ created: z.boolean(),
56
+ agentId: z.string(),
57
+ email: z.string().optional(),
58
+ orgId: z.string().optional(),
59
+ driveId: z.string().optional(),
60
+ });
61
+
62
+ const agentFsInviteStateSchema = z.object({
63
+ orgId: z.string(),
64
+ invited: z.boolean(),
65
+ });
66
+
67
+ const signedUrlResponseSchema = z.object({
68
+ url: z.string(),
69
+ expiresIn: z.number(),
70
+ });
71
+
41
72
  const capabilitiesRoute = route({
42
73
  method: "get",
43
74
  path: "/api/fs/capabilities",
@@ -45,7 +76,10 @@ const capabilitiesRoute = route({
45
76
  summary: "Get active file-storage provider capabilities",
46
77
  tags: ["FS"],
47
78
  responses: {
48
- 200: { description: "Active provider capabilities" },
79
+ 200: {
80
+ description: "Active provider capabilities",
81
+ schema: z.object({ providerId: z.string(), capabilities: providerCapabilitiesSchema }),
82
+ },
49
83
  401: { description: "Unauthorized" },
50
84
  },
51
85
  });
@@ -60,7 +94,7 @@ const ensureAgentCredentialsRoute = route({
60
94
  tags: ["FS"],
61
95
  body: z.object({}).optional(),
62
96
  responses: {
63
- 200: { description: "Credential state" },
97
+ 200: { description: "Credential state", schema: agentFsCredentialStateSchema },
64
98
  400: { description: "Missing agent id" },
65
99
  500: { description: "Provisioning failed" },
66
100
  },
@@ -80,7 +114,7 @@ const inviteMemberRoute = route({
80
114
  role: z.enum(["viewer", "editor", "admin"]).default("editor"),
81
115
  }),
82
116
  responses: {
83
- 200: { description: "Invite state ({ orgId, invited })" },
117
+ 200: { description: "Invite state ({ orgId, invited })", schema: agentFsInviteStateSchema },
84
118
  400: { description: "Invalid body" },
85
119
  500: { description: "Provisioning or invite failed" },
86
120
  },
@@ -99,7 +133,10 @@ const listTaskFilesRoute = route({
99
133
  tags: ["FS"],
100
134
  params: taskParams,
101
135
  responses: {
102
- 200: { description: "Task file attachments" },
136
+ 200: {
137
+ description: "Task file attachments",
138
+ schema: z.object({ attachments: z.array(TaskAttachmentSchema) }),
139
+ },
103
140
  404: { description: "Task not found" },
104
141
  },
105
142
  });
@@ -115,7 +152,7 @@ const uploadTaskFileRoute = route({
115
152
  params: taskParams,
116
153
  query: uploadQuery,
117
154
  responses: {
118
- 201: { description: "Uploaded task attachment" },
155
+ 201: { description: "Uploaded task attachment", schema: TaskAttachmentSchema },
119
156
  400: { description: "Validation error" },
120
157
  403: { description: "Caller cannot mutate this task" },
121
158
  404: { description: "Task not found" },
@@ -133,7 +170,7 @@ const getTaskFileRoute = route({
133
170
  tags: ["FS"],
134
171
  params: attachmentParams,
135
172
  responses: {
136
- 200: { description: "Task attachment metadata" },
173
+ 200: { description: "Task attachment metadata", schema: TaskAttachmentSchema },
137
174
  404: { description: "Task or attachment not found" },
138
175
  },
139
176
  });
@@ -147,7 +184,10 @@ const downloadTaskFileRoute = route({
147
184
  tags: ["FS"],
148
185
  params: attachmentParams,
149
186
  responses: {
150
- 200: { description: "Raw file bytes" },
187
+ 200: {
188
+ description: "Raw file bytes",
189
+ unstructured: "Streams raw binary file bytes (Content-Type varies per attachment)",
190
+ },
151
191
  404: { description: "Task, attachment, or provider object not found" },
152
192
  },
153
193
  });
@@ -161,7 +201,7 @@ const signedUrlTaskFileRoute = route({
161
201
  params: attachmentParams,
162
202
  query: signedUrlQuery,
163
203
  responses: {
164
- 200: { description: "Signed URL" },
204
+ 200: { description: "Signed URL", schema: signedUrlResponseSchema },
165
205
  404: { description: "Task, attachment, or provider object not found" },
166
206
  501: { description: "Active provider does not support signed URLs" },
167
207
  },
@@ -202,7 +242,7 @@ export async function handleFs(
202
242
  }
203
243
  try {
204
244
  const result = await ensureAgentFsCredentialsForAgent(agentId);
205
- json(res, result);
245
+ ensureAgentCredentialsRoute.respond(res, 200, result);
206
246
  } catch (error) {
207
247
  const message = scrubSecrets(error instanceof Error ? error.message : String(error));
208
248
  jsonError(res, `Failed to provision agent-fs credentials: ${message}`, 500);
@@ -215,7 +255,7 @@ export async function handleFs(
215
255
  if (!parsed) return true;
216
256
  try {
217
257
  const result = await inviteEmailToSharedOrg(parsed.body.email, parsed.body.role);
218
- json(res, result);
258
+ inviteMemberRoute.respond(res, 200, result);
219
259
  } catch (error) {
220
260
  const message = scrubSecrets(error instanceof Error ? error.message : String(error));
221
261
  jsonError(res, `Failed to invite member to agent-fs shared org: ${message}`, 500);
@@ -244,7 +284,7 @@ export async function handleFs(
244
284
  if (!parsed) return true;
245
285
  const attachment = findAttachment(parsed.params.taskId, parsed.params.attachmentId, res);
246
286
  if (!attachment) return true;
247
- json(res, attachment);
287
+ getTaskFileRoute.respond(res, 200, attachment);
248
288
  return true;
249
289
  }
250
290
 
@@ -275,13 +315,16 @@ export async function handleFs(
275
315
  const parsed = await listTaskFilesRoute.parse(req, res, pathSegments, queryParams);
276
316
  if (!parsed) return true;
277
317
  if (!getTaskById(parsed.params.taskId)) return notFound(res, "Task not found");
278
- json(res, { attachments: getTaskAttachments(parsed.params.taskId) });
318
+ listTaskFilesRoute.respond(res, 200, { attachments: getTaskAttachments(parsed.params.taskId) });
279
319
  return true;
280
320
  }
281
321
 
282
322
  if (capabilitiesRoute.match(req.method, pathSegments)) {
283
323
  const provider = getFileStorageProvider();
284
- json(res, { providerId: provider.id, capabilities: provider.capabilities });
324
+ capabilitiesRoute.respond(res, 200, {
325
+ providerId: provider.id,
326
+ capabilities: provider.capabilities,
327
+ });
285
328
  return true;
286
329
  }
287
330
 
@@ -338,7 +381,7 @@ async function sendUpload(
338
381
  isPrimary: query.isPrimary === "true",
339
382
  createdBy: auth?.kind === "user" ? auth.userId : undefined,
340
383
  });
341
- json(res, attachment, 201);
384
+ uploadTaskFileRoute.respond(res, 201, attachment);
342
385
  } catch (error) {
343
386
  try {
344
387
  await provider.delete(scope);
@@ -397,7 +440,7 @@ async function sendSignedUrl(
397
440
  }
398
441
  try {
399
442
  const url = await provider.url(scopeFromAttachment(attachment), { expiresIn });
400
- json(res, { url, expiresIn: Math.min(expiresIn ?? 3600, 3600) });
443
+ signedUrlTaskFileRoute.respond(res, 200, { url, expiresIn: Math.min(expiresIn ?? 3600, 3600) });
401
444
  } catch (error) {
402
445
  return sendProviderError(res, error);
403
446
  }
@@ -1,8 +1,16 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import { z } from "zod";
2
3
  import { checkHeartbeatChecklist, runHeartbeatSweep } from "../heartbeat/heartbeat";
3
4
  import { route } from "./route-def";
4
5
  import { json } from "./utils";
5
6
 
7
+ // ─── Response schemas ───────────────────────────────────────────────────────
8
+
9
+ const HeartbeatActionResultSchema = z.object({
10
+ success: z.literal(true),
11
+ message: z.string(),
12
+ });
13
+
6
14
  // ─── Route Definitions ───────────────────────────────────────────────────────
7
15
 
8
16
  const triggerSweep = route({
@@ -12,7 +20,7 @@ const triggerSweep = route({
12
20
  summary: "Trigger an immediate heartbeat sweep",
13
21
  tags: ["Heartbeat"],
14
22
  responses: {
15
- 200: { description: "Sweep completed successfully" },
23
+ 200: { description: "Sweep completed successfully", schema: HeartbeatActionResultSchema },
16
24
  401: { description: "Unauthorized" },
17
25
  },
18
26
  auth: { apiKey: true },
@@ -25,7 +33,10 @@ const triggerChecklist = route({
25
33
  summary: "Trigger an immediate heartbeat checklist check",
26
34
  tags: ["Heartbeat"],
27
35
  responses: {
28
- 200: { description: "Checklist check completed successfully" },
36
+ 200: {
37
+ description: "Checklist check completed successfully",
38
+ schema: HeartbeatActionResultSchema,
39
+ },
29
40
  401: { description: "Unauthorized" },
30
41
  },
31
42
  auth: { apiKey: true },
@@ -44,7 +55,7 @@ export async function handleHeartbeat(
44
55
 
45
56
  try {
46
57
  await runHeartbeatSweep();
47
- json(res, { success: true, message: "Heartbeat sweep completed" });
58
+ triggerSweep.respond(res, 200, { success: true, message: "Heartbeat sweep completed" });
48
59
  } catch (err) {
49
60
  const message = err instanceof Error ? err.message : "Unknown error during heartbeat sweep";
50
61
  json(res, { success: false, error: message }, 500);
@@ -58,7 +69,10 @@ export async function handleHeartbeat(
58
69
 
59
70
  try {
60
71
  await checkHeartbeatChecklist();
61
- json(res, { success: true, message: "Heartbeat checklist check completed" });
72
+ triggerChecklist.respond(res, 200, {
73
+ success: true,
74
+ message: "Heartbeat checklist check completed",
75
+ });
62
76
  } catch (err) {
63
77
  const message =
64
78
  err instanceof Error ? err.message : "Unknown error during heartbeat checklist check";
@@ -1,9 +1,9 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { z } from "zod";
3
3
  import { listInboxState, upsertInboxState } from "../be/db";
4
- import { InboxItemStatusSchema, InboxItemTypeSchema } from "../types";
4
+ import { InboxItemStateSchema, InboxItemStatusSchema, InboxItemTypeSchema } from "../types";
5
5
  import { route } from "./route-def";
6
- import { json, jsonError } from "./utils";
6
+ import { jsonError } from "./utils";
7
7
 
8
8
  // ─── Route Definitions ───────────────────────────────────────────────────────
9
9
 
@@ -19,7 +19,10 @@ const listState = route({
19
19
  itemType: InboxItemTypeSchema.optional(),
20
20
  }),
21
21
  responses: {
22
- 200: { description: "Inbox state rows" },
22
+ 200: {
23
+ description: "Inbox state rows",
24
+ schema: z.object({ items: z.array(InboxItemStateSchema) }),
25
+ },
23
26
  400: { description: "Validation error" },
24
27
  401: { description: "Unauthorized" },
25
28
  },
@@ -40,7 +43,10 @@ const upsertState = route({
40
43
  snoozeUntil: z.string().datetime().optional(),
41
44
  }),
42
45
  responses: {
43
- 200: { description: "Upserted inbox state row" },
46
+ 200: {
47
+ description: "Upserted inbox state row",
48
+ schema: z.object({ item: InboxItemStateSchema }),
49
+ },
44
50
  400: { description: "Validation error" },
45
51
  401: { description: "Unauthorized" },
46
52
  },
@@ -63,7 +69,7 @@ export async function handleInboxState(
63
69
  status: parsed.query.status,
64
70
  itemType: parsed.query.itemType,
65
71
  });
66
- json(res, { items });
72
+ listState.respond(res, 200, { items });
67
73
  return true;
68
74
  }
69
75
 
@@ -78,7 +84,7 @@ export async function handleInboxState(
78
84
  status: parsed.body.status,
79
85
  snoozeUntil: parsed.body.snoozeUntil,
80
86
  });
81
- json(res, { item });
87
+ upsertState.respond(res, 200, { item });
82
88
  } catch (err) {
83
89
  jsonError(res, err instanceof Error ? err.message : "Failed to upsert inbox state", 500);
84
90
  }
@@ -3,7 +3,6 @@ import Anthropic from "@anthropic-ai/sdk";
3
3
  import { z } from "zod";
4
4
  import { getResolvedConfig } from "../be/db";
5
5
  import { route } from "./route-def";
6
- import { json } from "./utils";
7
6
 
8
7
  // ─── Types ───────────────────────────────────────────────────────────────────
9
8
 
@@ -27,6 +26,25 @@ interface TestConnectionDeps {
27
26
  buildClient?: (apiKey: string) => ClaudeManagedTestClient;
28
27
  }
29
28
 
29
+ // ─── Response schemas ────────────────────────────────────────────────────────
30
+
31
+ const ClaudeManagedTestResultSchema = z.union([
32
+ z.object({
33
+ ok: z.literal(true),
34
+ agentName: z.string().nullable(),
35
+ model: z.string().nullable(),
36
+ }),
37
+ z.object({
38
+ ok: z.literal(false),
39
+ error: z.string(),
40
+ }),
41
+ ]);
42
+
43
+ const McpUserConfigSchema = z.object({
44
+ mcpBaseUrl: z.string(),
45
+ mcpUserUrl: z.string(),
46
+ });
47
+
30
48
  // ─── Route Definition ────────────────────────────────────────────────────────
31
49
 
32
50
  const claudeManagedTestRoute = route({
@@ -41,6 +59,7 @@ const claudeManagedTestRoute = route({
41
59
  200: {
42
60
  description:
43
61
  "Connection result — `{ ok: true, agentName, model }` on success or `{ ok: false, error }` on any failure (missing config, Anthropic API error). Always 200 OK.",
62
+ schema: ClaudeManagedTestResultSchema,
44
63
  },
45
64
  },
46
65
  });
@@ -55,6 +74,7 @@ const mcpUserConfigRoute = route({
55
74
  200: {
56
75
  description:
57
76
  "Server-derived MCP user config. `mcpBaseUrl` is the API server base URL and `mcpUserUrl` appends `/mcp-user`.",
77
+ schema: McpUserConfigSchema,
58
78
  },
59
79
  },
60
80
  });
@@ -116,7 +136,7 @@ export function createIntegrationsHandler(deps: TestConnectionDeps = {}) {
116
136
  ): Promise<boolean> {
117
137
  if (mcpUserConfigRoute.match(req.method, pathSegments)) {
118
138
  const mcpBaseUrl = resolveMcpBaseUrl();
119
- json(res, { mcpBaseUrl, mcpUserUrl: `${mcpBaseUrl}/mcp-user` });
139
+ mcpUserConfigRoute.respond(res, 200, { mcpBaseUrl, mcpUserUrl: `${mcpBaseUrl}/mcp-user` });
120
140
  return true;
121
141
  }
122
142
 
@@ -128,7 +148,7 @@ export function createIntegrationsHandler(deps: TestConnectionDeps = {}) {
128
148
  const missing: string[] = [];
129
149
  if (!apiKey) missing.push("ANTHROPIC_API_KEY");
130
150
  if (!agentId) missing.push("MANAGED_AGENT_ID");
131
- json(res, {
151
+ claudeManagedTestRoute.respond(res, 200, {
132
152
  ok: false,
133
153
  error: `Missing required config: ${missing.join(", ")}. Run \`bun run src/cli.tsx claude-managed-setup\` to populate.`,
134
154
  });
@@ -144,14 +164,14 @@ export function createIntegrationsHandler(deps: TestConnectionDeps = {}) {
144
164
  typeof agent.model === "string"
145
165
  ? agent.model
146
166
  : ((agent.model as { id?: string } | null | undefined)?.id ?? null);
147
- json(res, {
167
+ claudeManagedTestRoute.respond(res, 200, {
148
168
  ok: true,
149
169
  agentName: agent.name ?? null,
150
170
  model: modelId,
151
171
  });
152
172
  } catch (err) {
153
173
  const message = err instanceof Error ? err.message : String(err);
154
- json(res, { ok: false, error: message });
174
+ claudeManagedTestRoute.respond(res, 200, { ok: false, error: message });
155
175
  }
156
176
  return true;
157
177
  }