@desplega.ai/agent-swarm 1.158.0 → 1.160.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 (378) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-2mjwcsfh.js} +124 -27
  3. package/dist/{actions-p9hz5yat.js → actions-e43rxskr.js} +35 -30
  4. package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-5qx80j9k.js} +106 -16
  5. package/dist/{app-g26wdcz6.js → app-mc7xfagn.js} +12 -10
  6. package/dist/{assistant-wydfqkb3.js → assistant-sedkzv3b.js} +42 -31
  7. package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-nfb21z9a.js} +13 -14
  8. package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-cqz9swn5.js} +13 -12
  9. package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-m3656j2n.js} +14 -13
  10. package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-xbxf5d7w.js} +11 -9
  11. package/dist/boot-seeds-gsetbs5y.js +28 -0
  12. package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-dc6289mc.js} +28 -23
  13. package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-naaw51me.js} +4 -3
  14. package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-eacvchx0.js} +12 -3
  15. package/dist/claude-sdk-session-bqp2xvym.js +29722 -0
  16. package/dist/{cli-987hg0aa.js → cli-0jwe9034.js} +2 -2
  17. package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
  18. package/dist/{cli-c07r9a2m.js → cli-0q3avq3s.js} +12 -11
  19. package/dist/{cli-19jenss3.js → cli-0wgxmwf6.js} +31 -10
  20. package/dist/cli-0x01kwrd.js +491 -0
  21. package/dist/cli-0xqde53b.js +176 -0
  22. package/dist/cli-10nhj6xq.js +8355 -0
  23. package/dist/{cli-tt9k8r35.js → cli-12tszpk9.js} +479 -910
  24. package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
  25. package/dist/{cli-cqt19wg6.js → cli-1a2j50m0.js} +11 -2
  26. package/dist/{cli-xberxa1r.js → cli-25fcq4b4.js} +2 -2
  27. package/dist/{cli-npssxgaz.js → cli-2pdaysab.js} +52 -2
  28. package/dist/cli-3qhyh31j.js +166 -0
  29. package/dist/cli-447dt1h3.js +3828 -0
  30. package/dist/cli-4derpgyk.js +115 -0
  31. package/dist/{cli-00w2c66p.js → cli-523679eh.js} +13 -2
  32. package/dist/{cli-y95wp3tg.js → cli-58e8yr0d.js} +1 -1
  33. package/dist/{cli-24k806sc.js → cli-5nbmpy8w.js} +3 -3
  34. package/dist/{cli-53sz8gkz.js → cli-6cs6wzxt.js} +1 -1
  35. package/dist/cli-741yb0xx.js +1027 -0
  36. package/dist/cli-74h9hscv.js +69 -0
  37. package/dist/cli-7cxw8b60.js +14910 -0
  38. package/dist/{cli-xhaeg2nc.js → cli-7e398j1h.js} +1 -1
  39. package/dist/cli-7kyk9jr3.js +22 -0
  40. package/dist/cli-89s4ngw3.js +183 -0
  41. package/dist/{cli-9b13ggd1.js → cli-89v6kyq7.js} +1 -0
  42. package/dist/{cli-ftfvggzx.js → cli-9hsgwz4w.js} +1045 -581
  43. package/dist/{cli-rqz8qbw7.js → cli-a8dvhzrt.js} +59 -20
  44. package/dist/{cli-j57qkrpq.js → cli-azhgzetg.js} +395 -56
  45. package/dist/cli-br0qdrnp.js +2150 -0
  46. package/dist/{cli-rj19hc34.js → cli-btfv8y88.js} +1 -1
  47. package/dist/{cli-8qmme6vd.js → cli-c8a36vcc.js} +2 -2
  48. package/dist/cli-cpvsaqsw.js +333 -0
  49. package/dist/cli-d5czme6m.js +17805 -0
  50. package/dist/{cli-h38x0qhg.js → cli-et8m9xsv.js} +4 -3
  51. package/dist/{cli-x24pshtq.js → cli-evvtk35y.js} +6 -6
  52. package/dist/{cli-7s3syk22.js → cli-f8gy6r2r.js} +5 -5
  53. package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
  54. package/dist/{cli-9httdpyq.js → cli-g2whhdrf.js} +1 -1
  55. package/dist/cli-gt4hxb8p.js +99 -0
  56. package/dist/{cli-s8eet254.js → cli-jajnsm8q.js} +37184 -53838
  57. package/dist/{cli-7gtvvpbh.js → cli-jayjpjmb.js} +21 -12
  58. package/dist/{cli-68w09sjw.js → cli-jyk0ynw2.js} +27 -89
  59. package/dist/{cli-aehm1rbq.js → cli-jz4w45qt.js} +1152 -61
  60. package/dist/cli-kmcpfwn2.js +333856 -0
  61. package/dist/{cli-4198qc9v.js → cli-ktmyrtpn.js} +19 -3
  62. package/dist/{cli-j55n64b6.js → cli-mfe5btp9.js} +505 -189
  63. package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
  64. package/dist/{cli-1hkrch3k.js → cli-pm1rst0h.js} +46 -7
  65. package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
  66. package/dist/{cli-ggm92wcn.js → cli-ptr8h7kp.js} +20 -1
  67. package/dist/{cli-s8s499xy.js → cli-qbksc292.js} +3 -2
  68. package/dist/{cli-47m6dkd3.js → cli-qydf22qq.js} +597 -346
  69. package/dist/{cli-3gmcwfyx.js → cli-r1mxd1xh.js} +13 -6
  70. package/dist/cli-rb9m6rpe.js +4980 -0
  71. package/dist/cli-rykfmgwg.js +102 -0
  72. package/dist/{cli-5d10vd2m.js → cli-shkxm1vn.js} +6 -5
  73. package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
  74. package/dist/cli-tfky2838.js +756 -0
  75. package/dist/{cli-g8djp6qe.js → cli-tmqt7dpj.js} +40 -0
  76. package/dist/{cli-fgqxwrpv.js → cli-tpq83z89.js} +1631 -127
  77. package/dist/cli-trc24wk2.js +143 -0
  78. package/dist/{cli-aky8tctm.js → cli-tzx2ke1v.js} +3 -3
  79. package/dist/cli-v7fn5gms.js +1425 -0
  80. package/dist/{cli-wscdvcqz.js → cli-vctdqs5h.js} +1 -1
  81. package/dist/{cli-cw9zck8j.js → cli-xjcywfsf.js} +8 -4
  82. package/dist/{cli-k0g1sjzm.js → cli-ybgv4kw2.js} +18 -11
  83. package/dist/{cli-rjehvrts.js → cli-ydmcgve1.js} +8 -8
  84. package/dist/{cli-vhjwh6cz.js → cli-ygwm91nt.js} +1 -1
  85. package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
  86. package/dist/{cli-de1396j2.js → cli-ywybs5as.js} +7 -1
  87. package/dist/cli-zpep9q5e.js +491 -0
  88. package/dist/cli.js +41 -32
  89. package/dist/cloudflare-workers-ai-system-one-k6fbxw9d.js +46 -0
  90. package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-nmxcatww.js} +26 -17
  91. package/dist/{codex-hook-9hptmdhj.js → codex-hook-20me6byw.js} +2 -2
  92. package/dist/{codex-login-x3jk4etn.js → codex-login-w6fz4xbz.js} +50 -5
  93. package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-90aa80ev.js} +24 -17
  94. package/dist/{commands-e3bn0p75.js → commands-8wcq1kn7.js} +11 -9
  95. package/dist/{db-03v9kzr2.js → db-j40kc4se.js} +25 -11
  96. package/dist/{devin-adapter-8vgkwh29.js → devin-adapter-dpsv6hd9.js} +1 -1
  97. package/dist/{dispatcher-nrmg8yht.js → dispatcher-4zqy768d.js} +34 -29
  98. package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-mpnzgztj.js} +3 -3
  99. package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
  100. package/dist/execute-6e7qeyk4.js +1017 -0
  101. package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-k6n5nbpp.js} +6 -5
  102. package/dist/{google-vertex-809af514.js → google-vertex-44xxtcvp.js} +6 -5
  103. package/dist/{handlers-aqcedbyb.js → handlers-kjdce1w5.js} +37 -32
  104. package/dist/{hook-ybvmrmhq.js → hook-czx13n12.js} +19 -15
  105. package/dist/{hook-7vkskkre.js → hook-r2t4bqfg.js} +18 -14
  106. package/dist/{http-2eqa8t24.js → http-3bbb19be.js} +900 -145
  107. package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
  108. package/dist/{index-x71bvq0p.js → index-668me3f0.js} +35 -30
  109. package/dist/{index-c6zsv1bd.js → index-7aj368tw.js} +13 -12
  110. package/dist/{index-1yks3qcr.js → index-fg0dsp6c.js} +34 -29
  111. package/dist/{index-chgdkq36.js → index-kkv84jq9.js} +34 -29
  112. package/dist/{index-a3vn96n8.js → index-zg7vp46x.js} +198 -54
  113. package/dist/{keepalive-vtkf5n99.js → keepalive-yra8dy57.js} +13 -11
  114. package/dist/{lead-h3sp357x.js → lead-va0pad2c.js} +54 -49
  115. package/dist/llama-cpp-classify-tgj35k8d.js +335 -0
  116. package/dist/{maintenance-jzeqtk3q.js → maintenance-hqewtmyg.js} +14 -13
  117. package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-pc3ntnxt.js} +14 -17
  118. package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-7rdt1m9n.js} +12 -10
  119. package/dist/{onboard-tmsp0qbz.js → onboard-j6ms6d0x.js} +6 -5
  120. package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-bbancrr0.js} +21 -8
  121. package/dist/{openai-completions-9hjrqbw8.js → openai-completions-jpaj5g2b.js} +9 -12
  122. package/dist/{openai-responses-7b1pn0xt.js → openai-responses-200k1wx4.js} +23 -18
  123. package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-89rabkw2.js} +8 -7
  124. package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-8yx3487g.js} +5 -7
  125. package/dist/{otel-impl-9mz73q65.js → otel-impl-a792rjz0.js} +1 -1
  126. package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
  127. package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-g7qzvr3v.js} +295 -70
  128. package/dist/pricing-refresh-wx0tp7cb.js +39 -0
  129. package/dist/quickjs-r1pq2107.wasm +0 -0
  130. package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-8tzawacv.js} +12 -10
  131. package/dist/{render-v2-gryrh9h8.js → render-v2-mm7zswb8.js} +12 -10
  132. package/dist/runtime-zyssgse0.js +24 -0
  133. package/dist/{setup-q9720bt6.js → setup-8y70ys6t.js} +6 -5
  134. package/dist/typesafe-system-one-2ef30pec.js +27 -0
  135. package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-6nmk7h3n.js} +22 -13
  136. package/dist/{work-objects-b76x7deb.js → work-objects-fg6xkb0n.js} +37 -32
  137. package/dist/{worker-6vrxbv41.js → worker-wmwhxsph.js} +54 -49
  138. package/openapi.json +1314 -91
  139. package/package.json +12 -11
  140. package/src/be/db/agents.ts +20 -0
  141. package/src/be/db/api-keys.ts +70 -0
  142. package/src/be/db/tasks/human-free.ts +442 -0
  143. package/src/be/db/tasks/read.ts +56 -1
  144. package/src/be/db/tasks/write.ts +107 -11
  145. package/src/be/db.ts +274 -188
  146. package/src/be/harness-model-support.ts +182 -0
  147. package/src/be/human-free-drain.ts +67 -0
  148. package/src/be/memory/providers/sqlite-store.ts +51 -15
  149. package/src/be/migrations/182_task_human_free_flag.sql +63 -0
  150. package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
  151. package/src/be/migrations/184_model_catalog.sql +120 -0
  152. package/src/be/migrations/185_api_key_auth_failures.sql +6 -0
  153. package/src/be/migrations/186_api_key_seat_mismatch.sql +8 -0
  154. package/src/be/model-catalog-store.ts +644 -0
  155. package/src/be/model-tier-keys.ts +46 -0
  156. package/src/be/model-tier-resolution.ts +534 -0
  157. package/src/be/model-validation.ts +188 -0
  158. package/src/be/models-catalog.ts +61 -7
  159. package/src/be/modelsdev-cache.json +82 -0
  160. package/src/be/modelsdev-cache.ts +2 -0
  161. package/src/be/pricing-refresh.ts +154 -12
  162. package/src/be/scripts/typecheck.ts +15 -0
  163. package/src/be/seed/runner.ts +18 -12
  164. package/src/be/seed/types.ts +1 -0
  165. package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
  166. package/src/be/swarm-config-guard.ts +8 -0
  167. package/src/cli.tsx +10 -0
  168. package/src/commands/claude-managed-setup.ts +3 -1
  169. package/src/commands/codex-login.ts +67 -0
  170. package/src/commands/credential-outcome-report.ts +191 -0
  171. package/src/commands/rate-limit-outcome.ts +34 -4
  172. package/src/commands/runner.ts +357 -186
  173. package/src/commands/seat-mismatch-report.ts +46 -0
  174. package/src/extensions/apply-task-create.ts +27 -1
  175. package/src/extensions/contract-types.generated.ts +1 -1
  176. package/src/heartbeat/heartbeat.ts +79 -17
  177. package/src/heartbeat/templates.ts +2 -2
  178. package/src/http/agents.ts +62 -1
  179. package/src/http/api-keys.ts +140 -2
  180. package/src/http/codex-oauth-keep-warm.ts +3 -5
  181. package/src/http/index.ts +8 -0
  182. package/src/http/memory.ts +12 -5
  183. package/src/http/models-catalog.ts +357 -8
  184. package/src/http/poll.ts +54 -3
  185. package/src/http/schedules.ts +51 -6
  186. package/src/http/script-runs.ts +12 -0
  187. package/src/http/session-data.ts +2 -5
  188. package/src/http/stats.ts +6 -8
  189. package/src/http/status.ts +15 -1
  190. package/src/http/tasks.ts +90 -7
  191. package/src/http/usage-cache.ts +56 -15
  192. package/src/http/workflows.ts +46 -8
  193. package/src/mcp-client/http-client.ts +5 -0
  194. package/src/memory/recall-query.ts +74 -0
  195. package/src/prompts/base-prompt.ts +15 -4
  196. package/src/prompts/session-templates.ts +23 -1
  197. package/src/providers/acp-adapter.ts +1 -0
  198. package/src/providers/claude-adapter.ts +14 -5
  199. package/src/providers/claude-managed-adapter.ts +1 -0
  200. package/src/providers/claude-managed-models.ts +47 -161
  201. package/src/providers/claude-sdk-session.ts +3 -1
  202. package/src/providers/codex-adapter.ts +14 -14
  203. package/src/providers/codex-models.ts +65 -198
  204. package/src/providers/devin-adapter.ts +1 -0
  205. package/src/providers/dsh-adapter.ts +2 -1
  206. package/src/providers/harness-version.ts +83 -7
  207. package/src/providers/opencode-adapter.ts +53 -1
  208. package/src/providers/pi-binary-root/src/extensions/codemode/worker.ts +11 -0
  209. package/src/providers/pi-codemode-runtime.ts +17 -0
  210. package/src/providers/pi-mono-adapter.ts +361 -83
  211. package/src/providers/pi-mono-extension.ts +16 -1
  212. package/src/providers/reasoning-effort.ts +44 -140
  213. package/src/providers/types.ts +8 -0
  214. package/src/rbac/legacy-policy.ts +43 -0
  215. package/src/rbac/permissions.ts +15 -0
  216. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  217. package/src/scripts-runtime/types/stdlib.d.ts +15 -0
  218. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  219. package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
  220. package/src/server.ts +6 -0
  221. package/src/slack/assistant.ts +13 -0
  222. package/src/slack/message-text.ts +56 -2
  223. package/src/slack/render-v2.ts +87 -37
  224. package/src/slack/session-status.ts +541 -0
  225. package/src/slack/thread-buffer.ts +10 -2
  226. package/src/tasks/sibling-awareness.ts +3 -2
  227. package/src/telemetry.ts +5 -4
  228. package/src/templates.d.ts +6 -0
  229. package/src/tests/agents-list-model-display.test.ts +4 -6
  230. package/src/tests/api-key-auth-failure-route.test.ts +142 -0
  231. package/src/tests/api-key-tracking.test.ts +368 -1
  232. package/src/tests/base-prompt.test.ts +43 -2
  233. package/src/tests/claude-adapter.test.ts +68 -0
  234. package/src/tests/claude-managed-adapter.test.ts +12 -18
  235. package/src/tests/codex-adapter.test.ts +2 -1
  236. package/src/tests/codex-auth-failure.test.ts +52 -0
  237. package/src/tests/codex-gpt6-release.test.ts +66 -5
  238. package/src/tests/codex-login.test.ts +116 -0
  239. package/src/tests/context-window.test.ts +7 -5
  240. package/src/tests/credential-exhaustion-policy.test.ts +118 -3
  241. package/src/tests/credential-outcome-report.test.ts +171 -0
  242. package/src/tests/credential-status-api.test.ts +103 -2
  243. package/src/tests/db-extraction-contract.test.ts +1 -0
  244. package/src/tests/db-task-read-contract.test.ts +10 -1
  245. package/src/tests/db-task-write-contract.test.ts +535 -2
  246. package/src/tests/docs-index-footprint.test.js +3 -3
  247. package/src/tests/dokploy-deploy.test.ts +315 -0
  248. package/src/tests/dsh-adapter.test.ts +1 -1
  249. package/src/tests/explicit-model-validation.test.ts +583 -0
  250. package/src/tests/extensions-pre-task.test.ts +73 -0
  251. package/src/tests/fetch-resolved-env.test.ts +46 -0
  252. package/src/tests/hang-watchdog-worker.ts +47 -0
  253. package/src/tests/hang-watchdog.test.ts +45 -0
  254. package/src/tests/hang-watchdog.ts +30 -0
  255. package/src/tests/harness-model-guard.test.ts +123 -0
  256. package/src/tests/harness-model-support.test.ts +374 -0
  257. package/src/tests/harness-version.test.ts +70 -3
  258. package/src/tests/heartbeat-checklist-config.test.ts +71 -0
  259. package/src/tests/heartbeat-checklist.test.ts +4 -2
  260. package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
  261. package/src/tests/http-task-write-ownership.test.ts +172 -0
  262. package/src/tests/human-free-flag.test.ts +279 -0
  263. package/src/tests/human-free-oracle.ts +57 -0
  264. package/src/tests/human-free-reclassify.test.ts +669 -0
  265. package/src/tests/memory-fts-populate.test.ts +139 -0
  266. package/src/tests/memory-http-recall-gating.test.ts +125 -1
  267. package/src/tests/migration-046-budgets.test.ts +3 -3
  268. package/src/tests/model-catalog-migration.test.ts +134 -0
  269. package/src/tests/model-catalog-table.test.ts +249 -0
  270. package/src/tests/model-catalog-write-access.test.ts +387 -0
  271. package/src/tests/model-rate-limit-windows.test.ts +19 -0
  272. package/src/tests/model-tier-resolution.test.ts +611 -0
  273. package/src/tests/models-catalog.test.ts +1 -1
  274. package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
  275. package/src/tests/opencode-adapter.test.ts +157 -2
  276. package/src/tests/package-publish.test.ts +137 -36
  277. package/src/tests/pi-codemode.test.ts +246 -0
  278. package/src/tests/pi-mono-adapter.test.ts +3 -0
  279. package/src/tests/pi-native-mcp.test.ts +193 -0
  280. package/src/tests/pi-tool-exposure.test.ts +374 -0
  281. package/src/tests/preload.ts +3 -0
  282. package/src/tests/pricing-routes.test.ts +2 -2
  283. package/src/tests/prompt-template-session.test.ts +5 -3
  284. package/src/tests/rate-limit-event.test.ts +65 -0
  285. package/src/tests/rate-limit-outcome.test.ts +90 -0
  286. package/src/tests/rbac-engine.test.ts +77 -3
  287. package/src/tests/reasoning-effort.test.ts +109 -0
  288. package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
  289. package/src/tests/runner-key-completion-outcome.test.ts +112 -0
  290. package/src/tests/runner-key-outcome-late-reset.test.ts +224 -0
  291. package/src/tests/runner-registration-retry.test.ts +149 -0
  292. package/src/tests/runtime-model-catalog.test.ts +86 -0
  293. package/src/tests/script-connections.test.ts +9 -0
  294. package/src/tests/seat-mismatch-report.test.ts +85 -0
  295. package/src/tests/seed-scripts.test.ts +138 -0
  296. package/src/tests/seed.test.ts +25 -0
  297. package/src/tests/session-costs.test.ts +14 -11
  298. package/src/tests/slack-message-text.test.ts +101 -0
  299. package/src/tests/slack-render-v2.test.ts +96 -0
  300. package/src/tests/slack-session-status.test.ts +626 -0
  301. package/src/tests/slack-thread-buffer-context.test.ts +93 -0
  302. package/src/tests/status.test.ts +29 -0
  303. package/src/tests/steering-promotion.test.ts +51 -0
  304. package/src/tests/store-progress-ownership.test.ts +136 -0
  305. package/src/tests/subscription-plans.test.ts +28 -0
  306. package/src/tests/task-cascade-fail.test.ts +148 -2
  307. package/src/tests/task-citations.test.ts +3 -2
  308. package/src/tests/tool-annotations.test.ts +1 -1
  309. package/src/tests/usage-cache.test.ts +166 -0
  310. package/src/tests/workflow-agent-task.test.ts +30 -0
  311. package/src/tests/workflow-executors.test.ts +5 -2
  312. package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
  313. package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
  314. package/src/tests/workflow-system-one-decision.test.ts +2063 -0
  315. package/src/tests/workflow-triggers-v2.test.ts +320 -0
  316. package/src/tools/model-catalog/index.ts +137 -0
  317. package/src/tools/schedules/create-schedule.ts +17 -2
  318. package/src/tools/schedules/patch-schedule.ts +31 -1
  319. package/src/tools/schedules/update-schedule.ts +31 -1
  320. package/src/tools/send-task.ts +18 -1
  321. package/src/tools/store-progress.ts +49 -4
  322. package/src/tools/task-action.ts +14 -1
  323. package/src/tools/tool-config.ts +3 -1
  324. package/src/tools/workflows/create-workflow.ts +17 -4
  325. package/src/tools/workflows/patch-workflow-node.ts +17 -7
  326. package/src/tools/workflows/patch-workflow.ts +9 -2
  327. package/src/tools/workflows/update-workflow.ts +17 -4
  328. package/src/types.ts +41 -1
  329. package/src/utils/codex-auth-failure.ts +34 -0
  330. package/src/utils/context-window.ts +47 -53
  331. package/src/utils/credentials.ts +72 -30
  332. package/src/utils/error-tracker.ts +80 -0
  333. package/src/utils/harness-cli-version.ts +115 -0
  334. package/src/utils/harness-model-error.ts +33 -0
  335. package/src/utils/model-rate-limit-windows.ts +12 -0
  336. package/src/utils/runtime-model-catalog.ts +152 -0
  337. package/src/utils/spawn-model-guard.ts +43 -0
  338. package/src/utils/subscription-plans.ts +22 -0
  339. package/src/workflows/approval-resolution.ts +39 -0
  340. package/src/workflows/definition.ts +7 -0
  341. package/src/workflows/engine.ts +69 -1
  342. package/src/workflows/executors/agent-task.ts +16 -6
  343. package/src/workflows/executors/base.ts +53 -0
  344. package/src/workflows/executors/human-in-the-loop.ts +8 -8
  345. package/src/workflows/executors/index.ts +1 -0
  346. package/src/workflows/executors/registry.ts +2 -0
  347. package/src/workflows/executors/system-one-decision.ts +1232 -0
  348. package/src/workflows/executors/system-one-providers.ts +301 -0
  349. package/src/workflows/executors/workflow-llm.ts +13 -1
  350. package/src/workflows/model-validation.ts +45 -0
  351. package/src/workflows/patch-definition.ts +5 -0
  352. package/src/workflows/readiness.ts +86 -0
  353. package/src/workflows/recovery.ts +12 -12
  354. package/src/workflows/resume.ts +23 -9
  355. package/src/workflows/triggers.ts +85 -0
  356. package/templates/schedules/daily-blocker-digest/content.md +4 -5
  357. package/templates/skills/agent-browser/SKILL.md +6 -5
  358. package/templates/skills/agent-browser/content.md +6 -5
  359. package/templates/skills/heartbeat-runbook/config.json +1 -1
  360. package/templates/skills/heartbeat-runbook/content.md +2 -2
  361. package/templates/skills/swarm-scripts/SKILL.md +1 -1
  362. package/templates/skills/swarm-scripts/content.md +1 -1
  363. package/templates/skills/workflow-iterate/content.md +82 -1
  364. package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
  365. package/templates/workflows/pr-review-status-sweep/content.md +2 -0
  366. package/dist/boot-seeds-s3q01xj3.js +0 -26
  367. package/dist/claude-sdk-session-t442qz6f.js +0 -39108
  368. package/dist/cli-0xjkj1j7.js +0 -61
  369. package/dist/cli-20cj37vt.js +0 -335561
  370. package/dist/cli-2djtr51b.js +0 -48
  371. package/dist/cli-65hjmev5.js +0 -1141
  372. package/dist/cli-aw1jwp1r.js +0 -14075
  373. package/dist/cli-h967nb6r.js +0 -6603
  374. package/dist/cli-vapyk8sw.js +0 -20
  375. package/dist/cli-ws6rqycm.js +0 -247
  376. package/dist/pricing-refresh-v1cbr9te.js +0 -27
  377. package/src/providers/modelsdev-reasoning.json +0 -6453
  378. package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
