@desplega.ai/agent-swarm 1.158.0 → 1.159.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 (272) hide show
  1. package/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-qhch0n2v.js} +1 -1
  2. package/dist/{actions-p9hz5yat.js → actions-dtsh1mfb.js} +33 -29
  3. package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-f2h06k64.js} +8 -3
  4. package/dist/{app-g26wdcz6.js → app-y1b9srva.js} +10 -9
  5. package/dist/{assistant-wydfqkb3.js → assistant-31hwy5xj.js} +34 -30
  6. package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-0k1mfax5.js} +12 -13
  7. package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-48d2qpkt.js} +11 -11
  8. package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-w0bryhkr.js} +12 -12
  9. package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-t65wbrn7.js} +9 -8
  10. package/dist/{boot-seeds-s3q01xj3.js → boot-seeds-h7c32hvm.js} +12 -11
  11. package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-1t6611ne.js} +26 -21
  12. package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-werp659j.js} +4 -2
  13. package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-y6rdgbc1.js} +11 -2
  14. package/dist/claude-sdk-session-zhkyk8yw.js +29319 -0
  15. package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
  16. package/dist/{cli-wscdvcqz.js → cli-0n2e4x49.js} +1 -1
  17. package/dist/cli-0xqde53b.js +176 -0
  18. package/dist/{cli-xberxa1r.js → cli-10ym38qc.js} +2 -2
  19. package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
  20. package/dist/{cli-rqz8qbw7.js → cli-20035trh.js} +50 -18
  21. package/dist/{cli-k0g1sjzm.js → cli-31nkw3g3.js} +3 -3
  22. package/dist/cli-3gvrr8vj.js +1422 -0
  23. package/dist/{cli-c07r9a2m.js → cli-48527fpw.js} +9 -8
  24. package/dist/{cli-24k806sc.js → cli-4aj25szq.js} +2 -2
  25. package/dist/{cli-7gtvvpbh.js → cli-4tvgz99m.js} +7 -7
  26. package/dist/cli-503ya656.js +455 -0
  27. package/dist/{cli-cw9zck8j.js → cli-5f2hq128.js} +8 -4
  28. package/dist/{cli-s8eet254.js → cli-68bmme5p.js} +11078 -27833
  29. package/dist/{cli-ftfvggzx.js → cli-79aq9cgb.js} +794 -563
  30. package/dist/cli-7cxw8b60.js +14910 -0
  31. package/dist/cli-7kyk9jr3.js +22 -0
  32. package/dist/cli-7zttzetj.js +324 -0
  33. package/dist/{cli-y95wp3tg.js → cli-96qd5psa.js} +1 -1
  34. package/dist/{cli-g8djp6qe.js → cli-9yg65c1j.js} +22 -0
  35. package/dist/{cli-3gmcwfyx.js → cli-ardpzhjm.js} +5 -5
  36. package/dist/{cli-7s3syk22.js → cli-b6thtzac.js} +5 -5
  37. package/dist/{cli-00w2c66p.js → cli-bgv70kym.js} +13 -2
  38. package/dist/{cli-j55n64b6.js → cli-bv0a38v9.js} +163 -44
  39. package/dist/cli-ca2w7zv7.js +115 -0
  40. package/dist/cli-cpvsaqsw.js +333 -0
  41. package/dist/cli-cygyqj3b.js +754 -0
  42. package/dist/{cli-x24pshtq.js → cli-dvjpkkjd.js} +6 -6
  43. package/dist/{cli-5d10vd2m.js → cli-e0ndxhtk.js} +4 -4
  44. package/dist/{cli-4198qc9v.js → cli-e4xnc0qp.js} +15 -3
  45. package/dist/{cli-rjehvrts.js → cli-eg7cdg68.js} +8 -8
  46. package/dist/cli-ejpygnqz.js +3815 -0
  47. package/dist/{cli-fgqxwrpv.js → cli-fjtp3463.js} +1631 -127
  48. package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
  49. package/dist/{cli-47m6dkd3.js → cli-g9df2m37.js} +409 -168
  50. package/dist/cli-gzyh0j0g.js +8455 -0
  51. package/dist/cli-hgqfttfc.js +183 -0
  52. package/dist/cli-hj65wsts.js +17755 -0
  53. package/dist/cli-j9z8mfx3.js +476 -0
  54. package/dist/{cli-aehm1rbq.js → cli-kc3dp84d.js} +1135 -61
  55. package/dist/{cli-h38x0qhg.js → cli-kdnehe0t.js} +4 -3
  56. package/dist/{cli-20cj37vt.js → cli-m4excttz.js} +136 -2216
  57. package/dist/{cli-8qmme6vd.js → cli-mg24hkt4.js} +2 -2
  58. package/dist/{cli-xhaeg2nc.js → cli-mzkqpxee.js} +1 -1
  59. package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
  60. package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
  61. package/dist/{cli-rj19hc34.js → cli-qjxy52w7.js} +1 -1
  62. package/dist/{cli-53sz8gkz.js → cli-qv4jv8bx.js} +1 -1
  63. package/dist/cli-qvnnx4ct.js +1051 -0
  64. package/dist/cli-rb9m6rpe.js +4980 -0
  65. package/dist/{cli-tt9k8r35.js → cli-rfezr056.js} +476 -909
  66. package/dist/{cli-ggm92wcn.js → cli-rgjprct3.js} +1 -0
  67. package/dist/{cli-9httdpyq.js → cli-rrqmqr1s.js} +1 -1
  68. package/dist/cli-rykfmgwg.js +102 -0
  69. package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
  70. package/dist/{cli-68w09sjw.js → cli-vn0qv9cj.js} +25 -88
  71. package/dist/{cli-987hg0aa.js → cli-vwbbaecp.js} +2 -2
  72. package/dist/cli-wd9hz40d.js +69 -0
  73. package/dist/{cli-j57qkrpq.js → cli-wgjx0q33.js} +13 -13
  74. package/dist/cli-xgpf00sk.js +2149 -0
  75. package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
  76. package/dist/{cli-aky8tctm.js → cli-z9x0sh1t.js} +3 -3
  77. package/dist/{cli-1hkrch3k.js → cli-zw65mab6.js} +5 -5
  78. package/dist/cli.js +38 -29
  79. package/dist/cloudflare-workers-ai-system-one-1sg586qv.js +46 -0
  80. package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-jqfqqa21.js} +21 -14
  81. package/dist/{codex-hook-9hptmdhj.js → codex-hook-5eys3dya.js} +2 -2
  82. package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-zd341dsm.js} +21 -14
  83. package/dist/{commands-e3bn0p75.js → commands-vfsysz4s.js} +9 -8
  84. package/dist/{db-03v9kzr2.js → db-s3znhvbn.js} +15 -10
  85. package/dist/{dispatcher-nrmg8yht.js → dispatcher-5xn0c7rf.js} +32 -28
  86. package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-894mgfvs.js} +3 -3
  87. package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
  88. package/dist/execute-71ysgv35.js +965 -0
  89. package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-1nfcdphp.js} +5 -4
  90. package/dist/{google-vertex-809af514.js → google-vertex-1tqcd7b1.js} +5 -4
  91. package/dist/{handlers-aqcedbyb.js → handlers-5w4jwycr.js} +35 -31
  92. package/dist/{hook-7vkskkre.js → hook-853bxrr6.js} +16 -13
  93. package/dist/{hook-ybvmrmhq.js → hook-z0hbxj85.js} +16 -13
  94. package/dist/{http-2eqa8t24.js → http-akc3wy3m.js} +600 -123
  95. package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
  96. package/dist/{index-a3vn96n8.js → index-5sz16ksh.js} +187 -47
  97. package/dist/{index-c6zsv1bd.js → index-bjmjh9h4.js} +11 -11
  98. package/dist/{index-chgdkq36.js → index-cd151qkq.js} +32 -28
  99. package/dist/{index-x71bvq0p.js → index-k661dzzr.js} +33 -29
  100. package/dist/{index-1yks3qcr.js → index-ttpbhnjg.js} +32 -28
  101. package/dist/{keepalive-vtkf5n99.js → keepalive-8p9rpvz5.js} +11 -10
  102. package/dist/{lead-h3sp357x.js → lead-jp69z5h3.js} +49 -45
  103. package/dist/llama-cpp-classify-t2tccams.js +335 -0
  104. package/dist/{maintenance-jzeqtk3q.js → maintenance-fwx4ev0k.js} +12 -12
  105. package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-bhvwqcnk.js} +14 -17
  106. package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-cwcq1ctx.js} +10 -9
  107. package/dist/{onboard-tmsp0qbz.js → onboard-2b4q0t84.js} +6 -5
  108. package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-nekthgks.js} +21 -8
  109. package/dist/{openai-completions-9hjrqbw8.js → openai-completions-hxtzwj0s.js} +8 -11
  110. package/dist/{openai-responses-7b1pn0xt.js → openai-responses-w4r118w7.js} +22 -17
  111. package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-zz7xvjj6.js} +6 -4
  112. package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-b6sqqjgn.js} +4 -6
  113. package/dist/{otel-impl-9mz73q65.js → otel-impl-g8rs4vwr.js} +1 -1
  114. package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
  115. package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-n0me28wt.js} +29 -17
  116. package/dist/pricing-refresh-0v77385v.js +39 -0
  117. package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-krjyazbj.js} +10 -9
  118. package/dist/{render-v2-gryrh9h8.js → render-v2-3wjsp9aw.js} +10 -9
  119. package/dist/runtime-ktjysacx.js +24 -0
  120. package/dist/{setup-q9720bt6.js → setup-yeqryke6.js} +6 -5
  121. package/dist/typesafe-system-one-96960jxp.js +27 -0
  122. package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-dzdeq1sf.js} +21 -13
  123. package/dist/{work-objects-b76x7deb.js → work-objects-3pakpkw0.js} +35 -31
  124. package/dist/{worker-6vrxbv41.js → worker-85testgj.js} +49 -45
  125. package/openapi.json +863 -14
  126. package/package.json +9 -8
  127. package/src/be/db/agents.ts +20 -0
  128. package/src/be/db/tasks/human-free.ts +442 -0
  129. package/src/be/db/tasks/read.ts +56 -1
  130. package/src/be/db/tasks/write.ts +107 -11
  131. package/src/be/db.ts +89 -165
  132. package/src/be/harness-model-support.ts +182 -0
  133. package/src/be/human-free-drain.ts +67 -0
  134. package/src/be/migrations/182_task_human_free_flag.sql +63 -0
  135. package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
  136. package/src/be/migrations/184_model_catalog.sql +120 -0
  137. package/src/be/model-catalog-store.ts +644 -0
  138. package/src/be/model-tier-keys.ts +31 -0
  139. package/src/be/model-tier-resolution.ts +489 -0
  140. package/src/be/model-validation.ts +112 -0
  141. package/src/be/models-catalog.ts +61 -7
  142. package/src/be/modelsdev-cache.json +82 -0
  143. package/src/be/modelsdev-cache.ts +2 -0
  144. package/src/be/pricing-refresh.ts +154 -12
  145. package/src/be/scripts/typecheck.ts +15 -0
  146. package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
  147. package/src/be/swarm-config-guard.ts +6 -0
  148. package/src/cli.tsx +10 -0
  149. package/src/commands/claude-managed-setup.ts +3 -1
  150. package/src/commands/runner.ts +76 -2
  151. package/src/extensions/contract-types.generated.ts +1 -1
  152. package/src/heartbeat/heartbeat.ts +64 -15
  153. package/src/heartbeat/templates.ts +2 -2
  154. package/src/http/agents.ts +49 -1
  155. package/src/http/index.ts +8 -0
  156. package/src/http/models-catalog.ts +357 -8
  157. package/src/http/poll.ts +45 -2
  158. package/src/http/schedules.ts +36 -6
  159. package/src/http/session-data.ts +2 -5
  160. package/src/http/stats.ts +6 -8
  161. package/src/http/tasks.ts +26 -1
  162. package/src/http/usage-cache.ts +56 -15
  163. package/src/http/workflows.ts +35 -8
  164. package/src/prompts/session-templates.ts +1 -0
  165. package/src/providers/claude-managed-models.ts +47 -161
  166. package/src/providers/codex-adapter.ts +4 -4
  167. package/src/providers/codex-models.ts +65 -198
  168. package/src/providers/reasoning-effort.ts +44 -140
  169. package/src/rbac/legacy-policy.ts +25 -0
  170. package/src/rbac/permissions.ts +10 -0
  171. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  172. package/src/scripts-runtime/types/stdlib.d.ts +15 -0
  173. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  174. package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
  175. package/src/server.ts +6 -0
  176. package/src/slack/message-text.ts +56 -2
  177. package/src/slack/thread-buffer.ts +10 -2
  178. package/src/tests/agents-list-model-display.test.ts +4 -6
  179. package/src/tests/base-prompt.test.ts +3 -0
  180. package/src/tests/claude-adapter.test.ts +10 -0
  181. package/src/tests/claude-managed-adapter.test.ts +12 -18
  182. package/src/tests/codex-adapter.test.ts +2 -1
  183. package/src/tests/codex-gpt6-release.test.ts +66 -5
  184. package/src/tests/context-window.test.ts +7 -5
  185. package/src/tests/db-extraction-contract.test.ts +1 -0
  186. package/src/tests/db-task-read-contract.test.ts +5 -1
  187. package/src/tests/db-task-write-contract.test.ts +4 -1
  188. package/src/tests/docs-index-footprint.test.js +3 -3
  189. package/src/tests/explicit-model-validation.test.ts +332 -0
  190. package/src/tests/harness-model-support.test.ts +374 -0
  191. package/src/tests/heartbeat-checklist-config.test.ts +71 -0
  192. package/src/tests/heartbeat-checklist.test.ts +4 -2
  193. package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
  194. package/src/tests/human-free-flag.test.ts +279 -0
  195. package/src/tests/human-free-oracle.ts +57 -0
  196. package/src/tests/human-free-reclassify.test.ts +669 -0
  197. package/src/tests/migration-046-budgets.test.ts +3 -3
  198. package/src/tests/model-catalog-migration.test.ts +134 -0
  199. package/src/tests/model-catalog-table.test.ts +249 -0
  200. package/src/tests/model-catalog-write-access.test.ts +387 -0
  201. package/src/tests/model-tier-resolution.test.ts +519 -0
  202. package/src/tests/models-catalog.test.ts +1 -1
  203. package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
  204. package/src/tests/package-publish.test.ts +137 -36
  205. package/src/tests/pricing-routes.test.ts +2 -2
  206. package/src/tests/rbac-engine.test.ts +42 -2
  207. package/src/tests/reasoning-effort.test.ts +109 -0
  208. package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
  209. package/src/tests/runtime-model-catalog.test.ts +86 -0
  210. package/src/tests/script-connections.test.ts +5 -0
  211. package/src/tests/seed-scripts.test.ts +89 -0
  212. package/src/tests/session-costs.test.ts +14 -11
  213. package/src/tests/slack-message-text.test.ts +101 -0
  214. package/src/tests/slack-thread-buffer-context.test.ts +93 -0
  215. package/src/tests/task-cascade-fail.test.ts +148 -2
  216. package/src/tests/tool-annotations.test.ts +1 -1
  217. package/src/tests/usage-cache.test.ts +166 -0
  218. package/src/tests/workflow-executors.test.ts +5 -2
  219. package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
  220. package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
  221. package/src/tests/workflow-system-one-decision.test.ts +2063 -0
  222. package/src/tools/model-catalog/index.ts +137 -0
  223. package/src/tools/schedules/create-schedule.ts +16 -1
  224. package/src/tools/schedules/patch-schedule.ts +17 -0
  225. package/src/tools/schedules/update-schedule.ts +17 -0
  226. package/src/tools/send-task.ts +14 -0
  227. package/src/tools/task-action.ts +12 -0
  228. package/src/tools/tool-config.ts +3 -1
  229. package/src/tools/workflows/create-workflow.ts +9 -2
  230. package/src/tools/workflows/patch-workflow-node.ts +17 -7
  231. package/src/tools/workflows/patch-workflow.ts +9 -2
  232. package/src/tools/workflows/update-workflow.ts +9 -2
  233. package/src/types.ts +30 -1
  234. package/src/utils/context-window.ts +47 -53
  235. package/src/utils/harness-cli-version.ts +115 -0
  236. package/src/utils/harness-model-error.ts +33 -0
  237. package/src/utils/runtime-model-catalog.ts +137 -0
  238. package/src/workflows/approval-resolution.ts +39 -0
  239. package/src/workflows/definition.ts +7 -0
  240. package/src/workflows/engine.ts +69 -1
  241. package/src/workflows/executors/agent-task.ts +9 -0
  242. package/src/workflows/executors/base.ts +53 -0
  243. package/src/workflows/executors/human-in-the-loop.ts +8 -8
  244. package/src/workflows/executors/index.ts +1 -0
  245. package/src/workflows/executors/registry.ts +2 -0
  246. package/src/workflows/executors/system-one-decision.ts +1232 -0
  247. package/src/workflows/executors/system-one-providers.ts +301 -0
  248. package/src/workflows/executors/workflow-llm.ts +13 -1
  249. package/src/workflows/readiness.ts +86 -0
  250. package/src/workflows/recovery.ts +12 -12
  251. package/src/workflows/resume.ts +23 -9
  252. package/templates/schedules/daily-blocker-digest/content.md +4 -5
  253. package/templates/skills/agent-browser/SKILL.md +6 -5
  254. package/templates/skills/agent-browser/content.md +6 -5
  255. package/templates/skills/heartbeat-runbook/config.json +1 -1
  256. package/templates/skills/heartbeat-runbook/content.md +2 -2
  257. package/templates/skills/swarm-scripts/SKILL.md +1 -1
  258. package/templates/skills/swarm-scripts/content.md +1 -1
  259. package/templates/skills/workflow-iterate/content.md +81 -1
  260. package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
  261. package/templates/workflows/pr-review-status-sweep/content.md +2 -0
  262. package/dist/claude-sdk-session-t442qz6f.js +0 -39108
  263. package/dist/cli-0xjkj1j7.js +0 -61
  264. package/dist/cli-65hjmev5.js +0 -1141
  265. package/dist/cli-aw1jwp1r.js +0 -14075
  266. package/dist/cli-h967nb6r.js +0 -6603
  267. package/dist/cli-vapyk8sw.js +0 -20
  268. package/dist/cli-ws6rqycm.js +0 -247
  269. package/dist/pricing-refresh-v1cbr9te.js +0 -27
  270. package/src/providers/modelsdev-reasoning.json +0 -6453
  271. package/dist/{codex-login-x3jk4etn.js → codex-login-he9vrn90.js} +3 -3
  272. package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
