@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
package/src/http/pages.ts CHANGED
@@ -23,13 +23,15 @@ import {
23
23
  type Page,
24
24
  PageAuthModeSchema,
25
25
  PageContentTypeSchema,
26
+ PageSchema,
26
27
  type PageSummary,
28
+ PageVersionSchema,
27
29
  } from "../types";
28
30
  import { getAppUrl, getPublicMcpBaseUrl } from "../utils/constants";
29
31
  import { issuePageSessionCookie } from "../utils/page-session";
30
32
  import { resolveHttpFavoriteOwner } from "./favorite-owner";
31
33
  import { route } from "./route-def";
32
- import { BODY_TOO_LARGE, enforceContentLengthCap, json, jsonError } from "./utils";
34
+ import { BODY_TOO_LARGE, enforceContentLengthCap, jsonError } from "./utils";
33
35
 
34
36
  /**
35
37
  * Per-page body-size cap. Page bodies are stored as a TEXT column with no
@@ -57,6 +59,32 @@ function slugify(input: string): string {
57
59
  return slug || "page";
58
60
  }
59
61
 
62
+ // ─── Response Schemas ───────────────────────────────────────────────────────
63
+
64
+ /**
65
+ * Shape of a single page row as returned by the get-by-id / resolve-by-slug
66
+ * routes: the `Page` row (with its already-optional `favorite`, populated by
67
+ * `withFavoriteFlags` on the normal path but structurally optional to match
68
+ * the `decorated ?? page` defensive fallback in the handler) plus the two
69
+ * share-URL pointers added by `withShareUrls`.
70
+ */
71
+ const PageWithShareUrlsSchema = PageSchema.extend({
72
+ api_url: z.string(),
73
+ app_url: z.string(),
74
+ });
75
+
76
+ /**
77
+ * `/api/pages` list item. `body` / `passwordHash` are only present when the
78
+ * caller passes `?fields=full` (otherwise the slim `PageSummary` shape is
79
+ * used, which omits them entirely) — modeled as optional rather than a union
80
+ * since both shapes share every other field verbatim.
81
+ */
82
+ const PageListItemSchema = PageSchema.partial({ body: true, passwordHash: true }).extend({
83
+ favorite: z.boolean(),
84
+ api_url: z.string(),
85
+ app_url: z.string(),
86
+ });
87
+
60
88
  // ─── Route Definitions ──────────────────────────────────────────────────────
61
89
 
62
90
  const createPageRoute = route({
@@ -77,7 +105,16 @@ const createPageRoute = route({
77
105
  needsCredentials: z.array(z.string()).optional(),
78
106
  }),
79
107
  responses: {
80
- 201: { description: "Page created" },
108
+ 201: {
109
+ description: "Page created",
110
+ schema: z.object({
111
+ id: z.string(),
112
+ key: AssetKeySchema,
113
+ version: z.literal(1),
114
+ api_url: z.string(),
115
+ app_url: z.string(),
116
+ }),
117
+ },
81
118
  400: { description: "Invalid body" },
82
119
  409: { description: "Slug already exists for this agent" },
83
120
  },
@@ -91,7 +128,7 @@ const getPageRoute = route({
91
128
  tags: ["Pages"],
92
129
  params: z.object({ id: z.string() }),
93
130
  responses: {
94
- 200: { description: "Page row" },
131
+ 200: { description: "Page row", schema: PageWithShareUrlsSchema },
95
132
  404: { description: "Page not found" },
96
133
  },
97
134
  });
@@ -107,7 +144,7 @@ const resolvePageRoute = route({
107
144
  agentId: z.string().min(1).optional(),
108
145
  }),
109
146
  responses: {
110
- 200: { description: "Resolved page row" },
147
+ 200: { description: "Resolved page row", schema: PageWithShareUrlsSchema },
111
148
  404: { description: "Page not found" },
112
149
  },
113
150
  });
@@ -164,7 +201,10 @@ const updatePageRoute = route({
164
201
  needsCredentials: z.array(z.string()).nullable().optional(),
165
202
  }),
166
203
  responses: {
167
- 200: { description: "Page updated" },
204
+ 200: {
205
+ description: "Page updated",
206
+ schema: z.object({ id: z.string(), key: AssetKeySchema, version: z.number().int().min(1) }),
207
+ },
168
208
  404: { description: "Page not found" },
169
209
  413: { description: "Payload too large" },
170
210
  },
