@desplega.ai/agent-swarm 1.157.0 → 1.158.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 (256) hide show
  1. package/dist/{acp-adapter-qhqrk4q0.js → acp-adapter-xaq4n6pc.js} +1 -1
  2. package/dist/{actions-9rrg0zxm.js → actions-p9hz5yat.js} +15 -15
  3. package/dist/{app-enysy1zd.js → app-g26wdcz6.js} +4 -4
  4. package/dist/{artifact-td4q4dch.js → artifact-3v5t9jtg.js} +1 -1
  5. package/dist/{assistant-zegamy48.js → assistant-wydfqkb3.js} +16 -16
  6. package/dist/{boot-reembed-r6shx5qx.js → boot-reembed-g5ckg9yb.js} +6 -6
  7. package/dist/{boot-reembed-zfxb937k.js → boot-reembed-se8zmdcp.js} +5 -5
  8. package/dist/{boot-scrub-logs-k5y0se6r.js → boot-scrub-logs-wfxqan61.js} +3 -3
  9. package/dist/{seed-pricing-vqxa06qf.js → boot-seeds-s3q01xj3.js} +10 -10
  10. package/dist/{claude-adapter-5yc7hxwv.js → claude-adapter-tv2x4f36.js} +7 -7
  11. package/dist/{claude-managed-adapter-ea2aga6w.js → claude-managed-adapter-41b2s7fd.js} +2 -2
  12. package/dist/{claude-sdk-session-j4ywn3xf.js → claude-sdk-session-t442qz6f.js} +8 -8
  13. package/dist/{cli-e781c6f9.js → cli-0xjkj1j7.js} +1 -0
  14. package/dist/{cli-58rgvkt4.js → cli-1hkrch3k.js} +2 -2
  15. package/dist/{cli-yscm6cv8.js → cli-20cj37vt.js} +1866 -9
  16. package/dist/{cli-h0wdadsx.js → cli-4198qc9v.js} +7 -3
  17. package/dist/{cli-awwphhcs.js → cli-47m6dkd3.js} +362 -126
  18. package/dist/{cli-wb2zvbfg.js → cli-53sz8gkz.js} +1 -1
  19. package/dist/{cli-005pze6s.js → cli-5d10vd2m.js} +1 -1
  20. package/dist/{cli-v53rbqp3.js → cli-65hjmev5.js} +57 -17
  21. package/dist/{cli-scwq51gn.js → cli-68w09sjw.js} +7 -1
  22. package/dist/{cli-s98skd5r.js → cli-7gtvvpbh.js} +4 -4
  23. package/dist/{cli-y3nxwx0t.js → cli-7s3syk22.js} +18 -11
  24. package/dist/{cli-74h6f2bp.js → cli-8qmme6vd.js} +2 -2
  25. package/dist/{cli-wsed24mk.js → cli-987hg0aa.js} +2 -2
  26. package/dist/{cli-9n76jszt.js → cli-9httdpyq.js} +1 -1
  27. package/dist/{cli-f3va018d.js → cli-aky8tctm.js} +26 -93
  28. package/dist/{cli-sgyqyyxe.js → cli-c07r9a2m.js} +2 -1
  29. package/dist/{cli-c41wjx0r.js → cli-fgqxwrpv.js} +291 -12
  30. package/dist/{cli-01vfmq8c.js → cli-ftfvggzx.js} +607 -164
  31. package/dist/{cli-ytxs7mn1.js → cli-g8djp6qe.js} +17 -0
  32. package/dist/{cli-avkd6mqv.js → cli-h967nb6r.js} +551 -0
  33. package/dist/{cli-r9ee9c4w.js → cli-j55n64b6.js} +23 -22
  34. package/dist/{cli-9p33cz9h.js → cli-j57qkrpq.js} +86 -28
  35. package/dist/{cli-tjmghmm5.js → cli-q67y4wzm.js} +6 -3
  36. package/dist/{cli-n6zq3kbd.js → cli-rj19hc34.js} +1 -1
  37. package/dist/{cli-jytrw2na.js → cli-rjehvrts.js} +4 -4
  38. package/dist/{cli-tpv97m71.js → cli-rqz8qbw7.js} +245 -75
  39. package/dist/{cli-p37sg0a9.js → cli-s8eet254.js} +1 -1
  40. package/dist/cli-t1ze64h3.js +811 -0
  41. package/dist/{cli-6jjzxv3b.js → cli-ws6rqycm.js} +3 -2
  42. package/dist/{cli-xf5b7y1f.js → cli-wscdvcqz.js} +13 -5
  43. package/dist/{cli-5j7bm4tb.js → cli-x24pshtq.js} +5 -5
  44. package/dist/{cli-nbp6m6gp.js → cli-xberxa1r.js} +51 -26
  45. package/dist/{cli-vx9p3qdt.js → cli-xhaeg2nc.js} +1 -1
  46. package/dist/cli.js +29 -15
  47. package/dist/{codex-adapter-gns3s6c3.js → codex-adapter-cnm8wk11.js} +5 -5
  48. package/dist/{codex-session-runner-gqnxys5w.js → codex-session-runner-gfg547zj.js} +5 -5
  49. package/dist/{commands-hxy2y2sp.js → commands-e3bn0p75.js} +3 -3
  50. package/dist/{db-w8ghhsgf.js → db-03v9kzr2.js} +17 -3
  51. package/dist/{dispatcher-cp1rknjz.js → dispatcher-nrmg8yht.js} +14 -14
  52. package/dist/{dsh-adapter-1dwg97zv.js → dsh-adapter-k03nfqa6.js} +2 -2
  53. package/dist/{handlers-egfhr351.js → handlers-aqcedbyb.js} +17 -17
  54. package/dist/{hook-x4kftwzt.js → hook-7vkskkre.js} +19 -11
  55. package/dist/{hook-fbxjqfjz.js → hook-ybvmrmhq.js} +4 -4
  56. package/dist/{http-c7jfx990.js → http-2eqa8t24.js} +432 -195
  57. package/dist/{index-4k9hct21.js → index-1yks3qcr.js} +14 -14
  58. package/dist/{index-jtvpgb8f.js → index-a3vn96n8.js} +30 -27
  59. package/dist/{index-dc2ek6bw.js → index-c6zsv1bd.js} +5 -5
  60. package/dist/{index-yy07ef2s.js → index-chgdkq36.js} +14 -14
  61. package/dist/{index-r6bty6tb.js → index-x71bvq0p.js} +15 -15
  62. package/dist/{keepalive-f7ekefyc.js → keepalive-vtkf5n99.js} +5 -5
  63. package/dist/{lead-h053syts.js → lead-h3sp357x.js} +26 -26
  64. package/dist/{maintenance-a3w6zs54.js → maintenance-jzeqtk3q.js} +6 -6
  65. package/dist/{oauth-refresh-sweep-vra1dhkd.js → oauth-refresh-sweep-qp3dzmdp.js} +4 -4
  66. package/dist/{onboard-mfgzeqxs.js → onboard-tmsp0qbz.js} +278 -81
  67. package/dist/{opencode-adapter-z64xjzec.js → opencode-adapter-x6x2da1z.js} +3 -3
  68. package/dist/{otel-impl-2ez6xk57.js → otel-impl-9mz73q65.js} +416 -301
  69. package/dist/{pi-mono-adapter-2b7mw16r.js → pi-mono-adapter-m6dvv0dz.js} +2 -2
  70. package/dist/{pricing-refresh-6sw65tck.js → pricing-refresh-v1cbr9te.js} +5 -5
  71. package/dist/{rbac-roles-nm6924sy.js → rbac-roles-c9wqz83x.js} +4 -4
  72. package/dist/{render-v2-f026j6c4.js → render-v2-gryrh9h8.js} +4 -4
  73. package/dist/{setup-w606fr1q.js → setup-q9720bt6.js} +2 -2
  74. package/dist/{virtual-modules-8qheft18.js → virtual-modules-3htpgn8x.js} +1 -1
  75. package/dist/{work-objects-ezmggdne.js → work-objects-b76x7deb.js} +17 -17
  76. package/dist/{worker-1mbyv29k.js → worker-6vrxbv41.js} +26 -26
  77. package/openapi.json +1626 -629
  78. package/package.json +2 -1
  79. package/src/apps/catalog.generated.json +229 -0
  80. package/src/apps/page-validator.ts +71 -16
  81. package/src/apps/row-store.ts +66 -11
  82. package/src/apps/sync.ts +59 -17
  83. package/src/artifact-sdk/browser-sdk.ts +436 -2
  84. package/src/be/approval-sweeps.ts +109 -0
  85. package/src/be/boot-seeds.ts +40 -0
  86. package/src/be/chunking.ts +1 -1
  87. package/src/be/db/agents.ts +20 -0
  88. package/src/be/db/approvals.ts +78 -0
  89. package/src/be/db/tasks/read.ts +197 -39
  90. package/src/be/db/tasks/write.ts +41 -9
  91. package/src/be/db-client.ts +107 -48
  92. package/src/be/db.ts +219 -76
  93. package/src/be/events.ts +33 -1
  94. package/src/be/memory/graph-expansion.ts +10 -3
  95. package/src/be/memory/providers/sqlite-store.ts +55 -13
  96. package/src/be/memory/raters/retrieval.ts +5 -2
  97. package/src/be/memory/reranker.ts +26 -2
  98. package/src/be/memory/types.ts +2 -0
  99. package/src/be/migrations/166_approval_request_auto_cancellation.sql +9 -0
  100. package/src/be/migrations/171_memory_retrieval_relevance.sql +8 -0
  101. package/src/be/migrations/175_workflow_runs_list_index.sql +11 -0
  102. package/src/be/migrations/177_events_event_agent_created_index.sql +11 -0
  103. package/src/be/models-catalog.ts +2 -0
  104. package/src/be/modelsdev-cache.json +1857 -0
  105. package/src/be/modelsdev-cache.ts +14 -1
  106. package/src/be/pricing-normalize.ts +2 -0
  107. package/src/be/scripts/typecheck.ts +1 -0
  108. package/src/be/seed-pricing.ts +3 -1
  109. package/src/be/seed-skills/bundled-files.generated.json +1 -1
  110. package/src/be/steering.ts +53 -3
  111. package/src/be/swarm-config-guard.ts +3 -0
  112. package/src/be/task-citations.ts +29 -0
  113. package/src/cli.tsx +14 -1
  114. package/src/commands/onboard/compose-generator.ts +8 -3
  115. package/src/commands/onboard/container-engine.ts +220 -0
  116. package/src/commands/onboard/manifest.ts +1 -0
  117. package/src/commands/onboard/steps/health-check.tsx +4 -1
  118. package/src/commands/onboard/steps/prereq-check.tsx +50 -54
  119. package/src/commands/onboard/steps/start.tsx +31 -16
  120. package/src/commands/onboard/types.ts +7 -2
  121. package/src/commands/onboard.tsx +39 -4
  122. package/src/commands/profile-sync.ts +79 -26
  123. package/src/commands/runner.ts +8 -1
  124. package/src/extensions/catalog.generated.json +17 -0
  125. package/src/extensions/contract-types.generated.ts +1 -1
  126. package/src/heartbeat/heartbeat.ts +263 -103
  127. package/src/hooks/hook.ts +21 -12
  128. package/src/http/agents.ts +35 -0
  129. package/src/http/approval-requests.ts +136 -64
  130. package/src/http/apps.ts +14 -3
  131. package/src/http/config.ts +15 -1
  132. package/src/http/events.ts +62 -2
  133. package/src/http/index.ts +6 -4
  134. package/src/http/memory.ts +21 -7
  135. package/src/http/schedules.ts +1 -1
  136. package/src/http/session-data.ts +9 -4
  137. package/src/http/sessions.ts +25 -18
  138. package/src/http/stats.ts +19 -7
  139. package/src/http/status.ts +11 -1
  140. package/src/http/tasks.ts +40 -8
  141. package/src/http/webhooks.ts +67 -46
  142. package/src/http/workflows.ts +15 -20
  143. package/src/linear/outbound.ts +37 -15
  144. package/src/otel-impl.ts +151 -1
  145. package/src/otel.ts +14 -3
  146. package/src/prompts/memories.ts +19 -2
  147. package/src/providers/claude-managed-models.ts +8 -0
  148. package/src/providers/modelsdev-reasoning.json +551 -0
  149. package/src/rbac/legacy-policy.ts +16 -0
  150. package/src/rbac/permissions.ts +4 -0
  151. package/src/scheduler/schedule-task.ts +3 -0
  152. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  153. package/src/scripts-runtime/swarm-sdk.ts +9 -0
  154. package/src/scripts-runtime/types/stdlib.d.ts +1 -0
  155. package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
  156. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
  157. package/src/server.ts +12 -7
  158. package/src/slack/app.ts +35 -4
  159. package/src/slack/connection-state.ts +17 -0
  160. package/src/tasks/context-key.ts +1 -0
  161. package/src/tasks/worker-follow-up.ts +6 -2
  162. package/src/tests/agent-task-activity.test.ts +74 -0
  163. package/src/tests/agents-list-model-display.test.ts +6 -1
  164. package/src/tests/approval-auto-cancellation-migration.test.ts +104 -0
  165. package/src/tests/approval-requests.test.ts +395 -0
  166. package/src/tests/approval-sweeps-config.test.ts +47 -0
  167. package/src/tests/approval-sweeps.test.ts +508 -0
  168. package/src/tests/apps-kibo-blocks.test.ts +77 -0
  169. package/src/tests/apps-spike.test.ts +56 -1
  170. package/src/tests/apps-sync-engine.test.ts +20 -9
  171. package/src/tests/boot-seeds.test.ts +170 -0
  172. package/src/tests/cancel-approval-request-tool.test.ts +115 -0
  173. package/src/tests/claude-managed-adapter.test.ts +6 -0
  174. package/src/tests/config-key-filter.test.ts +80 -0
  175. package/src/tests/context-window.test.ts +1 -0
  176. package/src/tests/db-client-busy-retry.test.ts +96 -0
  177. package/src/tests/db-client.test.ts +60 -1
  178. package/src/tests/db-task-read-contract.test.ts +10 -0
  179. package/src/tests/db-task-write-contract.test.ts +9 -0
  180. package/src/tests/docs-index-footprint.test.js +3 -3
  181. package/src/tests/events-http.test.ts +103 -0
  182. package/src/tests/extensions-catalog.test.ts +22 -17
  183. package/src/tests/extensions-pre-task.test.ts +107 -0
  184. package/src/tests/fixtures/kibo-blocks-definition.json.txt +105 -0
  185. package/src/tests/follow-up-config-inheritance.test.ts +260 -0
  186. package/src/tests/heartbeat-orphan-resume.test.ts +84 -0
  187. package/src/tests/heartbeat-reboot-live.test.ts +95 -0
  188. package/src/tests/heartbeat-stall-cas.test.ts +80 -0
  189. package/src/tests/heartbeat.test.ts +99 -3
  190. package/src/tests/hook-mcp-config.test.ts +29 -0
  191. package/src/tests/http-api-integration.test.ts +16 -2
  192. package/src/tests/kv-storage.test.ts +62 -0
  193. package/src/tests/linear-outbound-sync.test.ts +120 -5
  194. package/src/tests/list-endpoint-slimming.test.ts +91 -0
  195. package/src/tests/memory-graph-organic-route.test.ts +155 -0
  196. package/src/tests/memory-http-recall-gating.test.ts +14 -4
  197. package/src/tests/memory-hybrid.test.ts +161 -1
  198. package/src/tests/memory-prompt-recall-crowdout.test.ts +297 -0
  199. package/src/tests/memory.test.ts +22 -0
  200. package/src/tests/onboard-container-engine.test.ts +240 -0
  201. package/src/tests/otel-event-loop-and-logs.test.ts +99 -0
  202. package/src/tests/pricing-routes.test.ts +14 -0
  203. package/src/tests/profile-sync-warning.test.ts +8 -4
  204. package/src/tests/promote-draft-task-route.test.ts +19 -0
  205. package/src/tests/provider-registration.test.ts +296 -42
  206. package/src/tests/rbac-engine.test.ts +30 -0
  207. package/src/tests/reasoning-effort.test.ts +8 -0
  208. package/src/tests/script-connections.test.ts +7 -0
  209. package/src/tests/sdk-allowlist.test.ts +39 -0
  210. package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
  211. package/src/tests/sessions-detail-batch.test.ts +250 -0
  212. package/src/tests/sessions.test.ts +118 -0
  213. package/src/tests/slack-config.test.ts +22 -0
  214. package/src/tests/sonnet-5-5-registry.test.ts +44 -0
  215. package/src/tests/status.test.ts +36 -1
  216. package/src/tests/swarm-diff.test.ts +316 -10
  217. package/src/tests/task-completion-idempotency.test.ts +2 -1
  218. package/src/tests/tasks-requested-by-filter.test.ts +3 -1
  219. package/src/tests/tool-annotations.test.ts +1 -1
  220. package/src/tests/ui-list-slim-projections.test.ts +158 -0
  221. package/src/tests/webhooks-task-blocked.test.ts +127 -0
  222. package/src/tests/workflow-approval-recovery.test.ts +203 -0
  223. package/src/tests/workflow-hitl-routing.test.ts +22 -1
  224. package/src/tests/workflow-hitl-slack-card.test.ts +52 -0
  225. package/src/tests/workflow-http-v2.test.ts +99 -8
  226. package/src/tests/workflow-retry-validation.test.ts +56 -0
  227. package/src/tests/workflow-tla-races.test.ts +389 -0
  228. package/src/tests/workflow-validation-port-routing.test.ts +113 -1
  229. package/src/tools/cancel-approval-request.ts +57 -0
  230. package/src/tools/memory-search.ts +1 -0
  231. package/src/tools/request-human-input.ts +4 -2
  232. package/src/tools/send-task.ts +4 -0
  233. package/src/tools/templates.ts +19 -0
  234. package/src/tools/tool-config.ts +2 -1
  235. package/src/tools/workflows/list-workflow-runs.ts +1 -0
  236. package/src/types.ts +38 -0
  237. package/src/utils/context-window.ts +1 -0
  238. package/src/workflows/approval-cancel.ts +152 -0
  239. package/src/workflows/approval-notifications.ts +84 -0
  240. package/src/workflows/checkpoint.ts +10 -5
  241. package/src/workflows/definition.ts +21 -0
  242. package/src/workflows/engine.ts +152 -21
  243. package/src/workflows/executors/human-in-the-loop.ts +1 -1
  244. package/src/workflows/recovery.ts +100 -45
  245. package/src/workflows/resume.ts +49 -18
  246. package/src/workflows/retry-poller.ts +71 -21
  247. package/templates/extensions/README.md +1 -0
  248. package/templates/extensions/github-sender-allowlist/README.md +18 -0
  249. package/templates/extensions/github-sender-allowlist/hooks.ts +55 -0
  250. package/templates/extensions/github-sender-allowlist/manifest.json +8 -0
  251. package/templates/skills/apps/content.md +20 -1
  252. package/templates/skills/code-quality/content.md +26 -1
  253. package/templates/skills/pages/content.md +16 -4
  254. package/templates/skills/pages/files/examples/multiplayer-board.html +3 -12
  255. package/dist/cli-2daksqps.js +0 -378
  256. package/dist/rbac-roles-3gxmnb20.js +0 -37
