@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
@@ -16,10 +16,68 @@ import {
16
16
  import { recordSessionCost } from "../otel";
17
17
  import { incrementServerSessionsProcessed } from "../server-runtime-counters";
18
18
  import type { SessionCost } from "../types";
19
- import { SessionCostModelBreakdownSchema } from "../types";
19
+ import { SessionCostModelBreakdownSchema, SessionCostSchema, SessionLogSchema } from "../types";
20
20
  import { route } from "./route-def";
21
21
  import { recomputeSessionCost } from "./session-cost-recompute";
22
- import { json, jsonError } from "./utils";
22
+ import { jsonError } from "./utils";
23
+
24
+ // ─── Response Schemas ────────────────────────────────────────────────────────
25
+
26
+ /** Mirrors `SessionCostSummaryTotals` in src/be/db.ts. */
27
+ const SessionCostSummaryTotalsSchema = z.object({
28
+ totalCostUsd: z.number(),
29
+ totalInputTokens: z.number().int(),
30
+ totalOutputTokens: z.number().int(),
31
+ totalCacheReadTokens: z.number().int(),
32
+ totalCacheWriteTokens: z.number().int(),
33
+ totalDurationMs: z.number().int(),
34
+ totalSessions: z.number().int(),
35
+ avgCostPerSession: z.number(),
36
+ attributedCostUsd: z.number(),
37
+ });
38
+
39
+ /** Mirrors `SessionCostDailyRow` in src/be/db.ts. */
40
+ const SessionCostDailyRowSchema = z.object({
41
+ date: z.string(),
42
+ costUsd: z.number(),
43
+ inputTokens: z.number().int(),
44
+ outputTokens: z.number().int(),
45
+ sessions: z.number().int(),
46
+ });
47
+
48
+ /** Mirrors `SessionCostByAgentRow` in src/be/db.ts. */
49
+ const SessionCostByAgentRowSchema = z.object({
50
+ agentId: z.string(),
51
+ costUsd: z.number(),
52
+ inputTokens: z.number().int(),
53
+ outputTokens: z.number().int(),
54
+ sessions: z.number().int(),
55
+ durationMs: z.number().int(),
56
+ });
57
+
58
+ /** Mirrors `SessionCostByUserRow` in src/be/db.ts. */
59
+ const SessionCostByUserRowSchema = z.object({
60
+ userId: z.string().nullable(),
61
+ costUsd: z.number(),
62
+ inputTokens: z.number().int(),
63
+ outputTokens: z.number().int(),
64
+ tasks: z.number().int(),
65
+ durationMs: z.number().int(),
66
+ });
67
+
68
+ /** Mirrors the return type of `getSessionCostSummary` in src/be/db.ts. */
69
+ const SessionCostSummarySchema = z.object({
70
+ totals: SessionCostSummaryTotalsSchema,
71
+ daily: z.array(SessionCostDailyRowSchema),
72
+ byAgent: z.array(SessionCostByAgentRowSchema),
73
+ byUser: z.array(SessionCostByUserRowSchema),
74
+ });
75
+
76
+ /** Mirrors `DashboardCostSummary` in src/be/db.ts. */
77
+ const DashboardCostSummarySchema = z.object({
78
+ costToday: z.number(),
79
+ costMtd: z.number(),
80
+ });
23
81
 
24
82
  // ─── Route Definitions ───────────────────────────────────────────────────────
25
83
 
@@ -37,7 +95,10 @@ const createSessionLogsRoute = route({
37
95
  cli: z.string().optional(),
38
96
  }),
39
97
  responses: {
40
- 201: { description: "Logs stored" },
98
+ 201: {
99
+ description: "Logs stored",
100
+ schema: z.object({ success: z.literal(true), count: z.number().int().nonnegative() }),
101
+ },
41
102
  400: { description: "Validation error" },
42
103
  },
43
104
  });