@@ -201,7 +241,15 @@ const listPagesRoute = route({
201
241
  fields: z.enum(["full", "slim"]).optional(),
202
242
  }),
203
243
  responses: {
204
- 200: { description: "Page list with totals + share-URL pointers" },
244
+ 200: {
245
+ description: "Page list with totals + share-URL pointers",
246
+ schema: z.object({
247
+ pages: z.array(PageListItemSchema),
248
+ total: z.number().int(),
249
+ limit: z.number().int(),
250
+ offset: z.number().int(),
251
+ }),
252
+ },
205
253
  },
206
254
  });
207
255
 
@@ -223,7 +271,21 @@ const listPageActionsRoute = route({
223
271
  summary: "List JSON-page action allowlist (with param JSON Schemas)",
224
272
  tags: ["Pages"],
225
273
  responses: {
226
- 200: { description: "Action allowlist" },
274
+ 200: {
275
+ description: "Action allowlist",
276
+ schema: z.object({
277
+ actions: z.array(
278
+ z.object({
279
+ name: z.string(),
280
+ description: z.string(),
281
+ // Rendered via `z.toJSONSchema()` — a draft-7 JSON Schema document,
282
+ // not otherwise modeled as a zod entity.
283
+ params: z.record(z.string(), z.unknown()),
284
+ sdkMethods: z.array(z.string()).optional(),
285
+ }),
286
+ ),
287
+ }),
288
+ },
227
289
  },
228
290
  });
229
291
 
@@ -265,7 +327,10 @@ const listPageVersionsRoute = route({
265
327
  tags: ["Pages"],
266
328
  params: z.object({ id: z.string() }),
267
329
  responses: {
268
- 200: { description: "Version list (newest first)" },
330
+ 200: {
331
+ description: "Version list (newest first)",
332
+ schema: z.object({ versions: z.array(PageVersionSchema) }),
333
+ },
269
334
  404: { description: "Page not found" },
270
335
  },
271
336
  });
@@ -278,7 +343,7 @@ const getPageVersionRoute = route({
278
343
  tags: ["Pages"],
279
344
  params: z.object({ id: z.string(), version: z.coerce.number().int().min(1) }),
280
345
  responses: {
281
- 200: { description: "Version snapshot" },
346
+ 200: { description: "Version snapshot", schema: PageVersionSchema },
282
347
  404: { description: "Page or version not found" },
283
348
  },
284
349
  });
@@ -412,17 +477,13 @@ export async function handlePages(
412
477
  });
413
478
  // First write has no prior snapshot — version 1 is implicit (the parent
414
479
  // IS v1). Subsequent edits land via PUT and bump the counter.