@@ -0,0 +1,541 @@
1
+ import { webApi } from "@slack/bolt";
2
+ import type { WebClient } from "@slack/web-api";
3
+ import {
4
+ getSlackTasksInThread,
5
+ isSlackThreadAwaitingHuman,
6
+ listSlackThreadsAwaitingHuman,
7
+ } from "../be/db";
8
+ import { type AgentTask, isTerminalTaskStatus } from "../types";
9
+ import { scrubSecrets } from "../utils/secret-scrubber";
10
+ import { getSlackApp } from "./app";
11
+
12
+ /**
13
+ * Slack's native "working" state for a thread (`agents.sessions.setStatus`,
14
+ * bot scope `chat:write`, no manifest change). Slack shows its loading UI
15
+ * while the session is `processing`, marks it `suspended` when the agent is
16
+ * blocked on a person, and clears the indicator on `active`.
17
+ *
18
+ * Three properties of the method shape this file:
19
+ * - It does not clear itself when the app posts a message (the legacy
20
+ * `assistant.threads.setStatus` did), and Slack drops a `processing`
21
+ * session after one hour. So the state is reconciled against the thread's
22
+ * tasks every render tick, and `processing` is re-asserted before the hour
23
+ * is up, so an ask that runs for many hours keeps its indicator.
24
+ * - It works in channel threads as well as DMs, and creates the session on
25
+ * first use.
26
+ * - Slack shows a Stop button only if the app subscribes to
27
+ * `agent_session_stopped`. The manifest does not, so there is none.
28
+ *
29
+ * The indicator is only ever a nicety. Every failure is absorbed here: the
30
+ * thread keeps the reaction and the tree message (DMs also keep the legacy
31
+ * indicator), and nothing in this file throws into a task.
32
+ *
33
+ * A write that got no answer (timeout, reset) may still have reached Slack, so
34
+ * the thread is marked `uncertain` and the next reconcile writes whatever it
35
+ * wants even when the books say "already clear". Writes go through an isolated
36
+ * client (see `statusTransport`) so such a write is aborted at the timeout
37
+ * instead of landing minutes later.
38
+ */
39
+
40
+ export type SlackSessionStatus = "processing" | "suspended" | "active";
41
+
42
+ /** Slack ends a `processing` session after one hour; re-assert well inside it. */
43
+ const REFRESH_MS = 30 * 60_000;
44
+ const DEFAULT_CALL_TIMEOUT_MS = 8_000;
45
+ /** The status client aborts its HTTP request this long before the local timeout backstops a transport that does not. */
46
+ const TRANSPORT_MARGIN_MS = 500;
47
+ let callTimeoutMs = DEFAULT_CALL_TIMEOUT_MS;
48
+ const RETRY_BASE_MS = 30_000;
49
+ const RETRY_MAX_MS = 10 * 60_000;
50
+ /** After a workspace-level refusal, stay off the native call this long, then probe again. */
51
+ const UNAVAILABLE_COOLDOWN_MS = 60 * 60_000;
52
+ /** Slack allows 50+ calls a minute (Tier 3); a restart with many live threads spreads over ticks. */
53
+ const CALLS_PER_TICK = 8;
54
+ /**
55
+ * A `processing` mark can precede its task row (a DM downloading attachments),
56
+ * so the sweep leaves a fresh one alone instead of clearing it under the task.
57
+ */
58
+ const ORPHAN_GRACE_MS = 2 * 60_000;
59
+
60
+ /**
61
+ * Errors that say this workspace, app or token cannot use the method at all:
62
+ * a missing scope, the feature off, a token that is not a bot token, a
63
+ * revoked install. Retrying per thread would only repeat them.
64
+ */
65
+ const WORKSPACE_ERRORS = new Set([
66
+ "missing_scope",
67
+ "not_allowed_token_type",
68
+ "feature_disabled",
69
+ "not_authed",
70
+ "invalid_auth",
71
+ "account_inactive",
72
+ "token_revoked",
73
+ "token_expired",
74
+ "access_denied",
75
+ "no_permission",
76
+ "accesslimited",
77
+ "enterprise_is_restricted",
78
+ "ekm_access_denied",
79
+ "org_login_required",
80
+ "method_deprecated",
81
+ "deprecated_endpoint",
82
+ "unknown_method",
83
+ ]);
84
+
85
+ /** Errors that answer differently on a later attempt. */
86
+ const TRANSIENT_ERRORS = new Set([
87
+ "ratelimited",
88
+ "rate_limited",
89
+ "internal_error",
90
+ "service_unavailable",
91
+ "fatal_error",
92
+ "request_timeout",
93
+ ]);
94
+
95
+ type ThreadEntry = {
96
+ /** The last status Slack accepted for the thread. */
97
+ status: SlackSessionStatus;
98
+ setAt: number;
99
+ failures: number;
100
+ retryAt: number;
101
+ /** Slack refused this thread (e.g. the bot is not in the channel); leave it alone until it goes idle. */
102
+ refused: boolean;
103
+ /**
104
+ * The last write got no answer, so Slack may hold a status that `status`
105
+ * does not show. Reconciling writes the desired status even if it equals
106
+ * `status`, and a clear is never skipped as "already clear".
107
+ */
108
+ uncertain: boolean;
109
+ /**
110
+ * A reconcile that knew the thread's tasks wanted a different status but the
111
+ * tick's budget was spent. Due on the next sweep, grace period or not.
112
+ */
113
+ deferred?: boolean;
114
+ };
115
+
116
+ const threads = new Map<string, ThreadEntry>();
117
+ const keyTails = new Map<string, Promise<unknown>>();
118
+ const warned = new Set<string>();
119
+ let unavailableUntil = 0;
120
+
121
+ type ApplyResult = "applied" | "current" | "idle" | "unavailable";
122
+
123
+ export type StatusBudget = { remaining: number };
124
+
125
+ function threadKey(channelId: string, threadTs: string): string {
126
+ return `${channelId}:${threadTs}`;
127
+ }
128
+
129
+ function splitKey(key: string): { channelId: string; threadTs: string } {
130
+ const at = key.indexOf(":");
131
+ return { channelId: key.slice(0, at), threadTs: key.slice(at + 1) };
132
+ }
133
+
134
+ function errorCode(error: unknown): string | undefined {
135
+ const code = (error as { data?: { error?: string } })?.data?.error;
136
+ return typeof code === "string" && code.length > 0 ? code : undefined;
137
+ }
138
+
139
+ function warnOnce(key: string, message: string): void {
140
+ if (warned.has(key)) return;
141
+ warned.add(key);
142
+ console.warn(scrubSecrets(`[Slack] ${message}`));
143
+ }
144
+
145
+ class CallTimeoutError extends Error {
146
+ constructor(ms: number) {
147
+ super(`timed out after ${ms}ms`);
148
+ this.name = "CallTimeoutError";
149
+ }
150
+ }
151
+
152
+ function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
153
+ let timer: ReturnType<typeof setTimeout> | undefined;
154
+ const timeout = new Promise<never>((_, reject) => {
155
+ timer = setTimeout(() => reject(new CallTimeoutError(ms)), ms);
156
+ });
157
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
158
+ }
159
+
160
+ type StatusTransport = Pick<WebClient, "apiCall">;
161
+ let transports = new WeakMap<WebClient, StatusTransport>();
162
+
163
+ /**
164
+ * The client native status writes go through. The shared Bolt client retries
165
+ * a failed call for up to 30 minutes and, on a 429, pauses its queue for every
166
+ * method, so a slow status write could land long after the ask ended and stall
167
+ * tree delivery. This is a sibling client with its own queue, no automatic
168
+ * retries, rate limits surfaced as errors, and an HTTP timeout that aborts the
169
+ * request. Anything that is not a real `WebClient` (a test double, or a
170
+ * transport that is already isolated) is used as it is.
171
+ */
172
+ function statusTransport(client: WebClient): StatusTransport {
173
+ if (!(client instanceof webApi.WebClient)) return client;
174
+ let transport = transports.get(client);
175
+ if (!transport) {
176
+ const timeout = Math.max(callTimeoutMs - TRANSPORT_MARGIN_MS, 1);
177
+ transport = new webApi.WebClient(client.token ?? process.env.SLACK_BOT_TOKEN, {
178
+ slackApiUrl: client.slackApiUrl,
179
+ retryConfig: { retries: 0 },
180
+ rejectRateLimitedCalls: true,
181
+ timeout,
182
+ // Axios' own timeout leaves the socket open under Bun; an abort signal closes it.
183
+ requestInterceptor: (config) => ({ ...config, signal: AbortSignal.timeout(timeout) }),
184
+ });
185
+ transports.set(client, transport);
186
+ }
187
+ return transport;
188
+ }
189
+
190
+ /** Runs `fn` after every earlier call for the same thread has finished. */
191
+ function withThreadLock<T>(key: string, fn: () => Promise<T>): Promise<T> {
192
+ const run = (keyTails.get(key) ?? Promise.resolve()).then(fn);
193
+ const tail = run.catch(() => undefined);
194
+ keyTails.set(key, tail);
195
+ void tail.then(() => {
196
+ if (keyTails.get(key) === tail) keyTails.delete(key);
197
+ });
198
+ return run;
199
+ }
200
+
201
+ /**
202
+ * The status a thread should show, from its tasks. Running work wins over a
203
+ * pending human request: the swarm is only "waiting on you" once nothing is
204
+ * left to run. A deferred ask (its task is `completed`, a schedule wakes it)
205
+ * reads `active` until the wake-up task appears.
206
+ */
207
+ function decideSlackSessionStatus(
208
+ tasks: readonly Pick<AgentTask, "status">[],
209
+ awaitingHuman: boolean,
210
+ ): SlackSessionStatus {
211
+ if (tasks.some((task) => !isTerminalTaskStatus(task.status))) return "processing";
212
+ return awaitingHuman ? "suspended" : "active";
213
+ }
214
+
215
+ async function desiredStatus(channelId: string, threadTs: string): Promise<SlackSessionStatus> {
216
+ const tasks = await getSlackTasksInThread(channelId, threadTs);
217
+ const running = tasks.some((task) => !isTerminalTaskStatus(task.status));
218
+ const awaitingHuman = running ? false : await isSlackThreadAwaitingHuman(channelId, threadTs);
219
+ return decideSlackSessionStatus(tasks, awaitingHuman);
220
+ }
221
+
222
+ type CallFailure = "unavailable" | "refused" | "transient";
223
+
224
+ function classify(error: unknown): { kind: CallFailure; code: string } {
225
+ const code = errorCode(error);
226
+ // No Slack verdict: a timeout, a socket reset, or a rate limit the client surfaced.
227
+ if (!code) return { kind: "transient", code: "no_slack_verdict" };
228
+ if (WORKSPACE_ERRORS.has(code)) return { kind: "unavailable", code };
229
+ if (TRANSIENT_ERRORS.has(code)) return { kind: "transient", code };
230
+ return { kind: "refused", code };
231
+ }
232
+
233
+ function retryAfterMs(error: unknown): number | undefined {
234
+ const seconds = (error as { retryAfter?: number })?.retryAfter;
235
+ return typeof seconds === "number" && seconds > 0 ? seconds * 1_000 : undefined;
236
+ }
237
+
238
+ /**
239
+ * Writes `desired` for one thread when it differs from what Slack last
240
+ * accepted (or when `processing` is about to lapse). Callers hold the thread
241
+ * lock.
242
+ */
243
+ async function applyStatus(
244
+ client: WebClient,
245
+ channelId: string,
246
+ threadTs: string,
247
+ desired: SlackSessionStatus,
248
+ budget?: StatusBudget,
249
+ ): Promise<ApplyResult> {
250
+ const key = threadKey(channelId, threadTs);
251
+ const now = Date.now();
252
+ const entry = threads.get(key);
253
+
254
+ // Slack refuses this workspace, or this thread: nothing to write, and
255
+ // nothing worth remembering about a status we can no longer clear.
256
+ if (now < unavailableUntil || entry?.refused) {
257
+ if (desired === "active") threads.delete(key);
258
+ return "unavailable";
259
+ }
260
+ if (desired === "active") {
261
+ // Nothing was ever set for this thread, or it is known to be clear. An
262
+ // entry whose last write went unanswered is not known to be either.
263
+ if (!entry || (entry.status === "active" && !entry.uncertain)) {
264
+ threads.delete(key);
265
+ return "idle";
266
+ }
267
+ } else if (
268
+ entry &&
269
+ entry.status === desired &&
270
+ !entry.uncertain &&
271
+ now - entry.setAt < REFRESH_MS
272
+ ) {
273
+ return "current";
274
+ }
275
+ if (entry && now < entry.retryAt) return "unavailable";
276
+ if (budget) {
277
+ if (budget.remaining <= 0) {
278
+ if (entry) threads.set(key, { ...entry, deferred: true });
279
+ return "unavailable";
280
+ }
281
+ budget.remaining--;
282
+ }
283
+
284
+ const pending = statusTransport(client).apiCall("agents.sessions.setStatus", {
285
+ channel_id: channelId,
286
+ thread_ts: threadTs,
287
+ status: desired,
288
+ });
289
+ try {
290
+ const result = (await withTimeout(pending, callTimeoutMs)) as { ok?: boolean; error?: string };
291
+ if (result?.ok === false) throw { data: { error: result.error ?? "unknown_error" } };
292
+ } catch (error) {
293
+ // The request is still out there. If it lands after we gave up, record
294
+ // what Slack now holds and reconcile against the thread's tasks again.
295
+ if (error instanceof CallTimeoutError) {
296
+ pending.then(
297
+ (late) => {
298
+ if ((late as { ok?: boolean })?.ok !== false) {
299
+ void noteLateWrite(client, channelId, threadTs, desired);
300
+ }
301
+ },
302
+ () => {},
303
+ );
304
+ }
305
+ const failure = classify(error);
306
+ if (failure.kind === "unavailable") {
307
+ unavailableUntil = now + UNAVAILABLE_COOLDOWN_MS;
308
+ warnOnce(
309
+ `unavailable:${failure.code}`,
310
+ `Native session status is unavailable (${failure.code}). Keeping the reaction and tree message${
311
+ failure.code === "missing_scope" ? "; the bot token needs the chat:write scope" : ""
312
+ }. Will probe again in ${UNAVAILABLE_COOLDOWN_MS / 60_000} min.`,
313
+ );
314
+ } else if (failure.kind === "refused") {
315
+ threads.set(key, {
316
+ status: entry?.status ?? "active",
317
+ setAt: entry?.setAt ?? 0,
318
+ failures: 0,
319
+ retryAt: 0,
320
+ refused: true,
321
+ uncertain: false,
322
+ });
323
+ warnOnce(
324
+ `refused:${failure.code}`,
325
+ `Slack refused a session status for ${channelId}/${threadTs} (${failure.code}). Leaving that thread on the reaction and tree message.`,
326
+ );
327
+ } else {
328
+ const failures = (entry?.failures ?? 0) + 1;
329
+ const backoff = Math.min(RETRY_BASE_MS * 2 ** (failures - 1), RETRY_MAX_MS);
330
+ threads.set(key, {
331
+ status: entry?.status ?? "active",
332
+ setAt: entry?.setAt ?? 0,
333
+ failures,
334
+ retryAt: now + Math.max(backoff, retryAfterMs(error) ?? 0),
335
+ refused: false,
336
+ // No verdict: the write may have reached Slack before the answer was lost.
337
+ uncertain: true,
338
+ });
339
+ warnOnce(
340
+ `transient:${failure.code}`,
341
+ `Setting the session status failed (${failure.code}); retrying with backoff.`,
342
+ );
343
+ }
344
+ return "unavailable";
345
+ }
346
+
347
+ if (desired === "active") threads.delete(key);
348
+ else {
349
+ threads.set(key, {
350
+ status: desired,
351
+ setAt: now,
352
+ failures: 0,
353
+ retryAt: 0,
354
+ refused: false,
355
+ uncertain: false,
356
+ });
357
+ }
358
+ return "applied";
359
+ }
360
+
361
+ /**
362
+ * A write we timed out on has now been answered, so Slack holds `landed`,
363
+ * which may not be what the thread's tasks call for by now (the ask finished
364
+ * while the request was out). Record that and reconcile again. The isolated
365
+ * transport aborts at its timeout, so this is the path for a transport that
366
+ * cannot, and for an answer that was only slow.
367
+ */
368
+ async function noteLateWrite(
369
+ client: WebClient,
370
+ channelId: string,
371
+ threadTs: string,
372
+ landed: SlackSessionStatus,
373
+ ): Promise<void> {
374
+ try {
375
+ const key = threadKey(channelId, threadTs);
376
+ await withThreadLock(key, async () => {
377
+ if (landed === "active") threads.delete(key);
378
+ else {
379
+ threads.set(key, {
380
+ status: landed,
381
+ setAt: Date.now(),
382
+ failures: 0,
383
+ retryAt: 0,
384
+ refused: false,
385
+ uncertain: false,
386
+ });
387
+ }
388
+ });
389
+ await reconcileSlackSessionStatus({ channelId, threadTs, client });
390
+ } catch (error) {
391
+ warnOnce(
392
+ "late-write",
393
+ `Reconciling a late session status write failed: ${error instanceof Error ? error.message : String(error)}`,
394
+ );
395
+ }
396
+ }
397
+
398
+ /** The pre-native DM indicator, kept as the fallback for an outcome that just landed. */
399
+ async function clearLegacyDmStatus(
400
+ client: WebClient,
401
+ channelId: string,
402
+ threadTs: string,
403
+ ): Promise<void> {
404
+ if (!channelId.startsWith("D")) return;
405
+ try {
406
+ await client.apiCall("assistant.threads.setStatus", {
407
+ channel_id: channelId,
408
+ thread_ts: threadTs,
409
+ status: "",
410
+ });
411
+ } catch (error) {
412
+ warnOnce(
413
+ "legacy-clear",
414
+ `Failed to clear the assistant status for ${channelId}/${threadTs}: ${error instanceof Error ? error.message : String(error)}`,
415
+ );
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Brings a thread's native status in line with its tasks: `processing` while
421
+ * any task runs, `suspended` while only a human request is open, `active`
422
+ * (cleared) otherwise. Idempotent and cheap: it calls Slack only when the
423
+ * status changes or a `processing` state needs re-asserting. Never throws.
424
+ *
425
+ * `outcomeDelivered` is set right after an outcome card landed. If the native
426
+ * call cannot be used, a DM then falls back to clearing the legacy indicator,
427
+ * exactly as before native status existed.
428
+ */
429
+ export async function reconcileSlackSessionStatus(input: {
430
+ channelId: string;
431
+ threadTs: string;
432
+ client?: WebClient;
433
+ budget?: StatusBudget;
434
+ outcomeDelivered?: boolean;
435
+ }): Promise<void> {
436
+ try {
437
+ const client = input.client ?? getSlackApp()?.client;
438
+ if (!client) return;
439
+ const { channelId, threadTs } = input;
440
+ await withThreadLock(threadKey(channelId, threadTs), async () => {
441
+ const desired = await desiredStatus(channelId, threadTs);
442
+ const result = await applyStatus(client, channelId, threadTs, desired, input.budget);
443
+ if (input.outcomeDelivered && result === "unavailable") {
444
+ await clearLegacyDmStatus(client, channelId, threadTs);
445
+ }
446
+ });
447
+ } catch (error) {
448
+ warnOnce(
449
+ "reconcile",
450
+ `Session status sync failed: ${error instanceof Error ? error.message : String(error)}`,
451
+ );
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Marks a thread `processing` right away, before any task row exists (a DM
457
+ * downloading attachments, for instance). Returns false when native status
458
+ * cannot be used for the thread, so the caller can fall back to its own
459
+ * indicator. Never throws.
460
+ */
461
+ export async function markSlackSessionProcessing(input: {
462
+ channelId: string;
463
+ threadTs: string;
464
+ client?: WebClient;
465
+ }): Promise<boolean> {
466
+ try {
467
+ const client = input.client ?? getSlackApp()?.client;
468
+ if (!client) return false;
469
+ const { channelId, threadTs } = input;
470
+ return await withThreadLock(threadKey(channelId, threadTs), async () => {
471
+ const result = await applyStatus(client, channelId, threadTs, "processing");
472
+ return result === "applied" || result === "current";
473
+ });
474
+ } catch {
475
+ return false;
476
+ }
477
+ }
478
+
479
+ /**
480
+ * One render tick's worth of status work. `reconcile` runs for each thread
481
+ * tree the tick visits; `finish` then covers the threads the tick did not
482
+ * reach: ones that only wait on a human (their tasks are all finished, so no
483
+ * tree is being rendered) and ones this process set a status on that must now
484
+ * be cleared or retried.
485
+ */
486
+ export function beginSlackStatusTick(): {
487
+ /** Shared by every native status write the tick starts; hand it to anything the tick calls that may write. */
488
+ budget: StatusBudget;
489
+ reconcile: (channelId: string, threadTs: string) => Promise<void>;
490
+ finish: () => Promise<void>;
491
+ } {
492
+ const budget: StatusBudget = { remaining: CALLS_PER_TICK };
493
+ const visited = new Set<string>();
494
+ return {
495
+ budget,
496
+ reconcile: async (channelId, threadTs) => {
497
+ visited.add(threadKey(channelId, threadTs));
498
+ await reconcileSlackSessionStatus({ channelId, threadTs, budget });
499
+ },
500
+ finish: async () => {
501
+ try {
502
+ const targets = new Map<string, { channelId: string; threadTs: string }>();
503
+ const now = Date.now();
504
+ for (const [key, entry] of threads) {
505
+ const inGrace =
506
+ entry.status === "processing" &&
507
+ !entry.uncertain &&
508
+ !entry.deferred &&
509
+ now - entry.setAt < ORPHAN_GRACE_MS;
510
+ if (inGrace) continue;
511
+ targets.set(key, splitKey(key));
512
+ }
513
+ for (const thread of await listSlackThreadsAwaitingHuman()) {
514
+ targets.set(threadKey(thread.channelId, thread.threadTs), thread);
515
+ }
516
+ for (const [key, thread] of targets) {
517
+ if (visited.has(key)) continue;
518
+ await reconcileSlackSessionStatus({ ...thread, budget });
519
+ }
520
+ } catch (error) {
521
+ warnOnce(
522
+ "tick-finish",
523
+ `Session status sweep failed: ${error instanceof Error ? error.message : String(error)}`,
524
+ );
525
+ }
526
+ },
527
+ };
528
+ }
529
+
530
+ export function _setSlackSessionStatusTimeoutForTests(ms: number | undefined): void {
531
+ callTimeoutMs = ms ?? DEFAULT_CALL_TIMEOUT_MS;
532
+ }
533
+
534
+ export function _resetSlackSessionStatusForTests(): void {
535
+ callTimeoutMs = DEFAULT_CALL_TIMEOUT_MS;
536
+ transports = new WeakMap();
537
+ threads.clear();
538
+ keyTails.clear();
539
+ warned.clear();
540
+ unavailableUntil = 0;
541
+ }
@@ -101,10 +101,13 @@ export async function instantFlush(key: string): Promise<void> {
101
101
 
102
102
  /**
103
103
  * Fetch thread context from Slack for the buffer flush task description.
104
+ * Messages in `excludeTs` (the buffered follow-ups themselves) are skipped:
105
+ * the description already carries them below the context block.
104
106
  */
105
107
  async function getThreadContextForBuffer(
106
108
  channelId: string,
107
109
  threadTs: string,
110
+ excludeTs: ReadonlySet<string>,
108
111
  botUserId?: string,
109
112
  ): Promise<string> {
110
113
  const app = getSlackApp();
@@ -121,7 +124,7 @@ async function getThreadContextForBuffer(
121
124
  if (messages.length === 0) return "";
122
125
 
123
126
  const formatted = messages
124
- .filter((m) => extractSlackMessageText(m) !== "")
127
+ .filter((m) => !(m.ts && excludeTs.has(m.ts)) && extractSlackMessageText(m) !== "")
125
128
  .map((m) => {
126
129
  const msg = m as Record<string, unknown>;
127
130
  const isBotMessage = msg.bot_id !== undefined || msg.subtype === "bot_message";
@@ -268,7 +271,12 @@ async function slackFlush(
268
271
  const lead = await getLeadAgent();
269
272
 
270
273
  // Thread context for the task
271
- const threadContext = await getThreadContextForBuffer(channelId, threadTs, botUserId);
274
+ const threadContext = await getThreadContextForBuffer(
275
+ channelId,
276
+ threadTs,
277
+ new Set(items.map((item) => item.ts)),
278
+ botUserId,
279
+ );
272
280
  const fullDescription = threadContext
273
281
  ? `<thread_context>\n${threadContext}\n</thread_context>\n\n${description}`
274
282
  : description;
@@ -151,13 +151,14 @@ export type PreparedTaskCreate = { description: string; options: CreateTaskOptio
151
151
  export async function prepareTaskWithSiblingAwareness(
152
152
  description: string,
153
153
  options: CreateTaskOptions,
154
- args: { origin?: TaskCreateOrigin } = {},
154
+ args: { origin?: TaskCreateOrigin; allowCustomModel?: boolean } = {},
155
155
  ): Promise<PreparedTaskCreate> {
156
156
  const preCreate = await applyPreTaskCreate({
157
157
  description,
158
158
  options,
159
159
  // Preserve the plan's REST default for callers without an explicit ingress origin.
160
160
  origin: args.origin ?? "rest",
161
+ allowCustomModel: args.allowCustomModel,
161
162
  });
162
163
  if (preCreate.kind === "blocked") {
163
164
  throw new TaskCreationBlockedError(preCreate.reason, preCreate.extension);
@@ -168,7 +169,7 @@ export async function prepareTaskWithSiblingAwareness(
168
169
  export async function createTaskWithSiblingAwareness(
169
170
  description: string,
170
171
  options: CreateTaskOptions,
171
- args: { origin?: TaskCreateOrigin } = {},
172
+ args: { origin?: TaskCreateOrigin; allowCustomModel?: boolean } = {},
172
173
  ): Promise<AgentTask> {
173
174
  const { description: d, options: o } = await prepareTaskWithSiblingAwareness(
174
175
  description,
package/src/telemetry.ts CHANGED
@@ -26,7 +26,8 @@ let cachedHasEmailChannel = false;
26
26
  let cachedInstallMethod = "manual";
27
27
  let cachedInstallPreset: string | undefined;
28
28
 
29
- function isEnabled(): boolean {
29
+ /** Effective opt-out state: `ANONYMIZED_TELEMETRY` (default on). Read at call time. */
30
+ export function isTelemetryEnabled(): boolean {
30
31
  return isEnvFlagEnabled("ANONYMIZED_TELEMETRY", true);
31
32
  }
32
33
 
@@ -310,7 +311,7 @@ export async function initTelemetry(
310
311
  setConfig: (key: string, value: string) => Promise<void> | void,
311
312
  options: InitTelemetryOptions = {},
312
313
  ): Promise<void> {
313
- if (!isEnabled()) return;
314
+ if (!isTelemetryEnabled()) return;
314
315
  source = sourceId;
315
316
  const generateIfMissing = options.generateIfMissing === true;
316
317
 
@@ -433,7 +434,7 @@ function getTelemetryEnvironment(): string {
433
434
 
434
435
  /** Fire-and-forget telemetry event. Never throws, never blocks. */
435
436
  export function track(options: TrackOptions): void {
436
- if (!isEnabled() || !installationId) return;
437
+ if (!isTelemetryEnabled() || !installationId) return;
437
438
  try {
438
439
  const payload = {
439
440
  product: PRODUCT,
@@ -619,7 +620,7 @@ export function emitIntegrationConnected(
619
620
  providerSlug: string | null | undefined,
620
621
  firstOfType: boolean,
621
622
  ): boolean {
622
- if (!isEnabled() || !installationId) return false;
623
+ if (!isTelemetryEnabled() || !installationId) return false;
623
624
  const provider = _resolveIntegrationProvider(providerSlug);
624
625
  telemetry.integration("connected", {
625
626
  type,
@@ -2,3 +2,9 @@ declare module "*.md" {
2
2
  const content: string;
3
3
  export default content;
4
4
  }
5
+
6
+ // Bun imports a .wasm file as its path (embedded in a compiled binary).
7
+ declare module "*.wasm" {
8
+ const path: string;
9
+ export default path;
10
+ }
@@ -103,11 +103,8 @@ describe("agents list model display", () => {
103
103
  provider: "Anthropic",
104
104
  providerId: "anthropic",
105
105
  });
106
- expect(getAgentModelPresentation("claude-mythos-5-1")).toMatchObject({
107
- label: "Claude Mythos 5.1",
108
- provider: "Anthropic",
109
- providerId: "anthropic",
110
- });
106
+ // claude-mythos-5-1 is invite-only and absent from models.dev: it is
107
+ // listed once an overlay row exists, not from a hard-coded entry.
111
108
  expect(getAgentModelPresentation("claude-opus-5-5")).toMatchObject({
112
109
  label: "Claude Opus 5.5",
113
110
  provider: "Anthropic",
@@ -148,8 +145,9 @@ describe("agents list model display", () => {
148
145
  provider: "Anthropic",
149
146
  providerId: "anthropic",
150
147
  });
148
+ // Newest catalogued mythos (5.1 needs an overlay row; see above).
151
149
  expect(getAgentModelPresentation("mythos")).toMatchObject({
152
- label: "Claude Mythos 5.1",
150
+ label: "Claude Mythos 5",
153
151
  provider: "Anthropic",
154
152
  providerId: "anthropic",
155
153
  });