@@ -57,7 +118,10 @@ const getSessionLogsByTask = route({
57
118
  limit: z.coerce.number().int().min(1).max(1000).optional(),
58
119
  }),
59
120
  responses: {
60
- 200: { description: "Session logs" },
121
+ 200: {
122
+ description: "Session logs",
123
+ schema: z.object({ logs: z.array(SessionLogSchema) }),
124
+ },
61
125
  404: { description: "Task not found" },
62
126
  },
63
127
  });
@@ -111,7 +175,10 @@ const createSessionCostRoute = route({
111
175
  createdAt: z.number().int().nonnegative().optional(),
112
176
  }),
113
177
  responses: {
114
- 201: { description: "Cost record stored" },
178
+ 201: {
179
+ description: "Cost record stored",
180
+ schema: z.object({ success: z.literal(true), cost: SessionCostSchema }),
181
+ },
115
182
  400: { description: "Validation error" },
116
183
  },
117
184
  });
@@ -131,7 +198,7 @@ const getSessionCostSummaryRoute = route({
131
198
  userId: z.string().optional(),
132
199
  }),
133
200
  responses: {
134
- 200: { description: "Cost summary" },
201
+ 200: { description: "Cost summary", schema: SessionCostSummarySchema },
135
202
  400: { description: "Invalid groupBy" },
136
203
  },
137
204
  });
@@ -143,7 +210,7 @@ const getDashboardCosts = route({
143
210
  summary: "Cost today and month-to-date for dashboard",
144
211
  tags: ["Session Data"],
145
212
  responses: {
146
- 200: { description: "Dashboard cost data" },
213
+ 200: { description: "Dashboard cost data", schema: DashboardCostSummarySchema },
147
214
  },
148
215
  });
149
216
 
@@ -161,7 +228,10 @@ const listSessionCosts = route({
161
228
  limit: z.coerce.number().int().min(1).optional(),
162
229
  }),
163
230
  responses: {
164
- 200: { description: "Session costs" },
231
+ 200: {
232
+ description: "Session costs",
233
+ schema: z.object({ costs: z.array(SessionCostSchema) }),
234
+ },
165
235
  },
166
236
  });
167
237
 
@@ -186,7 +256,10 @@ export async function handleSessionData(
186
256
  cli: parsed.body.cli || "claude",
187
257
  lines: parsed.body.lines,
188
258
  });
189
- json(res, { success: true, count: parsed.body.lines.length }, 201);
259
+ createSessionLogsRoute.respond(res, 201, {
260
+ success: true,
261
+ count: parsed.body.lines.length,
262
+ });
190
263
  } catch (error) {
191
264
  console.error("[HTTP] Failed to create session logs:", error);
192
265
  jsonError(res, "Failed to store session logs", 500);
@@ -203,7 +276,7 @@ export async function handleSessionData(
203
276
  return true;
204
277
  }
205
278
  const logs = getSessionLogsByTaskId(parsed.params.taskId, parsed.query?.limit);
206
- json(res, { logs });
279
+ getSessionLogsByTask.respond(res, 200, { logs });
207
280
  return true;
208
281
  }
209
282
 
@@ -302,7 +375,7 @@ export async function handleSessionData(
302
375
  },
303
376
  });
304
377
  incrementServerSessionsProcessed();
305
- json(res, { success: true, cost }, 201);
378
+ createSessionCostRoute.respond(res, 201, { success: true, cost });
306
379
  } catch (error) {
307
380
  console.error("[HTTP] Failed to create session cost:", error);
308
381
  jsonError(res, "Failed to store session cost", 500);
@@ -320,13 +393,13 @@ export async function handleSessionData(
320
393
  userId: parsed.query.userId || undefined,
321
394
  groupBy: parsed.query.groupBy || "both",
322
395
  });
323
- json(res, summary);
396
+ getSessionCostSummaryRoute.respond(res, 200, summary);
324
397
  return true;
325
398
  }
326
399
 