package/src/http/tasks.ts CHANGED
@@ -27,6 +27,7 @@ import {
27
27
  pauseTask,
28
28
  promoteDraftTask,
29
29
  resumeTask,
30
+ settleSupersededTaskDependents,
30
31
  supersedeTask,
31
32
  updateAgentStatusFromCapacity,
32
33
  updateTaskClaudeSessionId,
@@ -34,6 +35,7 @@ import {
34
35
  updateTaskTitle,
35
36
  updateTaskVcs,
36
37
  } from "../be/db";
38
+ import { explicitModelErrorForAgent } from "../be/model-validation";
37
39
  import {
38
40
  getTaskSteeringFields,
39
41
  markSteeringUndeliverable,
@@ -102,6 +104,9 @@ const AgentTaskSummarySchema = AgentTaskSchema.pick({
102
104
  scheduleId: true,
103
105
  model: true,
104
106
  modelTier: true,
107
+ resolvedModel: true,
108
+ modelSource: true,
109
+ modelAlias: true,
105
110
  effort: true,
106
111
  provider: true,
107
112
  requestedByUserId: true,
@@ -266,6 +271,11 @@ const createTask = route({
266
271
  requestedByUserId: z.string().optional(),
267
272
  model: z.string().optional(),
268
273
  modelTier: ModelTierSchema.optional(),
274
+ /**
275
+ * Accept a `model` the catalog does not list. Without it an unknown id is a 400; with it
276
+ * the id is stored as given (a fresh launch, a private deployment).
277
+ */
278
+ allowCustomModel: z.boolean().optional(),
269
279
  effort: ReasoningEffortSchema.optional(),
270
280
  /**
271
281
  * Create in `draft` status instead of the normal pending/unassigned/offered
@@ -287,7 +297,10 @@ const createTask = route({
287
297
  }),
288
298
  responses: {
289
299
  201: { description: "Task created", schema: AgentTaskSchema },
290
- 400: { description: "Validation error, or agentId/offeredTo targets an extension identity" },
300
+ 400: {
301
+ description:
302
+ "Validation error, an unknown `model` (set `allowCustomModel` to store a custom id), or agentId/offeredTo targets an extension identity",
303
+ },
291
304
  422: {
292
305
  description: "Task creation blocked by an extension",
293
306
  schema: z.object({
@@ -860,6 +873,17 @@ export async function handleTasks(
860
873
  if (lead) defaultAgentId = lead.id;
861
874
  }
862
875
 
876
+ const modelError = await explicitModelErrorForAgent({
877
+ model: splitLegacyModelAlias({ model: parsed.body.model, modelTier: parsed.body.modelTier })
878
+ .model,
879
+ allowCustomModel: parsed.body.allowCustomModel,
880
+ agentId: defaultAgentId,
881
+ });
882
+ if (modelError) {
883
+ jsonError(res, modelError, 400);
884
+ return true;
885
+ }
886
+
863
887
  const parentTask = parsed.body.parentTaskId
864
888
  ? await getTaskById(parsed.body.parentTaskId)
865
889
  : null;
@@ -1750,6 +1774,7 @@ export async function handleTasks(
1750
1774
  // `skipped` covers parent_not_found / lead_not_found edge cases — the
1751
1775
  // supersede already landed, so log + roll forward without a resume task.
1752
1776
  if (followUp.kind !== "created") {
1777
+ await settleSupersededTaskDependents(parsed.params.id, null);
1753
1778
  console.warn(
1754
1779
  `[Supersede] Task ${parsed.params.id.slice(0, 8)} superseded but resume creation skipped (${
1755
1780
  followUp.kind === "skipped" ? followUp.reason : followUp.kind
@@ -3,30 +3,71 @@
3
3
  * `/api/attribution/by-person`). Each open usage page polls them, and every
4
4
  * reply aggregates the whole window.
5
5
  *
6
- * A key holds the request filters plus a data version (see
7
- * `getUsageDataVersion`), so a new session or task misses the cache at once.
8
- * Changes to existing rows (a task re-attributed to another requester) show
9
- * after at most `USAGE_CACHE_TTL_MS`. A credential plan or name change
10
- * clears the cache.
6
+ * A key holds only the request filters. An entry is fresh for
7
+ * `USAGE_CACHE_TTL_MS` (the time bucket). Past that it is served as-is while one
8
+ * background load revalidates it, until its total age reaches
9
+ * `USAGE_CACHE_MAX_STALE_MS`; an older or missing entry blocks on a load.
10
+ * Concurrent misses for one key share that load.
11
+ *
12
+ * The key deliberately carries no data version. The old one changed on every
13
+ * new task or session, and the swarm creates one every few seconds, so it
14
+ * almost never hit. The cost is staleness: the first request past the bucket
15
+ * still gets the old value and only starts the refresh, so a new session shows
16
+ * up on the request after that. A client polling once per bucket sees it about
17
+ * one poll later. A credential plan or name change clears the cache.
11
18
  */
12
19
 
13
20
  export const USAGE_CACHE_TTL_MS = 30_000;
21
+ export const USAGE_CACHE_MAX_STALE_MS = 120_000;
14
22
  const MAX_ENTRIES = 100;
15
23
 
16
- const entries = new Map<string, { expiresAt: number; value: unknown }>();
24
+ const entries = new Map<string, { fetchedAt: number; value: unknown }>();
25
+ const loading = new Map<string, Promise<unknown>>();
26
+ // Bumped by `clearUsageCache`, so a load that started before the clear cannot
27
+ // write its (now outdated) result back.
28
+ let generation = 0;
29
+
30
+ function load<T>(key: string, loader: () => Promise<T>): Promise<T> {
31
+ const running = loading.get(key);
32
+ if (running) return running as Promise<T>;
33
+
34
+ const startedIn = generation;
35
+ const promise: Promise<T> = Promise.resolve()
36
+ .then(loader)
37
+ .then((value) => {
38
+ if (startedIn === generation) {
39
+ entries.delete(key);
40
+ // Map order is insertion order, so the first key is the oldest entry.
41
+ if (entries.size >= MAX_ENTRIES) entries.delete(entries.keys().next().value as string);
42
+ entries.set(key, { fetchedAt: Date.now(), value });
43
+ }
44
+ return value;
45
+ })
46
+ .finally(() => {
47
+ if (loading.get(key) === promise) loading.delete(key);
48
+ });
49
+ loading.set(key, promise);
50
+ return promise;
51
+ }
17
52
 
18
- export async function cachedUsageReport<T>(key: string, load: () => Promise<T>): Promise<T> {
19
- const now = Date.now();
53
+ export async function cachedUsageReport<T>(key: string, loader: () => Promise<T>): Promise<T> {
20
54
  const hit = entries.get(key);
21
- if (hit && hit.expiresAt > now) return hit.value as T;
22
- const value = await load();
23
- entries.delete(key);
24
- // Map order is insertion order, so the first key is the oldest entry.
25
- if (entries.size >= MAX_ENTRIES) entries.delete(entries.keys().next().value as string);
26
- entries.set(key, { expiresAt: now + USAGE_CACHE_TTL_MS, value });
27
- return value;
55
+ if (hit) {
56
+ const age = Date.now() - hit.fetchedAt;
57
+ if (age < USAGE_CACHE_TTL_MS) return hit.value as T;
58
+ if (age < USAGE_CACHE_MAX_STALE_MS) {
59
+ // Keep serving the stale value; a failed refresh retries on the next request.
60
+ load(key, loader).catch((error) => {
61
+ console.error(`[usage-cache] background refresh failed for ${key.split(":")[0]}:`, error);
62
+ });
63
+ return hit.value as T;
64
+ }
65
+ }
66
+ return load(key, loader);
28
67
  }
29
68
 
30
69
  export function clearUsageCache(): void {
70
+ generation++;
31
71
  entries.clear();
72
+ loading.clear();
32
73
  }
@@ -21,6 +21,7 @@ import {
21
21
  CooldownConfigSchema,
22
22
  InputValueSchema,
23
23
  TriggerConfigSchema,
24
+ type WorkflowDefinition,
24
25
  WorkflowDefinitionSchema,
25
26
  WorkflowEdgeSchema,
26
27
  WorkflowNodePatchSchema,
@@ -37,6 +38,7 @@ import { definitionNodeIds, generateEdges, validateDefinition } from "../workflo
37
38
  import { TriggerSchemaError } from "../workflows/engine";
38
39
  import { validateJsonSchema } from "../workflows/json-schema-validator";
39
40
  import { patchWorkflowDefinition } from "../workflows/patch-definition";
41
+ import { workflowSaveWarnings } from "../workflows/readiness";
40
42
  import { cancelWorkflowRun, retryFailedRun } from "../workflows/resume";
41
43
  import { handleWebhookTrigger, WebhookError } from "../workflows/triggers";
42
44
  import { snapshotAndUpdateWorkflow } from "../workflows/version";
@@ -49,6 +51,14 @@ import { jsonError, parseBody, triggerSchemaErrorResponse } from "./utils";
49
51
  /** `Workflow` decorated with the caller-scoped favorite flag (always set once `withFavoriteFlags` runs). */
50
52
  const WorkflowWithFavoriteSchema = WorkflowSchema.extend({ favorite: z.boolean() });
51
53
 
54
+ /**
55
+ * `Workflow` returned by a save (create / update / patch). `warnings` is present only when the
56
+ * save succeeded but a node cannot run yet, e.g. a system-one-decision node whose provider key is not configured.
57
+ */
58
+ const WorkflowSaveResponseSchema = WorkflowSchema.extend({
59
+ warnings: z.array(z.string()).optional(),
60
+ });
61
+
52
62
  /** `/api/workflows` slim list item — mirrors `WorkflowSummary` in src/types.ts (no exported schema there). */
53
63
  const WorkflowSummarySchema = WorkflowSchema.omit({
54
64
  definition: true,
@@ -85,6 +95,14 @@ const WorkflowRunPageSchema = z.object({
85
95
 
86
96
  const SuccessResponseSchema = z.object({ success: z.literal(true) });
87
97
 
98
+ /** Adds `warnings` to a just-saved workflow when a node in it cannot run yet. */
99
+ async function withSaveWarnings<T extends { definition: WorkflowDefinition }>(
100
+ workflow: T,
101
+ ): Promise<T & { warnings?: string[] }> {
102
+ const warnings = await workflowSaveWarnings(workflow.definition, getExecutorRegistry());
103
+ return warnings.length > 0 ? { ...workflow, warnings } : workflow;
104
+ }
105
+
88
106
  // ─── Route Definitions ───────────────────────────────────────────────────────
89
107
 
90
108
  const listWorkflowsRoute = route({
@@ -137,7 +155,7 @@ const createWorkflowRoute = route({
137
155
  vcsRepo: z.string().min(1).optional(),
138
156
  }),
139
157
  responses: {
140
- 201: { description: "Workflow created", schema: WorkflowSchema },
158
+ 201: { description: "Workflow created", schema: WorkflowSaveResponseSchema },
141
159
  400: { description: "Invalid definition" },
142
160
  },
143
161
  });
@@ -188,7 +206,10 @@ const updateWorkflowRoute = route({
188
206
  enabled: z.boolean().optional(),
189
207
  }),
190
208
  responses: {
191
- 200: { description: "Workflow updated (version snapshot created)", schema: WorkflowSchema },
209
+ 200: {
210
+ description: "Workflow updated (version snapshot created)",
211
+ schema: WorkflowSaveResponseSchema,
212
+ },
192
213
  400: { description: "Invalid definition" },
193
214
  404: { description: "Workflow not found" },
194
215
  },
@@ -203,7 +224,10 @@ const patchWorkflowRoute = route({
203
224
  params: z.object({ id: z.string() }),
204
225
  body: WorkflowPatchSchema.extend({ key: AssetKeySchema.optional() }),
205
226
  responses: {
206
- 200: { description: "Workflow patched (version snapshot created)", schema: WorkflowSchema },
227
+ 200: {
228
+ description: "Workflow patched (version snapshot created)",
229
+ schema: WorkflowSaveResponseSchema,
230
+ },
207
231
  400: { description: "Invalid patch or resulting definition" },
208
232
  404: { description: "Workflow not found" },
209
233
  },
@@ -218,7 +242,10 @@ const patchWorkflowNodeRoute = route({
218
242
  params: z.object({ id: z.string(), nodeId: z.string() }),
219
243
  body: WorkflowNodePatchSchema,
220
244
  responses: {
221
- 200: { description: "Node patched (version snapshot created)", schema: WorkflowSchema },
245
+ 200: {
246
+ description: "Node patched (version snapshot created)",
247
+ schema: WorkflowSaveResponseSchema,
248
+ },
222
249
  400: { description: "Invalid patch or resulting definition" },
223
250
  404: { description: "Workflow or node not found" },
224
251
  },
@@ -583,7 +610,7 @@ export async function handleWorkflows(
583
610
  },
584
611
  "api",
585
612
  );
586
- createWorkflowRoute.respond(res, 201, workflow);
613
+ createWorkflowRoute.respond(res, 201, await withSaveWarnings(workflow));
587
614
  return true;
588
615
  }
589
616
 
@@ -638,7 +665,7 @@ export async function handleWorkflows(
638
665
  );
639
666
  return true;
640
667
  }
641
- patchWorkflowNodeRoute.respond(res, 200, result.workflow);
668
+ patchWorkflowNodeRoute.respond(res, 200, await withSaveWarnings(result.workflow));
642
669
  return true;
643
670
  }
644
671
 
@@ -695,7 +722,7 @@ export async function handleWorkflows(
695
722
  );
696
723
  return true;
697
724
  }
698
- patchWorkflowRoute.respond(res, 200, result.workflow);
725
+ patchWorkflowRoute.respond(res, 200, await withSaveWarnings(result.workflow));
699
726
  return true;
700
727
  }
701
728
 
@@ -766,7 +793,7 @@ export async function handleWorkflows(
766
793
  res.end();
767
794
  return true;
768
795
  }
769
- updateWorkflowRoute.respond(res, 200, workflow);
796
+ updateWorkflowRoute.respond(res, 200, await withSaveWarnings(workflow));
770
797
  return true;
771
798
  }
772
799
 
@@ -202,6 +202,7 @@ agent-fs is the shared drive between agents and people. Documents (reports, dige
202
202
  Pages: only for polished artifacts shared with a wider audience or explicitly requested; see \`pages\` skill.
203
203
  A tool a person will use, with data and actions: build an app. See the \`apps\` skill.
204
204
  Link env: \`APP_URL\` for pages, \`MCP_BASE_URL\` for the API, \`AGENT_FS_LIVE_URL\` for files. Report missing variables.
205
+ For a file anyone outside the swarm opens, use an \`agent-fs share-create\` link; \`AGENT_FS_LIVE_URL\` links are for signed-in teammates.
205
206
  `,
206
207
  variables: [],
207
208
  category: "system",
@@ -1,48 +1,32 @@
1
1
  /**
2
- * Pricing table for Anthropic-managed Claude models, mirroring the layout of
3
- * `src/providers/codex-models.ts`. Rates are USD per million tokens (Mtok)
4
- * sourced from https://platform.claude.com/docs/en/about-claude/pricing
5
- * (verified 2026-09-02).
2
+ * Anthropic-managed Claude model facts, read from the model catalog
3
+ * (model-catalog phase 4). Rates are USD per million tokens (Mtok).
6
4
  *
7
- * The managed-agents API does NOT report dollar cost on the `span.model_request_end`
8
- * event — only token counts (`input_tokens`, `output_tokens`,
9
- * `cache_read_input_tokens`, `cache_creation_input_tokens`). Phase 4 of the
10
- * provider plan computes USD locally via {@link computeClaudeManagedCostUsd},
11
- * then folds in Anthropic's $0.08/session-hour runtime fee inside the adapter.
5
+ * There is no hand-maintained model list or price table any more: both come
6
+ * from the runtime catalog (`src/utils/runtime-model-catalog.ts` — the API's
7
+ * `model_catalog` + overlay rows, pulled over HTTP; the vendored models.dev
8
+ * snapshot offline). A new Claude model is listed and priced as soon as it
9
+ * lands in `model_catalog`, with no code change and no redeploy.
12
10
  *
13
- * Bump this file when Anthropic publishes new rates or new models.
11
+ * The managed-agents API does NOT report dollar cost on the
12
+ * `span.model_request_end` event — only token counts. The adapter computes
13
+ * USD locally via {@link computeClaudeManagedCostUsd}, then folds in
14
+ * Anthropic's $0.08/session-hour runtime fee. The API server recomputes the
15
+ * canonical price against the pricing table.
14
16
  *
15
17
  * Cache nomenclature mapping:
16
- * - `cache_read_input_tokens` → "cache hit" rate (cheapest)
17
- * - `cache_creation_input_tokens` → "cache write" rate (input × 1.25 for 5m TTL)
18
- * - regular `input_tokens` (uncached) → standard input rate
19
- *
20
- * Anthropic's pricing page lists 5-minute and 1-hour cache TTLs separately;
21
- * managed-agents currently uses the 5-minute breakpoint by default, which is
22
- * the rate captured here. If a future SDK release surfaces TTL on the usage
23
- * payload, refine these by TTL.
18
+ * - `cache_read_input_tokens` → catalog `cache_read`
19
+ * - `cache_creation_input_tokens` → catalog `cache_write` (5-minute TTL)
20
+ * - regular `input_tokens` (uncached) → catalog `input`
24
21
  */
22
+ import { harnessModelIds } from "@desplega/model-catalog";
23
+ import { runtimeCatalogModel, runtimeCatalogSection } from "../utils/runtime-model-catalog";
25
24
 
26
- /** Models supported by the managed-agents surface for the swarm worker. */
27
- export const CLAUDE_MANAGED_MODELS = [
28
- "claude-fable-5-1",
29
- "claude-mythos-5-1",
30
- "claude-opus-5-5",
31
- "claude-opus-5",
32
- "claude-fable-5",
33
- "claude-mythos-5",
34
- "claude-sonnet-5-5",
35
- "claude-sonnet-5",
36
- "claude-sonnet-4-6",
37
- "claude-opus-4-8",
38
- "claude-opus-4-7",
39
- "claude-opus-4-6",
40
- "claude-haiku-4-5",
41
- ] as const;
42
-
43
- export type ClaudeManagedModel = (typeof CLAUDE_MANAGED_MODELS)[number];
25
+ /** Managed-agents-selectable Claude models from the catalog, newest first. */
26
+ export function listClaudeManagedModels(): string[] {
27
+ return harnessModelIds("claude-managed", runtimeCatalogSection("anthropic"));
28
+ }
44
29
 
45
- /** Token rates per million tokens (USD). */
46
30
  export interface ClaudeManagedTokenPricing {
47
31
  /** USD per million uncached input tokens. */
48
32
  inputPerMillion: number;
@@ -54,131 +38,39 @@ export interface ClaudeManagedTokenPricing {
54
38
  cacheWritePerMillion: number;
55
39
  }
56
40
 
57
- /** Pricing per million tokens (USD), including an optional scheduled rate change. */
58
- export interface ClaudeManagedModelPricing extends ClaudeManagedTokenPricing {
59
- scheduledChange?: {
60
- /** ISO timestamp at which the replacement rates take effect. */
61
- effectiveAt: string;
62
- pricing: ClaudeManagedTokenPricing;
63
- };
64
- }
41
+ export type ClaudeManagedModelPricing = ClaudeManagedTokenPricing;
65
42
 
66
43
  /**
67
- * Anthropic public list pricing. Source:
68
- * https://platform.claude.com/docs/en/about-claude/pricing
69
- *
70
- * - claude-fable-5-1: $10 / $50 / $0.25 / $12.50 (verified 2026-09-02)
71
- * - claude-mythos-5-1: $10 / $50 / $0.25 / $12.50 (invite only, verified 2026-09-02)
72
- * - claude-fable-5: $10 / $50 / $1.00 / $12.50 (verified 2026-06-10)
73
- * - claude-mythos-5: $10 / $50 / $1.00 / $12.50 (limited availability, verified 2026-06-10)
74
- * - claude-opus-5-5: $4 / $20 / $0.20 / $5.00 (verified 2026-09-22)
75
- * - claude-opus-5: $5 / $25 / $0.50 / $6.25 (verified 2026-07-25)
76
- * - claude-sonnet-5-5: $2 / $10 / $0.20 / $2.50 (verified 2026-09-28)
77
- * - claude-sonnet-5: $2 / $10 / $0.20 / $2.50 (standard rate, verified 2026-08-29)
78
- * - claude-sonnet-4-6: $3 / $15 / $0.30 / $3.75 (in / out / cache-read / cache-write)
79
- * - claude-opus-4-8: $5 / $25 / $0.50 / $6.25 (verified 2026-05-28)
80
- * - claude-opus-4-7: $15 / $75 / $1.50 / $18.75 (STALE — was correct at launch, Anthropic has since dropped Opus to $5/$25)
81
- * - claude-opus-4-6: $5 / $25 / $0.50 / $6.25 (verified 2026-05-28)
82
- * - claude-haiku-4-5: $1 / $5 / $0.10 / $1.25
44
+ * Catalog pricing for a managed model. Dated ids fall back to their undated
45
+ * family id. Missing cache rates follow Anthropic's published multipliers
46
+ * (read = 0.1 × input, 5-minute write = 1.25 × input).
83
47
  */
84
- export const CLAUDE_MANAGED_MODEL_PRICING: Record<ClaudeManagedModel, ClaudeManagedModelPricing> = {
85
- "claude-fable-5-1": {
86
- inputPerMillion: 10.0,
87
- outputPerMillion: 50.0,
88
- cacheReadPerMillion: 0.25,
89
- cacheWritePerMillion: 12.5,
90
- },
91
- "claude-mythos-5-1": {
92
- inputPerMillion: 10.0,
93
- outputPerMillion: 50.0,
94
- cacheReadPerMillion: 0.25,
95
- cacheWritePerMillion: 12.5,
96
- },
97
- "claude-opus-5-5": {
98
- inputPerMillion: 4.0,
99
- outputPerMillion: 20.0,
100
- cacheReadPerMillion: 0.2,
101
- cacheWritePerMillion: 5.0,
102
- },
103
- "claude-opus-5": {
104
- inputPerMillion: 5.0,
105
- outputPerMillion: 25.0,
106
- cacheReadPerMillion: 0.5,
107
- cacheWritePerMillion: 6.25,
108
- },
109
- "claude-fable-5": {
110
- inputPerMillion: 10.0,
111
- outputPerMillion: 50.0,
112
- cacheReadPerMillion: 1.0,
113
- cacheWritePerMillion: 12.5,
114
- },
115
- "claude-mythos-5": {
116
- inputPerMillion: 10.0,
117
- outputPerMillion: 50.0,
118
- cacheReadPerMillion: 1.0,
119
- cacheWritePerMillion: 12.5,
120
- },
121
- "claude-sonnet-5-5": {
122
- inputPerMillion: 2.0,
123
- outputPerMillion: 10.0,
124
- cacheReadPerMillion: 0.2,
125
- cacheWritePerMillion: 2.5,
126
- },
127
- "claude-sonnet-5": {
128
- inputPerMillion: 2.0,
129
- outputPerMillion: 10.0,
130
- cacheReadPerMillion: 0.2,
131
- cacheWritePerMillion: 2.5,
132
- },
133
- "claude-sonnet-4-6": {
134
- inputPerMillion: 3.0,
135
- outputPerMillion: 15.0,
136
- cacheReadPerMillion: 0.3,
137
- cacheWritePerMillion: 3.75,
138
- },
139
- "claude-opus-4-8": {
140
- inputPerMillion: 5.0,
141
- outputPerMillion: 25.0,
142
- cacheReadPerMillion: 0.5,
143
- cacheWritePerMillion: 6.25,
144
- },
145
- "claude-opus-4-7": {
146
- inputPerMillion: 15.0,
147
- outputPerMillion: 75.0,
148
- cacheReadPerMillion: 1.5,
149
- cacheWritePerMillion: 18.75,
150
- },
151
- "claude-opus-4-6": {
152
- inputPerMillion: 5.0,
153
- outputPerMillion: 25.0,
154
- cacheReadPerMillion: 0.5,
155
- cacheWritePerMillion: 6.25,
156
- },
157
- "claude-haiku-4-5": {
158
- inputPerMillion: 1.0,
159
- outputPerMillion: 5.0,
160
- cacheReadPerMillion: 0.1,
161
- cacheWritePerMillion: 1.25,
162
- },
163
- };
48
+ export function getClaudeManagedModelPricing(model: string): ClaudeManagedModelPricing | undefined {
49
+ const cost =
50
+ runtimeCatalogModel("anthropic", model)?.cost ??
51
+ runtimeCatalogModel("anthropic", model.replace(/-\d{8}$/, ""))?.cost;
52
+ if (typeof cost?.input !== "number" || typeof cost.output !== "number") return undefined;
53
+ return {
54
+ inputPerMillion: cost.input,
55
+ outputPerMillion: cost.output,
56
+ cacheReadPerMillion: typeof cost.cache_read === "number" ? cost.cache_read : cost.input * 0.1,
57
+ cacheWritePerMillion:
58
+ typeof cost.cache_write === "number" ? cost.cache_write : cost.input * 1.25,
59
+ };
60
+ }
164
61
 
165
- /**
166
- * Models we've already warned about — keeps `console.warn` from spamming the
167
- * worker logs when an old session keeps replaying through `span.model_request_end`
168
- * events with an unrecognized model string.
169
- */
62
+ /** One warning per model per process for unpriced models. */
170
63
  const warnedUnknownModels = new Set<string>();
171
64
 
172
65
  /**
173
66
  * Compute USD cost for one Claude managed-agents session, given the SDK's
174
67
  * accumulated token counts.
175
68
  *
176
- * Returns `0` (with a deduplicated `console.warn`) for unknown model strings —
177
- * we'd rather under-report than make up a number on a typo.
69
+ * Returns `0` (with a deduplicated `console.warn`) for models the catalog does
70
+ * not price — we'd rather under-report than make up a number on a typo.
178
71
  *
179
- * Note: the runtime $0.08/session-hour fee is NOT folded in here. The adapter
180
- * computes that separately because it depends on the session's wallclock
181
- * `durationMs`, which is provider-state, not token-state.
72
+ * Note: the runtime $0.08/session-hour fee is NOT folded in here; the adapter
73
+ * computes it from the session's wallclock.
182
74
  */
183
75
  export function computeClaudeManagedCostUsd(
184
76
  model: string,
@@ -186,24 +78,18 @@ export function computeClaudeManagedCostUsd(
186
78
  outputTokens: number,
187
79
  cacheReadTokens: number,
188
80
  cacheWriteTokens: number,
189
- pricedAtMs: number = Date.now(),
190
81
  ): number {
191
- const configuredPricing = CLAUDE_MANAGED_MODEL_PRICING[model as ClaudeManagedModel];
192
- if (!configuredPricing) {
82
+ const pricing = getClaudeManagedModelPricing(model);
83
+ if (!pricing) {
193
84
  if (!warnedUnknownModels.has(model)) {
194
85
  warnedUnknownModels.add(model);
195
86
  console.warn(
196
- `[claude-managed-models] Unknown model "${model}" — returning $0 cost. ` +
197
- `Add it to CLAUDE_MANAGED_MODEL_PRICING in src/providers/claude-managed-models.ts.`,
87
+ `[claude-managed-models] Unpriced model "${model}" — returning $0 cost. ` +
88
+ "Add it to the model catalog (refresh or overlay row).",
198
89
  );
199
90
  }
200
91
  return 0;
201
92
  }
202
- const pricing =
203
- configuredPricing.scheduledChange &&
204
- pricedAtMs >= Date.parse(configuredPricing.scheduledChange.effectiveAt)
205
- ? configuredPricing.scheduledChange.pricing
206
- : configuredPricing;
207
93
  const inputCost = (inputTokens / 1_000_000) * pricing.inputPerMillion;
208
94
  const outputCost = (outputTokens / 1_000_000) * pricing.outputPerMillion;
209
95
  const cacheReadCost = (cacheReadTokens / 1_000_000) * pricing.cacheReadPerMillion;
@@ -558,16 +558,16 @@ export async function buildCodexConfig(
558
558
  // at `~/.codex/config.toml` (Phase 6). Repeating them here makes local dev
559
559
  // (no baseline file) behave identically to the Docker worker.
560
560
  //
561
- // `features.hooks` / `features.plugin_hooks` enable Codex's hook system and
562
- // the hooks contributed by installed Codex plugins (context-mode's plugin:
563
- // routing injection, PreToolUse safety blocks, output capture).
561
+ // Keep ChatGPT apps/connectors disabled because their `codex_apps` tools use
562
+ // the worker's host OAuth identity. Hooks remain enabled for Codex steering
563
+ // and the installed context-mode plugin.
564
564
  return {
565
565
  model,
566
566
  approval_policy: "never",
567
567
  sandbox_mode: "danger-full-access",
568
568
  skip_git_repo_check: true,
569
569
  show_raw_agent_reasoning: false,
570
- features: { hooks: true, plugin_hooks: true },
570
+ features: { apps: false, hooks: true, plugin_hooks: true },
571
571
  mcp_servers: mcpServers as CodexConfig,
572
572
  ...reasoningConfig,
573
573
  };