@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
@@ -1,8 +1,18 @@
1
+ import * as z from "zod";
1
2
  import { getAgentById, getAgentCurrentTask, getTaskById } from "@/be/db";
2
3
  import { can } from "@/rbac";
3
4
  import { chatThreadOfContextKey } from "@/tasks/context-key";
4
5
  import type { RequestInfo } from "@/tools/utils";
5
6
 
7
+ /** The `attachments` input of `chat-reply` and `chat-post`. */
8
+ export const chatAttachmentsSchema = z
9
+ .array(z.string().min(1))
10
+ .max(10)
11
+ .optional()
12
+ .describe(
13
+ "Files to send after the text, up to 10: attachment ids of your current task (from get-task-details) or file paths in your agent-fs drive, such as thoughts/<agent-id>/report.pdf, read with your own agent-fs credential. Telegram shows JPEG and PNG as photos, MP4 as video, MP3 and M4A as audio, and anything else as a document. A file over the platform upload limit (Telegram 50 MB, Discord 10 MB) is not sent, and the thread gets a note naming it.",
14
+ );
15
+
6
16
  /** Chat SDK verbs; the legacy policy grants them to the lead only. */
7
17
  export type ChatVerb = "integration.chat.post" | "integration.chat.read" | "integration.chat.dm";
8
18
 
@@ -2,8 +2,13 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import * as z from "zod";
3
3
  import { CHAT_SDK_ADAPTER_NAMES } from "@/integrations/chat-sdk/config";
4
4
  import { errorMessage } from "@/integrations/chat-sdk/logger";
5
- import { adapterOfChatId, postChatMessage } from "@/integrations/chat-sdk/outbound";
6
- import { canUseChatVerb } from "@/tools/chat-access";
5
+ import {
6
+ adapterOfChatId,
7
+ postChatMessage,
8
+ unsentFilesSummary,
9
+ } from "@/integrations/chat-sdk/outbound";
10
+ import { resolveChatAttachmentRefs } from "@/integrations/chat-sdk/outbound-files";
11
+ import { canUseChatVerb, chatAttachmentsSchema } from "@/tools/chat-access";
7
12
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
8
13
 
9
14
  export const registerChatPostTool = (server: McpServer) => {
@@ -27,6 +32,7 @@ export const registerChatPostTool = (server: McpServer) => {
27
32
  .optional()
28
33
  .describe("Full SDK channel id, such as slack:C123. Ignored when threadId is set."),
29
34
  text: z.string().min(1).max(20_000).describe("Markdown text to post."),
35
+ attachments: chatAttachmentsSchema,
30
36
  }),
31
37
  outputSchema: swarmToolOutputSchema({
32
38
  yourAgentId: z.string().optional(),
@@ -34,9 +40,12 @@ export const registerChatPostTool = (server: McpServer) => {
34
40
  adapter: z.string().optional(),
35
41
  threadId: z.string().optional(),
36
42
  channelId: z.string().optional(),
43
+ unsentFiles: z
44
+ .array(z.looseObject({ name: z.string().optional(), reason: z.string().optional() }))
45
+ .optional(),
37
46
  }),
38
47
  },