327
400
  if (getDashboardCosts.match(req.method, pathSegments)) {
328
401
  const dashboardCosts = getDashboardCostSummary();
329
- json(res, dashboardCosts);
402
+ getDashboardCosts.respond(res, 200, dashboardCosts);
330
403
  return true;
331
404
  }
332
405
 
@@ -352,7 +425,7 @@ export async function handleSessionData(
352
425
  costs = getAllSessionCosts(limit);
353
426
  }
354
427
 
355
- json(res, { costs });
428
+ listSessionCosts.respond(res, 200, { costs });
356
429
  return true;
357
430
  }
358
431
 
@@ -2,8 +2,69 @@ import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { z } from "zod";
3
3
  import { countSessions, getRootTaskChain, getTaskById, listRecentSessions } from "../be/db";
4
4
  import { getTaskSteeringFields } from "../be/steering";
5
+ import { AgentTaskSchema, AgentTaskStatusSchema, SteerModeSchema } from "../types";
5
6
  import { route } from "./route-def";
6
- import { json, jsonError } from "./utils";
7
+ import { jsonError } from "./utils";
8
+
9
+ // ─── Response Schemas ────────────────────────────────────────────────────────
10
+
11
+ /**
12
+ * `/api/sessions` list item's `root` when slim (default) — mirrors the
13
+ * `AgentTaskSummary` TS type in ../types: the `task` text truncated to a
14
+ * bounded preview and completion/integration/context blobs dropped. Kept in
15
+ * lock-step with that type's `Pick<...>` field list (mirrors the sibling
16
+ * definition in src/http/tasks.ts — not imported from there since it isn't
17
+ * exported).
18
+ */
19
+ const AgentTaskSummarySchema = AgentTaskSchema.pick({
20
+ id: true,
21
+ key: true,
22
+ agentId: true,
23
+ creatorAgentId: true,
24
+ task: true,
25
+ title: true,
26
+ status: true,
27
+ source: true,
28
+ taskType: true,
29
+ tags: true,
30
+ priority: true,
31
+ dependsOn: true,
32
+ offeredTo: true,
33
+ acceptedAt: true,
34
+ parentTaskId: true,
35
+ scheduleId: true,
36
+ model: true,
37
+ modelTier: true,
38
+ effort: true,
39
+ provider: true,
40
+ requestedByUserId: true,
41
+ progress: true,
42
+ createdAt: true,
43
+ lastUpdatedAt: true,
44
+ finishedAt: true,
45
+ peakContextPercent: true,
46
+ totalCostUsd: true,
47
+ });
48
+
49
+ /**
50
+ * A `/api/sessions` list item. `root` is a full `AgentTask` when
51
+ * `?fields=full`, an `AgentTaskSummary` otherwise (default).
52
+ */
53
+ const SessionListItemSchema = z.object({
54
+ root: z.union([AgentTaskSchema, AgentTaskSummarySchema]),
55
+ chainTaskCount: z.number().int(),
56
+ lastActivityAt: z.string(),
57
+ latestStatus: AgentTaskStatusSchema,
58
+ });
59
+
60
+ /**
61
+ * A full `AgentTask` decorated with `getTaskSteeringFields` — the shape of
62
+ * `root` and each `chain` entry on `GET /api/sessions/{rootTaskId}`.
63
+ */
64
+ const TaskWithSteeringSchema = AgentTaskSchema.extend({
65
+ isLeadTask: z.boolean(),
66
+ supportedSteerModes: z.array(SteerModeSchema),
67
+ });
7
68
 
8
69
  // ─── Route Definitions ───────────────────────────────────────────────────────
9
70
 
@@ -32,7 +93,15 @@ const listSessions = route({
32
93
  fields: z.enum(["full", "slim"]).optional(),
33
94
  }),
