@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/tasks.ts CHANGED
@@ -44,7 +44,9 @@ import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
44
44
  import { guardTerminalTaskResultWrite } from "../tasks/terminal-result-guard";
45
45
  import { createResumeFollowUp, createWorkerTaskFollowUp } from "../tasks/worker-follow-up";
46
46
  import {
47
+ AgentLogSchema,
47
48
  type AgentTask,
49
+ AgentTaskSchema,
48
50
  type AgentTaskSource,
49
51
  AgentTaskSourceSchema,
50
52
  type AgentTaskStatus,
@@ -56,14 +58,106 @@ import {
56
58
  ProviderNameSchema,
57
59
  ReasoningEffortSchema,
58
60
  ResumeReasonSchema,
61
+ SteeringMessageSchema,
59
62
  SteeringSourceSchema,
60
63
  SteerModeSchema,
64
+ SteerResultSchema,
61
65
  splitLegacyModelAlias,
66
+ TaskAttachmentSchema,
62
67
  } from "../types";
63
68
  import { getRequestAuth } from "../utils/request-auth-context";
64
69
  import { scrubSecrets } from "../utils/secret-scrubber";
65
70
  import { route } from "./route-def";
66
- import { json, jsonError, parseBody } from "./utils";
71
+ import { jsonError, parseBody } from "./utils";
72
+
73
+ // ─── Response Schemas ────────────────────────────────────────────────────────
74
+
75
+ /**
76
+ * `/api/tasks` + `/api/sessions` list item shape — mirrors the `AgentTaskSummary`
77
+ * TS type in ../types (a strict field subset of `AgentTask`): the `task` text
78
+ * truncated to a bounded preview and completion/integration/context blobs
79
+ * dropped. Kept in lock-step with that type's `Pick<...>` field list.
80
+ */
81
+ const AgentTaskSummarySchema = AgentTaskSchema.pick({
82
+ id: true,
83
+ key: true,
84
+ agentId: true,
85
+ creatorAgentId: true,
86
+ task: true,
87
+ title: true,
88
+ status: true,
89
+ source: true,
90
+ taskType: true,
91
+ tags: true,
92
+ priority: true,
93
+ dependsOn: true,
94
+ offeredTo: true,
95
+ acceptedAt: true,
96
+ parentTaskId: true,
97
+ scheduleId: true,
98
+ model: true,
99
+ modelTier: true,
100
+ effort: true,
101
+ provider: true,
102
+ requestedByUserId: true,
103
+ progress: true,
104
+ createdAt: true,
105
+ lastUpdatedAt: true,
106
+ finishedAt: true,
107
+ peakContextPercent: true,
108
+ totalCostUsd: true,
109
+ });
110
+
111
+ /** Shared by cancel/pause/resume — each sends `{ success: true, task }` on success. */
112
+ const TaskActionResultSchema = z.object({
113
+ success: z.literal(true),
114
+ task: AgentTaskSchema,
115
+ });
116
+
117
+ /** Shared by the steering-message delivered/handled callbacks — `{ message }`. */
118
+ const SteeringMessageEnvelopeSchema = z.object({ message: SteeringMessageSchema });
119
+
120
+ /** Shared by the steering-message list routes — `{ messages }`. */
121
+ const SteeringMessagesListSchema = z.object({ messages: z.array(SteeringMessageSchema) });
122
+
123
+ /** `POST /api/steering-messages/{id}/undeliverable` — promotion result. */
124
+ const SteeringUndeliverableResponseSchema = z.object({
125
+ message: SteeringMessageSchema,
126
+ promotedTaskId: z.string().optional(),
127
+ });
128
+
129
+ /** `GET /api/tasks/{id}` — full task decorated with steering + logs + attachments. */
130
+ const GetTaskResponseSchema = AgentTaskSchema.extend({
131
+ isLeadTask: z.boolean(),
132
+ supportedSteerModes: z.array(SteerModeSchema),
133
+ logs: z.array(AgentLogSchema),
134
+ attachments: z.array(TaskAttachmentSchema),
135
+ });
136
+
137
+ const FinishTaskSuccessSchema = z.object({
138
+ success: z.literal(true),
139
+ alreadyFinished: z.boolean(),
140
+ task: AgentTaskSchema,
141
+ message: z.string().optional(),
142
+ wasNoOp: z.literal(true).optional(),
143
+ wasForcedOverwrite: z.literal(true).optional(),
144
+ });
145
+
146
+ const FinishTaskConflictSchema = z.object({
147
+ success: z.literal(false),
148
+ message: z.string(),
149
+ task: AgentTaskSchema,
150
+ alreadyFinished: z.literal(true),
151
+ error: z.string(),
152
+ });
153
+
154
+ const SupersedeTaskResponseSchema = z.object({
155
+ success: z.literal(true),
156
+ kind: z.enum(["alreadyFinished", "workflow-failed", "resumed"]),
157
+ task: AgentTaskSchema.nullable(),
158
+ resumeTaskId: z.string().nullable(),
159
+ resumeTaskStatus: AgentTaskStatusSchema.optional(),
160
+ });
67
161
 
68
162
  // ─── Route Definitions ───────────────────────────────────────────────────────
69
163
 
@@ -104,7 +198,13 @@ const listTasks = route({
104
198
  fields: z.enum(["full", "slim"]).optional(),
105
199
  }),
106
200
  responses: {
107
- 200: { description: "Paginated task list" },
201
+ 200: {
202
+ description: "Paginated task list",
203
+ schema: z.object({
204
+ tasks: z.union([z.array(AgentTaskSchema), z.array(AgentTaskSummarySchema)]),
205
+ total: z.number().int(),
206
+ }),
207
+ },
108
208
  400: { description: "Validation error (e.g. unknown status token)" },
109
209
  },
110
210
  });