@@ -1,10 +1,10 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { z } from "zod";
3
3
  import { countSessions, getRootTaskChain, getTaskById, listRecentSessions } from "../be/db";
4
- import { getTaskSteeringFields } from "../be/steering";
5
- import { getTaskCitations, TaskCitationSchema } from "../be/task-citations";
4
+ import { getTaskSteeringFieldsForTasks } from "../be/steering";
5
+ import { getTaskCitationsForTasks, TaskCitationSchema } from "../be/task-citations";
6
6
  import { mintSessionToken, revokeSessionToken } from "../be/users";
7
- import { AgentTaskSchema, AgentTaskStatusSchema, SteerModeSchema } from "../types";
7
+ import { type AgentTask, AgentTaskSchema, AgentTaskStatusSchema, SteerModeSchema } from "../types";
8
8
  import { getRequestAuth } from "../utils/request-auth-context";
9
9
  import { route } from "./route-def";
10
10
  import { jsonError } from "./utils";
@@ -144,6 +144,12 @@ const listSessions = route({
144
144
  * excluded. Omit to return every session (legacy / non-UI callers).
145
145
  */
146
146
  requestedByUserId: z.string().min(1).optional(),
147
+ /**
148
+ * When present, restrict results to root tasks whose `contextKey` starts
149
+ * with this literal prefix. The UI contextual session panel passes its page
150
+ * key plus a trailing `:` (e.g. `task:ui:workflow:abc:`).
151
+ */
152
+ contextKeyPrefix: z.string().min(1).optional(),
147
153
  /** `full` restores the legacy shape (full root `AgentTask`); default is slim. */
148
154
  fields: z.enum(["full", "slim"]).optional(),
149
155
  }),