34
95
  responses: {
35
- 200: { description: "Recent sessions ordered by chain-wide last activity" },
96
+ 200: {
97
+ description: "Recent sessions ordered by chain-wide last activity",
98
+ schema: z.object({
99
+ sessions: z.array(SessionListItemSchema),
100
+ total: z.number().int(),
101
+ limit: z.number().int(),
102
+ offset: z.number().int(),
103
+ }),
104
+ },
36
105
  401: { description: "Unauthorized" },
37
106
  },
38
107
  auth: { apiKey: true },
@@ -46,7 +115,13 @@ const getSession = route({
46
115
  tags: ["Sessions"],
47
116
  params: z.object({ rootTaskId: z.string() }),
48
117
  responses: {
49
- 200: { description: "Root task + chain (ordered by createdAt)" },
118
+ 200: {
119
+ description: "Root task + chain (ordered by createdAt)",
120
+ schema: z.object({
121
+ root: TaskWithSteeringSchema,
122
+ chain: z.array(TaskWithSteeringSchema),
123
+ }),
124
+ },
50
125
  401: { description: "Unauthorized" },
51
126
  404: { description: "Root task not found" },
52
127
  },
@@ -89,7 +164,7 @@ export async function handleSessions(
89
164
  q: parsed.query.q,
90
165
  requestedByUserId: parsed.query.requestedByUserId,
91
166
  });
92
- json(res, {
167
+ listSessions.respond(res, 200, {
93
168
  sessions,
94
169
  total,
95
170
  limit: parsed.query.limit ?? 25,
@@ -107,7 +182,7 @@ export async function handleSessions(
107
182
  return true;
108
183
  }
109
184
  const chain = getRootTaskChain(parsed.params.rootTaskId);
110
- json(res, {
185
+ getSession.respond(res, 200, {
111
186
  root: { ...root, ...getTaskSteeringFields(root) },
112
187
  chain: chain.map((task) => ({ ...task, ...getTaskSteeringFields(task) })),
113
188
  });
@@ -18,8 +18,14 @@ import {
18
18
  } from "../be/db";
19
19
  import { parseSkillContent } from "../be/skill-parser";
20
20
  import { computeAgentSkillsSignature, syncSkillsToFilesystem } from "../be/skill-sync";
21
+ import {
22
+ AgentSkillSchema,
23
+ SkillFileSchema,
24
+ SkillSchema,
25
+ SkillWithInstallInfoSchema,
26
+ } from "../types";
21
27
  import { route } from "./route-def";
22
- import { json, jsonError } from "./utils";
28
+ import { jsonError } from "./utils";
23
29
 
24
30
  const SYSTEM_DEFAULT_SKILL_LOCKED_MESSAGE =
25
31
  "This skill is system-managed and cannot be edited from the UI; it is re-seeded on each start. Fork it under a new name to customize.";
@@ -35,6 +41,72 @@ const skillFileWithPathSchema = skillFileBodySchema.extend({
35
41
  path: z.string().min(1),
36
42
  });
37
43
 
44
+ // ─── Response Schemas ────────────────────────────────────────────────────────
45
+
46
+ /** Bundled-file manifest entry — `SkillFile` minus the heavy `content` field. */
47
+ const skillFileManifestEntrySchema = SkillFileSchema.omit({ content: true });
48
+
49
+ const skillsListResponseSchema = z.object({
50
+ skills: z.array(SkillSchema),
51
+ total: z.number(),
52
+ });
53
+
54
+ const skillFileManifestResponseSchema = z.object({
55
+ files: z.array(skillFileManifestEntrySchema),
56
+ total: z.number(),
57
+ });
58
+
59
+ const bulkUpsertSkillFilesResponseSchema = z.object({
60
+ files: z.array(SkillFileSchema),
61
+ total: z.number(),
62
+ skill: SkillSchema.nullable(),
63
+ });
64
+
65
+ const skillFileResponseSchema = z.object({ file: SkillFileSchema });
66
+
67
+ const skillFileWithSkillResponseSchema = z.object({
68
+ file: SkillFileSchema,
69
+ skill: SkillSchema.nullable(),
70
+ });
71
+
72
+ const deleteSkillFileResponseSchema = z.object({
73
+ success: z.literal(true),
74
+ skill: SkillSchema.nullable(),
75
+ });
76
+
77
+ const skillWrapperResponseSchema = z.object({ skill: SkillSchema });
78
+
79
+ const deleteSuccessResponseSchema = z.object({ success: z.literal(true) });
80
+
81
+ const installSkillResponseSchema = z.object({ agentSkill: AgentSkillSchema });
82
+
83
+ const uninstallSkillResponseSchema = z.object({ success: z.boolean() });
84
+
85
+ const syncRemoteResponseSchema = z.object({
86
+ updated: z.number(),
87
+ checked: z.number(),
88
+ errors: z.array(z.string()),
89
+ });
90
+
91
+ const syncFilesystemResponseSchema = z.object({
92
+ synced: z.number(),
93
+ removed: z.number(),
94
+ errors: z.array(z.string()),
95
+ message: z.string(),
96
+ });
97
+
98
+ const agentSkillsListResponseSchema = z.object({
99
+ skills: z.array(SkillWithInstallInfoSchema),
100
+ total: z.number(),
101
+ signature: z.string(),
102
+ });
103
+
104
+ const agentSkillsSignatureResponseSchema = z.object({
105
+ hash: z.string(),
106
+ count: z.number(),
107
+ generatedAt: z.string(),
108
+ });
109
+
38
110
  function decodeSkillFilePath(pathSegments: string[]): string {
39
111
  return pathSegments
40
112
  .slice(4)
@@ -63,7 +135,7 @@ const listSkillsRoute = route({
63
135
  fields: z.enum(["full", "slim"]).optional(),
64
136
  }),
65
137
  responses: {
66
- 200: { description: "Skill list" },
138
+ 200: { description: "Skill list", schema: skillsListResponseSchema },
67
139
  },
68
140
  });
69
141
 
@@ -76,7 +148,7 @@ const getSkillRoute = route({
76
148
  auth: { apiKey: true },
77
149
  params: z.object({ id: z.string() }),
78
150
  responses: {
79
- 200: { description: "Skill details" },
151
+ 200: { description: "Skill details", schema: SkillSchema },
80
152
  404: { description: "Skill not found" },
81
153
  },
82
154
  });
@@ -91,7 +163,7 @@ const listSkillFilesRoute = route({
91
163
  auth: { apiKey: true },
92
164
  params: z.object({ id: z.string() }),
93
165
  responses: {
94
- 200: { description: "Skill file manifest" },
166
+ 200: { description: "Skill file manifest", schema: skillFileManifestResponseSchema },
95
167
  404: { description: "Skill not found" },
96
168
  },
97
169
  });
@@ -109,7 +181,7 @@ const bulkUpsertSkillFilesRoute = route({
109
181
  files: z.array(skillFileWithPathSchema).max(100),
110
182
  }),
111
183
  responses: {
112
- 200: { description: "Skill files upserted" },
184
+ 200: { description: "Skill files upserted", schema: bulkUpsertSkillFilesResponseSchema },
113
185
  400: { description: "Validation error" },
114
186
  404: { description: "Skill not found" },
115
187
  },
@@ -125,7 +197,7 @@ const getSkillFileRoute = route({
125
197
  auth: { apiKey: true },
126
198
  params: z.object({ id: z.string(), path: z.string() }),
127
199
  responses: {
128
- 200: { description: "Skill file" },
200
+ 200: { description: "Skill file", schema: skillFileResponseSchema },
129
201
  404: { description: "Skill or file not found" },
130
202
  },
131
203
  });
@@ -142,7 +214,7 @@ const upsertSkillFileRoute = route({
142
214
  params: z.object({ id: z.string(), path: z.string() }),
143
215
  body: skillFileBodySchema,
144
216
  responses: {
145
- 200: { description: "Skill file upserted" },
217
+ 200: { description: "Skill file upserted", schema: skillFileWithSkillResponseSchema },
146
218
  400: { description: "Validation error" },
147
219
  404: { description: "Skill not found" },
148
220
  },
@@ -159,7 +231,7 @@ const deleteSkillFileRoute = route({
159
231
  rbac: { permission: "skill.update.any" },
160
232
  params: z.object({ id: z.string(), path: z.string() }),
161
233
  responses: {
162
- 200: { description: "Skill file deleted" },
234
+ 200: { description: "Skill file deleted", schema: deleteSkillFileResponseSchema },
163
235
  404: { description: "Skill or file not found" },
164
236
  },
165
237
  });
@@ -180,7 +252,7 @@ const createSkillRoute = route({
180
252
  systemDefault: z.boolean().optional(),
181
253
  }),
182
254
  responses: {
183
- 201: { description: "Skill created" },
255
+ 201: { description: "Skill created", schema: skillWrapperResponseSchema },
184
256
  400: { description: "Validation error" },
185
257
  },
186
258
  });
@@ -196,7 +268,7 @@ const updateSkillRoute = route({
196
268
  params: z.object({ id: z.string() }),
197
269
  body: z.record(z.string(), z.unknown()),
198
270
  responses: {
199
- 200: { description: "Skill updated" },
271
+ 200: { description: "Skill updated", schema: skillWrapperResponseSchema },
200
272
  403: { description: "System-managed skills cannot be edited" },
201
273
  404: { description: "Skill not found" },
202
274
  },
@@ -212,7 +284,7 @@ const deleteSkillRoute = route({
212
284
  rbac: { permission: "skill.delete.any" },
213
285
  params: z.object({ id: z.string() }),
214
286
  responses: {
215
- 200: { description: "Skill deleted" },
287
+ 200: { description: "Skill deleted", schema: deleteSuccessResponseSchema },
216
288
  403: { description: "System-managed skills cannot be deleted" },
217
289
  404: { description: "Skill not found" },
218
290
  },
@@ -231,7 +303,7 @@ const installSkillRoute = route({
231
303
  agentId: z.string(),
232
304
  }),
233
305
  responses: {
234
- 200: { description: "Skill installed" },
306
+ 200: { description: "Skill installed", schema: installSkillResponseSchema },
235
307
  404: { description: "Skill not found" },
236
308
  },
237
309
  });
@@ -246,7 +318,7 @@ const uninstallSkillRoute = route({
246
318
  rbac: { permission: "skill.uninstall.any" },
247
319
  params: z.object({ id: z.string(), agentId: z.string() }),
248
320
  responses: {
249
- 200: { description: "Skill uninstalled" },
321
+ 200: { description: "Skill uninstalled", schema: uninstallSkillResponseSchema },
250
322
  },
251
323
  });
252
324
 
@@ -265,7 +337,7 @@ const installRemoteRoute = route({
265
337
  isComplex: z.boolean().optional(),
266
338
  }),
267
339
  responses: {
268
- 201: { description: "Remote skill installed" },
340
+ 201: { description: "Remote skill installed", schema: skillWrapperResponseSchema },
269
341
  400: { description: "Fetch failed" },
270
342
  },
271
343
  });
@@ -283,7 +355,7 @@ const syncRemoteRoute = route({
283
355
  force: z.boolean().optional(),
284
356
  }),
285
357
  responses: {
286
- 200: { description: "Sync results" },
358
+ 200: { description: "Sync results", schema: syncRemoteResponseSchema },
287
359
  },
288
360
  });
289
361
 
@@ -296,7 +368,7 @@ const syncFilesystemRoute = route({
296
368
  auth: { apiKey: true, agentId: true },
297
369
  rbac: { ungated: "self-scoped: syncs the caller's own agent FS" },
298
370
  responses: {
299
- 200: { description: "Filesystem sync results" },
371
+ 200: { description: "Filesystem sync results", schema: syncFilesystemResponseSchema },
300
372
  },
301
373
  });
302
374
 
@@ -309,7 +381,7 @@ const getAgentSkillsRoute = route({
309
381
  auth: { apiKey: true },
310
382
  params: z.object({ id: z.string() }),
311
383
  responses: {
312
- 200: { description: "Agent skills list" },
384
+ 200: { description: "Agent skills list", schema: agentSkillsListResponseSchema },
313
385
  },
314
386
  });
315
387
 
@@ -324,7 +396,7 @@ const getAgentSkillsSignatureRoute = route({
324
396
  auth: { apiKey: true },
325
397
  params: z.object({ id: z.string() }),
326
398
  responses: {
327
- 200: { description: "Skills signature" },
399
+ 200: { description: "Skills signature", schema: agentSkillsSignatureResponseSchema },
328
400
  },
329
401
  });
330
402
 
@@ -342,7 +414,11 @@ export async function handleSkills(
342
414
  const parsed = await getAgentSkillsSignatureRoute.parse(req, res, pathSegments, queryParams);
343
415
  if (!parsed) return true;
344
416
  const sig = computeAgentSkillsSignature(parsed.params.id);
345
- json(res, { hash: sig.hash, count: sig.count, generatedAt: new Date().toISOString() });
417
+ getAgentSkillsSignatureRoute.respond(res, 200, {
418
+ hash: sig.hash,
419
+ count: sig.count,
420
+ generatedAt: new Date().toISOString(),
421
+ });
346
422
  return true;
347
423
  }
348
424
 
@@ -352,7 +428,7 @@ export async function handleSkills(
352
428
  if (!parsed) return true;
353
429
  const skills = getAgentSkills(parsed.params.id);
354
430
  const signature = computeAgentSkillsSignature(parsed.params.id).hash;
355
- json(res, { skills, total: skills.length, signature });
431
+ getAgentSkillsRoute.respond(res, 200, { skills, total: skills.length, signature });
356
432
  return true;
357
433
  }
358
434
 
@@ -400,7 +476,7 @@ export async function handleSkills(
400
476
  isComplex: parsed.body.isComplex ?? false,
401
477
  });
402
478
 
403
- json(res, { skill }, 201);
479
+ installRemoteRoute.respond(res, 201, { skill });
404
480
  } catch (err) {
405
481
  jsonError(res, err instanceof Error ? err.message : "Unknown error", 400);
406
482
  }
@@ -462,7 +538,7 @@ export async function handleSkills(
462
538
  }
463
539
  }
464
540
 
465
- json(res, { updated, checked: remoteSkills.length, errors });
541
+ syncRemoteRoute.respond(res, 200, { updated, checked: remoteSkills.length, errors });
466
542
  return true;
467
543
  }
