@desplega.ai/agent-swarm 2.4.3 → 2.4.5

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 (270) hide show
  1. package/dist/{acp-adapter-1885dpr7.js → acp-adapter-2wdkktv6.js} +1 -2
  2. package/dist/{actions-pn76n32q.js → actions-c40ebcm4.js} +18 -18
  3. package/dist/{amp-adapter-af84rx1d.js → amp-adapter-wb67m95z.js} +2 -2
  4. package/dist/{app-6sv8mhzm.js → app-353vre8g.js} +3 -3
  5. package/dist/{assistant-sc2tf825.js → assistant-sj1njh23.js} +21 -21
  6. package/dist/{boot-reembed-y3m0ydzj.js → boot-reembed-b9c3az3c.js} +1 -1
  7. package/dist/{boot-reembed-gvxjs2py.js → boot-reembed-qdkvxeem.js} +1 -1
  8. package/dist/{boot-scrub-sweep-1j9mk05f.js → boot-scrub-sweep-m3wh8eks.js} +2 -2
  9. package/dist/{boot-seeds-37sp626b.js → boot-seeds-j2273s54.js} +4 -4
  10. package/dist/{claude-adapter-rp0jtnwr.js → claude-adapter-m7q0a085.js} +2 -2
  11. package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-5ptb6tzs.js} +2 -2
  12. package/dist/{claude-managed-setup-rdt248k7.js → claude-managed-setup-smrqk5j5.js} +3 -3
  13. package/dist/{claude-sdk-session-53rcw5we.js → claude-sdk-session-38xnz21e.js} +8284 -8267
  14. package/dist/{cli-6eyncg0y.js → cli-018r4jq6.js} +34 -6
  15. package/dist/{cli-sds5zzs8.js → cli-1f3qzw6y.js} +78 -12
  16. package/dist/{cli-f0p0f6ja.js → cli-1sp861s4.js} +7 -7
  17. package/dist/{cli-dm8p2dpf.js → cli-1syn2xy1.js} +10 -10
  18. package/dist/{cli-tkf4x6ha.js → cli-37j04k05.js} +11 -11
  19. package/dist/{cli-v87dr6e3.js → cli-3a45zcgm.js} +1 -1
  20. package/dist/{cli-1n9gdg56.js → cli-43x64hv3.js} +4 -4
  21. package/dist/{cli-p0qerd2r.js → cli-4zfqgjc5.js} +2 -2
  22. package/dist/{cli-aa3tb29r.js → cli-7c9qs530.js} +1 -1
  23. package/dist/{cli-9qtjfd60.js → cli-848hxdwj.js} +1 -1
  24. package/dist/{cli-1xsraagq.js → cli-94gwyg7m.js} +8 -8
  25. package/dist/{cli-0dxxwfkt.js → cli-9hstj09h.js} +17 -14
  26. package/dist/{cli-1012xb7j.js → cli-9yqgm9nd.js} +22 -22
  27. package/dist/{cli-pygdsgh8.js → cli-a92r1127.js} +1 -1
  28. package/dist/{cli-8vverzf7.js → cli-ba8ne1es.js} +5 -0
  29. package/dist/{cli-bxrqvpg5.js → cli-be2161h1.js} +7 -7
  30. package/dist/{cli-npfe9t1s.js → cli-bnes4z6b.js} +2 -2
  31. package/dist/{cli-aypbg9r2.js → cli-cxxe61f6.js} +1 -1
  32. package/dist/{cli-vq9gqarq.js → cli-dk44dxec.js} +1 -1
  33. package/dist/{cli-836ehxmt.js → cli-eww120g8.js} +3 -2
  34. package/dist/{cli-a0kcty51.js → cli-f01h25yt.js} +1 -1
  35. package/dist/{cli-mzkwkbdy.js → cli-f0g3pgvf.js} +1 -1
  36. package/dist/{cli-1ma335fz.js → cli-ftc1g5tj.js} +2 -2
  37. package/dist/{cli-4yef8ghv.js → cli-fv5hepyy.js} +3 -3
  38. package/dist/{cli-58vwn100.js → cli-g35m2xwm.js} +4 -4
  39. package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
  40. package/dist/{cli-gx0j2khs.js → cli-j99azg3g.js} +3 -3
  41. package/dist/{cli-er83cjbn.js → cli-k8g16gz3.js} +416 -116
  42. package/dist/{cli-qp2m4btv.js → cli-kpw04va0.js} +2 -2
  43. package/dist/{cli-8y99kfey.js → cli-ms85309d.js} +33 -15
  44. package/dist/{cli-kgnkyabk.js → cli-n72rg53p.js} +1555 -645
  45. package/dist/{cli-e4d78xsv.js → cli-p14m2cnp.js} +1 -1
  46. package/dist/{cli-1eszr343.js → cli-qfxc84gh.js} +1 -1
  47. package/dist/{cli-wk28hzqn.js → cli-qwhy2qma.js} +1 -1
  48. package/dist/{cli-73ezfhha.js → cli-st8aw1m6.js} +228 -11
  49. package/dist/{cli-zvbb4n0e.js → cli-tw4p90jb.js} +92 -192
  50. package/dist/{cli-038fga5k.js → cli-v0yrfmdh.js} +148 -183
  51. package/dist/{cli-6aen3ezb.js → cli-v8fjakwf.js} +1 -1
  52. package/dist/{cli-99jez72r.js → cli-w9etnnxk.js} +6 -1
  53. package/dist/{cli-vcgscaan.js → cli-wt0jf9hm.js} +217 -80
  54. package/dist/{cli-s9rr4gr2.js → cli-xpa64rek.js} +1065 -305
  55. package/dist/{cli-h0mgxann.js → cli-z7d29rw9.js} +2 -2
  56. package/dist/cli.js +45 -20
  57. package/dist/{codex-adapter-2zrmab9b.js → codex-adapter-tn6k5mkg.js} +1 -1
  58. package/dist/{codex-session-runner-kbkz62vc.js → codex-session-runner-7vjt7jbw.js} +1 -1
  59. package/dist/{commands-hd574th6.js → commands-t7rz9146.js} +2 -2
  60. package/dist/{controller-pz5mcfg4.js → controller-m2jasma5.js} +2 -2
  61. package/dist/{controllers-connect-5rgq2hfk.js → controllers-connect-9ftpex99.js} +19 -19
  62. package/dist/{cursor-adapter-mgaprq0j.js → cursor-adapter-mjv79e4z.js} +1 -1
  63. package/dist/{db-6nf1207q.js → db-6sfrn9g3.js} +1 -1
  64. package/dist/{db-h06dxp05.js → db-rn673r06.js} +3 -3
  65. package/dist/{dispatcher-zthpy4m1.js → dispatcher-c155xz0p.js} +17 -17
  66. package/dist/{dsh-adapter-64dhw0j7.js → dsh-adapter-3mmszc1x.js} +1 -1
  67. package/dist/{external-channel-x01t7ke6.js → external-channel-35k6tkv8.js} +2 -2
  68. package/dist/{grok-adapter-h21jv594.js → grok-adapter-tfrq7tg1.js} +2 -3
  69. package/dist/handlers-7gw64dxw.js +39 -0
  70. package/dist/{heartbeat-zzfksckz.js → heartbeat-dyg4npkp.js} +20 -20
  71. package/dist/{hook-z4zhzj1f.js → hook-drspbwpe.js} +1 -1
  72. package/dist/{hook-3xfqhh2s.js → hook-x26b31bg.js} +1 -1
  73. package/dist/{http-q813tmya.js → http-pk972xyw.js} +571 -294
  74. package/dist/{index-va3y63es.js → index-6srwvd5b.js} +1 -2
  75. package/dist/{index-x8xbyn1t.js → index-arytgj5q.js} +1 -1
  76. package/dist/{index-wbbs14c5.js → index-bgdyvcnv.js} +17 -17
  77. package/dist/{index-s98037v0.js → index-cbxkmfc5.js} +1 -2
  78. package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
  79. package/dist/{index-m0a4ydqj.js → index-gabbcdn2.js} +17 -17
  80. package/dist/{index-220tk9bw.js → index-jwxkkex2.js} +139 -76
  81. package/dist/index-tfb26zhy.js +41 -0
  82. package/dist/{keepalive-y5t1my7m.js → keepalive-7yrng7px.js} +3 -3
  83. package/dist/{lead-2xwtq66b.js → lead-7e02z839.js} +29 -30
  84. package/dist/{maintenance-ybfsaedv.js → maintenance-e6cemdqt.js} +1 -1
  85. package/dist/{node-3v3f08f7.js → node-de6akczr.js} +10 -9
  86. package/dist/{oauth-refresh-sweep-909jmn12.js → oauth-refresh-sweep-yw3xdbcj.js} +2 -2
  87. package/dist/{onboard-xn6fkkys.js → onboard-0a9y5yrc.js} +2 -2
  88. package/dist/{otel-impl-e6mn0z13.js → otel-impl-trjpkfb7.js} +1 -1
  89. package/dist/{pricing-refresh-ygd362ep.js → pricing-refresh-d2b5my0y.js} +3 -3
  90. package/dist/{rbac-roles-mb470p4e.js → rbac-roles-hey3yv9d.js} +2 -2
  91. package/dist/{rbac-roles-c9axvfnz.js → rbac-roles-nxh5ahwk.js} +2 -2
  92. package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
  93. package/dist/{render-v2-g0306nh1.js → render-v2-rr3p2atg.js} +3 -3
  94. package/dist/{resume-v315hnq6.js → resume-2efkf1s1.js} +5 -5
  95. package/dist/{session-access-2zg285a1.js → session-access-s2mttxpw.js} +6 -4
  96. package/dist/{setup-a4mtf02k.js → setup-vypma5yt.js} +1 -1
  97. package/dist/{work-objects-9fr0fgtn.js → work-objects-rvhckdkp.js} +20 -20
  98. package/dist/{worker-qae22jeb.js → worker-5snytpbd.js} +29 -30
  99. package/dist/worker-token-xf11yyds.js +136 -0
  100. package/openapi.json +303 -13
  101. package/package.json +8 -8
  102. package/src/apps/action-runner.ts +4 -0
  103. package/src/apps/catalog.generated.json +209 -4
  104. package/src/apps/page-validator.ts +21 -3
  105. package/src/be/chunking.ts +42 -9
  106. package/src/be/db/tasks/draft-text.ts +15 -0
  107. package/src/be/db/tasks/read.ts +14 -1
  108. package/src/be/db.ts +5 -1
  109. package/src/be/memory/index-content.ts +36 -17
  110. package/src/be/memory/providers/openai-embedding.ts +115 -61
  111. package/src/be/memory/providers/sqlite-store.ts +69 -23
  112. package/src/be/memory/reranker.ts +31 -1
  113. package/src/be/memory/search-candidates.ts +47 -0
  114. package/src/be/memory/types.ts +14 -0
  115. package/src/be/migrations/232_agent_session_tokens.sql +41 -0
  116. package/src/be/scripts/typecheck.ts +2 -0
  117. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +116 -55
  118. package/src/be/seed-scripts/index.ts +2 -2
  119. package/src/be/swarm-config-guard.ts +40 -1
  120. package/src/be/task-attachment-store.ts +30 -0
  121. package/src/be/users.ts +169 -12
  122. package/src/cli.tsx +24 -0
  123. package/src/commands/worker-token.ts +164 -0
  124. package/src/extensions/catalog.generated.json +4 -4
  125. package/src/extensions/contract-types.generated.ts +1 -1
  126. package/src/extensions/contract.ts +7 -0
  127. package/src/fs/provider.ts +13 -1
  128. package/src/heartbeat/heartbeat.ts +31 -11
  129. package/src/http/apps.ts +43 -5
  130. package/src/http/codex-oauth-keep-warm.ts +27 -9
  131. package/src/http/config.ts +5 -3
  132. package/src/http/core.ts +2 -0
  133. package/src/http/fs.ts +54 -34
  134. package/src/http/mcp-body.ts +37 -0
  135. package/src/http/mcp-user.ts +3 -5
  136. package/src/http/mcp.ts +6 -7
  137. package/src/http/memory.ts +26 -15
  138. package/src/http/page-proxy.ts +61 -5
  139. package/src/http/pages-public.ts +27 -44
  140. package/src/http/pages.ts +4 -4
  141. package/src/http/script-runs.ts +2 -0
  142. package/src/http/session-access.ts +51 -2
  143. package/src/http/sessions.ts +249 -10
  144. package/src/http/tasks.ts +4 -32
  145. package/src/http/users.ts +12 -1
  146. package/src/integrations/chat-sdk/adapters.ts +7 -3
  147. package/src/integrations/chat-sdk/attachments.ts +90 -12
  148. package/src/integrations/chat-sdk/facts.ts +120 -0
  149. package/src/integrations/chat-sdk/inbound.ts +156 -8
  150. package/src/integrations/chat-sdk/limits.ts +24 -0
  151. package/src/integrations/chat-sdk/outbound-files.ts +186 -0
  152. package/src/integrations/chat-sdk/outbound.ts +234 -4
  153. package/src/integrations/chat-sdk/relay.ts +4 -1
  154. package/src/integrations/chat-sdk/router.ts +28 -2
  155. package/src/integrations/chat-sdk/steering.ts +9 -0
  156. package/src/integrations/chat-sdk/templates.ts +14 -0
  157. package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
  158. package/src/mcp-limits.ts +20 -0
  159. package/src/pages/browser-sdk.ts +8 -3
  160. package/src/pages/feedback-overlay.ts +1 -1
  161. package/src/providers/acp-adapter.ts +33 -13
  162. package/src/providers/acp-swarm-events.ts +1 -1
  163. package/src/providers/amp-adapter.ts +1 -1
  164. package/src/providers/codex-adapter.ts +106 -119
  165. package/src/providers/codex-agents-md.ts +35 -229
  166. package/src/providers/codex-oauth/storage.ts +1 -1
  167. package/src/providers/cursor-adapter.ts +1 -1
  168. package/src/rbac/legacy-policy.ts +3 -1
  169. package/src/rbac/permissions.ts +4 -0
  170. package/src/realtime/transport.ts +1 -2
  171. package/src/script-workflows/workflow-ctx.ts +1 -0
  172. package/src/scripts-runtime/types/stdlib.d.ts +2 -0
  173. package/src/scripts-runtime/types/swarm-extension.d.ts +8 -0
  174. package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
  175. package/src/server-user.ts +2 -0
  176. package/src/server.ts +2 -0
  177. package/src/slack/assistant.ts +2 -2
  178. package/src/slack/files.ts +2 -0
  179. package/src/slack/handlers.ts +4 -4
  180. package/src/slack/inbound-files.ts +39 -6
  181. package/src/slack/thread-buffer.ts +2 -1
  182. package/src/tasks/task-terminal-effects.ts +21 -17
  183. package/src/tests/acp-adapter.test.ts +10 -23
  184. package/src/tests/acp-fake-agent.ts +36 -0
  185. package/src/tests/acp-swarm-events.test.ts +1 -1
  186. package/src/tests/agent-session-tokens.test.ts +452 -0
  187. package/src/tests/apps-action-parent-task.test.ts +295 -0
  188. package/src/tests/apps-card-grid.test.ts +197 -0
  189. package/src/tests/apps-catalog-additions.test.ts +120 -0
  190. package/src/tests/apps-tree.test.ts +129 -0
  191. package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
  192. package/src/tests/chat-sdk-config.test.ts +11 -0
  193. package/src/tests/chat-sdk-facts.test.ts +23 -0
  194. package/src/tests/chat-sdk-inbound.test.ts +558 -4
  195. package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
  196. package/src/tests/codex-adapter.test.ts +167 -242
  197. package/src/tests/codex-oauth-keep-warm.test.ts +85 -1
  198. package/src/tests/config-api-only-keys.test.ts +9 -0
  199. package/src/tests/config-session-auth.test.ts +14 -0
  200. package/src/tests/cors-allowlist.test.ts +1 -1
  201. package/src/tests/docs-index-footprint.test.js +2 -2
  202. package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
  203. package/src/tests/grok-adapter.test.ts +3 -3
  204. package/src/tests/heartbeat-checklist.test.ts +33 -0
  205. package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
  206. package/src/tests/heartbeat-jev-gate.test.ts +265 -1
  207. package/src/tests/kv-page-proxy.test.ts +91 -5
  208. package/src/tests/launch-password-rejection.test.ts +2 -2
  209. package/src/tests/mcp-input-caps.test.ts +294 -0
  210. package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
  211. package/src/tests/memory-embedding-input-cap.test.ts +114 -0
  212. package/src/tests/memory.test.ts +14 -0
  213. package/src/tests/migration-232-agent-session-tokens.test.ts +67 -0
  214. package/src/tests/page-guest-session.test.ts +19 -8
  215. package/src/tests/page-proxy-authed.test.ts +1 -1
  216. package/src/tests/page-proxy.test.ts +17 -10
  217. package/src/tests/page-session.test.ts +102 -1
  218. package/src/tests/pages-authed-mode.test.ts +82 -12
  219. package/src/tests/pages-password-mode.test.ts +5 -5
  220. package/src/tests/rbac-engine.test.ts +1 -0
  221. package/src/tests/realtime-transport.test.ts +13 -7
  222. package/src/tests/runtime-child-token-cleanup.test.ts +3 -3
  223. package/src/tests/script-connections.test.ts +2 -0
  224. package/src/tests/script-runs-http.test.ts +84 -0
  225. package/src/tests/scripts-http.test.ts +10 -1
  226. package/src/tests/session-route-posture.test.ts +2 -0
  227. package/src/tests/swarm-config-export-to-env.test.ts +22 -3
  228. package/src/tests/task-audio-transcription.test.ts +299 -0
  229. package/src/tests/task-completion-memory-chunking.test.ts +434 -0
  230. package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
  231. package/src/tests/transcription.test.ts +299 -0
  232. package/src/tests/worker-token-cli.test.ts +76 -0
  233. package/src/tests/workflow-executors.test.ts +4 -2
  234. package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
  235. package/src/tests/workflow-system-one-decision.test.ts +2 -1
  236. package/src/tests/workflow-system-one-extraction.test.ts +805 -0
  237. package/src/tools/chat-access.ts +10 -0
  238. package/src/tools/chat-post.ts +31 -11
  239. package/src/tools/chat-reply.ts +32 -10
  240. package/src/tools/memory-search.ts +3 -1
  241. package/src/tools/system-one-decide.ts +17 -4
  242. package/src/types.ts +8 -1
  243. package/src/utils/config-env-export.ts +3 -1
  244. package/src/utils/page-session.ts +133 -20
  245. package/src/utils/request-auth-context.ts +2 -1
  246. package/src/utils/transcription.ts +246 -0
  247. package/src/workflows/definition.ts +7 -1
  248. package/src/workflows/engine.ts +18 -12
  249. package/src/workflows/executors/index.ts +1 -0
  250. package/src/workflows/executors/registry.ts +2 -0
  251. package/src/workflows/executors/system-one-decision.ts +84 -262
  252. package/src/workflows/executors/system-one-extract-wire.ts +57 -0
  253. package/src/workflows/executors/system-one-extraction.ts +830 -0
  254. package/src/workflows/executors/system-one-providers.ts +316 -114
  255. package/src/workflows/executors/system-one-transport.ts +264 -0
  256. package/templates/extensions/slack-reply-gate/README.md +24 -22
  257. package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
  258. package/templates/extensions/slack-reply-gate/manifest.json +2 -2
  259. package/templates/official/lead/HEARTBEAT.md +1 -1
  260. package/templates/skills/apps/SKILL.md +31 -7
  261. package/templates/skills/apps/content.md +31 -7
  262. package/templates/skills/chat-sdk/content.md +6 -2
  263. package/templates/skills/code-quality/content.md +2 -1
  264. package/templates/skills/heartbeat-runbook/content.md +8 -8
  265. package/templates/skills/pages/content.md +6 -4
  266. package/templates/skills/swarm-scripts/SKILL.md +2 -2
  267. package/templates/skills/swarm-scripts/content.md +2 -2
  268. package/dist/cli-v1sjhn52.js +0 -7
  269. package/dist/handlers-2qjw434w.js +0 -39
  270. package/dist/index-d0g18bax.js +0 -41