39
- async ({ adapter, threadId, channelId, text }, requestInfo) => {
48
+ async ({ adapter, threadId, channelId, text, attachments }, requestInfo) => {
40
49
  const data = { yourAgentId: requestInfo.agentId };
41
50
  const target = threadId ?? channelId;
42
51
  if (!target) return toolErr("Give a threadId or a channelId.", { data });
@@ -49,22 +58,33 @@ export const registerChatPostTool = (server: McpServer) => {
49
58
  { data },
50
59
  );
51
60
  }
61
+ const resolved = await resolveChatAttachmentRefs(
62
+ attachments ?? [],
63
+ requestInfo.sourceTaskId,
64
+ requestInfo.agentId,
65
+ );
66
+ if ("error" in resolved) return toolErr(resolved.error, { data });
52
67
  try {
53
68
  const sent = await postChatMessage({
54
69
  adapter,
55
70
  ...(threadId ? { threadId } : { channelId }),
56
71
  text,
57
72
  taskId: requestInfo.sourceTaskId,
73
+ files: resolved.files,
58
74
  });
59
- return toolOk(`Posted to ${adapter} ${threadId ? "thread" : "channel"} ${target}.`, {
60
- data: {
61
- ...data,
62
- messageId: sent.messageIds.at(-1),
63
- adapter: sent.adapter,
64
- threadId: sent.threadId,
65
- channelId: sent.channelId,
75
+ return toolOk(
76
+ `Posted to ${adapter} ${threadId ? "thread" : "channel"} ${target}.${unsentFilesSummary(sent.unsentFiles)}`,
77
+ {
78
+ data: {
79
+ ...data,
80
+ messageId: sent.messageIds.at(-1),
81
+ adapter: sent.adapter,
82
+ threadId: sent.threadId,
83
+ channelId: sent.channelId,
84
+ unsentFiles: sent.unsentFiles,
85
+ },
66
86
  },
67
- });
87
+ );
68
88
  } catch (error) {
69
89
  const message = errorMessage(error);
70
90
  return toolErr(`Could not post: ${message}`, { data });
@@ -2,8 +2,14 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import * as z from "zod";
3
3
  import { getAgentCurrentTask, getTaskById } from "@/be/db";
4
4
  import { errorMessage } from "@/integrations/chat-sdk/logger";
5
- import { ChatAdapterUnavailableError, postChatMessage } from "@/integrations/chat-sdk/outbound";
5
+ import {
6
+ ChatAdapterUnavailableError,
7
+ postChatMessage,
8
+ unsentFilesSummary,
9
+ } from "@/integrations/chat-sdk/outbound";
10
+ import { resolveChatAttachmentRefs } from "@/integrations/chat-sdk/outbound-files";
6
11
  import { chatThreadOfContextKey } from "@/tasks/context-key";
12
+ import { chatAttachmentsSchema } from "@/tools/chat-access";
7
13
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
8
14
 
9
15
  export const registerChatReplyTool = (server: McpServer) => {
@@ -12,19 +18,23 @@ export const registerChatReplyTool = (server: McpServer) => {
12
18
  {
13
19
  title: "Reply in the chat thread of this task",
14
20
  description:
15
- "Post a message to the Telegram, Slack, Discord or Teams thread your current task came from. Works only on a task whose contextKey starts with task:chat-sdk:. The task's final output is posted to the thread automatically when the task ends, so use this only for a question to the user or a short progress note on long work. Markdown is supported; long text is split into several messages.",
21
+ "Post a message to the Telegram, Slack, Discord or Teams thread your current task came from. Works only on a task whose contextKey starts with task:chat-sdk:. The task's final output is posted to the thread automatically when the task ends, so use this only for a question to the user, a short progress note on long work, or to send files (attachments). Markdown is supported; long text is split into several messages. When the final output of a task is posted, the files attached to that task go with it.",
16
22
  annotations: { openWorldHint: true },
17
23
  inputSchema: z.object({
18
24
  text: z.string().min(1).max(20_000).describe("Markdown text to post to the thread."),
25
+ attachments: chatAttachmentsSchema,
19
26
  }),
20
27
  outputSchema: swarmToolOutputSchema({
21
28
  yourAgentId: z.string().optional(),
22
29
  messageId: z.string().optional(),
23
30
  adapter: z.string().optional(),
24
31
  threadId: z.string().optional(),
32
+ unsentFiles: z
33
+ .array(z.looseObject({ name: z.string().optional(), reason: z.string().optional() }))
34
+ .optional(),
25
35
  }),
26
36
  },
27
- async ({ text }, requestInfo) => {
37
+ async ({ text, attachments }, requestInfo) => {
28
38
  if (!requestInfo.agentId) return toolErr("Agent ID not found.");
29
39
  const data = { yourAgentId: requestInfo.agentId };
30
40
 
@@ -46,21 +56,33 @@ export const registerChatReplyTool = (server: McpServer) => {
46
56
  );
47
57
  }
48
58
 
59
+ const resolved = await resolveChatAttachmentRefs(
60
+ attachments ?? [],
61
+ task.id,
62
+ requestInfo.agentId,
63
+ );
64
+ if ("error" in resolved) return toolErr(resolved.error, { data });
65
+
49
66
  try {
50
67
  const sent = await postChatMessage({
51
68
  adapter: thread.adapter,
52
69
  threadId: thread.threadId,
53
70
  text,
54
71
  taskId: task.id,
72
+ files: resolved.files,
55
73
  });
56
- return toolOk(`Posted to ${thread.adapter} thread ${sent.threadId}.`, {
57
- data: {
58
- ...data,
59
- messageId: sent.messageIds.at(-1),
60
- adapter: sent.adapter,
61
- threadId: sent.threadId,
74
+ return toolOk(
75
+ `Posted to ${thread.adapter} thread ${sent.threadId}.${unsentFilesSummary(sent.unsentFiles)}`,
76
+ {
77
+ data: {
78
+ ...data,
79
+ messageId: sent.messageIds.at(-1),
80
+ adapter: sent.adapter,
81
+ threadId: sent.threadId,
82
+ unsentFiles: sent.unsentFiles,
83
+ },
62
84
  },
63
- });
85
+ );
64
86
  } catch (error) {
65
87
  const message = errorMessage(error);
66
88
  return toolErr(
@@ -11,6 +11,7 @@ import {
11
11
  recordRetrievals,
12
12
  } from "@/be/memory/raters/retrieval";
13
13
  import { rerank } from "@/be/memory/reranker";
14
+ import { searchMemoryCandidates } from "@/be/memory/search-candidates";
14
15
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
15
16
  import type { AgentMemorySource } from "@/types";
16
17
  import { AgentMemoryScopeSchema, AgentMemorySourceSchema } from "@/types";
@@ -122,7 +123,8 @@ export const registerMemorySearchTool = (server: McpServer) => {
122
123
  const queryEmbedding = await provider.embed(query);
123
124
 
124
125
  const candidateLimit = limit * CANDIDATE_SET_MULTIPLIER;
125
- const candidates = await store.search(
126
+ const candidates = await searchMemoryCandidates(
127
+ store,
126
128
  queryEmbedding ?? new Float32Array(0),
127
129
  requestInfo.agentId,
128
130
  {
@@ -10,7 +10,7 @@ import {
10
10
  import { SYSTEM_ONE_PROVIDER_IDS } from "@/workflows/executors/system-one-providers";
11
11
 
12
12
  /**
13
- * One typed decision (Jev and the other SystemOne hosts) made in the API
13
+ * One typed decision (Jev, Clef and the other SystemOne hosts) made in the API
14
14
  * process, the same way a `system-one-decision` workflow node makes it. The
15
15
  * provider credential is read and sent here and never reaches the caller, so a
16
16
  * script can ask for a decision without a credential binding in its own realm.
@@ -22,14 +22,16 @@ export const registerSystemOneDecideTool = (server: McpServer) => {
22
22
  title: "Ask a SystemOne decision model",
23
23
  annotations: { destructiveHint: false, openWorldHint: true },
24
24
  description:
25
- "Send a state and typed questions (noul, choice, score) to a SystemOne decision host such as Jev, and return the validated answers. " +
25
+ "Send a state and typed questions (noul, choice, score) to a SystemOne decision host such as Jev or Clef, and return the validated answers. " +
26
26
  "Same request, provider list, credentials and validation as the `system-one-decision` workflow node, without its human review band. " +
27
27
  "The provider credential is a global secret that stays on the server; this tool never returns it.",
28
28
  inputSchema: z.object({
29
29
  provider: z
30
30
  .enum(SYSTEM_ONE_PROVIDER_IDS)
31
31
  .optional()
32
- .describe("Decision host (default typesafe)"),
32
+ .describe(
33
+ "Decision host (default: the SYSTEM_ONE_DEFAULT_PROVIDER global config, typesafe when unset)",
34
+ ),
33
35
  model: z.string().min(1).optional().describe("Provider model id (default: the provider's)"),
34
36
  state: z
35
37
  .union([z.string(), z.array(z.string()), z.record(z.string(), z.unknown())])
@@ -55,6 +57,12 @@ export const registerSystemOneDecideTool = (server: McpServer) => {
55
57
  answers: z.record(z.string(), z.unknown()).optional(),
56
58
  usage: z.record(z.string(), z.unknown()).optional(),
57
59
  requestId: z.string().optional(),
60
+ providerUnavailable: z
61
+ .boolean()
62
+ .optional()
63
+ .describe(
64
+ "On an error: true when this deployment refuses the provider (e.g. OpenRouter behind a gateway) and no request was sent, so a caller choosing automatically can try its next provider",
65
+ ),
58
66
  }),
59
67
  },
60
68
  async ({ provider, model, state, questions, timeoutMs, maxRetries }, requestInfo) => {
@@ -88,7 +96,12 @@ export const registerSystemOneDecideTool = (server: McpServer) => {
88
96
  return toolErr("The system-one-decision executor is not available");
89
97
  }
90
98
  const decided = await executor.decide(parsed.data);
91
- if (!decided.ok) return toolErr(decided.error);
99
+ if (!decided.ok) {
100
+ return toolErr(
101
+ decided.error,
102
+ decided.deploymentRefused ? { data: { providerUnavailable: true } } : {},
103
+ );
104
+ }
92
105
 
93
106
  const { output } = decided;
94
107
  return toolOk(`Decision from ${output.model}`, {
package/src/types.ts CHANGED
@@ -905,6 +905,12 @@ export const CreateTaskOptionsSchema = z.object({
905
905
  * contract on completion (which would block the control task — DES-523).
906
906
  */
907
907
  inheritParentOutputSchema: z.boolean().optional(),
908
+ /**
909
+ * When a `parentTaskId` is set and `requestedByUserId` is not, the child
910
+ * inherits the parent's requester by default. Set this to `false` when the
911
+ * child answers a different person (a chat message whose sender is unknown).
912
+ */
913
+ inheritParentRequester: z.boolean().optional(),
908
914
  /**
909
915
  * Skip parent routing requirements only for a child with its own explicit
910
916
  * Lead-only control-plane authorization.
@@ -2287,7 +2293,7 @@ export const WorkflowNodeSchema = z
2287
2293
  type: z
2288
2294
  .string()
2289
2295
  .describe(
2290
- "Executor type: 'agent-task', 'script', 'swarm-script', 'raw-llm', 'system-one-decision', 'validate', 'property-match'",
2296
+ "Executor type: 'agent-task', 'script', 'swarm-script', 'raw-llm', 'system-one-decision', 'system-one-extraction', 'validate', 'property-match'",
2291
2297
  ),
2292
2298
  label: z.string().optional().describe("Human-readable label for UI display"),
2293
2299
  config: z
@@ -2297,6 +2303,7 @@ export const WorkflowNodeSchema = z
2297
2303
  "For script: { runtime, script, args?, timeout? }. " +
2298
2304
  "For swarm-script: { scriptName, scope?, pinHash?, args?, fsMode?, timeoutMs? (1000-300000) }. " +
2299
2305
  "For system-one-decision (typed decisions, Jev by default): { provider? ('typesafe' default | 'openrouter' | 'laya'; a literal, never a {{token}}), state, questions: { <id>: { type: 'noul'|'choice'|'score', instructions, criteria? } }, returns: { <id>: { type } }, model? (provider-specific; unset = the provider's default, and laya has none so it sends no model and picks its own checkpoint), timeoutMs?, maxRetries? (0-3), humanReview? { band: { min, max } (0-1, inclusive), approvers: { users?, roles?, policy }, title?, timeout?, notifications? } }; each provider needs its own global secret (TYPESAFE_API_KEY, OPENROUTER_API_KEY, or LAYA_API_KEY; laya also needs the global config LAYA_URL), and a save warns and a run fails before any node executes when one is missing; system-one-decision nodes must not set retry or validation.retry. With humanReview, an answer whose confidence is inside the band waits for a person (human-in-the-loop approval), and next must map ports { approved, rejected?, timeout? }. " +
2306
+ "For system-one-extraction (typed extraction, Cloudflare Clairic by default): { provider? ('cloudflare'; a literal), state (text to read; may be empty when images are sent), fields: { <id>: { type: 'text'|'number'|'date'|'time'|'datetime'|'duration'|'list'|'estimate'|'forecast', instructions?, ...type options (number: unit?, integer?, decimals?; duration: unit? (seconds..years), integer?, decimals?; list: items ('text'|'number'|'date'); estimate: min, max, step?, scale?, unit?; forecast: min, max, horizon?, interval?, scale?, unit?; text/number/date/time/datetime/duration: null_threshold?) } }, images? (max 4 base64 data: URLs or { content_type, base64 }), referenceDate? (YYYY-MM-DD), model?, timeoutMs?, maxRetries? (0-3) }; output answers.<id> carries value (null when the input does not say), guess, p_null, confidence, span/spans, and for estimate/forecast median/p10/p90. Needs the global secret CLOUDFLARE_API_TOKEN (or CLOUDFLARE_API_KEY) and the global config CLOUDFLARE_ACCOUNT_ID; must not set retry or validation.retry. " +
2300
2307
  "Agent-task templates and ordinary config values support {{interpolation}} from the node's inputs context, including trigger and declared upstream aliases. " +
2301
2308
  "SECURITY: executable source for script/swarm-script nodes does not interpolate trigger.* or upstream node outputs; only input/workflow/swarm/run values are allowed in inline script source, and named swarm-script source is not workflow-interpolated. " +
2302
2309
  'Pass dynamic values through config.args instead (inline script receives them as argv, with runtime: "bash" running bash -c <script> ...args so args[0] is $0 and args[1] is $1; swarm-script receives its args object). ' +
@@ -8,7 +8,8 @@
8
8
  * binding instead.
9
9
  *
10
10
  * Keys below are read only by the API server (integration signing secrets, app
11
- * private keys, OAuth client secrets, the API's own agent-fs key). They can
11
+ * private keys, OAuth client secrets, the API's own agent-fs and
12
+ * speech-to-text keys). They can
12
13
  * never be exported: the config API rejects `exportToEnv: true` for them and
13
14
  * every reader treats a stored true as false.
14
15
  */
@@ -25,6 +26,7 @@ export const ENV_EXPORT_RESTRICTED_KEYS: ReadonlySet<string> = new Set([
25
26
  "AZURE_DEVOPS_WEBHOOK_SECRET",
26
27
  "AGENTMAIL_WEBHOOK_SECRET",
27
28
  "API_AGENT_FS_API_KEY",
29
+ "TRANSCRIPTION_API_KEY",
28
30
  ]);
29
31
 
30
32
  /**
@@ -246,23 +246,83 @@ export function parseCookieHeader(
246
246
  }
247
247
 
248
248
  /**
249
- * One-shot helper: pull `page_session` out of a request's `Cookie` header and
250
- * verify it. Returns the parsed payload on success, `null` on any failure
251
- * (no cookie, malformed, bad signature, expired, etc.).
252
- *
253
- * Shared by `/@swarm/api/*` (`src/http/page-proxy.ts`) and the authed `/p/:id`
254
- * branch (`src/http/pages-public.ts`) so both call sites converge on the same
255
- * verification semantics.
249
+ * The single shared cookie name used before sessions became per-page. Every
250
+ * launch overwrote it, so only the newest page in a browser had a session.
251
+ * Still read for back-compat, but only for the page its payload names.
252
+ */
253
+ export const LEGACY_PAGE_SESSION_COOKIE = "page_session";
254
+
255
+ /** Page ids are 32 hex chars today; anything outside the cookie-name-safe set is refused. */
256
+ const COOKIE_SAFE_PAGE_ID = /^[A-Za-z0-9_-]{1,64}$/;
257
+
258
+ export function isCookieSafePageId(pageId: string): boolean {
259
+ return COOKIE_SAFE_PAGE_ID.test(pageId);
260
+ }
261
+
262
+ /** Per-page cookie name, so sessions for different pages coexist in one browser. */
263
+ export function pageSessionCookieName(pageId: string): string {
264
+ return `${LEGACY_PAGE_SESSION_COOKIE}_${pageId}`;
265
+ }
266
+
267
+ export interface VerifiedPageSession {
268
+ /** The raw signed token, forwarded upstream by the page proxy. */
269
+ token: string;
270
+ payload: PageSessionPayload;
271
+ }
272
+
273
+ type CookieRequest = { headers: { cookie?: string | string[] | undefined } };
274
+
275
+ async function verifyNamedCookie(
276
+ req: CookieRequest,
277
+ name: string,
278
+ ): Promise<VerifiedPageSession | null> {
279
+ const token = parseCookieHeader(req.headers.cookie, name);
280
+ const payload = await verifyPageSession(token);
281
+ return payload && token ? { token, payload } : null;
282
+ }
283
+
284
+ /**
285
+ * Find and verify the session for `pageId`: `page_session_<pageId>` first,
286
+ * then the legacy `page_session`. A cookie whose signed payload names a
287
+ * different page is ignored, so the result always satisfies
288
+ * `payload.pageId === pageId`. Returns `null` on any failure (no cookie,
289
+ * malformed, bad signature, expired, wrong page).
290
+ */
291
+ export async function extractPageSession(
292
+ req: CookieRequest,
293
+ pageId: string,
294
+ ): Promise<VerifiedPageSession | null> {
295
+ if (!isCookieSafePageId(pageId)) return null;
296
+ for (const name of [pageSessionCookieName(pageId), LEGACY_PAGE_SESSION_COOKIE]) {
297
+ const session = await verifyNamedCookie(req, name);
298
+ if (session?.payload.pageId === pageId) return session;
299
+ }
300
+ return null;
301
+ }
302
+
303
+ /**
304
+ * Verify the legacy `page_session` cookie for whatever page it names. Only for
305
+ * callers that send no page id (clients predating per-page cookies).
306
+ */
307
+ export async function extractLegacyPageSession(
308
+ req: CookieRequest,
309
+ ): Promise<VerifiedPageSession | null> {
310
+ return verifyNamedCookie(req, LEGACY_PAGE_SESSION_COOKIE);
311
+ }
312
+
313
+ /**
314
+ * Payload-only form of `extractPageSession`. Shared by the `/p/:id` handler
315
+ * (`src/http/pages-public.ts`) and the realtime upgrade
316
+ * (`src/realtime/transport.ts`) so they converge on the same semantics.
256
317
  *
257
318
  * Accepts an object with a `headers.cookie` field — duck-typed to keep this
258
319
  * helper test-friendly without dragging `node:http` types into the utility.
259
320
  */
260
- export async function extractAndVerifyCookie(req: {
261
- headers: { cookie?: string | string[] | undefined };
262
- }): Promise<PageSessionPayload | null> {
263
- const token = parseCookieHeader(req.headers.cookie, "page_session");
264
- if (!token) return null;
265
- return verifyPageSession(token);
321
+ export async function extractAndVerifyCookie(
322
+ req: CookieRequest,
323
+ pageId: string,
324
+ ): Promise<PageSessionPayload | null> {
325
+ return (await extractPageSession(req, pageId))?.payload ?? null;
266
326
  }
267
327
 
268
328
  // ───────────────────────────────────────────────────────────────────────────
@@ -284,6 +344,10 @@ const PAGE_SESSION_TTL_SECONDS = 3600;
284
344
  * - `dev=true` → `SameSite=Lax` without `Secure` (works on http://localhost).
285
345
  * - `dev=false` → `SameSite=None; Secure` (cross-site iframe embedding in prod).
286
346
  *
347
+ * The cookie is named `page_session_<pageId>`, so launching page B leaves
348
+ * page A's session intact. Responses should go through `pageSessionSetCookies`,
349
+ * which also bounds how many of these a browser keeps.
350
+ *
287
351
  * The caller is responsible for setting `Set-Cookie` on the response — this
288
352
  * helper only builds the string. TTL is 1 hour; renewed on every issuance.
289
353
  */
@@ -299,13 +363,12 @@ export async function issuePageSessionCookie(
299
363
  ...(opts.name ? { name: opts.name } : {}),
300
364
  ...(opts.op ? { op: true as const } : {}),
301
365
  });
302
- const attrs = [
303
- `page_session=${token}`,
304
- "HttpOnly",
305
- "Path=/",
306
- `Max-Age=${PAGE_SESSION_TTL_SECONDS}`,
307
- ];
308
- if (opts.dev) {
366
+ return buildCookie(pageSessionCookieName(pageId), token, PAGE_SESSION_TTL_SECONDS, opts.dev);
367
+ }
368
+
369
+ function buildCookie(name: string, value: string, maxAge: number, dev: boolean): string {
370
+ const attrs = [`${name}=${value}`, "HttpOnly", "Path=/", `Max-Age=${maxAge}`];
371
+ if (dev) {
309
372
  attrs.push("SameSite=Lax");
310
373
  } else {
311
374
  attrs.push("SameSite=None");
@@ -313,3 +376,53 @@ export async function issuePageSessionCookie(
313
376
  }
314
377
  return attrs.join("; ");
315
378
  }
379
+
380
+ /**
381
+ * Most per-page session cookies one browser keeps, counting the one being
382
+ * issued. Each is ~220 bytes on the wire, and `Path=/` sends all of them on
383
+ * every request to this host, so an unbounded jar grows past the server's
384
+ * 16 KB header limit (431 on every route, `/health` included) after ~75
385
+ * launches within the hour. 20 keeps the jar near 4.5 KB.
386
+ */
387
+ export const MAX_PAGE_SESSION_COOKIES = 20;
388
+
389
+ const PAGE_SESSION_COOKIE_RE = new RegExp(
390
+ `(?:^|;\\s*)(${LEGACY_PAGE_SESSION_COOKIE}_[A-Za-z0-9_-]{1,64})=([^;]*)`,
391
+ "g",
392
+ );
393
+
394
+ /**
395
+ * `Set-Cookie` values for a launch of `pageId`: the new session, plus an
396
+ * expiring cookie for every other per-page session in the request's jar that
397
+ * is invalid or expired, or falls outside the `MAX_PAGE_SESSION_COOKIES - 1`
398
+ * newest. The browser then holds at most `MAX_PAGE_SESSION_COOKIES` of them.
399
+ * An evicted page's open tab loses its session, the same as after expiry.
400
+ */
401
+ export async function pageSessionSetCookies(
402
+ req: CookieRequest,
403
+ pageId: string,
404
+ opts: { dev: boolean; uid?: string; name?: string; op?: true },
405
+ ): Promise<string[]> {
406
+ const issued = await issuePageSessionCookie(pageId, opts);
407
+ const header = Array.isArray(req.headers.cookie)
408
+ ? req.headers.cookie.join("; ")
409
+ : (req.headers.cookie ?? "");
410
+ const keep: { name: string; exp: number; order: number }[] = [];
411
+ const evict = new Set<string>();
412
+ const seen = new Set([pageSessionCookieName(pageId)]);
413
+ for (const [, name, token] of header.matchAll(PAGE_SESSION_COOKIE_RE)) {
414
+ if (!name || seen.has(name)) continue;
415
+ seen.add(name);
416
+ const payload = await verifyPageSession(token);
417
+ if (payload && name === pageSessionCookieName(payload.pageId)) {
418
+ keep.push({ name, exp: payload.exp, order: keep.length });
419
+ } else {
420
+ evict.add(name);
421
+ }
422
+ }
423
+ // `exp` has one-second granularity. Browsers list older cookies first, so a
424
+ // later position breaks ties toward the more recent launch.
425
+ keep.sort((a, b) => b.exp - a.exp || b.order - a.order);
426
+ for (const { name } of keep.slice(MAX_PAGE_SESSION_COOKIES - 1)) evict.add(name);
427
+ return [issued, ...[...evict].map((name) => buildCookie(name, "", 0, opts.dev))];
428
+ }
@@ -8,9 +8,10 @@ export type HttpRequestAuth = (
8
8
  /** Password-page viewer with no user identity. Least privilege: every gate denies it. */
9
9
  | { kind: "guest" }
10
10
  | { kind: "controller"; controllerId: string; tokenId: string; expiresAt: string }
11
- /** Ephemeral token bound to an ACP task or a worker runtime (aseph_ prefix). */
11
+ /** Session token bound to an ACP task, a worker runtime, or only its agent (aseph_ prefix). */
12
12
  | {
13
13
  kind: "agent";
14
+ tokenKind: "task" | "runtime" | "agent";
14
15
  agentId: string;
15
16
  taskId: string | null;
16
17
  runtimeId: string | null;