468
544
 
@@ -476,7 +552,7 @@ export async function handleSkills(
476
552
  }
477
553
 
478
554
  const result = syncSkillsToFilesystem(agentId);
479
- json(res, {
555
+ syncFilesystemRoute.respond(res, 200, {
480
556
  synced: result.synced,
481
557
  removed: result.removed,
482
558
  errors: result.errors,
@@ -498,7 +574,7 @@ export async function handleSkills(
498
574
 
499
575
  try {
500
576
  const agentSkill = installSkill(parsed.body.agentId, parsed.params.id);
501
- json(res, { agentSkill });
577
+ installSkillRoute.respond(res, 200, { agentSkill });
502
578
  } catch (err) {
503
579
  jsonError(res, err instanceof Error ? err.message : "Install failed", 400);
504
580
  }
@@ -511,7 +587,7 @@ export async function handleSkills(
511
587
  if (!parsed) return true;
512
588
 
513
589
  const removed = uninstallSkill(parsed.params.agentId, parsed.params.id);
514
- json(res, { success: removed });
590
+ uninstallSkillRoute.respond(res, 200, { success: removed });
515
591
  return true;
516
592
  }
517
593
 
@@ -533,7 +609,7 @@ export async function handleSkills(
533
609
  includeContent,
534
610
  });
535
611
 
536
- json(res, { skills, total: skills.length });
612
+ listSkillsRoute.respond(res, 200, { skills, total: skills.length });
537
613
  return true;
538
614
  }
539
615
 
@@ -549,7 +625,7 @@ export async function handleSkills(
549
625
  }
550
626
 
551
627
  const files = listSkillFileManifest(parsed.params.id);
552
- json(res, { files, total: files.length });
628
+ listSkillFilesRoute.respond(res, 200, { files, total: files.length });
553
629
  return true;
554
630
  }
555
631
 
@@ -571,7 +647,11 @@ export async function handleSkills(
571
647
  try {
572
648
  const files = upsertSkillFiles(parsed.params.id, parsed.body.files);
573
649
  const updatedSkill = getSkillById(parsed.params.id);
574
- json(res, { files, total: files.length, skill: updatedSkill });
650
+ bulkUpsertSkillFilesRoute.respond(res, 200, {
651
+ files,
652
+ total: files.length,
653
+ skill: updatedSkill,
654
+ });
575
655
  } catch (err) {
576
656
  jsonError(res, err instanceof Error ? err.message : "Failed to upsert files", 400);
577
657
  }
@@ -595,7 +675,7 @@ export async function handleSkills(
595
675
  jsonError(res, "Skill file not found", 404);
596
676
  return true;
597
677
  }
598
- json(res, { file });
678
+ getSkillFileRoute.respond(res, 200, { file });
599
679
  } catch (err) {
600
680
  jsonError(res, err instanceof Error ? err.message : "Invalid file path", 400);
601
681
  }
@@ -623,7 +703,7 @@ export async function handleSkills(
623
703
  ...parsed.body,
624
704
  });
625
705
  const updatedSkill = getSkillById(parsed.params.id);
626
- json(res, { file, skill: updatedSkill });
706
+ upsertSkillFileRoute.respond(res, 200, { file, skill: updatedSkill });
627
707
  } catch (err) {
628
708
  jsonError(res, err instanceof Error ? err.message : "Failed to upsert file", 400);
629
709
  }