@@ -67,6 +67,7 @@ declare module "swarm-extension" {
67
67
  sourceTaskId?: string | undefined;
68
68
  outputSchema?: Record<string, unknown> | undefined;
69
69
  inheritParentOutputSchema?: boolean | undefined;
70
+ inheritParentRequester?: boolean | undefined;
70
71
  inheritParentRoutingAffinity?: boolean | undefined;
71
72
  inheritParentFollowUpConfig?: boolean | undefined;
72
73
  followUpConfig?:
@@ -485,8 +486,15 @@ declare module "swarm-extension" {
485
486
  userName?: string;
486
487
  /** The buffered messages, joined. */
487
488
  text: string;
489
+ /** True for an @mention and for a reply to one of the bot's messages. */
488
490
  botMentioned: boolean;
489
491
  isDirectMessage: boolean;
492
+ /** Platform chat type, such as Telegram `private`, `group`, `supergroup` or `channel`. */
493
+ chatType?: string;
494
+ /** Set for a Telegram supergroup with topics. */
495
+ isForum?: boolean;
496
+ /** A buffered message replies to one of the bot's own messages. */
497
+ replyToBot: boolean;
490
498
  /** Set when earlier swarm tasks exist in this thread. */
491
499
  threadContext?: {
492
500
  adapter: string;
@@ -35,6 +35,8 @@ declare module "swarm-sdk" {
35
35
  routingNote?: string;
36
36
  tags?: string[];
37
37
  priority?: number;
38
+ /** Task IDs that must complete before this task starts. */
39
+ dependsOn?: string[];
38
40
  offerMode?: boolean;
39
41
  dir?: string;
40
42
  vcsRepo?: string;
@@ -4,6 +4,7 @@ import * as z from "zod";
4
4
  import pkg from "../package.json";
5
5
  import { enqueueAdmissionRow } from "./be/rbac-audit";
6
6
  import { getUserGrant } from "./be/rbac-roles";
7
+ import { MCP_MAX_TOOL_INPUT_ELEMENTS } from "./mcp-limits";
7
8
  import {
8
9
  type AdmissionRbac,
9
10
  decideToolAdmission,
@@ -107,6 +108,7 @@ export function createUserServer(user: User): McpServer {
107
108
  capabilities: {
108
109
  logging: {},
109
110
  },
111
+ maxToolInputElements: MCP_MAX_TOOL_INPUT_ELEMENTS,
110
112
  },
111
113
  );
112
114
 
package/src/server.ts CHANGED
@@ -7,6 +7,7 @@ import { isSteeringEnabled } from "./be/steering";
7
7
  import { registerGithubTaskReactions } from "./github/task-reactions";
8
8
  import { loadGlobalConfigsIntoEnv } from "./http/core";
9
9
  import { isChatSdkEnabled } from "./integrations/chat-sdk/config";
10
+ import { MCP_MAX_TOOL_INPUT_ELEMENTS } from "./mcp-limits";
10
11
  import { resolveTemplate } from "./prompts/resolver";
11
12
  import { isRbacEnabled } from "./rbac";
12
13
  import { getSlackConfiguration } from "./slack/config";
@@ -450,6 +451,7 @@ export async function createServer(
450
451
  capabilities: {
451
452
  logging: {},
452
453
  },
454
+ maxToolInputElements: MCP_MAX_TOOL_INPUT_ELEMENTS,
453
455
  },
454
456
  );
455
457
 
@@ -204,13 +204,13 @@ export function createAssistant(): Assistant {
204
204
  // Fetch shared files before the task text is final: a file that can't be
205
205
  // downloaded is flagged in it.
206
206
  if (files?.length) await safeSetStatus("Downloading attachments...");
207
- await using inbound = await fetchSlackFiles(client, files);
207
+ await using inbound = await fetchSlackFiles(client, files, { transcribe: true });
208
208
  // Any in-body `<@U…>` mention the requester typed is rewritten via
209
209
  // the identity primitive before it reaches agent-visible task text —
210
210
  // never a raw Slack ID. Bot-mention routing checks above use the
211
211
  // raw `messageText`, not this rendered copy.
212
212
  const renderedMessageText = await rewriteSlackMentions(
213
- buildEffectiveText(messageText, inbound.files, inbound.failed),
213
+ buildEffectiveText(messageText, inbound.files, inbound.failed, inbound.notes),
214
214
  cachedBotUserId ?? undefined,
215
215
  );
216
216
 
@@ -22,6 +22,8 @@ export interface SlackFile {
22
22
  title?: string;
23
23
  mimetype: string;
24
24
  filetype: string;
25
+ /** `slack_audio` on a voice clip recorded in Slack. */
26
+ subtype?: string;
25
27
  size: number;
26
28
  url_private: string;
27
29
  url_private_download: string;
@@ -702,8 +702,8 @@ export function registerMessageHandler(app: App): void {
702
702
  return ignoreSlackInbound("rate_limited");
703
703
  }
704
704
 
705
- await using inbound = await fetchSlackFiles(client, msg.files);
706
- const taskText = buildEffectiveText(msg.text, inbound.files, inbound.failed);
705
+ await using inbound = await fetchSlackFiles(client, msg.files, { transcribe: true });
706
+ const taskText = buildEffectiveText(msg.text, inbound.files, inbound.failed, inbound.notes);
707
707
  const taskDescription = isImplicitMention
708
708
  ? taskText
709
709
  : extractTaskFromMessage(taskText, botUserId);
@@ -778,9 +778,9 @@ export function registerMessageHandler(app: App): void {
778
778
  }
779
779
 
780
780
  // Extract task description (the text plus a `[File: …]` line per attachment)
781
- await using inbound = await fetchSlackFiles(client, msg.files);
781
+ await using inbound = await fetchSlackFiles(client, msg.files, { transcribe: true });
782
782
  const taskDescription = extractTaskFromMessage(
783
- buildEffectiveText(msg.text, inbound.files, inbound.failed),
783
+ buildEffectiveText(msg.text, inbound.files, inbound.failed, inbound.notes),
784
784
  botUserId,
785
785
  );
786
786
  if (!taskDescription) {
@@ -13,6 +13,8 @@
13
13
  * could not be attached — never dropped silently. A download failure is
14
14
  * also flagged on that line; a storage failure happens after the text is
15
15
  * written, so it shows up only in the reply and in the missing attachment.
16
+ * Audio files (voice clips) a user shares get their transcript on the next
17
+ * line when transcription is configured, and keep the audio attached.
16
18
  */
17
19
  import { mkdtemp, rm } from "node:fs/promises";
18
20
  import { tmpdir } from "node:os";
@@ -27,6 +29,7 @@ import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
27
29
  import type { AgentTask, CreateTaskOptions, TaskAttachment } from "../types";
28
30
  import { scrubSecrets } from "../utils/secret-scrubber";
29
31
  import { taskAttachmentFetchCommand } from "../utils/task-attachment-links";
32
+ import { transcribeAudioNotes } from "../utils/transcription";
30
33
  import { getFileInfo, type SlackFile } from "./files";
31
34
  import { noteSlackInboundSideEffect } from "./inbound-dispatch";
32
35
  // Side-effect import: registers all Slack event templates in the in-memory registry
@@ -73,6 +76,8 @@ export type InboundSlackFiles = {
73
76
  files: SlackFile[];
74
77
  fetched: FetchedSlackFile[];
75
78
  failed: SlackFileFailure[];
79
+ /** Task-text note per audio file id (a transcript), filled when fetched with `transcribe`. */
80
+ notes?: Map<string, string>;
76
81
  /** Deletes the downloaded copies; hold the batch with `await using`. */
77
82
  [Symbol.asyncDispose](): Promise<void>;
78
83
  };
@@ -96,9 +101,14 @@ export function formatFileSize(bytes: number): string {
96
101
  * Build a text representation of file attachments for inclusion in messages.
97
102
  * Each file is formatted as: [File: filename.ext (mimetype, size) id=FILE_ID],
98
103
  * followed by `(not attached: <reason>)` when the file never made it into the
99
- * task's attachments.
104
+ * task's attachments, and by the file's note (a voice-clip transcript) on the
105
+ * next line.
100
106
  */
101
- export function buildAttachmentText(files: SlackFile[], failed: SlackFileFailure[] = []): string {
107
+ export function buildAttachmentText(
108
+ files: SlackFile[],
109
+ failed: SlackFileFailure[] = [],
110
+ notes: Map<string, string> = new Map(),
111
+ ): string {
102
112
  const reasons = new Map(failed.map((f) => [f.file.id, f.reason]));
103
113
  return files
104
114
  .map((f) => {
@@ -106,7 +116,8 @@ export function buildAttachmentText(files: SlackFile[], failed: SlackFileFailure
106
116
  const size = typeof f.size === "number" ? formatFileSize(f.size) : "unknown size";
107
117
  const line = `[File: ${f.name ?? f.id} (${f.mimetype ?? "unknown type"}, ${size}) id=${f.id}]`;
108
118
  const reason = reasons.get(f.id);
109
- return reason ? `${line} (not attached: ${reason})` : line;
119
+ const note = notes.get(f.id);
120
+ return [reason ? `${line} (not attached: ${reason})` : line, note].filter(Boolean).join("\n");
110
121
  })
111
122
  .join("\n");
112
123
  }
@@ -121,15 +132,16 @@ export function buildEffectiveText(
121
132
  text: string | undefined,
122
133
  files?: SlackFile[],
123
134
  failed?: SlackFileFailure[],
135
+ notes?: Map<string, string>,
124
136
  ): string {
125
137
  const hasText = !!text?.trim();
126
138
  const hasFiles = files && files.length > 0;
127
139
 
128
140
  if (hasText && hasFiles) {
129
- return `${text}\n\n${buildAttachmentText(files, failed)}`;
141
+ return `${text}\n\n${buildAttachmentText(files, failed, notes)}`;
130
142
  }
131
143
  if (hasFiles) {
132
- return buildAttachmentText(files, failed);
144
+ return buildAttachmentText(files, failed, notes);
133
145
  }
134
146
  return text || "";
135
147
  }
@@ -147,11 +159,14 @@ export function bufferedFileFailures(files: SlackFile[] | undefined): SlackFileF
147
159
  * Download every file on a Slack message with the bot token, each to its own
148
160
  * temp file. Never throws: a file that can't be fetched lands in `failed` with
149
161
  * a reason the user and the agent can read. Hold the result with `await using`
150
- * so the temp files go away with it.
162
+ * so the temp files go away with it. With `transcribe`, every downloaded audio
163
+ * file also gets a `notes` entry: its transcript, the failure, or a line saying
164
+ * no provider is configured.
151
165
  */
152
166
  export async function fetchSlackFiles(
153
167
  client: WebClient,
154
168
  files: SlackFile[] | undefined,
169
+ options: { transcribe?: boolean } = {},
155
170
  ): Promise<InboundSlackFiles> {
156
171
  let dir: string | undefined;
157
172
  const result: InboundSlackFiles = {
@@ -182,9 +197,27 @@ export async function fetchSlackFiles(
182
197
  result.fetched.push({ file, ...outcome });
183
198
  }
184
199
  }
200
+ if (options.transcribe) {
201
+ result.notes = await transcribeAudioNotes(
202
+ result.fetched
203
+ .filter(({ file }) => isSlackAudio(file))
204
+ .map(({ file, path }) => ({
205
+ key: file.id,
206
+ name: file.name ?? file.id,
207
+ data: Bun.file(path),
208
+ mimeType: file.mimetype,
209
+ })),
210
+ "[Slack]",
211
+ );
212
+ }
185
213
  return result;
186
214
  }
187
215
 
216
+ /** An audio file, or a voice clip recorded in Slack (its MIME type can read `video/…`). */
217
+ function isSlackAudio(file: SlackFile): boolean {
218
+ return file.subtype === "slack_audio" || Boolean(file.mimetype?.startsWith("audio/"));
219
+ }
220
+
188
221
  /** Does the start of an HTML response read like Slack's sign-in page? */
189
222
  function looksLikeSignIn(head: Uint8Array[]): boolean {
190
223
  const joined = new Uint8Array(head.reduce((total, part) => total + part.byteLength, 0));
@@ -177,7 +177,7 @@ async function slackFlush(
177
177
  const app = getSlackApp();
178
178
  const files = items.flatMap((item) => item.files ?? []);
179
179
  await using inbound = app
180
- ? await fetchSlackFiles(app.client, files)
180
+ ? await fetchSlackFiles(app.client, files, { transcribe: true })
181
181
  : {
182
182
  files,
183
183
  fetched: [],
@@ -196,6 +196,7 @@ async function slackFlush(
196
196
  item.text,
197
197
  item.files?.map((file) => resolvedFiles.get(file.id) ?? file),
198
198
  inbound.failed,
199
+ inbound.notes,
199
200
  ),
200
201
  )
201
202
  .join("\n---\n"),
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  import { getSessionLogsByTaskId } from "@/be/db";
19
- import { getEmbeddingProvider, getMemoryStore } from "@/be/memory";
19
+ import { indexMemoryContent, taskCompletionMemoryKey } from "@/be/memory/index-content";
20
20
  import { getRetrievalsForTask } from "@/be/memory/raters/retrieval";
21
21
  import { runServerRaters } from "@/be/memory/raters/run-server-raters";
22
22
  import { shouldPersistTaskCompletionMemory } from "@/memory/automatic-task-gate";
@@ -55,21 +55,22 @@ export async function runTaskTerminalEffects(args: {
55
55
  // Skip indexing if there's truly no content
56
56
  if (taskContent.length < 30) return;
57
57
 
58
- const store = getMemoryStore();
59
- const provider = getEmbeddingProvider();
60
-
61
- const memory = await store.store({
58
+ // Same path as memory-store: chunk, store, and embed each chunk, so a
59
+ // long output never meets the embedding model's input cap. The per-task
60
+ // key makes the chunks one document, like a keyed manual memory, and a
61
+ // repeat write for the task replaces that document atomically.
62
+ const key = taskCompletionMemoryKey(taskId);
63
+ await indexMemoryContent({
62
64
  agentId: agentId ?? null,
63
65
  content: taskContent,
64
66
  name: `Task: ${safeTask.slice(0, 80)}`,
65
67
  scope: "agent",
66
68
  source: "task_completion",
67
69
  sourceTaskId: taskId,
70
+ key,
71
+ intent: "task completion memory",
72
+ replaceExisting: true,
68
73
  });
69
- const embedding = await provider.embed(taskContent);
70
- if (embedding) {
71
- await store.updateEmbedding(memory.id, embedding, provider.name);
72
- }
73
74
 
74
75
  // Auto-promote high-value completions to swarm memory (P3)
75
76
  const shouldShareWithSwarm =
@@ -80,24 +81,27 @@ export async function runTaskTerminalEffects(args: {
80
81
 
81
82
  if (shouldShareWithSwarm) {
82
83
  try {
83
- const swarmMemory = await store.store({
84
+ await indexMemoryContent({
84
85
  agentId: agentId ?? null,
85
- scope: "swarm",
86
- name: `Shared: ${safeTask.slice(0, 80)}`,
87
86
  content: `Task completed by agent ${agentId}:\n\n${taskContent}`,
87
+ name: `Shared: ${safeTask.slice(0, 80)}`,
88
+ scope: "swarm",
88
89
  source: "task_completion",
89
90
  sourceTaskId: taskId,
91
+ key,
92
+ intent: "task completion memory",
93
+ replaceExisting: true,
90
94
  });
91
- const swarmEmbedding = await provider.embed(taskContent);
92
- if (swarmEmbedding) {
93
- await store.updateEmbedding(swarmMemory.id, swarmEmbedding, provider.name);
94
- }
95
95
  } catch {
96
96
  // Non-blocking — swarm memory promotion failure is not critical
97
97
  }
98
98
  }
99
- } catch {
99
+ } catch (err) {
100
100
  // Non-blocking — task completion memory failure should not affect task status
101
+ console.error(
102
+ "[task-terminal-effects] task completion memory write failed:",
103
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
104
+ );
101
105
  }
102
106
  })().catch((err) =>
103
107
  console.error(
@@ -3,7 +3,7 @@ import { chmodSync, existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
3
3
  import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
- import type { Usage } from "@agentclientprotocol/sdk";
6
+ import type { Usage } from "@agentclientprotocol/sdk/unstable";
7
7
  import { normalizeSessionLogs } from "../../apps/ui/src/logs-parser";
8
8
  import {
9
9
  closeDb,
@@ -29,6 +29,7 @@ import {
29
29
  resolveAcpTarget,
30
30
  } from "../providers/acp-targets";
31
31
  import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
32
+ import { ACP_SDK_PATH, serveFakeAcpAgent } from "./acp-fake-agent";
32
33
  import { listenOnFreePort } from "./test-net";
33
34
 
34
35
  const tmpDirs: string[] = [];
@@ -148,16 +149,11 @@ describe("ACPAdapter", () => {
148
149
  }) => {
149
150
  const cwd = makeTempDir();
150
151
  const agentPath = join(cwd, "fake-acp-agent.ts");
151
- const sdkPath = join(process.cwd(), "node_modules/@agentclientprotocol/sdk/dist/acp.js");
152
152
  await Bun.write(
153
153
  agentPath,
154
154
  `
155
155
  import { Readable, Writable } from "node:stream";
156
- import {
157
- AgentSideConnection,
158
- PROTOCOL_VERSION,
159
- ndJsonStream,
160
- } from "${sdkPath}";
156
+ import { PROTOCOL_VERSION, agent, methods, ndJsonStream } from "${ACP_SDK_PATH}";
161
157
 
162
158
  class FakeAgent {
163
159
  constructor(connection) {
@@ -313,10 +309,7 @@ class FakeAgent {
313
309
  async cancel() {}
314
310
  }
315
311
 
316
- const input = Writable.toWeb(process.stdout);
317
- const output = Readable.toWeb(process.stdin);
318
- const stream = ndJsonStream(input, output);
319
- new AgentSideConnection((connection) => new FakeAgent(connection), stream);
312
+ ${serveFakeAcpAgent("FakeAgent")}
320
313
  `,
321
314
  );
322
315
 
@@ -952,7 +945,6 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
952
945
  const binDir = makeTempDir();
953
946
  const captureFile = join(binDir, "capture.json");
954
947
  const agentPath = join(binDir, "fake-copilot-acp.ts");
955
- const sdkPath = join(process.cwd(), "node_modules/@agentclientprotocol/sdk/dist/acp.js");
956
948
  const repoAgentsMd = "# Repo rules\nKeep this file as is.\n";
957
949
  await Bun.write(join(cwd, "AGENTS.md"), repoAgentsMd);
958
950
  await Bun.write(
@@ -961,7 +953,7 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
961
953
  import { readFileSync } from "node:fs";
962
954
  import { join } from "node:path";
963
955
  import { Readable, Writable } from "node:stream";
964
- import { AgentSideConnection, PROTOCOL_VERSION, ndJsonStream } from "${sdkPath}";
956
+ import { PROTOCOL_VERSION, agent, methods, ndJsonStream } from "${ACP_SDK_PATH}";
965
957
  class FakeCopilot {
966
958
  constructor(connection) { this.connection = connection; }
967
959
  async initialize() {
@@ -986,8 +978,7 @@ class FakeCopilot {
986
978
  }
987
979
  async cancel() {}
988
980
  }
989
- const stream = ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
990
- new AgentSideConnection((connection) => new FakeCopilot(connection), stream);
981
+ ${serveFakeAcpAgent("FakeCopilot")}
991
982
  `,
992
983
  );
993
984
  const copilotStub = join(binDir, "copilot");
@@ -1191,7 +1182,6 @@ new AgentSideConnection((connection) => new FakeCopilot(connection), stream);
1191
1182
  test("completes before the revoke response and logs a failed revocation", async () => {
1192
1183
  const cwd = makeTempDir();
1193
1184
  const agentPath = join(cwd, "fake-acp-ephem-agent.ts");
1194
- const sdkPath = join(process.cwd(), "node_modules/@agentclientprotocol/sdk/dist/acp.js");
1195
1185
 
1196
1186
  const captureFile = join(cwd, "captured-auth.txt");
1197
1187
 
@@ -1199,7 +1189,7 @@ new AgentSideConnection((connection) => new FakeCopilot(connection), stream);
1199
1189
  agentPath,
1200
1190
  `
1201
1191
  import { Readable, Writable } from "node:stream";
1202
- import { AgentSideConnection, PROTOCOL_VERSION, ndJsonStream } from "${sdkPath}";
1192
+ import { PROTOCOL_VERSION, agent, methods, ndJsonStream } from "${ACP_SDK_PATH}";
1203
1193
  class FakeAgent {
1204
1194
  constructor(connection) { this.connection = connection; }
1205
1195
  async initialize() {
@@ -1223,8 +1213,7 @@ class FakeAgent {
1223
1213
  }
1224
1214
  async cancel() {}
1225
1215
  }
1226
- const stream = ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
1227
- new AgentSideConnection((connection) => new FakeAgent(connection), stream);
1216
+ ${serveFakeAcpAgent("FakeAgent")}
1228
1217
  `,
1229
1218
  );
1230
1219
 
@@ -1361,12 +1350,11 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
1361
1350
  ])("$name", async ({ costs, totalCostUsd, costSource }) => {
1362
1351
  const cwd = makeTempDir();
1363
1352
  const agentPath = join(cwd, "fake-acp-cost-agent.ts");
1364
- const sdkPath = join(process.cwd(), "node_modules/@agentclientprotocol/sdk/dist/acp.js");
1365
1353
  await Bun.write(
1366
1354
  agentPath,
1367
1355
  `
1368
1356
  import { Readable, Writable } from "node:stream";
1369
- import { AgentSideConnection, PROTOCOL_VERSION, ndJsonStream } from "${sdkPath}";
1357
+ import { PROTOCOL_VERSION, agent, methods, ndJsonStream } from "${ACP_SDK_PATH}";
1370
1358
  class FakeAgent {
1371
1359
  constructor(connection) { this.connection = connection; }
1372
1360
  async initialize() {
@@ -1384,8 +1372,7 @@ class FakeAgent {
1384
1372
  }
1385
1373
  async cancel() {}
1386
1374
  }
1387
- const stream = ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
1388
- new AgentSideConnection((connection) => new FakeAgent(connection), stream);
1375
+ ${serveFakeAcpAgent("FakeAgent")}
1389
1376
  `,
1390
1377
  );
1391
1378
  const { close: closeTokenStub, apiUrl } = await startTokenStubServer(
@@ -0,0 +1,36 @@
1
+ import { join } from "node:path";
2
+
3
+ // Fake ACP targets run from temp dirs, so they import the SDK by path. The
4
+ // unstable v1 entry point lets them return turn `usage` like OpenCode does.
5
+ export const ACP_SDK_PATH = join(
6
+ process.cwd(),
7
+ "node_modules/@agentclientprotocol/sdk/dist/v1/unstable/acp.js",
8
+ );
9
+
10
+ /**
11
+ * Script tail that serves a fake agent class over stdio with the SDK's agent
12
+ * app API. The class implements the ACP agent methods and gets
13
+ * `{ sessionUpdate }` to notify the client. The script must import `agent`,
14
+ * `methods`, and `ndJsonStream` from ACP_SDK_PATH, and `Readable`/`Writable`
15
+ * from node:stream.
16
+ */
17
+ export function serveFakeAcpAgent(className: string): string {
18
+ return `
19
+ const fake = new ${className}({
20
+ sessionUpdate: (params) => connection.client.notify(methods.client.session.update, params),
21
+ });
22
+ const app = agent()
23
+ .onRequest(methods.agent.initialize, ({ params }) => fake.initialize(params))
24
+ .onRequest(methods.agent.session.new, ({ params }) => fake.newSession(params))
25
+ .onRequest(methods.agent.session.prompt, ({ params }) => fake.prompt(params))
26
+ .onNotification(methods.agent.session.cancel, ({ params }) => fake.cancel(params));
27
+ if (fake.setSessionConfigOption) {
28
+ app.onRequest(methods.agent.session.setConfigOption, ({ params }) =>
29
+ fake.setSessionConfigOption(params),
30
+ );
31
+ }
32
+ const connection = app.connect(
33
+ ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin)),
34
+ );
35
+ `;
36
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import type { SessionNotification } from "@agentclientprotocol/sdk";
2
+ import type { SessionNotification } from "@agentclientprotocol/sdk/unstable";
3
3
  import { acpReportedCostUsd, translateAcpSessionNotification } from "../providers/acp-swarm-events";
4
4
 
5
5
  function notification(update: SessionNotification["update"]): SessionNotification {