415
- json(
416
- res,
417
- {
418
- id: page.id,
419
- key: page.key,
420
- version: 1,
421
- api_url: `${getApiBaseUrl()}/p/${page.id}`,
422
- app_url: `${getAppBaseUrl()}/pages/${page.slug}`,
423
- },
424
- 201,
425
- );
480
+ createPageRoute.respond(res, 201, {
481
+ id: page.id,
482
+ key: page.key,
483
+ version: 1,
484
+ api_url: `${getApiBaseUrl()}/p/${page.id}`,
485
+ app_url: `${getAppBaseUrl()}/pages/${page.slug}`,
486
+ });
426
487
  } catch (err) {
427
488
  if (err instanceof AssetKeyAuthorizationError) {
428
489
  jsonError(res, err.message, err.statusCode);
@@ -444,13 +505,13 @@ export async function handlePages(
444
505
  if (listPageActionsRoute.match(req.method, pathSegments)) {
445
506
  const sdkSchema = z.toJSONSchema(swarmSdkActionParamsSchema, { target: "draft-7" });
446
507
  const callSchema = z.toJSONSchema(swarmCallActionParamsSchema, { target: "draft-7" });
447
- json(res, {
508
+ listPageActionsRoute.respond(res, 200, {
448
509
  actions: [
449
510
  {
450
511
  name: "swarm.sdk",
451
512
  description: "Invoke a method on the in-SPA Swarm SDK with the viewer's bearer.",
452
513
  params: sdkSchema,
453
- sdkMethods: SDK_METHODS,
514
+ sdkMethods: [...SDK_METHODS],
454
515
  },
455
516
  {
456
517
  name: "swarm.call",
@@ -478,7 +539,7 @@ export async function handlePages(
478
539
  }
479
540
  const favoriteScope = resolveHttpFavoriteOwner(req, myAgentId)?.scope;
480
541
  const [decorated] = withFavoriteFlags([page], { favoriteScope, itemType: "page" });
481
- json(res, withShareUrls(decorated ?? page));
542
+ resolvePageRoute.respond(res, 200, withShareUrls(decorated ?? page));
482
543
  return true;
483
544
  }
484
545
 
@@ -513,7 +574,7 @@ export async function handlePages(
513
574
  }
514
575
  const favoriteScope = resolveHttpFavoriteOwner(req, myAgentId)?.scope;
515
576
  const decoratedPages = withFavoriteFlags(pages, { favoriteScope, itemType: "page" });
516
- json(res, {
577
+ listPagesRoute.respond(res, 200, {
517
578
  pages: decoratedPages.map(withShareUrls),
518
579
  // Filter-aware total (real row count, not the current page's length) so
519
580
  // the UI pager reflects all pages, not just what this request returned.
@@ -545,7 +606,7 @@ export async function handlePages(
545
606
  res.end();
546
607
  return true;
547
608
  }
548
- json(res, version);
609
+ getPageVersionRoute.respond(res, 200, version);
549
610
  return true;
550
611
  }
551
612
 
@@ -564,7 +625,7 @@ export async function handlePages(
564
625
  return true;
565
626
  }
566
627
  const versions = getPageVersions(parsed.params.id);
567
- json(res, { versions });
628
+ listPageVersionsRoute.respond(res, 200, { versions });
568
629
  return true;
569
630
  }
570
631
 
@@ -586,7 +647,7 @@ export async function handlePages(
586
647
  }
587
648
  const favoriteScope = resolveHttpFavoriteOwner(req, myAgentId)?.scope;
588
649
  const [decorated] = withFavoriteFlags([page], { favoriteScope, itemType: "page" });
589
- json(res, withShareUrls(decorated ?? page));
650
+ getPageRoute.respond(res, 200, withShareUrls(decorated ?? page));
590
651
  return true;
591
652
  }
592
653
 
@@ -651,7 +712,11 @@ export async function handlePages(
651
712
  res.end();
652
713
  return true;
653
714
  }
654
- json(res, { id: updated.id, key: updated.key, version: pageEditCounter(updated.id) });
715
+ updatePageRoute.respond(res, 200, {
716
+ id: updated.id,
717
+ key: updated.key,
718
+ version: pageEditCounter(updated.id),
719
+ });
655
720
  return true;
656
721
  }
657
722
 
package/src/http/poll.ts CHANGED
@@ -29,8 +29,14 @@ import { renderIdentity, resolveIdentity } from "../be/identity";
29
29
  import { hasCapability } from "../server";
30
30
  import { fetchChannelActivity } from "../slack/channel-activity";
31
31
  import { telemetry } from "../telemetry";
32
+ import {
33
+ AgentTaskSchema,
34
+ BudgetRefusedTriggerSchema,
35
+ TaskAttachmentSchema,
36
+ UserSchema,
37
+ } from "../types";
32
38
  import { route } from "./route-def";
33
- import { json, jsonError } from "./utils";
39
+ import { jsonError } from "./utils";
34
40
 
35
41
  // ─── Budget-refused trigger envelope ────────────────────────────────────────
36
42
 
@@ -70,6 +76,78 @@ function buildBudgetRefusedTrigger(refusal: {
70
76
 
71
77
  // ─── Route Definitions ───────────────────────────────────────────────────────
72
78
 
79
+ // Slim attachment projection sent on `task_assigned` triggers — mirrors the
80
+ // fields built by `attachmentsForTrigger` below (id, name, mimeType, sizeBytes).
81
+ const PollTriggerAttachmentSchema = TaskAttachmentSchema.pick({
82
+ id: true,
83
+ name: true,
84
+ mimeType: true,
85
+ sizeBytes: true,
86
+ });
87
+
88
+ // Requester projection sent on `task_assigned` triggers — either the resolved
89
+ // user's identity fields, or (when no `requestedByUserId` is recorded) just a
90
+ // rendered `name` for the UNKNOWN-identity sentinel.
91
+ const PollRequestedBySchema = UserSchema.pick({
92
+ name: true,
93
+ email: true,
94
+ role: true,
95
+ notes: true,
96
+ });
97
+
98
+ const PollTaskOfferedTriggerSchema = z.object({
99
+ type: z.literal("task_offered"),
100
+ taskId: z.string(),
101
+ task: AgentTaskSchema,
102
+ });
103
+
104
+ const PollTaskAssignedTriggerSchema = z.object({
105
+ type: z.literal("task_assigned"),
106
+ taskId: z.string(),
107
+ task: AgentTaskSchema.extend({
108
+ attachments: z.array(PollTriggerAttachmentSchema),
109
+ }),
110
+ requestedBy: PollRequestedBySchema.optional(),
111
+ });
112
+
113
+ // `budget_refused` reuses the canonical `BudgetRefusedTriggerSchema` entity
114
+ // (src/types.ts) — it was already defined as the wire shape for this exact
115
+ // `/api/poll` trigger back in Phase 3, mirrored by `buildBudgetRefusedTrigger`
116
+ // below.
117
+
118
+ const PollUnreadMentionsTriggerSchema = z.object({
119
+ type: z.literal("unread_mentions"),
120
+ mentionsCount: z.number(),
121
+ claimedChannels: z.array(z.string()),
122
+ });
123
+
124
+ const PollChannelActivityTriggerSchema = z.object({
125
+ type: z.literal("channel_activity"),
126
+ count: z.number(),
127
+ messages: z.array(
128
+ z.object({
129
+ channelId: z.string(),
130
+ channelName: z.string().optional(),
131
+ ts: z.string(),
132
+ user: z.string(),
133
+ text: z.string(),
134
+ }),
135
+ ),
136
+ cursorUpdates: z.array(z.object({ channelId: z.string(), ts: z.string() })),
137
+ });
138
+
139
+ const PollTriggerSchema = z.discriminatedUnion("type", [
140
+ PollTaskOfferedTriggerSchema,
141
+ PollTaskAssignedTriggerSchema,
142
+ BudgetRefusedTriggerSchema,
143
+ PollUnreadMentionsTriggerSchema,
144
+ PollChannelActivityTriggerSchema,
145
+ ]);
146
+
147
+ const pollResponseSchema = z.object({
148
+ trigger: PollTriggerSchema.nullable(),
149
+ });
150
+
73
151
  const pollTriggers = route({
74
152
  method: "get",
75
153
  path: "/api/poll",
@@ -78,7 +156,7 @@ const pollTriggers = route({
78
156
  tags: ["Poll"],
79
157
  auth: { apiKey: true, agentId: true },
80
158
  responses: {
81
- 200: { description: "Trigger data or null" },
159
+ 200: { description: "Trigger data or null", schema: pollResponseSchema },
82
160
  400: { description: "Missing X-Agent-ID" },
83
161
  404: { description: "Agent not found" },
84
162
  },
@@ -128,7 +206,10 @@ const commitCursorsRoute = route({
128
206
  ),
129
207
  }),
130
208
  responses: {
131
- 200: { description: "Cursors committed" },
209
+ 200: {
210
+ description: "Cursors committed",
211
+ schema: z.object({ success: z.literal(true), committed: z.number().int().nonnegative() }),
212
+ },
132
213
  400: { description: "Invalid request" },
133
214
  },
134
215
  });
@@ -151,7 +232,10 @@ export async function handlePoll(
151
232
  upsertChannelActivityCursor(channelId, ts);
152
233
  }
153
234
  }
154
- json(res, { success: true, committed: parsed.body.cursorUpdates.length });
235
+ commitCursorsRoute.respond(res, 200, {
236
+ success: true,
237
+ committed: parsed.body.cursorUpdates.length,
238
+ });
155
239
  return true;
156
240
  }
157
241
 
@@ -535,7 +619,7 @@ export async function handlePoll(
535
619
  refusalSideEffects?: unknown;
536
620
  [key: string]: unknown;
537
621
  };
538
- json(res, publicResult);
622
+ pollTriggers.respond(res, 200, publicResult as z.infer<typeof pollResponseSchema>);
539
623
  return true;
540
624
  }
541
625
 
@@ -22,7 +22,7 @@ import {
22
22
  import { PricingProviderSchema, PricingRowSchema, PricingTokenClassSchema } from "../types";
23
23
  import { scrubSecrets } from "../utils/secret-scrubber";
24
24
  import { route } from "./route-def";
25
- import { json, jsonError } from "./utils";
25
+ import { jsonError } from "./utils";
26
26
 
27
27
  // ─── Helpers ─────────────────────────────────────────────────────────────────
28
28
 
@@ -61,7 +61,10 @@ const listPricingForTriple = route({
61
61
  tags: ["Pricing"],
62
62
  params: PricingTriplePathParams,
63
63
  responses: {
64
- 200: { description: "Pricing rows (latest first)" },
64
+ 200: {
65
+ description: "Pricing rows (latest first)",
66
+ schema: z.object({ rows: z.array(PricingRowSchema) }),
67
+ },
65
68
  },
66
69
  });
67
70
 
@@ -128,7 +131,7 @@ export async function handlePricing(
128
131
  if (listAllPricing.match(req.method, pathSegments)) {
129
132
  const parsed = await listAllPricing.parse(req, res, pathSegments, queryParams);
130
133
  if (!parsed) return true;
131
- json(res, { rows: getAllPricingRows() });
134
+ listAllPricing.respond(res, 200, { rows: getAllPricingRows() });
132
135
  return true;
133
136
  }
134
137
 
@@ -147,7 +150,7 @@ export async function handlePricing(
147
150
  jsonError(res, "No pricing row in effect", 404);
148
151
  return true;
149
152
  }
150
- json(res, row);
153
+ getActivePricing.respond(res, 200, row);
151
154
  return true;
152
155
  }
153
156
 
@@ -193,7 +196,7 @@ export async function handlePricing(
193
196
  parsed.params.model,
194
197
  parsed.params.tokenClass,
195
198
  );
196
- json(res, { rows });
199
+ listPricingForTriple.respond(res, 200, { rows });
197
200
  return true;
198
201
  }
199
202
 
@@ -224,7 +227,7 @@ export async function handlePricing(
224
227
  },
225
228
  });
226
229
 
227
- json(res, row, 201);
230
+ insertPricing.respond(res, 201, row);
228
231
  } catch (err) {
229
232
  const message = err instanceof Error ? err.message : String(err);
230
233
  // bun:sqlite raises SQLITE_CONSTRAINT for PK collision. Surface as 409.
@@ -12,9 +12,42 @@ import {
12
12
  } from "../be/db";
13
13
  import { getAllTemplateDefinitions, getTemplateDefinition } from "../prompts/registry";
14
14
  import { resolveTemplate } from "../prompts/resolver";
15
+ import { PromptTemplateHistorySchema, PromptTemplateSchema } from "../types";
15
16
  import { interpolate } from "../utils/template";
16
17
  import { route } from "./route-def";
17
- import { json, jsonError } from "./utils";
18
+ import { jsonError } from "./utils";
19
+
20
+ // ─── Response Schemas ────────────────────────────────────────────────────────
21
+
22
+ /** Mirrors `EventTemplateDefinition["variables"]` entries from src/prompts/registry.ts */
23
+ const VariableDefinitionSchema = z.object({
24
+ name: z.string(),
25
+ description: z.string(),
26
+ example: z.string().optional(),
27
+ });
28
+
29
+ /** Mirrors the plain-object projection of `EventTemplateDefinition` the handlers send. */
30
+ const EventTemplateDefinitionResponseSchema = z.object({
31
+ eventType: z.string(),
32
+ header: z.string(),
33
+ defaultBody: z.string(),
34
+ variables: z.array(VariableDefinitionSchema),
35
+ category: z.enum(["event", "system", "common", "task_lifecycle", "session"]),
36
+ });
37
+
38
+ /** Mirrors `ResolveResult` from src/prompts/resolver.ts */
39
+ const ResolveResultSchema = z.object({
40
+ text: z.string(),
41
+ templateId: z.string().optional(),
42
+ scope: z.string().optional(),
43
+ skipped: z.boolean(),
44
+ unresolved: z.array(z.string()),
45
+ });
46
+
47
+ /** Mirrors the return type of `resolvePromptTemplate` in src/be/db.ts */
48
+ const DbResolveResultSchema = z
49
+ .union([z.object({ template: PromptTemplateSchema }), z.object({ skip: z.literal(true) })])
50
+ .nullable();
18
51
 
19
52
  // ─── Route Definitions ───────────────────────────────────────────────────────
20
53
 
@@ -30,7 +63,14 @@ const resolvedRoute = route({
30
63
  repoId: z.string().optional(),
31
64
  }),
32
65
  responses: {
33
- 200: { description: "Resolved template info" },
66
+ 200: {
67
+ description: "Resolved template info",
68
+ schema: z.object({
69
+ resolution: ResolveResultSchema,
70
+ dbResult: DbResolveResultSchema,
71
+ definition: EventTemplateDefinitionResponseSchema.nullable(),
72
+ }),
73
+ },
34
74
  400: { description: "Missing eventType" },
35
75
  },
36
76
  auth: { apiKey: true },
@@ -43,7 +83,10 @@ const eventsRoute = route({
43
83
  summary: "List all registered event types with their available variables",
44
84
  tags: ["PromptTemplates"],
45
85
  responses: {
46
- 200: { description: "List of event template definitions" },
86
+ 200: {
87
+ description: "List of event template definitions",
88
+ schema: z.object({ events: z.array(EventTemplateDefinitionResponseSchema) }),
89
+ },
47
90
  },
48
91
  auth: { apiKey: true },
49
92
  });
@@ -60,7 +103,10 @@ const previewRoute = route({
60
103
  variables: z.record(z.string(), z.unknown()).optional(),
61
104
  }),
62
105
  responses: {
63
- 200: { description: "Rendered template preview" },
106
+ 200: {
107
+ description: "Rendered template preview",
108
+ schema: z.object({ rendered: z.string(), unresolved: z.array(z.string()) }),
109
+ },
64
110
  400: { description: "Validation error" },
65
111
  },
66
112
  auth: { apiKey: true },
@@ -79,7 +125,7 @@ const renderRoute = route({
79
125
  repoId: z.string().optional(),
80
126
  }),
81
127
  responses: {
82
- 200: { description: "Fully resolved and interpolated template" },
128
+ 200: { description: "Fully resolved and interpolated template", schema: ResolveResultSchema },
83
129
  400: { description: "Validation error" },
84
130
  },
85
131
  auth: { apiKey: true },
@@ -94,7 +140,10 @@ const checkoutRoute = route({
94
140
  params: z.object({ id: z.string() }),
95
141
  body: z.object({ version: z.number() }),
96
142
  responses: {
97
- 200: { description: "Checked-out template" },
143
+ 200: {
144
+ description: "Checked-out template",
145
+ schema: z.object({ template: PromptTemplateSchema }),
146
+ },
98
147
  400: { description: "Validation error" },
99
148
  404: { description: "Template or version not found" },
100
149
  },
@@ -109,7 +158,7 @@ const resetRoute = route({
109
158
  tags: ["PromptTemplates"],
110
159
  params: z.object({ id: z.string() }),
111
160
  responses: {
112
- 200: { description: "Reset template" },
161
+ 200: { description: "Reset template", schema: z.object({ template: PromptTemplateSchema }) },
113
162
  404: { description: "Template not found or no code default available" },
114
163
  },
115
164
  auth: { apiKey: true },
@@ -123,7 +172,13 @@ const getByIdRoute = route({
123
172
  tags: ["PromptTemplates"],
124
173
  params: z.object({ id: z.string() }),
125
174
  responses: {
126
- 200: { description: "Template with history" },
175
+ 200: {
176
+ description: "Template with history",
177
+ schema: z.object({
178
+ template: PromptTemplateSchema,
179
+ history: z.array(PromptTemplateHistorySchema),
180
+ }),
181
+ },
127
182
  404: { description: "Template not found" },
128
183
  },
129
184
  auth: { apiKey: true },
@@ -137,7 +192,10 @@ const deleteByIdRoute = route({
137
192
  tags: ["PromptTemplates"],
138
193
  params: z.object({ id: z.string() }),
139
194
  responses: {
140
- 200: { description: "Template deleted" },
195
+ 200: {
196
+ description: "Template deleted",
197
+ schema: z.object({ deleted: z.literal(true) }),
198
+ },
141
199
  400: { description: "Cannot delete default template" },
142
200
  404: { description: "Template not found" },
143
201
  },
@@ -157,7 +215,10 @@ const listRoute = route({
157
215
  isDefault: z.enum(["true", "false"]).optional(),
158
216
  }),
159
217
  responses: {
160
- 200: { description: "List of prompt templates" },
218
+ 200: {
219
+ description: "List of prompt templates",
220
+ schema: z.object({ templates: z.array(PromptTemplateSchema) }),
221
+ },
161
222
  },
162
223
  auth: { apiKey: true },
163
224
  });
@@ -178,7 +239,10 @@ const upsertRoute = route({
178
239
  changeReason: z.string().optional(),
179
240
  }),
180
241
  responses: {
181
- 200: { description: "Upserted template" },
242
+ 200: {
243
+ description: "Upserted template",
244
+ schema: z.object({ template: PromptTemplateSchema }),
245
+ },
182
246
  400: { description: "Validation error" },
183
247
  },
184
248
  auth: { apiKey: true },
@@ -207,7 +271,7 @@ export async function handlePromptTemplates(
207
271
  const dbResult = resolvePromptTemplate(eventType, agentId, repoId);
208
272
  const definition = getTemplateDefinition(eventType);
209
273
 
210
- json(res, {
274
+ resolvedRoute.respond(res, 200, {
211
275
  resolution: result,
212
276
  dbResult,
213
277
  definition: definition
@@ -229,7 +293,7 @@ export async function handlePromptTemplates(
229
293
  if (!parsed) return true;
230
294
 
231
295
  const definitions = getAllTemplateDefinitions();
232
- json(res, {
296
+ eventsRoute.respond(res, 200, {
233
297
  events: definitions.map((d) => ({
234
298
  eventType: d.eventType,
235
299
  header: d.header,
@@ -253,7 +317,7 @@ export async function handlePromptTemplates(
253
317
  const composed = header ? `${header}\n\n${templateBody}` : templateBody;
254
318
  const { result: rendered, unresolved } = interpolate(composed, variables ?? {});
255
319
 
256
- json(res, { rendered, unresolved });
320
+ previewRoute.respond(res, 200, { rendered, unresolved });
257
321
  return true;
258
322
  }
259
323
 
@@ -264,7 +328,7 @@ export async function handlePromptTemplates(
264
328
 
265
329
  const { eventType, variables, agentId, repoId } = parsed.body;
266
330
  const result = resolveTemplate(eventType, variables ?? {}, { agentId, repoId });
267
- json(res, result);
331
+ renderRoute.respond(res, 200, result);
268
332
  return true;
269
333
  }
270
334
 
@@ -275,7 +339,7 @@ export async function handlePromptTemplates(
275
339
 
276
340
  try {
277
341
  const template = checkoutPromptTemplate(parsed.params.id, parsed.body.version);
278
- json(res, { template });
342
+ checkoutRoute.respond(res, 200, { template });
279
343
  } catch (error) {
280
344
  const message = error instanceof Error ? error.message : "Unknown error";
281
345
  if (message.includes("not found")) {
@@ -306,7 +370,7 @@ export async function handlePromptTemplates(
306
370
 
307
371
  try {
308
372
  const template = resetPromptTemplateToDefault(parsed.params.id, definition.defaultBody);
309
- json(res, { template });
373
+ resetRoute.respond(res, 200, { template });
310
374
  } catch (error) {
311
375
  const message = error instanceof Error ? error.message : "Unknown error";
312
376
  jsonError(res, message, 400);
@@ -326,7 +390,7 @@ export async function handlePromptTemplates(
326
390
  }
327
391
 
328
392
  const history = getPromptTemplateHistory(parsed.params.id);
329
- json(res, { template, history });
393
+ getByIdRoute.respond(res, 200, { template, history });
330
394
  return true;
331
395
  }
332
396
 
@@ -341,7 +405,7 @@ export async function handlePromptTemplates(
341
405
  jsonError(res, "Prompt template not found", 404);
342
406
  return true;
343
407
  }
344
- json(res, { deleted: true });
408
+ deleteByIdRoute.respond(res, 200, { deleted: true });
345
409
  } catch (error) {
346
410
  const message = error instanceof Error ? error.message : "Unknown error";
347
411
  jsonError(res, message, 400);
@@ -361,7 +425,7 @@ export async function handlePromptTemplates(
361
425
  isDefault: parsed.query.isDefault ? parsed.query.isDefault === "true" : undefined,
362
426
  });
363
427
 
364
- json(res, { templates });
428
+ listRoute.respond(res, 200, { templates });
365
429
  return true;
366
430
  }
367
431
 
@@ -400,7 +464,7 @@ export async function handlePromptTemplates(
400
464
  changedBy,
401
465
  changeReason,
402
466
  });
403
- json(res, { template });
467
+ upsertRoute.respond(res, 200, { template });
404
468
  } catch (error) {
405
469
  const message = error instanceof Error ? error.message : "Unknown error";
406
470
  jsonError(res, message, 500);