@@ -652,7 +732,7 @@ export async function handleSkills(
652
732
  return true;
653
733
  }
654
734
  const updatedSkill = getSkillById(parsed.params.id);
655
- json(res, { success: true, skill: updatedSkill });
735
+ deleteSkillFileRoute.respond(res, 200, { success: true, skill: updatedSkill });
656
736
  } catch (err) {
657
737
  jsonError(res, err instanceof Error ? err.message : "Invalid file path", 400);
658
738
  }
@@ -669,7 +749,7 @@ export async function handleSkills(
669
749
  jsonError(res, "Skill not found", 404);
670
750
  return true;
671
751
  }
672
- json(res, skill);
752
+ getSkillRoute.respond(res, 200, skill);
673
753
  return true;
674
754
  }
675
755
 
@@ -696,7 +776,7 @@ export async function handleSkills(
696
776
  userInvocable: pm.userInvocable,
697
777
  systemDefault: parsed.body.systemDefault,
698
778
  });
699
- json(res, { skill }, 201);
779
+ createSkillRoute.respond(res, 201, { skill });
700
780
  } catch (err) {
701
781
  jsonError(res, err instanceof Error ? err.message : "Create failed", 400);
702
782
  }
@@ -768,7 +848,7 @@ export async function handleSkills(
768
848
  jsonError(res, "Skill not found", 404);
769
849
  return true;
770
850
  }
771
- json(res, { skill });
851
+ updateSkillRoute.respond(res, 200, { skill });
772
852
  return true;
773
853
  }
774
854
 
@@ -792,7 +872,7 @@ export async function handleSkills(
792
872
  jsonError(res, "Skill not found", 404);
793
873
  return true;
794
874
  }
795
- json(res, { success: true });
875
+ deleteSkillRoute.respond(res, 200, { success: true });
796
876
  return true;
797
877
  }
798
878