@@ -135,7 +235,7 @@ const createTask = route({
135
235
  effort: ReasoningEffortSchema.optional(),
136
236
  }),
137
237
  responses: {
138
- 201: { description: "Task created" },
238
+ 201: { description: "Task created", schema: AgentTaskSchema },
139
239
  400: { description: "Validation error" },
140
240
  },
141
241
  });
@@ -168,7 +268,7 @@ const updateSession = route({
168
268
  }),
169
269
  ]),
170
270
  responses: {
171
- 200: { description: "Session ID updated" },
271
+ 200: { description: "Session ID updated", schema: AgentTaskSchema },
172
272
  404: { description: "Task not found" },
173
273
  },
174
274
  });
@@ -181,7 +281,7 @@ const cancelTaskRoute = route({
181
281
  tags: ["Tasks"],
182
282
  params: z.object({ id: z.string() }),
183
283
  responses: {
184
- 200: { description: "Task cancelled" },
284
+ 200: { description: "Task cancelled", schema: TaskActionResultSchema },
185
285
  400: { description: "Cannot cancel terminal task" },
186
286
  404: { description: "Task not found" },
187
287
  },
@@ -206,7 +306,10 @@ const steerTaskRoute = route({
206
306
  }),
207
307
  rbac: { permission: "task.steer.own" },
208
308
  responses: {
209
- 200: { description: "Steering accepted (see `outcome` for what actually happened)" },
309
+ 200: {
310
+ description: "Steering accepted (see `outcome` for what actually happened)",
311
+ schema: SteerResultSchema,
312
+ },
210
313
  400: { description: "Validation error" },
211
314
  403: { description: "Caller cannot steer this task" },
212
315
  404: { description: "Task not found" },
@@ -224,7 +327,7 @@ const getTaskSteeringMessagesRoute = route({
224
327
  tags: ["Tasks"],
225
328
  params: z.object({ id: z.string() }),
226
329
  responses: {
227
- 200: { description: "Steering messages" },
330
+ 200: { description: "Steering messages", schema: SteeringMessagesListSchema },
228
331
  404: { description: "Task not found" },
229
332
  },
230
333
  });
@@ -238,7 +341,7 @@ const getPendingSteeringMessagesRoute = route({
238
341
  query: z.object({ taskId: z.string().optional() }),
239
342
  auth: { apiKey: true, agentId: true },
240
343
  responses: {
241
- 200: { description: "Pending steering messages" },
344
+ 200: { description: "Pending steering messages", schema: SteeringMessagesListSchema },
242
345
  400: { description: "Missing X-Agent-ID header" },
243
346
  404: { description: "Agent not found" },
244
347
  },
@@ -258,7 +361,10 @@ const markSteeringDeliveredRoute = route({
258
361
  "worker callback scoped by required X-Agent-ID and verified against the steering message task assignee",
259
362
  },
260
363
  responses: {
261
- 200: { description: "Steering message delivery recorded" },
364
+ 200: {
365
+ description: "Steering message delivery recorded",
366
+ schema: SteeringMessageEnvelopeSchema,
367
+ },
262
368
  400: { description: "Missing X-Agent-ID header or validation error" },
263
369
  403: { description: "Steering message task is assigned to another agent" },
264
370
  404: { description: "Agent or steering message not found" },
@@ -280,7 +386,10 @@ const markSteeringHandledRoute = route({
280
386
  "worker acknowledgement scoped by required X-Agent-ID and verified against the steering message task assignee",
281
387
  },
282
388
  responses: {
283
- 200: { description: "Steering message acknowledgement recorded" },
389
+ 200: {
390
+ description: "Steering message acknowledgement recorded",
391
+ schema: SteeringMessageEnvelopeSchema,
392
+ },
284
393
  400: { description: "Missing X-Agent-ID header or validation error" },
285
394
  403: { description: "Steering message task is assigned to another agent" },
286
395
  404: { description: "Agent or steering message not found" },
@@ -301,7 +410,10 @@ const markSteeringUndeliverableRoute = route({
301
410
  "worker callback scoped by required X-Agent-ID and verified against the steering message task assignee",
302
411
  },
303
412
  responses: {
304
- 200: { description: "Steering message promoted to a follow-up task" },
413
+ 200: {
414
+ description: "Steering message promoted to a follow-up task",
415
+ schema: SteeringUndeliverableResponseSchema,
416
+ },
305
417
  400: { description: "Missing X-Agent-ID header or validation error" },
306
418
  403: { description: "Steering message task is assigned to another agent" },
307
419
  404: { description: "Agent or steering message not found" },
@@ -322,7 +434,7 @@ const getTask = route({
322
434
  logsLimit: z.coerce.number().int().min(1).max(1000).optional(),
323
435
  }),
324
436
  responses: {
325
- 200: { description: "Task with logs and attachments" },
437
+ 200: { description: "Task with logs and attachments", schema: GetTaskResponseSchema },
326
438
  404: { description: "Task not found" },
327
439
  },
328
440
  });
@@ -336,7 +448,7 @@ const updateTaskProgressRoute = route({
336
448
  params: z.object({ id: z.string() }),
337
449
  body: z.object({ progress: z.string().min(1) }),
338
450
  responses: {
339
- 200: { description: "Progress updated" },
451
+ 200: { description: "Progress updated", schema: z.object({ success: z.literal(true) }) },
340
452
  404: { description: "Task not found" },
341
453
  },
342
454
  });
@@ -356,11 +468,14 @@ const finishTask = route({
356
468
  }),
357
469
  auth: { apiKey: true, agentId: true },
358
470
  responses: {
359
- 200: { description: "Task finished" },
471
+ 200: { description: "Task finished", schema: FinishTaskSuccessSchema },
360
472
  400: { description: "Invalid status" },
361
473
  403: { description: "Not assigned to this agent" },
362
474
  404: { description: "Task not found" },
363
- 409: { description: "Differing terminal result text was discarded" },
475
+ 409: {
476
+ description: "Differing terminal result text was discarded",
477
+ schema: FinishTaskConflictSchema,
478
+ },
364
479
  },
365
480
  });
366
481
 
@@ -372,7 +487,10 @@ const listPausedTasks = route({
372
487
  tags: ["Tasks"],
373
488
  auth: { apiKey: true, agentId: true },
374
489
  responses: {
375
- 200: { description: "Paused task list" },
490
+ 200: {
491
+ description: "Paused task list",
492
+ schema: z.object({ tasks: z.array(AgentTaskSchema) }),
493
+ },
376
494
  },
377
495
  });
378
496
 
@@ -384,7 +502,7 @@ const pauseTaskRoute = route({
384
502
  tags: ["Tasks"],
385
503
  params: z.object({ id: z.string() }),
386
504
  responses: {
387
- 200: { description: "Task paused" },
505
+ 200: { description: "Task paused", schema: TaskActionResultSchema },
388
506
  400: { description: "Task not in_progress" },
389
507
  403: { description: "Task belongs to another agent" },
390
508
  404: { description: "Task not found" },
@@ -399,7 +517,7 @@ const resumeTaskRoute = route({
399
517
  tags: ["Tasks"],
400
518
  params: z.object({ id: z.string() }),
401
519
  responses: {
402
- 200: { description: "Task resumed" },
520
+ 200: { description: "Task resumed", schema: TaskActionResultSchema },
403
521
  400: { description: "Task not paused" },
404
522
  403: { description: "Task belongs to another agent" },
405
523
  404: { description: "Task not found" },
@@ -418,7 +536,10 @@ const supersedeTaskRoute = route({
418
536
  body: z.object({ reason: ResumeReasonSchema }),
419
537
  auth: { apiKey: true, agentId: true },
420
538
  responses: {
421
- 200: { description: "Task superseded (or workflow-failed)" },
539
+ 200: {
540
+ description: "Task superseded (or workflow-failed)",
541
+ schema: SupersedeTaskResponseSchema,
542
+ },
422
543
  400: { description: "Task not in_progress" },
423
544
  403: { description: "Task belongs to another agent" },
424
545
  404: { description: "Task not found" },
@@ -439,7 +560,7 @@ const updateTaskVcsRoute = route({
439
560
  vcsUrl: z.string().url(),
440
561
  }),
441
562
  responses: {
442
- 200: { description: "VCS info updated" },
563
+ 200: { description: "VCS info updated", schema: AgentTaskSchema },
443
564
  404: { description: "Task not found" },
444
565
  },
445
566
  auth: { apiKey: true },
@@ -458,7 +579,7 @@ const updateTaskTitleRoute = route({
458
579
  title: z.string().trim().max(120).nullable(),
459
580
  }),
460
581
  responses: {
461
- 200: { description: "Title updated" },
582
+ 200: { description: "Title updated", schema: z.object({ task: AgentTaskSchema }) },
462
583
  404: { description: "Task not found" },
463
584
  },
464
585
  auth: { apiKey: true },
@@ -576,7 +697,7 @@ export async function handleTasks(
576
697
  const tasks =
577
698
  parsed.query.fields === "full" ? getAllTasks(filters) : getAllTasks(filters, { slim: true });
578
699
  const total = getTasksCount(filters);
579
- json(res, { tasks, total });
700
+ listTasks.respond(res, 200, { tasks, total });
580
701
  return true;
581
702
  }
582
703
 
@@ -671,7 +792,7 @@ export async function handleTasks(
671
792
  },
672
793
  });
673
794
 
674
- json(res, task, 201);
795
+ createTask.respond(res, 201, task);
675
796
  } catch (error) {
676
797
  console.error("[HTTP] Failed to create task:", error);
677
798
  jsonError(res, "Failed to create task", 500);
@@ -695,7 +816,7 @@ export async function handleTasks(
695
816
  jsonError(res, "Task not found", 404);
696
817
  return true;
697
818
  }
698
- json(res, task);
819
+ updateSession.respond(res, 200, task);
699
820
  return true;
700
821
  }
701
822
 
@@ -783,7 +904,7 @@ export async function handleTasks(
783
904
  updateAgentStatusFromCapacity(task.agentId);
784
905
  }
785
906
 
786
- json(res, { success: true, task: cancelledTask });
907
+ cancelTaskRoute.respond(res, 200, { success: true, task: cancelledTask });
787
908
  return true;
788
909
  }
789
910
 
@@ -830,7 +951,7 @@ export async function handleTasks(
830
951
  createdByAgentId,
831
952
  createdByUserId: requestedByUserId,
832
953
  });
833
- json(res, result);
954
+ steerTaskRoute.respond(res, 200, result);
834
955
  } catch (error) {
835
956
  if (error instanceof SteeringRequestError) {
836
957
  jsonError(res, error.message, error.statusCode);
@@ -849,7 +970,9 @@ export async function handleTasks(
849
970
  jsonError(res, "Task not found", 404);
850
971
  return true;
851
972
  }
852
- json(res, { messages: getSteeringMessagesForTask(parsed.params.id) });
973
+ getTaskSteeringMessagesRoute.respond(res, 200, {
974
+ messages: getSteeringMessagesForTask(parsed.params.id),
975
+ });
853
976
  return true;
854
977
  }
855
978
 
@@ -870,11 +993,13 @@ export async function handleTasks(
870
993
  const task = getTaskById(parsed.query.taskId);
871
994
  const messages =
872
995
  task?.agentId === myAgentId ? getPendingSteeringForTask(parsed.query.taskId) : [];
873
- json(res, { messages });
996
+ getPendingSteeringMessagesRoute.respond(res, 200, { messages });
874
997
  return true;
875
998
  }
876
999
 
877
- json(res, { messages: getPendingSteeringForAgent(myAgentId) });
1000
+ getPendingSteeringMessagesRoute.respond(res, 200, {
1001
+ messages: getPendingSteeringForAgent(myAgentId),
1002
+ });
878
1003
  return true;
879
1004
  }
880
1005
 
@@ -902,7 +1027,7 @@ export async function handleTasks(
902
1027
  return true;
903
1028
  }
904
1029
  if (message.status !== "pending") {
905
- json(res, { message });
1030
+ markSteeringDeliveredRoute.respond(res, 200, { message });
906
1031
  return true;
907
1032
  }
908
1033
 
@@ -912,7 +1037,7 @@ export async function handleTasks(
912
1037
  jsonError(res, "Failed to mark steering message delivered", 500);
913
1038
  return true;
914
1039
  }
915
- json(res, { message: delivered });
1040
+ markSteeringDeliveredRoute.respond(res, 200, { message: delivered });
916
1041
  return true;
917
1042
  }
918
1043
 
@@ -940,7 +1065,7 @@ export async function handleTasks(
940
1065
  return true;
941
1066
  }
942
1067
  if (message.status !== "delivered") {
943
- json(res, { message });
1068
+ markSteeringHandledRoute.respond(res, 200, { message });
944
1069
  return true;
945
1070
  }
946
1071
 
@@ -961,7 +1086,7 @@ export async function handleTasks(
961
1086
  jsonError(res, "Failed to mark steering message handled", 500);
962
1087
  return true;
963
1088
  }
964
- json(res, { message: handled });
1089
+ markSteeringHandledRoute.respond(res, 200, { message: handled });
965
1090
  return true;
966
1091
  }
967
1092
 
@@ -989,7 +1114,7 @@ export async function handleTasks(
989
1114
  return true;
990
1115
  }
991
1116
  if (message.status !== "pending") {
992
- json(res, {
1117
+ markSteeringUndeliverableRoute.respond(res, 200, {
993
1118
  message,
994
1119
  promotedTaskId: message.promotedTaskId,
995
1120
  });
@@ -997,7 +1122,11 @@ export async function handleTasks(
997
1122
  }
998
1123
 
999
1124
  try {
1000
- json(res, markSteeringUndeliverable(message.id, parsed.body.reason));
1125
+ markSteeringUndeliverableRoute.respond(
1126
+ res,
1127
+ 200,
1128
+ markSteeringUndeliverable(message.id, parsed.body.reason),
1129
+ );
1001
1130
  } catch (error) {
1002
1131
  if (error instanceof SteeringRequestError) {
1003
1132
  jsonError(res, error.message, error.statusCode);
@@ -1020,7 +1149,7 @@ export async function handleTasks(
1020
1149
 
1021
1150
  const logs = getLogsByTaskId(parsed.params.id, parsed.query.logsLimit ?? 200);
1022
1151
  const attachments = getTaskAttachments(parsed.params.id);
1023
- json(res, { ...task, ...getTaskSteeringFields(task), logs, attachments });
1152
+ getTask.respond(res, 200, { ...task, ...getTaskSteeringFields(task), logs, attachments });
1024
1153
  return true;
1025
1154
  }
1026
1155
 
@@ -1035,7 +1164,7 @@ export async function handleTasks(
1035
1164
  }
1036
1165
 
1037
1166
  updateTaskProgress(parsed.params.id, parsed.body.progress);
1038
- json(res, { success: true });
1167
+ updateTaskProgressRoute.respond(res, 200, { success: true });
1039
1168
  return true;
1040
1169
  }
1041
1170
 
@@ -1048,7 +1177,51 @@ export async function handleTasks(
1048
1177
  const parsed = await finishTask.parse(req, res, pathSegments, queryParams);
1049
1178
  if (!parsed) return true;
1050
1179
 
1051
- const result = getDb().transaction(() => {
1180
+ // Explicit (plain, non-derived) return type: without it TS infers a merged
1181
+ // shape across these branches and the `"success" in result` / `"error" in
1182
+ // result` narrowing below stops working. A `Pick`/`Extract`-derived alias
1183
+ // for the middle (guard-handled) variant was tried and also defeated
1184
+ // narrowing, so its fields are spelled out by hand here — keep in sync
1185
+ // with the `handled: true` branch of `TerminalResultGuardResult`
1186
+ // (../tasks/terminal-result-guard) minus the `handled` discriminant.
1187
+ type FinishTaskTransactionResult =
1188
+ | { error: string; status: number }
1189
+ | {
1190
+ success: boolean;
1191
+ message: string;
1192
+ task: AgentTask;
1193
+ wasNoOp?: boolean;
1194
+ wasForcedOverwrite?: boolean;
1195
+ alreadyFinished: true;
1196
+ }
1197
+ | { success: true; task: AgentTask; alreadyFinished: true }
1198
+ | { task: AgentTask; wasPaused: boolean };
1199
+
1200
+ // User-defined type guards (rather than inline `"x" in r && r.x` checks)
1201
+ // so TS narrows both branches of each condition — plain `&&`/`!(...)`
1202
+ // guards only narrow the true-branch (or don't narrow at all when negated),
1203
+ // leaving `result` unnarrowed (and e.g. `.task` inaccessible) for the rest
1204
+ // of this handler. Each predicate below evaluates the exact same runtime
1205
+ // condition the inline checks used to.
1206
+ const hasFinishError = (
1207
+ r: FinishTaskTransactionResult,
1208
+ ): r is Extract<FinishTaskTransactionResult, { error: string }> => "error" in r && !!r.error;
1209
+ const isFinishConflict = (
1210
+ r: FinishTaskTransactionResult,
1211
+ ): r is {
1212
+ success: false;
1213
+ message: string;
1214
+ task: AgentTask;
1215
+ wasNoOp?: boolean;
1216
+ wasForcedOverwrite?: boolean;
1217
+ alreadyFinished: true;
1218
+ } => "success" in r && r.success === false;
1219
+ const isFreshFinishResult = (
1220
+ r: FinishTaskTransactionResult,
1221
+ ): r is Extract<FinishTaskTransactionResult, { task: AgentTask; wasPaused: boolean }> =>
1222
+ !("alreadyFinished" in r && r.alreadyFinished);
1223
+
1224
+ const result = getDb().transaction((): FinishTaskTransactionResult => {
1052
1225
  const task = getTaskById(parsed.params.id);
1053
1226
 
1054
1227
  if (!task) {
@@ -1099,24 +1272,20 @@ export async function handleTasks(
1099
1272
  return { task: updatedTask, wasPaused };
1100
1273
  })();
1101
1274
 
1102
- if ("error" in result && result.error) {
1103
- jsonError(res, result.error, (result as { status?: number }).status ?? 500);
1275
+ if (hasFinishError(result)) {
1276
+ jsonError(res, result.error, result.status ?? 500);
1104
1277
  return true;
1105
1278
  }
1106
1279
 
1107
- if ("success" in result && result.success === false) {
1108
- json(
1109
- res,
1110
- {
1111
- ...result,
1112
- error: "message" in result ? result.message : "Terminal result write was discarded",
1113
- },
1114
- 409,
1115
- );
1280
+ if (isFinishConflict(result)) {
1281
+ finishTask.respond(res, 409, {
1282
+ ...result,
1283
+ error: "message" in result ? result.message : "Terminal result write was discarded",
1284
+ });
1116
1285
  return true;
1117
1286
  }
1118
1287
 
1119
- if (result.task && !("alreadyFinished" in result && result.alreadyFinished)) {
1288
+ if (result.task && isFreshFinishResult(result)) {
1120
1289
  const finishEventId = parsed.body.status === "completed" ? "completed" : "failed";
1121
1290
 
1122
1291
  ensure({
@@ -1155,7 +1324,7 @@ export async function handleTasks(
1155
1324
  }
1156
1325
  }
1157
1326
 
1158
- json(res, {
1327
+ finishTask.respond(res, 200, {
1159
1328
  success: true,
1160
1329
  alreadyFinished: "alreadyFinished" in result ? result.alreadyFinished : false,
1161
1330
  task: result.task,
@@ -1174,7 +1343,7 @@ export async function handleTasks(
1174
1343
  return true;
1175
1344
  }
1176
1345
  const pausedTasks = getPausedTasksForAgent(myAgentId);
1177
- json(res, { tasks: pausedTasks });
1346
+ listPausedTasks.respond(res, 200, { tasks: pausedTasks });
1178
1347
  return true;
1179
1348
  }
1180
1349
 
@@ -1220,7 +1389,7 @@ export async function handleTasks(
1220
1389
  conditions: [{ timeout_ms: 3_600_000 }], // 1 hour
1221
1390
  });
1222
1391
 
1223
- json(res, { success: true, task: pausedTask });
1392
+ pauseTaskRoute.respond(res, 200, { success: true, task: pausedTask });
1224
1393
  return true;
1225
1394
  }
1226
1395
 
@@ -1232,7 +1401,7 @@ export async function handleTasks(
1232
1401
  jsonError(res, "Task not found", 404);
1233
1402
  return true;
1234
1403
  }
1235
- json(res, task);
1404
+ updateTaskVcsRoute.respond(res, 200, task);
1236
1405
  return true;
1237
1406
  }
1238
1407
 
@@ -1244,7 +1413,7 @@ export async function handleTasks(
1244
1413
  jsonError(res, "Task not found", 404);
1245
1414
  return true;
1246
1415
  }
1247
- json(res, { task });
1416
+ updateTaskTitleRoute.respond(res, 200, { task });
1248
1417
  return true;
1249
1418
  }
1250
1419
 
@@ -1290,7 +1459,7 @@ export async function handleTasks(
1290
1459
  conditions: [{ timeout_ms: 86_400_000 }], // 1 day: tasks may stay paused for extended periods
1291
1460
  });
1292
1461
 
1293
- json(res, { success: true, task: resumedTask });
1462
+ resumeTaskRoute.respond(res, 200, { success: true, task: resumedTask });
1294
1463
  return true;
1295
1464
  }
1296
1465
 
@@ -1313,7 +1482,7 @@ export async function handleTasks(
1313
1482
  // response (mirrors finishTask). Caller treats this as a successful
1314
1483
  // supersede.
1315
1484
  if (isTerminalTaskStatus(task.status)) {
1316
- json(res, {
1485
+ supersedeTaskRoute.respond(res, 200, {
1317
1486
  success: true,
1318
1487
  kind: "alreadyFinished",
1319
1488
  task,
@@ -1343,7 +1512,7 @@ export async function handleTasks(
1343
1512
  reason: parsed.body.reason,
1344
1513
  },
1345
1514
  });
1346
- json(res, {
1515
+ supersedeTaskRoute.respond(res, 200, {
1347
1516
  success: true,
1348
1517
  kind: "workflow-failed",
1349
1518
  task: failed,
@@ -1365,7 +1534,7 @@ export async function handleTasks(
1365
1534
  // Worker won the race (terminal transition between status check and
1366
1535
  // this UPDATE). Treat as `alreadyFinished` — no resume child is created.
1367
1536
  const fresh = getTaskById(parsed.params.id);
1368
- json(res, {
1537
+ supersedeTaskRoute.respond(res, 200, {
1369
1538
  success: true,
1370
1539
  kind: "alreadyFinished",
1371
1540
  task: fresh,
@@ -1389,7 +1558,7 @@ export async function handleTasks(
1389
1558
  followUp.kind === "skipped" ? followUp.reason : followUp.kind
1390
1559
  })`,
1391
1560
  );
1392
- json(res, {
1561
+ supersedeTaskRoute.respond(res, 200, {
1393
1562
  success: true,
1394
1563
  kind: "resumed",
1395
1564
  task: superseded,
@@ -1413,7 +1582,7 @@ export async function handleTasks(
1413
1582
  },
1414
1583
  });
1415
1584
 
1416
- json(res, {
1585
+ supersedeTaskRoute.respond(res, 200, {
1417
1586
  success: true,
1418
1587
  kind: "resumed",
1419
1588
  task: superseded,