@@ -235,18 +241,20 @@ export async function handleSessions(
235
241
  source: sources,
236
242
  q: parsed.query.q,
237
243
  requestedByUserId: parsed.query.requestedByUserId,
244
+ contextKeyPrefix: parsed.query.contextKeyPrefix,
238
245
  };
239
246
  // List responses default to slim (root is a task summary); `?fields=full` restores it.
240
247
  const sessions =
241
248
  parsed.query.fields === "full"
242
249
  ? await listRecentSessions(baseOpts)
243
250
  : await listRecentSessions({ ...baseOpts, slim: true });
244
- // Filter-aware total: same `source`/`q`/`requestedByUserId` WHERE as the
245
- // list query, so the UI pager reflects the filtered result set.
251
+ // Filter-aware total: same WHERE as the list query, so the UI pager
252
+ // reflects the filtered result set.
246
253
  const total = await countSessions({
247
254
  source: sources,
248
255
  q: parsed.query.q,
249
256
  requestedByUserId: parsed.query.requestedByUserId,
257
+ contextKeyPrefix: parsed.query.contextKeyPrefix,
250
258
  });
251
259
  listSessions.respond(res, 200, {
252
260
  sessions,
@@ -266,20 +274,19 @@ export async function handleSessions(
266
274
  return true;
267
275
  }
268
276
  const chain = await getRootTaskChain(parsed.params.rootTaskId);
269
- getSession.respond(res, 200, {
270
- root: {
271
- ...root,
272
- ...(await getTaskSteeringFields(root)),
273
- citations: await getTaskCitations(root.id),
274
- },
275
- chain: await Promise.all(
276
- chain.map(async (task) => ({
277
- ...task,
278
- ...(await getTaskSteeringFields(task)),
279
- citations: await getTaskCitations(task.id),
280
- })),
281
- ),
277
+ // Fixed query count regardless of chain length: one agents lookup and one
278
+ // citations lookup for the whole chain (chunked past 500 ids).
279
+ const tasks = [root, ...chain];
280
+ const [steering, citations] = await Promise.all([
281
+ getTaskSteeringFieldsForTasks(tasks),
282
+ getTaskCitationsForTasks(tasks.map((task) => task.id)),
283
+ ]);
284
+ const decorate = (task: AgentTask) => ({
285
+ ...task,
286
+ ...steering.get(task.id)!,
287
+ citations: citations.get(task.id)!,
282
288
  });
289
+ getSession.respond(res, 200, { root: decorate(root), chain: chain.map(decorate) });
283
290
  return true;
284
291
  }
285
292
 
package/src/http/stats.ts CHANGED
@@ -18,6 +18,7 @@ import { AgentLogSchema, ScheduledTaskSchema, ServiceSchema } from "../types";
18
18
  import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
19
19
  import { resolveHttpFavoriteOwner } from "./favorite-owner";
20
20
  import { route } from "./route-def";
21
+ import { scheduleSummaryWithFavoriteSchema } from "./schedules";
21
22
 
22
23
  // ─── Response schemas ────────────────────────────────────────────────────────
23
24
 
@@ -183,6 +184,8 @@ const listScheduledTasks = route({
183
184
  path: "/api/scheduled-tasks",
184
185
  pattern: ["api", "scheduled-tasks"],
185
186
  summary: "List scheduled tasks",
187
+ description:
188
+ "Returns full schedules by default. Pass `fields=slim` for the list-view shape, which swaps the full `taskTemplate` for a bounded `taskTemplatePreview`. Fetch one schedule in full via `GET /api/schedules/{id}`.",
186
189
  tags: ["Stats"],
187
190
  query: z.object({
188
191
  enabled: z.enum(["true", "false"]).optional(),
@@ -192,11 +195,18 @@ const listScheduledTasks = route({
192
195
  targetType: z.enum(["agent-task", "workflow", "script"]).optional(),
193
196
  workflowId: z.string().uuid().optional(),
194
197
  scriptName: z.string().optional(),
198
+ /** `slim` is the list-view shape (no full `taskTemplate`); default is full. */
199
+ fields: z.enum(["full", "slim"]).optional(),
195
200
  }),
196
201
  responses: {
197
202
  200: {
198
203
  description: "Scheduled tasks list",
199
- schema: z.object({ scheduledTasks: z.array(ScheduledTaskSchema) }),
204
+ schema: z.object({
205
+ scheduledTasks: z.union([
206
+ z.array(ScheduledTaskSchema),
207
+ z.array(scheduleSummaryWithFavoriteSchema),
208
+ ]),
209
+ }),
200
210
  },
201
211
  },
202
212
  });
@@ -301,7 +311,7 @@ export async function handleStats(
301
311
  if (listScheduledTasks.match(req.method, pathSegments)) {
302
312
  const parsed = await listScheduledTasks.parse(req, res, pathSegments, queryParams);
303
313
  if (!parsed) return true;
304
- const scheduledTasks = await getScheduledTasks({
314
+ const filters = {
305
315
  enabled: parsed.query.enabled !== undefined ? parsed.query.enabled === "true" : undefined,
306
316
  name: parsed.query.name || undefined,
307
317
  scheduleType: (parsed.query.scheduleType as "recurring" | "one_time") || undefined,
@@ -312,13 +322,15 @@ export async function handleStats(
312
322
  targetType: parsed.query.targetType,
313
323
  workflowId: parsed.query.workflowId,
314
324
  scriptName: parsed.query.scriptName,
315
- });
325
+ };
316
326
  const favoriteScope = (await resolveHttpFavoriteOwner(req, myAgentId))?.scope;
327
+ const favoriteOpts = { favoriteScope, itemType: "schedule" as const };
328
+ // Opt-in: API, MCP and script callers that don't ask keep the full rows.
317
329
  listScheduledTasks.respond(res, 200, {
318
- scheduledTasks: await withFavoriteFlags(scheduledTasks, {
319
- favoriteScope,
320
- itemType: "schedule",
321
- }),
330
+ scheduledTasks:
331
+ parsed.query.fields === "slim"
332
+ ? await withFavoriteFlags(await getScheduledTasks(filters, { slim: true }), favoriteOpts)
333
+ : await withFavoriteFlags(await getScheduledTasks(filters), favoriteOpts),
322
334
  });
323
335
  return true;
324
336
  }
@@ -36,6 +36,7 @@ import {
36
36
  import { getEmbeddingProvider } from "../be/memory";
37
37
  import { getFileStorageProvider } from "../fs/registry";
38
38
  import { getSlackConfiguration } from "../slack/config";
39
+ import { getSlackConnectionState } from "../slack/connection-state";
39
40
  import { type AgentCredStatus, AutomationIntegrationIdSchema, ProviderNameSchema } from "../types";
40
41
  import { route } from "./route-def";
41
42
  import { json, jsonError } from "./utils";
@@ -438,6 +439,15 @@ function slackMilestone(state: AutomationSetupStates["slack"]): SetupMilestone {
438
439
  };
439
440
  }
440
441
 
442
+ if (config.mode === "socket" && getSlackConnectionState() === "connected") {
443
+ return {
444
+ id: "slack",
445
+ label: "Slack configured",
446
+ state: "verified",
447
+ action_url: automationIntegrationFixUrl("slack"),
448
+ };
449
+ }
450
+
441
451
  return {
442
452
  id: "slack",
443
453
  label: "Slack configured",
@@ -445,7 +455,7 @@ function slackMilestone(state: AutomationSetupStates["slack"]): SetupMilestone {
445
455
  hint:
446
456
  config.mode === "http"
447
457
  ? "HTTP credentials are configured, but HTTP ingress is unavailable until the receiver is installed."
448
- : "Socket Mode credentials are configured; a live connection has not been verified.",
458
+ : "Socket Mode credentials are configured, but the bot is not connected to Slack.",
449
459
  action_url: automationIntegrationFixUrl("slack"),
450
460
  };
451
461
  }
package/src/http/tasks.ts CHANGED
@@ -113,6 +113,21 @@ const AgentTaskSummarySchema = AgentTaskSchema.pick({
113
113
  totalCostUsd: true,
114
114
  });
115
115
 
116
+ /** `/api/tasks?fields=timeline` item — mirrors `AgentTaskTimelineItem` in ../types. */
117
+ const AgentTaskTimelineItemSchema = AgentTaskSchema.pick({
118
+ id: true,
119
+ agentId: true,
120
+ parentTaskId: true,
121
+ task: true,
122
+ title: true,
123
+ status: true,
124
+ createdAt: true,
125
+ lastUpdatedAt: true,
126
+ finishedAt: true,
127
+ peakContextTokens: true,
128
+ totalCostUsd: true,
129
+ });
130
+
116
131
  /** Shared by cancel/pause/resume — each sends `{ success: true, task }` on success. */
117
132
  const TaskActionResultSchema = z.object({
118
133
  success: z.literal(true),
@@ -173,7 +188,7 @@ const listTasks = route({
173
188
  pattern: ["api", "tasks"],
174
189
  summary: "List tasks with filters",
175
190
  description:
176
- "Returns tasks with the full `task` text replaced by a bounded `taskPreview` and completion/integration blobs dropped by default — list views only need the preview. Pass `fields=full` to restore the full `AgentTask`. Fetch a single task in full via `GET /api/tasks/{id}`.",
191
+ "Returns tasks with the full `task` text replaced by a bounded `taskPreview` and completion/integration blobs dropped by default — list views only need the preview. Pass `fields=full` to restore the full `AgentTask`, or `fields=timeline` for the narrow shape the dashboard timeline draws. Fetch a single task in full via `GET /api/tasks/{id}`. `total` (the filtered row count, ignoring limit/offset) is computed only with `includeTotal=true`.",
177
192
  tags: ["Tasks"],
178
193
  query: z.object({
179
194
  /** Single status, or comma-separated list (e.g. "failed,cancelled"). */
@@ -200,15 +215,25 @@ const listTasks = route({
200
215
  orderBy: z.enum(["lastUpdatedAt", "createdAt"]).optional(),
201
216
  limit: z.coerce.number().int().optional(),
202
217
  offset: z.coerce.number().int().optional(),
203
- /** `full` restores the legacy shape (full `task` text + all fields); default is slim. */
204
- fields: z.enum(["full", "slim"]).optional(),
218
+ /**
219
+ * `full` restores the legacy shape (full `task` text + all fields);
220
+ * `timeline` is the dashboard timeline's narrow shape; default is slim.
221
+ */
222
+ fields: z.enum(["full", "slim", "timeline"]).optional(),
223
+ /** `true` adds `total`, a filtered COUNT(*) that pagers need. Omitted by default. */
224
+ includeTotal: z.enum(["true", "false"]).optional(),
205
225
  }),
206
226
  responses: {
207
227
  200: {
208
228
  description: "Paginated task list",
209
229
  schema: z.object({
210
- tasks: z.union([z.array(AgentTaskSchema), z.array(AgentTaskSummarySchema)]),
211
- total: z.number().int(),
230
+ tasks: z.union([
231
+ z.array(AgentTaskSchema),
232
+ z.array(AgentTaskSummarySchema),
233
+ z.array(AgentTaskTimelineItemSchema),
234
+ ]),
235
+ /** Present only when the request passed `includeTotal=true`. */
236
+ total: z.number().int().optional(),
212
237
  }),
213
238
  },
214
239
  400: { description: "Validation error (e.g. unknown status token)" },
@@ -785,9 +810,16 @@ export async function handleTasks(
785
810
  const tasks =
786
811
  parsed.query.fields === "full"
787
812
  ? await getAllTasks(filters)
788
- : await getAllTasks(filters, { slim: true });
789
- const total = await getTasksCount(filters);
790
- listTasks.respond(res, 200, { tasks, total });
813
+ : parsed.query.fields === "timeline"
814
+ ? await getAllTasks(filters, { fields: "timeline" })
815
+ : await getAllTasks(filters, { slim: true });
816
+ // The COUNT(*) re-scans every matching row, so only pagers pay for it.
817
+ if (parsed.query.includeTotal === "true") {
818
+ const total = await getTasksCount(filters);
819
+ listTasks.respond(res, 200, { tasks, total });
820
+ } else {
821
+ listTasks.respond(res, 200, { tasks });
822
+ }
791
823
  return true;
792
824
  }
793
825
 
@@ -52,6 +52,7 @@ import type { KapsoConfig } from "../integrations/kapso/config";
52
52
  import { getKapsoConfig } from "../integrations/kapso/config";
53
53
  import type { KapsoWebhookPayload } from "../integrations/kapso/inbound";
54
54
  import { resolveKapsoRequestedByUserId, routeKapsoInbound } from "../integrations/kapso/inbound";
55
+ import { TaskCreationBlockedError } from "../tasks/errors";
55
56
  import { getExecutorRegistry } from "../workflows";
56
57
  import { workflowEventBus } from "../workflows/event-bus";
57
58
  import { handleWebhookTrigger, verifyHmacSignature, WebhookError } from "../workflows/triggers";
@@ -67,8 +68,34 @@ import { route } from "./route-def";
67
68
  const WebhookDispatchResultSchema = z.object({
68
69
  created: z.boolean(),
69
70
  taskId: z.string().optional(),
71
+ skipped: z.boolean().optional(),
72
+ reason: z.string().optional(),
73
+ extension: z.object({ id: z.string(), name: z.string() }).optional(),
70
74
  });
71
75
 
76
+ type WebhookDispatchResult = z.infer<typeof WebhookDispatchResultSchema>;
77
+
78
+ /**
79
+ * Runs a webhook's task-creating handler. A `pre.task.create` extension block
80
+ * is an expected outcome, not a server error: the delivery is acked with a
81
+ * skipped result so the provider does not retry and the caller still emits the
82
+ * delivery's workflow events. Any other error propagates.
83
+ */
84
+ async function runBlockableHandler(
85
+ source: string,
86
+ handler: () => Promise<WebhookDispatchResult>,
87
+ ): Promise<WebhookDispatchResult> {
88
+ try {
89
+ return await handler();
90
+ } catch (err) {
91
+ if (!(err instanceof TaskCreationBlockedError)) throw err;
92
+ console.log(
93
+ `[${source}] Task creation blocked by extension ${err.extension.name}: ${err.reason}`,
94
+ );
95
+ return { created: false, skipped: true, reason: err.reason, extension: err.extension };
96
+ }
97
+ }
98
+
72
99
  const githubWebhook = route({
73
100
  method: "post",
74
101
  path: "/api/github/webhook",
@@ -249,37 +276,32 @@ export async function handleWebhooks(
249
276
 
250
277
  console.log(`[GitHub] Received ${eventType} event`);
251
278
 
252
- let result: { created: boolean; taskId?: string } = { created: false };
279
+ let result: WebhookDispatchResult = { created: false };
253
280
 
254
281
  try {
255
- switch (eventType) {
256
- case "pull_request":
257
- result = await handlePullRequest(body as PullRequestEvent);
258
- break;
259
- case "issues":
260
- result = await handleIssue(body as IssueEvent);
261
- break;
262
- case "issue_comment":
263
- result = await handleComment(body as CommentEvent, "issue_comment");
264
- break;
265
- case "pull_request_review_comment":
266
- result = await handleComment(body as CommentEvent, "pull_request_review_comment");
267
- break;
268
- case "pull_request_review":
269
- result = await handlePullRequestReview(body as PullRequestReviewEvent);
270
- break;
271
- case "check_run":
272
- result = await handleCheckRun(body as CheckRunEvent);
273
- break;
274
- case "check_suite":
275
- result = await handleCheckSuite(body as CheckSuiteEvent);
276
- break;
277
- case "workflow_run":
278
- result = await handleWorkflowRun(body as WorkflowRunEvent);
279
- break;
280
- default:
281
- console.log(`[GitHub] Ignoring unsupported event type: ${eventType}`);
282
- }
282
+ result = await runBlockableHandler("GitHub", async () => {
283
+ switch (eventType) {
284
+ case "pull_request":
285
+ return await handlePullRequest(body as PullRequestEvent);
286
+ case "issues":
287
+ return await handleIssue(body as IssueEvent);
288
+ case "issue_comment":
289
+ return await handleComment(body as CommentEvent, "issue_comment");
290
+ case "pull_request_review_comment":
291
+ return await handleComment(body as CommentEvent, "pull_request_review_comment");
292
+ case "pull_request_review":
293
+ return await handlePullRequestReview(body as PullRequestReviewEvent);
294
+ case "check_run":
295
+ return await handleCheckRun(body as CheckRunEvent);
296
+ case "check_suite":
297
+ return await handleCheckSuite(body as CheckSuiteEvent);
298
+ case "workflow_run":
299
+ return await handleWorkflowRun(body as WorkflowRunEvent);
300
+ default:
301
+ console.log(`[GitHub] Ignoring unsupported event type: ${eventType}`);
302
+ return { created: false };
303
+ }
304
+ });
283
305
 
284
306
  // Emit workflow trigger event for matching event types
285
307
  switch (eventType) {
@@ -376,25 +398,24 @@ export async function handleWebhooks(
376
398
  const objectKind = body.object_kind as string | undefined;
377
399
  console.log(`[GitLab] Received ${objectKind} event`);
378
400
 
379
- let result: { created: boolean; taskId?: string } = { created: false };
401
+ let result: WebhookDispatchResult = { created: false };
380
402
 
381
403
  try {
382
- switch (objectKind) {
383
- case "merge_request":
384
- result = await handleMergeRequest(body as unknown as MergeRequestEvent);
385
- break;
386
- case "issue":
387
- result = await handleGitLabIssue(body as unknown as GitLabIssueEvent);
388
- break;
389
- case "note":
390
- result = await handleNote(body as unknown as NoteEvent);
391
- break;
392
- case "pipeline":
393
- result = await handlePipeline(body as unknown as PipelineEvent);
394
- break;
395
- default:
396
- console.log(`[GitLab] Ignoring unsupported event type: ${objectKind}`);
397
- }
404
+ result = await runBlockableHandler("GitLab", async () => {
405
+ switch (objectKind) {
406
+ case "merge_request":
407
+ return await handleMergeRequest(body as unknown as MergeRequestEvent);
408
+ case "issue":
409
+ return await handleGitLabIssue(body as unknown as GitLabIssueEvent);
410
+ case "note":
411
+ return await handleNote(body as unknown as NoteEvent);
412
+ case "pipeline":
413
+ return await handlePipeline(body as unknown as PipelineEvent);
414
+ default:
415
+ console.log(`[GitLab] Ignoring unsupported event type: ${objectKind}`);
416
+ return { created: false };
417
+ }
418
+ });
398
419
 
399
420
  // Emit workflow trigger events for GitLab
400
421
  switch (objectKind) {
@@ -10,7 +10,6 @@ import {
10
10
  getWorkflowRunStepsByRunId,
11
11
  getWorkflowVersion,
12
12
  getWorkflowVersions,
13
- listWorkflowRuns,
14
13
  listWorkflowRunsPage,
15
14
  listWorkflows,
16
15
  type updateWorkflow,
@@ -29,6 +28,7 @@ import {
29
28
  WorkflowRunSchema,
30
29
  WorkflowRunStatusSchema,
31
30
  WorkflowRunStepSchema,
31
+ WorkflowRunSummarySchema,
32
32
  WorkflowSchema,
33
33
  WorkflowVersionSchema,
34
34
  } from "../types";
@@ -68,9 +68,12 @@ const ExecutorTypeInfoSchema = z.object({
68
68
  outputSchema: z.record(z.string(), z.unknown()),
69
69
  });
70
70
 
71
+ /** Page size when a caller sends no `limit`. */
72
+ const DEFAULT_WORKFLOW_RUNS_LIMIT = 50;
73
+
71
74
  /** Mirrors `WorkflowRunPage` in src/be/db.ts. */
72
75
  const WorkflowRunPageSchema = z.object({
73
- runs: z.array(WorkflowRunSchema),
76
+ runs: z.array(WorkflowRunSummarySchema),
74
77
  page: z.object({
75
78
  limit: z.number().int(),
76
79
  offset: z.number().int(),
@@ -273,7 +276,8 @@ const listWorkflowRunsRoute = route({
273
276
  method: "get",
274
277
  path: "/api/workflows/{id}/runs",
275
278
  pattern: ["api", "workflows", null, "runs"],
276
- summary: "List runs for a workflow",
279
+ summary: "List runs for a workflow, newest first",
280
+ description: `Returns one page of runs without \`context\` (fetch it with GET /api/workflow-runs/{id}). \`limit\` defaults to ${DEFAULT_WORKFLOW_RUNS_LIMIT} and is capped at 100; follow \`page.nextOffset\` while \`page.hasMore\` is true.`,
277
281
  tags: ["Workflows"],
278
282
  params: z.object({ id: z.string() }),
279
283
  query: z.object({
@@ -283,8 +287,8 @@ const listWorkflowRunsRoute = route({
283
287
  }),
284
288
  responses: {
285
289
  200: {
286
- description: "Workflow run list",
287
- schema: z.union([z.array(WorkflowRunSchema), WorkflowRunPageSchema]),
290
+ description: "One page of workflow runs",
291
+ schema: WorkflowRunPageSchema,
288
292
  },
289
293
  },
290
294
  });
@@ -847,21 +851,12 @@ export async function handleWorkflows(
847
851
  if (listWorkflowRunsRoute.match(req.method, pathSegments)) {
848
852
  const parsed = await listWorkflowRunsRoute.parse(req, res, pathSegments, queryParams);
849
853
  if (!parsed) return true;
850
- const paginationRequested =
851
- parsed.query?.limit !== undefined || parsed.query?.offset !== undefined;
852
- if (paginationRequested) {
853
- const page = await listWorkflowRunsPage(parsed.params.id, {
854
- status: parsed.query?.status,
855
- limit: parsed.query?.limit ?? 20,
856
- offset: parsed.query?.offset ?? 0,
857
- });
858
- listWorkflowRunsRoute.respond(res, 200, page);
859
- return true;
860
- }
861
- // Preserve the pre-pagination response for the UI when limit/offset are
862
- // omitted: a bare array containing every matching run.
863
- const runs = await listWorkflowRuns(parsed.params.id, { status: parsed.query?.status });
864
- listWorkflowRunsRoute.respond(res, 200, runs);
854
+ const page = await listWorkflowRunsPage(parsed.params.id, {
855
+ status: parsed.query?.status,
856
+ limit: parsed.query?.limit ?? DEFAULT_WORKFLOW_RUNS_LIMIT,
857
+ offset: parsed.query?.offset ?? 0,
858
+ });
859
+ listWorkflowRunsRoute.respond(res, 200, page);
865
860
  return true;
866
861
  }
867
862
 
@@ -102,6 +102,31 @@ async function handleTaskProgress(data: unknown): Promise<void> {
102
102
  });
103
103
  }
104
104
 
105
+ // Linear documents no size limit for a comment or an AgentActivity body. 30k chars stays under
106
+ // the 32,767 figure other Linear integrations use, and fits any realistic task output.
107
+ export const LINEAR_BODY_MAX = 30_000;
108
+
109
+ /**
110
+ * Build the body of the single Linear comment that carries a task's outcome.
111
+ *
112
+ * The text goes in as-is: no `+++ Title … +++` wrapper (Linear renders that as a
113
+ * collapsed section) and no boilerplate. A body over `LINEAR_BODY_MAX` is cut and ends
114
+ * with a visible note that points at the swarm task for the full text.
115
+ */
116
+ export function formatLinearOutcomeBody(text: string, taskId: string): string {
117
+ if (text.length <= LINEAR_BODY_MAX) return text;
118
+
119
+ const dashboardUrl = (process.env.SWARM_DASHBOARD_URL || process.env.APP_URL)?.replace(
120
+ /\/+$/,
121
+ "",
122
+ );
123
+ const where = dashboardUrl ? `${dashboardUrl}/tasks/${taskId}` : `swarm task \`${taskId}\``;
124
+ const note = (shown: number) =>
125
+ `\n\n---\n_Truncated: showing ${shown} of ${text.length} characters. Full text: ${where}_`;
126
+ const shown = LINEAR_BODY_MAX - note(LINEAR_BODY_MAX).length;
127
+ return `${text.slice(0, shown).trimEnd()}${note(shown)}`;
128
+ }
129
+
105
130
  async function handleTaskCompleted(data: unknown): Promise<void> {
106
131
  const { taskId, output } = data as { taskId: string; output?: string };
107
132
  if (!taskId) return;
@@ -112,11 +137,11 @@ async function handleTaskCompleted(data: unknown): Promise<void> {
112
137
  if (shouldSkipForLoopPrevention(sync)) return;
113
138
 
114
139
  const sessionId = taskSessionMap.get(taskId);
115
- const body = output
116
- ? `Task completed.\n\n+++ Output\n${output.slice(0, 2000)}\n+++`
117
- : "Task completed.";
140
+ const body = output?.trim() ? formatLinearOutcomeBody(output, taskId) : "Task completed.";
118
141
 
119
- // Prefer AgentSession activity (shows in the agent panel) over issue comment (avoids duplication)
142
+ // One comment only. A `response` activity ends the AgentSession and Linear renders it as a
143
+ // comment on the issue, so it carries the output itself. The plain comment below runs only
144
+ // when there is no session to end.
120
145
  if (sessionId) {
121
146
  endAgentSession(sessionId, body, "response").catch((err) => {
122
147
  console.error(`[Linear Outbound] Failed to end AgentSession for task ${taskId}:`, err);
@@ -133,10 +158,7 @@ async function handleTaskCompleted(data: unknown): Promise<void> {
133
158
  console.log("[Linear Outbound] No Linear client available, skipping sync for", taskId);
134
159
  return;
135
160
  }
136
- const comment = output
137
- ? `Task completed by swarm agent.\n\n+++ Output\n${output.slice(0, 2000)}\n+++`
138
- : "Task completed by swarm agent.";
139
- await client.createComment({ issueId: sync.externalId, body: comment });
161
+ await client.createComment({ issueId: sync.externalId, body });
140
162
  console.log(`[Linear Outbound] Posted completion comment for task ${taskId}`);
141
163
  } catch (error) {
142
164
  console.error(
@@ -162,11 +184,14 @@ async function handleTaskFailed(data: unknown): Promise<void> {
162
184
  if (shouldSkipForLoopPrevention(sync)) return;
163
185
 
164
186
  const sessionId = taskSessionMap.get(taskId);
165
- const body = failureReason
166
- ? `Task failed.\n\n+++ Error Details\n${failureReason.slice(0, 2000)}\n+++`
187
+ // Keep the one-line "Task failed." lead: without it a plain fallback comment reads like
188
+ // a normal answer.
189
+ const body = failureReason?.trim()
190
+ ? formatLinearOutcomeBody(`Task failed.\n\n${failureReason}`, taskId)
167
191
  : "Task failed.";
168
192
 
169
- // Prefer AgentSession error activity over issue comment (avoids duplication)
193
+ // One comment only: the `error` activity ends the AgentSession and carries the reason.
194
+ // The plain comment below runs only when there is no session to end.
170
195
  if (sessionId) {
171
196
  endAgentSession(sessionId, body, "error").catch((err) => {
172
197
  console.error(`[Linear Outbound] Failed to end AgentSession for task ${taskId}:`, err);
@@ -183,10 +208,7 @@ async function handleTaskFailed(data: unknown): Promise<void> {
183
208
  console.log("[Linear Outbound] No Linear client available, skipping sync for", taskId);
184
209
  return;
185
210
  }
186
- const comment = failureReason
187
- ? `Task failed.\n\n+++ Error Details\n${failureReason.slice(0, 2000)}\n+++`
188
- : "Task failed.";
189
- await client.createComment({ issueId: sync.externalId, body: comment });
211
+ await client.createComment({ issueId: sync.externalId, body });
190
212
  console.log(`[Linear Outbound] Posted failure comment for task ${taskId}`);
191
213
  } catch (error) {
192
214
  console.error(