@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
@@ -0,0 +1,2149 @@
1
+ import {
2
+ createSwarmEventHandler
3
+ } from "./cli-hsxj0yar.js";
4
+ import {
5
+ resolveCodexPrompt
6
+ } from "./cli-r94z9p1z.js";
7
+ import {
8
+ readPkgVersion
9
+ } from "./cli-2djtr51b.js";
10
+ import {
11
+ buildOtelTraceparentEnv
12
+ } from "./cli-q2j38vre.js";
13
+ import {
14
+ summarizeSession
15
+ } from "./cli-5f2hq128.js";
16
+ import {
17
+ CTX_MODE_NUDGE_EVERY
18
+ } from "./cli-zm7njwzd.js";
19
+ import {
20
+ CONTEXT_FORMULA,
21
+ clampContextPercent,
22
+ computeContextUsedUnified
23
+ } from "./cli-wd9hz40d.js";
24
+ import {
25
+ SessionErrorTracker
26
+ } from "./cli-npssxgaz.js";
27
+ import {
28
+ applyReasoningEffort
29
+ } from "./cli-ca2w7zv7.js";
30
+ import {
31
+ buildRatingsFromLlm,
32
+ fetchRetrievalsForTask,
33
+ getMemoryRaterNames,
34
+ postRatings
35
+ } from "./cli-cpvsaqsw.js";
36
+ import {
37
+ swarmRuntimeInstanceId
38
+ } from "./cli-re8z5p8p.js";
39
+ import {
40
+ runtimeCatalogModel
41
+ } from "./cli-m4excttz.js";
42
+ import {
43
+ detachedProcessGroup,
44
+ registerProcessGroup,
45
+ terminateProcessGroup
46
+ } from "./cli-y89t4nca.js";
47
+ import {
48
+ getApiKey
49
+ } from "./cli-f14fvzag.js";
50
+ import {
51
+ CodexOAuthRefreshError,
52
+ credentialsToAuthJson,
53
+ getValidCodexOAuth
54
+ } from "./cli-vhjwh6cz.js";
55
+ import {
56
+ DEFAULT_MODEL_TIER_MAP,
57
+ init_types
58
+ } from "./cli-e4xnc0qp.js";
59
+ import {
60
+ init_src
61
+ } from "./cli-7zttzetj.js";
62
+ import {
63
+ init_package,
64
+ package_default
65
+ } from "./cli-48527fpw.js";
66
+ import {
67
+ init_secret_scrubber,
68
+ scrubSecrets
69
+ } from "./cli-r2ap2czm.js";
70
+ import {
71
+ __require
72
+ } from "./cli-bc05qbss.js";
73
+
74
+ // src/providers/codex-adapter.ts
75
+ import { existsSync as nodeExistsSync } from "node:fs";
76
+ import { join as join2 } from "node:path";
77
+ init_secret_scrubber();
78
+
79
+ // src/providers/codex-agents-md.ts
80
+ import { lstat, mkdir, readlink, realpath, symlink, unlink } from "node:fs/promises";
81
+ import { homedir } from "node:os";
82
+ import { dirname, join, resolve, sep } from "node:path";
83
+ var BLOCK_OPEN = "<swarm_system_prompt>";
84
+ var BLOCK_CLOSE = "</swarm_system_prompt>";
85
+ var BLOCK_REGEX = /<swarm_system_prompt>[\s\S]*?<\/swarm_system_prompt>\n?/;
86
+ var DEFAULT_STATE_DIR = join(homedir(), ".cache", "agent-swarm", "codex-agents-md");
87
+ var NOOP_HANDLE = {
88
+ cleanup: async () => {}
89
+ };
90
+ async function readIfExists(path) {
91
+ const file = Bun.file(path);
92
+ return await file.exists() ? await file.text() : null;
93
+ }
94
+ function recordPathFor(stateDir, realCwd) {
95
+ const key = new Bun.CryptoHasher("sha256").update(realCwd).digest("hex");
96
+ return join(stateDir, `${key}.json`);
97
+ }
98
+ async function readRecord(recordPath, realCwd) {
99
+ try {
100
+ const record = JSON.parse(await Bun.file(recordPath).text());
101
+ if (record.cwd === realCwd && typeof record.linkTarget === "string") {
102
+ return { cwd: record.cwd, linkTarget: record.linkTarget };
103
+ }
104
+ } catch {}
105
+ return null;
106
+ }
107
+ async function inTreeTarget(realCwd, agentsMdPath, linkTarget) {
108
+ const target = await realpath(resolve(dirname(agentsMdPath), linkTarget)).catch(() => null);
109
+ const prefix = realCwd.endsWith(sep) ? realCwd : `${realCwd}${sep}`;
110
+ if (target === null || !target.startsWith(prefix)) {
111
+ return null;
112
+ }
113
+ const stat = await lstat(target).catch(() => null);
114
+ return stat?.isFile() ? target : null;
115
+ }
116
+ async function restoreSymlink(agentsMdPath, realCwd, linkTarget) {
117
+ const stat = await lstat(agentsMdPath).catch(() => null);
118
+ const current = stat?.isFile() ? await Bun.file(agentsMdPath).text() : null;
119
+ await unlink(agentsMdPath).catch(() => {});
120
+ await symlink(linkTarget, agentsMdPath);
121
+ if (current === null) {
122
+ return;
123
+ }
124
+ const target = await inTreeTarget(realCwd, agentsMdPath, linkTarget);
125
+ if (target === null) {
126
+ return;
127
+ }
128
+ const stripped = current.replace(BLOCK_REGEX, "");
129
+ if (stripped !== await Bun.file(target).text()) {
130
+ await Bun.write(target, stripped);
131
+ }
132
+ }
133
+ async function writeCodexAgentsMd(cwd, systemPrompt, options = {}) {
134
+ if (!cwd || !systemPrompt) {
135
+ return NOOP_HANDLE;
136
+ }
137
+ const agentsMdPath = join(cwd, "AGENTS.md");
138
+ const claudeMdPath = join(cwd, "CLAUDE.md");
139
+ const stateDir = options.stateDir ?? DEFAULT_STATE_DIR;
140
+ const realCwd = await realpath(cwd).catch(() => resolve(cwd));
141
+ const recordPath = recordPathFor(stateDir, realCwd);
142
+ const block = `${BLOCK_OPEN}
143
+ ${systemPrompt}
144
+ ${BLOCK_CLOSE}`;
145
+ let stat = await lstat(agentsMdPath).catch(() => null);
146
+ const record = await readRecord(recordPath, realCwd);
147
+ if (record) {
148
+ if (stat?.isFile() && BLOCK_REGEX.test(await Bun.file(agentsMdPath).text())) {
149
+ await restoreSymlink(agentsMdPath, realCwd, record.linkTarget);
150
+ stat = await lstat(agentsMdPath);
151
+ }
152
+ await unlink(recordPath).catch(() => {});
153
+ }
154
+ if (stat?.isSymbolicLink()) {
155
+ const linkTarget = await readlink(agentsMdPath);
156
+ const target = await inTreeTarget(realCwd, agentsMdPath, linkTarget);
157
+ const targetContent = target !== null ? await Bun.file(target).text() : "";
158
+ await mkdir(stateDir, { recursive: true });
159
+ await Bun.write(recordPath, JSON.stringify({ cwd: realCwd, linkTarget }));
160
+ await unlink(agentsMdPath);
161
+ await Bun.write(agentsMdPath, `${block}
162
+ ${targetContent.replace(BLOCK_REGEX, "")}`);
163
+ return {
164
+ async cleanup() {
165
+ try {
166
+ await restoreSymlink(agentsMdPath, realCwd, linkTarget);
167
+ await unlink(recordPath).catch(() => {});
168
+ } catch {}
169
+ }
170
+ };
171
+ }
172
+ let createdFresh = false;
173
+ let newContent;
174
+ if (!stat) {
175
+ const claudeContent = await readIfExists(claudeMdPath);
176
+ newContent = claudeContent !== null ? `${block}
177
+
178
+ ${claudeContent}` : `${block}
179
+ `;
180
+ createdFresh = true;
181
+ } else {
182
+ const existingContent = await Bun.file(agentsMdPath).text();
183
+ if (BLOCK_REGEX.test(existingContent)) {
184
+ newContent = existingContent.replace(BLOCK_REGEX, `${block}
185
+ `);
186
+ } else {
187
+ newContent = `${block}
188
+ ${existingContent}`;
189
+ }
190
+ }
191
+ await Bun.write(agentsMdPath, newContent);
192
+ return {
193
+ async cleanup() {
194
+ try {
195
+ if (createdFresh) {
196
+ await Bun.$`rm -f ${agentsMdPath}`.quiet().nothrow();
197
+ return;
198
+ }
199
+ const currentFile = Bun.file(agentsMdPath);
200
+ if (!await currentFile.exists()) {
201
+ return;
202
+ }
203
+ const currentContent = await currentFile.text();
204
+ const stripped = currentContent.replace(BLOCK_REGEX, "");
205
+ await Bun.write(agentsMdPath, stripped);
206
+ } catch {}
207
+ }
208
+ };
209
+ }
210
+
211
+ // src/providers/codex-app-server.ts
212
+ init_package();
213
+ init_secret_scrubber();
214
+ var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
215
+ var CLOSE_GRACE_MS = 250;
216
+ var EXIT_STREAM_DRAIN_MS = 100;
217
+ var MAX_STDOUT_LINE_LENGTH = 16 * 1024 * 1024;
218
+ var MAX_STDERR_LINE_LENGTH = 64 * 1024;
219
+ var MAX_STDERR_TAIL_LENGTH = 16 * 1024;
220
+
221
+ class CodexAppServerRpcError extends Error {
222
+ code;
223
+ data;
224
+ constructor(method, error) {
225
+ super(`Codex app-server ${method} failed (${error.code}): ${error.message}`);
226
+ this.name = "CodexAppServerRpcError";
227
+ this.code = error.code;
228
+ this.data = error.data;
229
+ }
230
+ }
231
+
232
+ class CodexAppServer {
233
+ proc;
234
+ pending = new Map;
235
+ notificationListeners = new Set;
236
+ closeListeners = new Set;
237
+ requestTimeoutMs;
238
+ onStderr;
239
+ ready;
240
+ stderrDone;
241
+ nextId = 0;
242
+ stderrTail = "";
243
+ initializedVersion;
244
+ terminalError;
245
+ closePromise;
246
+ constructor(options) {
247
+ this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
248
+ if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs <= 0) {
249
+ throw new Error("Codex app-server requestTimeoutMs must be a positive number");
250
+ }
251
+ this.onStderr = options.onStderr;
252
+ const codexPath = options.codexPath ?? resolveCodexPath(options.env.PATH);
253
+ const command = [codexPath, "app-server", "--listen", "stdio://"];
254
+ const env = Object.fromEntries(Object.entries(options.env).filter((entry) => entry[1] !== undefined));
255
+ this.proc = Bun.spawn(command, {
256
+ env,
257
+ stdin: "pipe",
258
+ stdout: "pipe",
259
+ stderr: "pipe"
260
+ });
261
+ this.consumeStdout().catch((error) => this.fail(asError(error)));
262
+ this.stderrDone = this.consumeStderr().catch((error) => {
263
+ this.fail(asError(error));
264
+ });
265
+ this.proc.exited.then(async (exitCode) => {
266
+ await Promise.race([this.stderrDone, Bun.sleep(EXIT_STREAM_DRAIN_MS)]);
267
+ if (this.closePromise)
268
+ return;
269
+ const suffix = this.stderrTail ? `: ${this.stderrTail}` : "";
270
+ this.fail(new Error(`Codex app-server exited with code ${exitCode}${suffix}`));
271
+ });
272
+ this.ready = Promise.resolve().then(() => this.initialize());
273
+ this.ready.catch(() => {});
274
+ }
275
+ async request(method, params) {
276
+ await this.ready;
277
+ return this.requestRaw(method, params);
278
+ }
279
+ get version() {
280
+ return this.initializedVersion;
281
+ }
282
+ onNotification(listener) {
283
+ this.notificationListeners.add(listener);
284
+ return () => this.notificationListeners.delete(listener);
285
+ }
286
+ onClose(listener) {
287
+ if (this.terminalError) {
288
+ listener(this.terminalError);
289
+ return () => {};
290
+ }
291
+ this.closeListeners.add(listener);
292
+ return () => this.closeListeners.delete(listener);
293
+ }
294
+ close() {
295
+ if (this.closePromise)
296
+ return this.closePromise;
297
+ this.rejectPending(new Error("Codex app-server closed"));
298
+ this.closePromise = this.terminate();
299
+ return this.closePromise;
300
+ }
301
+ async initialize() {
302
+ const response = await this.requestRaw("initialize", {
303
+ clientInfo: {
304
+ name: "agent-swarm",
305
+ title: "Agent Swarm",
306
+ version: package_default.version
307
+ },
308
+ capabilities: {
309
+ experimentalApi: false,
310
+ requestAttestation: false
311
+ }
312
+ });
313
+ if (typeof response.userAgent === "string") {
314
+ this.initializedVersion = parseVersion(response.userAgent);
315
+ }
316
+ this.write({ method: "initialized" });
317
+ }
318
+ requestRaw(method, params) {
319
+ if (this.terminalError)
320
+ return Promise.reject(this.terminalError);
321
+ if (this.closePromise)
322
+ return Promise.reject(new Error("Codex app-server closed"));
323
+ const id = this.nextId++;
324
+ return new Promise((resolve2, reject) => {
325
+ const timer = setTimeout(() => {
326
+ this.pending.delete(id);
327
+ reject(new Error(`Codex app-server ${method} timed out after ${this.requestTimeoutMs}ms`));
328
+ }, this.requestTimeoutMs);
329
+ this.pending.set(id, {
330
+ method,
331
+ resolve: resolve2,
332
+ reject,
333
+ timer
334
+ });
335
+ try {
336
+ this.write(params === undefined ? { method, id } : { method, id, params });
337
+ } catch (error) {
338
+ clearTimeout(timer);
339
+ this.pending.delete(id);
340
+ reject(asError(error));
341
+ }
342
+ });
343
+ }
344
+ write(message) {
345
+ if (this.terminalError)
346
+ throw this.terminalError;
347
+ if (this.closePromise)
348
+ throw new Error("Codex app-server closed");
349
+ const stdin = this.proc.stdin;
350
+ stdin.write(`${JSON.stringify(message)}
351
+ `);
352
+ stdin.flush();
353
+ }
354
+ async consumeStdout() {
355
+ await readLines(this.proc.stdout, MAX_STDOUT_LINE_LENGTH, (line) => this.handleLine(line));
356
+ }
357
+ async consumeStderr() {
358
+ await readLines(this.proc.stderr, MAX_STDERR_LINE_LENGTH, (line) => {
359
+ const scrubbed = scrubSecrets(line);
360
+ this.stderrTail = `${this.stderrTail}${this.stderrTail ? `
361
+ ` : ""}${scrubbed}`.slice(-MAX_STDERR_TAIL_LENGTH);
362
+ if (!this.onStderr)
363
+ return;
364
+ try {
365
+ this.onStderr(scrubbed);
366
+ } catch (error) {
367
+ console.error(`[codex-app-server] stderr listener failed: ${scrubSecrets(asError(error).message)}`);
368
+ }
369
+ });
370
+ }
371
+ handleLine(line) {
372
+ let message;
373
+ try {
374
+ message = JSON.parse(line);
375
+ } catch (error) {
376
+ throw new Error("Codex app-server emitted malformed JSON", {
377
+ cause: error
378
+ });
379
+ }
380
+ if (!isRecord(message)) {
381
+ throw new Error("Codex app-server emitted a non-object JSON message");
382
+ }
383
+ if (isRequestId(message.id)) {
384
+ if (typeof message.method === "string") {
385
+ this.rejectServerRequest(message.id, message.method);
386
+ return;
387
+ }
388
+ this.handleResponse(message.id, message);
389
+ return;
390
+ }
391
+ if (typeof message.method === "string") {
392
+ const notification = {
393
+ method: message.method,
394
+ ...Object.hasOwn(message, "params") ? { params: message.params } : {},
395
+ ...typeof message.emittedAtMs === "number" ? { emittedAtMs: message.emittedAtMs } : {}
396
+ };
397
+ for (const listener of this.notificationListeners) {
398
+ try {
399
+ listener(notification);
400
+ } catch (error) {
401
+ console.error(`[codex-app-server] notification listener failed: ${scrubSecrets(asError(error).message)}`);
402
+ }
403
+ }
404
+ return;
405
+ }
406
+ throw new Error("Codex app-server emitted an invalid protocol message");
407
+ }
408
+ handleResponse(id, message) {
409
+ const pending = this.pending.get(id);
410
+ if (!pending)
411
+ return;
412
+ this.pending.delete(id);
413
+ clearTimeout(pending.timer);
414
+ if (isRpcError(message.error)) {
415
+ pending.reject(new CodexAppServerRpcError(pending.method, message.error));
416
+ return;
417
+ }
418
+ if (Object.hasOwn(message, "result")) {
419
+ pending.resolve(message.result);
420
+ return;
421
+ }
422
+ pending.reject(new Error(`Codex app-server returned an invalid response for ${pending.method}`));
423
+ }
424
+ rejectServerRequest(id, method) {
425
+ this.write({
426
+ id,
427
+ error: {
428
+ code: -32601,
429
+ message: `Client does not support server request: ${method}`
430
+ }
431
+ });
432
+ }
433
+ fail(error) {
434
+ if (this.terminalError || this.closePromise)
435
+ return;
436
+ this.terminalError = error;
437
+ this.rejectPending(error);
438
+ this.closePromise = this.terminate();
439
+ for (const listener of this.closeListeners) {
440
+ try {
441
+ listener(error);
442
+ } catch (listenerError) {
443
+ console.error(`[codex-app-server] close listener failed: ${scrubSecrets(asError(listenerError).message)}`);
444
+ }
445
+ }
446
+ this.closeListeners.clear();
447
+ }
448
+ rejectPending(error) {
449
+ for (const pending of this.pending.values()) {
450
+ clearTimeout(pending.timer);
451
+ pending.reject(error);
452
+ }
453
+ this.pending.clear();
454
+ }
455
+ async terminate() {
456
+ try {
457
+ this.proc.stdin.end();
458
+ } catch {}
459
+ try {
460
+ this.proc.kill("SIGTERM");
461
+ } catch {
462
+ return;
463
+ }
464
+ const exited = this.proc.exited.then(() => {
465
+ return;
466
+ });
467
+ await Promise.race([exited, Bun.sleep(CLOSE_GRACE_MS)]);
468
+ if (this.proc.exitCode === null) {
469
+ try {
470
+ this.proc.kill("SIGKILL");
471
+ } catch {
472
+ return;
473
+ }
474
+ await Promise.race([exited, Bun.sleep(CLOSE_GRACE_MS)]);
475
+ }
476
+ }
477
+ }
478
+ async function readLines(stream, maxLineLength, onLine) {
479
+ const reader = stream.getReader();
480
+ const decoder = new TextDecoder;
481
+ let buffered = "";
482
+ try {
483
+ while (true) {
484
+ const { done, value } = await reader.read();
485
+ buffered += decoder.decode(value, { stream: !done });
486
+ let newline = buffered.indexOf(`
487
+ `);
488
+ while (newline !== -1) {
489
+ const line = buffered.slice(0, newline).replace(/\r$/, "");
490
+ buffered = buffered.slice(newline + 1);
491
+ if (line.length > maxLineLength) {
492
+ throw new Error(`Codex app-server output line exceeded ${maxLineLength} characters`);
493
+ }
494
+ if (line)
495
+ onLine(line);
496
+ newline = buffered.indexOf(`
497
+ `);
498
+ }
499
+ if (buffered.length > maxLineLength) {
500
+ throw new Error(`Codex app-server output line exceeded ${maxLineLength} characters`);
501
+ }
502
+ if (done)
503
+ break;
504
+ }
505
+ if (buffered) {
506
+ const line = buffered.replace(/\r$/, "");
507
+ if (line.length > maxLineLength) {
508
+ throw new Error(`Codex app-server output line exceeded ${maxLineLength} characters`);
509
+ }
510
+ onLine(line);
511
+ }
512
+ } finally {
513
+ reader.releaseLock();
514
+ }
515
+ }
516
+ function resolveCodexPath(path) {
517
+ const fromPath = Bun.which("codex", { PATH: path ?? process.env.PATH });
518
+ if (fromPath)
519
+ return fromPath;
520
+ try {
521
+ return Bun.resolveSync("@openai/codex/bin/codex.js", import.meta.dir);
522
+ } catch {
523
+ return "codex";
524
+ }
525
+ }
526
+ function parseVersion(userAgent) {
527
+ return userAgent.match(/\b\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?\b/)?.[0];
528
+ }
529
+ function isRecord(value) {
530
+ return typeof value === "object" && value !== null && !Array.isArray(value);
531
+ }
532
+ function isRequestId(value) {
533
+ return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
534
+ }
535
+ function isRpcError(value) {
536
+ return isRecord(value) && typeof value.code === "number" && Number.isFinite(value.code) && typeof value.message === "string";
537
+ }
538
+ function asError(value) {
539
+ return value instanceof Error ? value : new Error(String(value));
540
+ }
541
+
542
+ // src/providers/codex-models.ts
543
+ init_src();
544
+ init_types();
545
+ var CODEX_DEFAULT_MODEL = DEFAULT_MODEL_TIER_MAP.codex.regular;
546
+ var CLAUDE_SHORTNAME_TIER = {
547
+ fable: "ultra",
548
+ opus: "smart",
549
+ sonnet: "regular",
550
+ haiku: "smol"
551
+ };
552
+ function resolveCodexModel(modelStr) {
553
+ if (!modelStr)
554
+ return CODEX_DEFAULT_MODEL;
555
+ const normalized = modelStr.toLowerCase();
556
+ const tier = CLAUDE_SHORTNAME_TIER[normalized];
557
+ return tier ? DEFAULT_MODEL_TIER_MAP.codex[tier] : normalized;
558
+ }
559
+ var UNKNOWN_CONTEXT_WINDOW = 200000;
560
+ function getCodexContextWindow(model) {
561
+ const context = runtimeCatalogModel("openai", model)?.limit?.context;
562
+ return typeof context === "number" && context > 0 ? context : UNKNOWN_CONTEXT_WINDOW;
563
+ }
564
+ function getCodexModelPricing(model) {
565
+ const cost = runtimeCatalogModel("openai", model)?.cost;
566
+ if (typeof cost?.input !== "number" || typeof cost.output !== "number")
567
+ return;
568
+ return {
569
+ inputPerMillion: cost.input,
570
+ cachedInputPerMillion: typeof cost.cache_read === "number" ? cost.cache_read : cost.input / 10,
571
+ outputPerMillion: cost.output
572
+ };
573
+ }
574
+ var _warnedUnknownCodexModels = new Set;
575
+ function computeCodexCostUsd(model, inputTokens, cachedInputTokens, outputTokens) {
576
+ const pricing = getCodexModelPricing(model);
577
+ if (!pricing) {
578
+ if (!_warnedUnknownCodexModels.has(model)) {
579
+ _warnedUnknownCodexModels.add(model);
580
+ console.warn(`[codex] unpriced model ${JSON.stringify(model)} — adapter cost will report $0; ` + "add it to the model catalog (refresh or overlay row). Server-side recompute will tag costSource='unpriced' if the pricing table has no rows.");
581
+ }
582
+ return 0;
583
+ }
584
+ const uncachedInput = Math.max(0, inputTokens - cachedInputTokens);
585
+ const inputCost = uncachedInput / 1e6 * pricing.inputPerMillion;
586
+ const cachedCost = cachedInputTokens / 1e6 * pricing.cachedInputPerMillion;
587
+ const outputCost = outputTokens / 1e6 * pricing.outputPerMillion;
588
+ return inputCost + cachedCost + outputCost;
589
+ }
590
+
591
+ // src/providers/codex-oauth/home.ts
592
+ import os from "node:os";
593
+ function codexUserHome() {
594
+ return process.env.HOME || os.homedir();
595
+ }
596
+
597
+ // src/providers/codex-swarm-events.ts
598
+ function createCodexSwarmEventHandler(opts) {
599
+ return createSwarmEventHandler({
600
+ ...opts,
601
+ sessionIdFallbackPrefix: "codex"
602
+ });
603
+ }
604
+
605
+ // src/providers/codex-adapter.ts
606
+ function checkCodexCredentials(env, opts = {}) {
607
+ const homeDir = opts.homeDir ?? env.HOME ?? "/root";
608
+ const existsSync = opts.fs?.existsSync ?? nodeExistsSync;
609
+ const authFile = `${homeDir}/.codex/auth.json`;
610
+ if (existsSync(authFile)) {
611
+ return { ready: true, missing: [], satisfiedBy: "file" };
612
+ }
613
+ if (env.OPENAI_API_KEY || env.CODEX_OAUTH) {
614
+ return {
615
+ ready: true,
616
+ missing: [],
617
+ satisfiedBy: "side-effect-pending",
618
+ hint: "Credential present in env; entrypoint will materialise ~/.codex/auth.json on next boot."
619
+ };
620
+ }
621
+ if (Object.keys(env).some((k) => /^codex_oauth_\d+$/.test(k))) {
622
+ return {
623
+ ready: true,
624
+ missing: [],
625
+ satisfiedBy: "side-effect-pending",
626
+ hint: "Codex OAuth credential pool configured; runner will materialise auth.json per-task."
627
+ };
628
+ }
629
+ return {
630
+ ready: false,
631
+ missing: ["OPENAI_API_KEY", "CODEX_OAUTH", authFile],
632
+ hint: "Set OPENAI_API_KEY (entrypoint runs `codex login --with-api-key`), or store CODEX_OAUTH in swarm_config, or place a pre-authenticated `~/.codex/auth.json` in the worker home."
633
+ };
634
+ }
635
+ async function resolveCodexAuthMode(config, emit, deps = {}) {
636
+ const fsModule = await import("node:fs/promises");
637
+ const homedir2 = deps.homedir ?? codexUserHome;
638
+ const fs = deps.fs ?? {
639
+ readFile: (path, encoding) => fsModule.readFile(path, encoding),
640
+ mkdir: (path, opts) => fsModule.mkdir(path, opts),
641
+ writeFile: (path, data, opts) => fsModule.writeFile(path, data, opts)
642
+ };
643
+ const authJsonPath = join2(homedir2(), ".codex", "auth.json");
644
+ const readAuthMode = async () => {
645
+ try {
646
+ const raw = await fs.readFile(authJsonPath, "utf-8");
647
+ const parsed = JSON.parse(raw);
648
+ return typeof parsed.auth_mode === "string" ? parsed.auth_mode : null;
649
+ } catch {
650
+ return null;
651
+ }
652
+ };
653
+ let currentMode = await readAuthMode();
654
+ const slot = config.codexSlot ?? 0;
655
+ const isPoolSlot = config.codexSlot !== undefined;
656
+ if (config.apiUrl && config.apiKey && (currentMode !== "chatgpt" || isPoolSlot)) {
657
+ let oauthCreds = null;
658
+ try {
659
+ oauthCreds = await getValidCodexOAuth(config.apiUrl, config.apiKey, slot);
660
+ } catch (err) {
661
+ if (isPoolSlot) {
662
+ throw new Error(buildPoolRevalidationFailureReason(err, slot));
663
+ }
664
+ const message = err instanceof Error ? err.message : String(err);
665
+ emit({
666
+ type: "raw_stderr",
667
+ content: `[codex] OAuth revalidation failed (non-fatal, non-pool auth): ${message}
668
+ `
669
+ });
670
+ }
671
+ if (!oauthCreds && isPoolSlot) {
672
+ throw new Error(`[auth-error] Codex pool slot ${slot} revalidation failed: no credentials found in config store — the slot may have just been quarantined; re-run codex-login for this slot if this persists.`);
673
+ }
674
+ if (oauthCreds) {
675
+ try {
676
+ const authJson = credentialsToAuthJson(oauthCreds, {
677
+ includeRefreshToken: !isPoolSlot
678
+ });
679
+ await fs.mkdir(join2(homedir2(), ".codex"), { recursive: true, mode: 448 });
680
+ await fs.writeFile(authJsonPath, JSON.stringify(authJson, null, 2), { mode: 384 });
681
+ const verb = currentMode === null ? "Restored" : currentMode === "chatgpt" ? "Revalidated" : "Upgraded api-key auth.json to";
682
+ emit({
683
+ type: "raw_stderr",
684
+ content: `[codex] ${verb} OAuth credentials from config store
685
+ `
686
+ });
687
+ currentMode = "chatgpt";
688
+ } catch (err) {
689
+ const message = err instanceof Error ? err.message : String(err);
690
+ emit({
691
+ type: "raw_stderr",
692
+ content: `[codex] Failed to write auth.json: ${message}
693
+ `
694
+ });
695
+ }
696
+ }
697
+ }
698
+ return currentMode;
699
+ }
700
+ function buildPoolRevalidationFailureReason(err, slot) {
701
+ if (err instanceof CodexOAuthRefreshError) {
702
+ if (err.reason === "lock_timeout") {
703
+ return `[auth-error] Codex pool slot ${slot} [...${err.keySuffix}] revalidation failed: timed out waiting for the refresh lock — transient, will retry on next task.`;
704
+ }
705
+ return `[auth-error] Codex pool slot ${slot} [...${err.keySuffix}] revalidation failed: refresh rejected (${err.status ?? "unknown status"} ${err.body ?? ""}) — credential likely revoked; re-run codex-login for this slot.`;
706
+ }
707
+ const message = err instanceof Error ? err.message : String(err);
708
+ return `[auth-error] Codex pool slot ${slot} revalidation failed: ${message}`;
709
+ }
710
+ async function buildCodexConfig(config, model, emit) {
711
+ const mcpServers = {};
712
+ const runtimeInstanceId = swarmRuntimeInstanceId();
713
+ mcpServers["agent-swarm"] = {
714
+ url: `${config.apiUrl}/mcp`,
715
+ http_headers: {
716
+ Authorization: `Bearer ${config.apiKey}`,
717
+ "X-Agent-ID": config.agentId,
718
+ "X-Source-Task-Id": config.taskId ?? "",
719
+ ...runtimeInstanceId ? { "X-Runtime-Instance-ID": runtimeInstanceId } : {}
720
+ },
721
+ enabled: true,
722
+ startup_timeout_sec: 30,
723
+ tool_timeout_sec: 120
724
+ };
725
+ if (config.apiUrl && config.apiKey && config.agentId) {
726
+ try {
727
+ const res = await fetch(`${config.apiUrl}/api/agents/${config.agentId}/mcp-servers?resolveSecrets=true`, {
728
+ headers: {
729
+ Authorization: `Bearer ${config.apiKey}`,
730
+ "X-Agent-ID": config.agentId
731
+ }
732
+ });
733
+ if (res.ok) {
734
+ const data = await res.json();
735
+ for (const srv of data.servers ?? []) {
736
+ if (!srv.isActive || !srv.isEnabled)
737
+ continue;
738
+ if (srv.transport === "stdio") {
739
+ if (!srv.command)
740
+ continue;
741
+ let parsedArgs = [];
742
+ try {
743
+ parsedArgs = srv.args ? JSON.parse(srv.args) : [];
744
+ } catch {}
745
+ mcpServers[srv.name] = {
746
+ command: srv.command,
747
+ args: parsedArgs,
748
+ env: srv.resolvedEnv ?? {},
749
+ enabled: true,
750
+ startup_timeout_sec: 30,
751
+ tool_timeout_sec: 120
752
+ };
753
+ continue;
754
+ }
755
+ if (srv.transport === "http") {
756
+ if (!srv.url)
757
+ continue;
758
+ let parsedHeaders = {};
759
+ try {
760
+ parsedHeaders = srv.headers ? JSON.parse(srv.headers) : {};
761
+ } catch {}
762
+ mcpServers[srv.name] = {
763
+ url: srv.url,
764
+ http_headers: { ...parsedHeaders, ...srv.resolvedHeaders ?? {} },
765
+ enabled: true,
766
+ startup_timeout_sec: 30,
767
+ tool_timeout_sec: 120
768
+ };
769
+ continue;
770
+ }
771
+ if (srv.transport === "sse") {
772
+ emit({
773
+ type: "raw_stderr",
774
+ content: `[codex] Skipping MCP server "${srv.name}": SSE transport is not yet supported by Codex (tracked in openai/codex#2129).
775
+ `
776
+ });
777
+ }
778
+ }
779
+ } else {
780
+ emit({
781
+ type: "raw_stderr",
782
+ content: `[codex] Failed to fetch installed MCP servers: HTTP ${res.status}. Continuing with only the swarm MCP server.
783
+ `
784
+ });
785
+ }
786
+ } catch (err) {
787
+ const message = err instanceof Error ? err.message : String(err);
788
+ emit({
789
+ type: "raw_stderr",
790
+ content: `[codex] Failed to fetch installed MCP servers: ${message}. Continuing with only the swarm MCP server.
791
+ `
792
+ });
793
+ }
794
+ }
795
+ if (process.env.CONTEXT_MODE_DISABLED !== "true") {
796
+ mcpServers["context-mode"] = {
797
+ command: "context-mode",
798
+ enabled: true,
799
+ startup_timeout_sec: 30,
800
+ tool_timeout_sec: 120
801
+ };
802
+ }
803
+ const reasoningApplication = applyReasoningEffort("codex", model, config.reasoningEffort);
804
+ const reasoningConfig = reasoningApplication.kind === "codex-config" ? reasoningApplication.config : {};
805
+ return {
806
+ model,
807
+ approval_policy: "never",
808
+ sandbox_mode: "danger-full-access",
809
+ skip_git_repo_check: true,
810
+ show_raw_agent_reasoning: false,
811
+ features: { apps: false, hooks: true, plugin_hooks: true },
812
+ mcp_servers: mcpServers,
813
+ ...reasoningConfig
814
+ };
815
+ }
816
+ function textInput(text) {
817
+ return [{ type: "text", text, text_elements: [] }];
818
+ }
819
+ function normalizeAppServerItem(raw) {
820
+ const id = typeof raw.id === "string" ? raw.id : "unknown";
821
+ switch (raw.type) {
822
+ case "agentMessage":
823
+ return { id, type: "agent_message", text: typeof raw.text === "string" ? raw.text : "" };
824
+ case "reasoning":
825
+ return {
826
+ id,
827
+ type: "reasoning",
828
+ summary: Array.isArray(raw.summary) ? raw.summary.filter((v) => typeof v === "string") : [],
829
+ content: Array.isArray(raw.content) ? raw.content.filter((v) => typeof v === "string") : []
830
+ };
831
+ case "plan":
832
+ return { id, type: "todo_list", items: typeof raw.text === "string" ? [raw.text] : [] };
833
+ case "commandExecution":
834
+ return {
835
+ id,
836
+ type: "command_execution",
837
+ command: typeof raw.command === "string" ? raw.command : "",
838
+ aggregated_output: typeof raw.aggregatedOutput === "string" ? raw.aggregatedOutput : null,
839
+ exit_code: typeof raw.exitCode === "number" ? raw.exitCode : null,
840
+ status: typeof raw.status === "string" ? raw.status : undefined
841
+ };
842
+ case "fileChange":
843
+ return {
844
+ id,
845
+ type: "file_change",
846
+ changes: Array.isArray(raw.changes) ? raw.changes.flatMap((change) => {
847
+ if (!change || typeof change !== "object")
848
+ return [];
849
+ const value = change;
850
+ const kindValue = value.kind;
851
+ const kind = typeof kindValue === "string" ? kindValue : kindValue && typeof kindValue === "object" ? kindValue.type : undefined;
852
+ return [
853
+ {
854
+ kind: typeof kind === "string" ? kind : "update",
855
+ path: typeof value.path === "string" ? value.path : ""
856
+ }
857
+ ];
858
+ }) : [],
859
+ status: typeof raw.status === "string" ? raw.status : undefined
860
+ };
861
+ case "mcpToolCall":
862
+ return {
863
+ id,
864
+ type: "mcp_tool_call",
865
+ server: typeof raw.server === "string" ? raw.server : "",
866
+ tool: typeof raw.tool === "string" ? raw.tool : "",
867
+ arguments: raw.arguments,
868
+ status: typeof raw.status === "string" ? raw.status : undefined,
869
+ result: raw.result,
870
+ error: raw.error
871
+ };
872
+ case "webSearch":
873
+ return { id, type: "web_search", query: typeof raw.query === "string" ? raw.query : "" };
874
+ default:
875
+ return {
876
+ id,
877
+ type: "unknown",
878
+ originalType: typeof raw.type === "string" ? raw.type : undefined,
879
+ value: raw
880
+ };
881
+ }
882
+ }
883
+ function normalizeUsage(raw) {
884
+ if (!raw || typeof raw !== "object")
885
+ return;
886
+ const value = raw;
887
+ return {
888
+ input_tokens: typeof value.inputTokens === "number" ? value.inputTokens : 0,
889
+ cached_input_tokens: typeof value.cachedInputTokens === "number" ? value.cachedInputTokens : 0,
890
+ cache_write_input_tokens: typeof value.cacheWriteInputTokens === "number" ? value.cacheWriteInputTokens : 0,
891
+ output_tokens: typeof value.outputTokens === "number" ? value.outputTokens : 0,
892
+ reasoning_output_tokens: typeof value.reasoningOutputTokens === "number" ? value.reasoningOutputTokens : 0
893
+ };
894
+ }
895
+ function subtractUsage(total, previous) {
896
+ return {
897
+ input_tokens: Math.max(0, total.input_tokens - (previous?.input_tokens ?? 0)),
898
+ cached_input_tokens: Math.max(0, total.cached_input_tokens - (previous?.cached_input_tokens ?? 0)),
899
+ cache_write_input_tokens: Math.max(0, total.cache_write_input_tokens - (previous?.cache_write_input_tokens ?? 0)),
900
+ output_tokens: Math.max(0, total.output_tokens - (previous?.output_tokens ?? 0)),
901
+ reasoning_output_tokens: Math.max(0, total.reasoning_output_tokens - (previous?.reasoning_output_tokens ?? 0))
902
+ };
903
+ }
904
+
905
+ class AppServerCodexThread {
906
+ client;
907
+ id;
908
+ get version() {
909
+ return this.client.version;
910
+ }
911
+ activeTurnId = null;
912
+ activePush = null;
913
+ activeFinish = null;
914
+ activeFail = null;
915
+ latestTotalUsage;
916
+ completedTotalUsage;
917
+ emittedThreadStart = false;
918
+ unsubscribe;
919
+ unsubscribeClose;
920
+ turnWaiters = [];
921
+ constructor(client, threadId) {
922
+ this.client = client;
923
+ this.id = threadId;
924
+ this.unsubscribe = client.onNotification((notification) => this.handleNotification(notification));
925
+ this.unsubscribeClose = client.onClose((error) => this.activeFail?.(error));
926
+ }
927
+ async runStreamed(input) {
928
+ if (this.activePush)
929
+ throw new Error("Codex turn already active");
930
+ const pending = [];
931
+ let wake = null;
932
+ let finished = false;
933
+ let failure = null;
934
+ const push = (event) => {
935
+ pending.push(event);
936
+ wake?.();
937
+ wake = null;
938
+ };
939
+ const finish = () => {
940
+ finished = true;
941
+ wake?.();
942
+ wake = null;
943
+ };
944
+ this.activePush = push;
945
+ this.activeFinish = finish;
946
+ this.activeFail = (error) => {
947
+ failure = error;
948
+ this.activePush = null;
949
+ this.activeFinish = null;
950
+ finish();
951
+ };
952
+ if (!this.emittedThreadStart) {
953
+ this.emittedThreadStart = true;
954
+ push({ type: "thread.started", thread_id: this.id });
955
+ }
956
+ try {
957
+ const response = await this.client.request("turn/start", {
958
+ threadId: this.id,
959
+ input: textInput(input)
960
+ });
961
+ if (!finished)
962
+ this.setActiveTurn(response.turn.id);
963
+ } catch (error) {
964
+ this.activePush = null;
965
+ this.activeFinish = null;
966
+ this.activeFail = null;
967
+ finish();
968
+ throw error;
969
+ }
970
+ const events = async function* () {
971
+ while (!finished || pending.length > 0) {
972
+ if (pending.length === 0) {
973
+ await new Promise((resolve2) => {
974
+ wake = resolve2;
975
+ });
976
+ continue;
977
+ }
978
+ yield pending.shift();
979
+ }
980
+ if (failure)
981
+ throw failure;
982
+ };
983
+ return { events: events() };
984
+ }
985
+ async steer(input) {
986
+ const turnId = this.activeTurnId ?? await new Promise((resolve2, reject) => this.turnWaiters.push({ resolve: resolve2, reject }));
987
+ await this.client.request("turn/steer", {
988
+ threadId: this.id,
989
+ expectedTurnId: turnId,
990
+ input: textInput(input)
991
+ });
992
+ }
993
+ async interrupt() {
994
+ if (!this.activeTurnId)
995
+ return;
996
+ await this.client.request("turn/interrupt", {
997
+ threadId: this.id,
998
+ turnId: this.activeTurnId
999
+ });
1000
+ }
1001
+ async close() {
1002
+ for (const waiter of this.turnWaiters.splice(0)) {
1003
+ waiter.reject(new Error("Codex session has completed"));
1004
+ }
1005
+ this.unsubscribe();
1006
+ this.unsubscribeClose();
1007
+ await this.client.close();
1008
+ }
1009
+ handleNotification(notification) {
1010
+ const params = notification.params && typeof notification.params === "object" ? notification.params : {};
1011
+ if (params.threadId !== undefined && params.threadId !== this.id)
1012
+ return;
1013
+ const push = this.activePush;
1014
+ if (!push)
1015
+ return;
1016
+ switch (notification.method) {
1017
+ case "turn/started": {
1018
+ const turn = params.turn;
1019
+ if (typeof turn?.id === "string")
1020
+ this.setActiveTurn(turn.id);
1021
+ push({ type: "turn.started", turn_id: this.activeTurnId ?? undefined });
1022
+ break;
1023
+ }
1024
+ case "item/started":
1025
+ case "item/completed": {
1026
+ if (!params.item || typeof params.item !== "object")
1027
+ break;
1028
+ push({
1029
+ type: notification.method === "item/started" ? "item.started" : "item.completed",
1030
+ item: normalizeAppServerItem(params.item)
1031
+ });
1032
+ break;
1033
+ }
1034
+ case "item/agentMessage/delta":
1035
+ if (typeof params.itemId === "string" && typeof params.delta === "string") {
1036
+ push({ type: "message.delta", item_id: params.itemId, delta: params.delta });
1037
+ }
1038
+ break;
1039
+ case "thread/tokenUsage/updated": {
1040
+ const tokenUsage = params.tokenUsage;
1041
+ this.latestTotalUsage = normalizeUsage(tokenUsage?.total);
1042
+ break;
1043
+ }
1044
+ case "error": {
1045
+ const error = params.error;
1046
+ if (typeof error?.message === "string") {
1047
+ push({
1048
+ type: "error",
1049
+ message: error.message,
1050
+ willRetry: params.willRetry === true,
1051
+ codexErrorInfo: typeof error.codexErrorInfo === "string" ? error.codexErrorInfo : null
1052
+ });
1053
+ }
1054
+ break;
1055
+ }
1056
+ case "turn/completed": {
1057
+ const turn = params.turn;
1058
+ if (typeof turn?.id === "string" && this.activeTurnId && turn.id !== this.activeTurnId)
1059
+ break;
1060
+ const usage = this.latestTotalUsage ? subtractUsage(this.latestTotalUsage, this.completedTotalUsage) : undefined;
1061
+ if (this.latestTotalUsage)
1062
+ this.completedTotalUsage = this.latestTotalUsage;
1063
+ if (turn?.status === "failed") {
1064
+ push({
1065
+ type: "turn.failed",
1066
+ error: {
1067
+ message: typeof turn.error?.message === "string" ? turn.error.message : "Codex turn failed",
1068
+ codexErrorInfo: typeof turn.error?.codexErrorInfo === "string" ? turn.error.codexErrorInfo : null
1069
+ },
1070
+ usage
1071
+ });
1072
+ } else if (turn?.status === "interrupted") {
1073
+ push({ type: "turn.interrupted", usage });
1074
+ } else {
1075
+ push({ type: "turn.completed", usage });
1076
+ }
1077
+ this.activeTurnId = null;
1078
+ for (const waiter of this.turnWaiters.splice(0)) {
1079
+ waiter.reject(new Error("Codex turn has completed"));
1080
+ }
1081
+ this.activePush = null;
1082
+ this.activeFail = null;
1083
+ this.activeFinish?.();
1084
+ this.activeFinish = null;
1085
+ break;
1086
+ }
1087
+ }
1088
+ }
1089
+ setActiveTurn(turnId) {
1090
+ this.activeTurnId = turnId;
1091
+ for (const waiter of this.turnWaiters.splice(0))
1092
+ waiter.resolve(turnId);
1093
+ }
1094
+ }
1095
+
1096
+ class CodexSession {
1097
+ thread;
1098
+ config;
1099
+ agentsMdHandle;
1100
+ resolvedModel;
1101
+ contextWindow;
1102
+ skillsDir;
1103
+ summarizeDeps;
1104
+ listeners = [];
1105
+ eventQueue = [];
1106
+ logFileHandle;
1107
+ startedAt = Date.now();
1108
+ completionPromise;
1109
+ resolveCompletion;
1110
+ abortController = null;
1111
+ abortReason = "cancelled";
1112
+ queuedPrompts = [];
1113
+ transcript = [];
1114
+ abortRef = { current: null };
1115
+ _sessionId;
1116
+ numTurns = 0;
1117
+ accumulatedUsage = null;
1118
+ aborted = false;
1119
+ settled = false;
1120
+ acceptsSteering = true;
1121
+ errorTracker = new SessionErrorTracker;
1122
+ appliedReasoningEffort;
1123
+ pendingResult = null;
1124
+ constructor(thread, config, agentsMdHandle, resolvedModel, initialEvents = [], skillsDir, summarizeDeps = {}) {
1125
+ this.thread = thread;
1126
+ this.config = config;
1127
+ this.agentsMdHandle = agentsMdHandle;
1128
+ this.resolvedModel = resolvedModel;
1129
+ this.contextWindow = getCodexContextWindow(resolvedModel);
1130
+ this.skillsDir = skillsDir ?? process.env.CODEX_SKILLS_DIR ?? join2(codexUserHome(), ".codex", "skills");
1131
+ this.summarizeDeps = summarizeDeps;
1132
+ this.logFileHandle = Bun.file(config.logFile).writer();
1133
+ const reasoningApplication = applyReasoningEffort("codex", resolvedModel, config.reasoningEffort);
1134
+ this.appliedReasoningEffort = reasoningApplication.kind === "codex-config" ? config.reasoningEffort ?? null : null;
1135
+ this.completionPromise = new Promise((resolve2) => {
1136
+ this.resolveCompletion = resolve2;
1137
+ });
1138
+ if (config.taskId && config.apiUrl && config.apiKey) {
1139
+ this.listeners.push(createCodexSwarmEventHandler({
1140
+ apiUrl: config.apiUrl,
1141
+ apiKey: config.apiKey,
1142
+ agentId: config.agentId,
1143
+ taskId: config.taskId,
1144
+ abortRef: this.abortRef
1145
+ }));
1146
+ }
1147
+ for (const event of initialEvents) {
1148
+ this.emit(event);
1149
+ }
1150
+ this.runSession();
1151
+ }
1152
+ get sessionId() {
1153
+ return this._sessionId ?? this.thread.id ?? undefined;
1154
+ }
1155
+ onEvent(listener) {
1156
+ this.listeners.push(listener);
1157
+ for (const event of this.eventQueue) {
1158
+ listener(event);
1159
+ }
1160
+ this.eventQueue.length = 0;
1161
+ }
1162
+ async waitForCompletion() {
1163
+ return this.completionPromise;
1164
+ }
1165
+ emitTransportStderr(line) {
1166
+ this.emit({ type: "raw_stderr", content: `${line}
1167
+ ` });
1168
+ }
1169
+ async abort(reason) {
1170
+ if (this.aborted || this.settled)
1171
+ return;
1172
+ this.aborted = true;
1173
+ this.abortReason = reason ?? "cancelled";
1174
+ if (this.abortController) {
1175
+ this.abortController.abort(this.abortReason);
1176
+ return;
1177
+ }
1178
+ try {
1179
+ await this.thread.interrupt();
1180
+ } catch (error) {
1181
+ this.emit({
1182
+ type: "raw_stderr",
1183
+ content: `[codex] Failed to interrupt turn: ${String(error)}
1184
+ `
1185
+ });
1186
+ }
1187
+ }
1188
+ async deliverSteering({ mode, text }) {
1189
+ if (!this.acceptsSteering || this.settled || this.aborted) {
1190
+ return { delivered: false, reason: "Codex session has completed" };
1191
+ }
1192
+ if (mode === "queue") {
1193
+ return new Promise((resolve2) => this.queuedPrompts.push({ text, resolve: resolve2 }));
1194
+ }
1195
+ try {
1196
+ await this.thread.steer(text);
1197
+ return { delivered: true, mode: "steer" };
1198
+ } catch (error) {
1199
+ return { delivered: false, reason: scrubSecrets(String(error)) };
1200
+ }
1201
+ }
1202
+ emit(event) {
1203
+ const scrubbed = JSON.parse(scrubSecrets(JSON.stringify(event)));
1204
+ try {
1205
+ this.logFileHandle.write(`${JSON.stringify({ ...scrubbed, timestamp: new Date().toISOString() })}
1206
+ `);
1207
+ } catch {}
1208
+ if (this.listeners.length > 0) {
1209
+ for (const listener of this.listeners) {
1210
+ try {
1211
+ listener(scrubbed);
1212
+ } catch {}
1213
+ }
1214
+ } else {
1215
+ this.eventQueue.push(scrubbed);
1216
+ }
1217
+ }
1218
+ settle(result) {
1219
+ if (this.settled)
1220
+ return;
1221
+ this.settled = true;
1222
+ for (const queued of this.queuedPrompts.splice(0)) {
1223
+ queued.resolve({
1224
+ delivered: false,
1225
+ reason: "Codex session ended before the queued turn started"
1226
+ });
1227
+ }
1228
+ this.pendingResult = { ...result, appliedReasoningEffort: this.appliedReasoningEffort };
1229
+ }
1230
+ buildCostData(usage, isError) {
1231
+ const inputTokens = usage?.input_tokens ?? 0;
1232
+ const cachedInputTokens = usage?.cached_input_tokens ?? 0;
1233
+ const outputTokens = usage?.output_tokens ?? 0;
1234
+ const reasoningOutputTokens = usage?.reasoning_output_tokens ?? 0;
1235
+ return {
1236
+ sessionId: "",
1237
+ taskId: this.config.taskId,
1238
+ agentId: this.config.agentId,
1239
+ totalCostUsd: computeCodexCostUsd(this.resolvedModel, inputTokens, cachedInputTokens, outputTokens),
1240
+ inputTokens,
1241
+ outputTokens,
1242
+ reasoningOutputTokens,
1243
+ cacheReadTokens: cachedInputTokens,
1244
+ cacheWriteTokens: usage?.cache_write_input_tokens,
1245
+ durationMs: Date.now() - this.startedAt,
1246
+ numTurns: this.numTurns,
1247
+ model: this.resolvedModel,
1248
+ isError,
1249
+ provider: "codex"
1250
+ };
1251
+ }
1252
+ toolNameForItem(item) {
1253
+ switch (item.type) {
1254
+ case "command_execution":
1255
+ return "bash";
1256
+ case "file_change": {
1257
+ const first = item.changes[0];
1258
+ if (!first)
1259
+ return "Edit";
1260
+ return first.kind === "add" ? "Write" : first.kind === "delete" ? "Delete" : "Edit";
1261
+ }
1262
+ case "mcp_tool_call":
1263
+ return item.tool;
1264
+ case "web_search":
1265
+ return "WebSearch";
1266
+ default:
1267
+ return item.type;
1268
+ }
1269
+ }
1270
+ toolArgsForItem(item) {
1271
+ switch (item.type) {
1272
+ case "command_execution":
1273
+ return { command: item.command };
1274
+ case "file_change":
1275
+ return { changes: item.changes };
1276
+ case "mcp_tool_call": {
1277
+ const mcpItem = item;
1278
+ return { server: mcpItem.server, tool: mcpItem.tool, arguments: mcpItem.arguments };
1279
+ }
1280
+ case "web_search":
1281
+ return { query: item.query };
1282
+ default:
1283
+ return {};
1284
+ }
1285
+ }
1286
+ isToolItem(item) {
1287
+ return item.type === "command_execution" || item.type === "file_change" || item.type === "mcp_tool_call" || item.type === "web_search";
1288
+ }
1289
+ shortenItemResult(item) {
1290
+ switch (item.type) {
1291
+ case "command_execution": {
1292
+ const cmd = item;
1293
+ const stdout = (cmd.aggregated_output ?? "").slice(0, 500);
1294
+ return `exit=${cmd.exit_code ?? "?"} status=${cmd.status ?? "?"} stdout=${stdout}`;
1295
+ }
1296
+ case "file_change": {
1297
+ const fc = item;
1298
+ const summarised = (fc.changes ?? []).slice(0, 5).map((c) => `${c.kind}:${c.path}`).join(",");
1299
+ return `changes=[${summarised}]`;
1300
+ }
1301
+ case "mcp_tool_call": {
1302
+ const mcp = item;
1303
+ return `server=${mcp.server} tool=${mcp.tool} status=${mcp.status ?? "?"}`;
1304
+ }
1305
+ case "web_search": {
1306
+ const ws = item;
1307
+ return `query=${(ws.query ?? "").slice(0, 200)}`;
1308
+ }
1309
+ default:
1310
+ return JSON.stringify(item).slice(0, 500);
1311
+ }
1312
+ }
1313
+ handleEvent(event) {
1314
+ this.emit({ type: "raw_log", content: JSON.stringify(event) });
1315
+ switch (event.type) {
1316
+ case "thread.started": {
1317
+ this._sessionId = event.thread_id;
1318
+ const codexVersion = this.thread.version ?? readPkgVersion("@openai/codex");
1319
+ this.emit({
1320
+ type: "session_init",
1321
+ sessionId: event.thread_id,
1322
+ provider: "codex",
1323
+ ...codexVersion ? { harnessVariantMeta: { version: codexVersion } } : {}
1324
+ });
1325
+ break;
1326
+ }
1327
+ case "turn.started": {
1328
+ this.numTurns += 1;
1329
+ break;
1330
+ }
1331
+ case "item.started": {
1332
+ if (this.isToolItem(event.item)) {
1333
+ this.emit({
1334
+ type: "tool_start",
1335
+ toolCallId: event.item.id,
1336
+ toolName: this.toolNameForItem(event.item),
1337
+ args: this.toolArgsForItem(event.item)
1338
+ });
1339
+ this.transcript.push(`Tool[${this.toolNameForItem(event.item)}] started: ${JSON.stringify(this.toolArgsForItem(event.item)).slice(0, 500)}`);
1340
+ }
1341
+ break;
1342
+ }
1343
+ case "item.updated": {
1344
+ const updatedItem = event.item;
1345
+ if (updatedItem.type === "agent_message") {
1346
+ const msg = updatedItem;
1347
+ if (msg.text) {
1348
+ this.emit({
1349
+ type: "custom",
1350
+ name: "codex.message_delta",
1351
+ data: { itemId: updatedItem.id, text: msg.text }
1352
+ });
1353
+ }
1354
+ }
1355
+ break;
1356
+ }
1357
+ case "message.delta": {
1358
+ this.emit({
1359
+ type: "custom",
1360
+ name: "codex.message_delta",
1361
+ data: { itemId: event.item_id, text: event.delta }
1362
+ });
1363
+ break;
1364
+ }
1365
+ case "item.completed": {
1366
+ const { item } = event;
1367
+ if (this.isToolItem(item)) {
1368
+ this.emit({
1369
+ type: "tool_end",
1370
+ toolCallId: item.id,
1371
+ toolName: this.toolNameForItem(item),
1372
+ result: item
1373
+ });
1374
+ this.transcript.push(`Tool[${this.toolNameForItem(item)}] completed: ${this.shortenItemResult(item)}`);
1375
+ break;
1376
+ }
1377
+ switch (item.type) {
1378
+ case "agent_message": {
1379
+ const msg = item;
1380
+ if (msg.text) {
1381
+ this.emit({ type: "message", role: "assistant", content: msg.text });
1382
+ this.transcript.push(`Assistant: ${msg.text}`);
1383
+ }
1384
+ break;
1385
+ }
1386
+ case "reasoning": {
1387
+ const r = item;
1388
+ const summary = Array.isArray(r.summary) ? r.summary.join(`
1389
+ `) : r.summary;
1390
+ const content = Array.isArray(r.content) ? r.content.join(`
1391
+ `) : undefined;
1392
+ const text = r.text || summary || content || "";
1393
+ if (text) {
1394
+ this.emit({
1395
+ type: "custom",
1396
+ name: "codex.reasoning",
1397
+ data: { itemId: r.id, text }
1398
+ });
1399
+ }
1400
+ break;
1401
+ }
1402
+ case "todo_list": {
1403
+ const todo = item;
1404
+ this.emit({
1405
+ type: "custom",
1406
+ name: "codex.todo_list",
1407
+ data: { itemId: todo.id, items: todo.items ?? [] }
1408
+ });
1409
+ break;
1410
+ }
1411
+ case "error": {
1412
+ const errItem = item;
1413
+ this.emit({
1414
+ type: "error",
1415
+ message: this.formatTerminalError(errItem.message).message
1416
+ });
1417
+ break;
1418
+ }
1419
+ }
1420
+ break;
1421
+ }
1422
+ case "turn.completed": {
1423
+ this.accountUsage(event.usage);
1424
+ break;
1425
+ }
1426
+ case "turn.failed": {
1427
+ this.accountUsage(event.usage);
1428
+ const { message } = this.formatTerminalError(event.error.message, event.error.codexErrorInfo);
1429
+ this.emit({ type: "error", message });
1430
+ break;
1431
+ }
1432
+ case "turn.interrupted":
1433
+ this.accountUsage(event.usage);
1434
+ break;
1435
+ case "error": {
1436
+ const { message } = this.formatTerminalError(event.message, event.codexErrorInfo);
1437
+ if (event.willRetry === true) {
1438
+ this.emit({ type: "raw_stderr", content: `[codex] ${message} (retrying)
1439
+ ` });
1440
+ } else {
1441
+ this.emit({ type: "error", message });
1442
+ }
1443
+ break;
1444
+ }
1445
+ }
1446
+ }
1447
+ accountUsage(usage) {
1448
+ if (!usage)
1449
+ return;
1450
+ this.accumulatedUsage = this.accumulatedUsage ? {
1451
+ input_tokens: this.accumulatedUsage.input_tokens + usage.input_tokens,
1452
+ cached_input_tokens: this.accumulatedUsage.cached_input_tokens + usage.cached_input_tokens,
1453
+ cache_write_input_tokens: this.accumulatedUsage.cache_write_input_tokens + usage.cache_write_input_tokens,
1454
+ output_tokens: this.accumulatedUsage.output_tokens + usage.output_tokens,
1455
+ reasoning_output_tokens: this.accumulatedUsage.reasoning_output_tokens + usage.reasoning_output_tokens
1456
+ } : usage;
1457
+ const contextUsed = computeContextUsedUnified({
1458
+ inputTokens: usage.input_tokens,
1459
+ cacheReadTokens: 0,
1460
+ cacheCreateTokens: 0,
1461
+ outputTokens: usage.output_tokens
1462
+ });
1463
+ this.emit({
1464
+ type: "context_usage",
1465
+ contextUsedTokens: contextUsed,
1466
+ contextTotalTokens: this.contextWindow,
1467
+ contextPercent: clampContextPercent(contextUsed, this.contextWindow) ?? 0,
1468
+ outputTokens: usage.output_tokens,
1469
+ contextFormula: CONTEXT_FORMULA
1470
+ });
1471
+ }
1472
+ formatTerminalError(raw, codexErrorInfo) {
1473
+ const normalized = `${codexErrorInfo ?? ""} ${raw}`.toLowerCase();
1474
+ const overflowPatterns = [
1475
+ "context length exceeded",
1476
+ "maximum context length",
1477
+ "too many tokens",
1478
+ "input too long",
1479
+ "request too large",
1480
+ "context_length_exceeded",
1481
+ "contextwindowexceeded"
1482
+ ];
1483
+ if (overflowPatterns.some((p) => normalized.includes(p))) {
1484
+ return {
1485
+ message: `[context-overflow] Codex turn exceeded the model's context window for ${this.resolvedModel} (${this.contextWindow.toLocaleString()} tokens). Codex does not auto-compact conversation history like Claude does — start a fresh task or split the work into smaller turns. Original error: ${raw}`,
1486
+ category: "context_overflow"
1487
+ };
1488
+ }
1489
+ const usageLimitPatterns = ["usage limit", "upgrade to pro", "usagelimitexceeded"];
1490
+ if (usageLimitPatterns.some((p) => normalized.includes(p))) {
1491
+ return {
1492
+ message: `[usage-limit] Codex account quota exhausted — upgrade plan or wait for monthly reset. Original error: ${raw}`,
1493
+ category: "usage_limit"
1494
+ };
1495
+ }
1496
+ const rateLimitPatterns = [
1497
+ "rate limit",
1498
+ "rate_limit",
1499
+ "ratelimit",
1500
+ "too many requests",
1501
+ "http 429",
1502
+ " 429 "
1503
+ ];
1504
+ if (rateLimitPatterns.some((p) => normalized.includes(p))) {
1505
+ return {
1506
+ message: `[rate-limit] Codex API rate limit hit. Original error: ${raw}`,
1507
+ category: "rate_limit"
1508
+ };
1509
+ }
1510
+ const authPatterns = [
1511
+ "unauthorized",
1512
+ "http 401",
1513
+ " 401 ",
1514
+ "invalid api key",
1515
+ "invalid_api_key",
1516
+ "missing api key",
1517
+ "no api key",
1518
+ "authentication failed",
1519
+ "failed to refresh token",
1520
+ "invalid 'refresh_token'"
1521
+ ];
1522
+ if (authPatterns.some((p) => normalized.includes(p))) {
1523
+ return {
1524
+ message: `[auth-error] Codex authentication failed — check OPENAI_API_KEY or ChatGPT login. Original error: ${raw}`,
1525
+ category: "authentication_failed"
1526
+ };
1527
+ }
1528
+ return { message: raw };
1529
+ }
1530
+ async runSession() {
1531
+ this.abortController = new AbortController;
1532
+ this.abortController.signal.addEventListener("abort", () => {
1533
+ this.aborted = true;
1534
+ if (typeof this.abortController?.signal.reason === "string") {
1535
+ this.abortReason = this.abortController.signal.reason;
1536
+ }
1537
+ this.thread.interrupt().catch((error) => {
1538
+ this.emit({
1539
+ type: "raw_stderr",
1540
+ content: `[codex] Failed to interrupt turn: ${String(error)}
1541
+ `
1542
+ });
1543
+ });
1544
+ }, { once: true });
1545
+ this.abortRef.current = this.abortController;
1546
+ let terminalError;
1547
+ let sawTurnCompleted = false;
1548
+ let sawTurnInterrupted = false;
1549
+ try {
1550
+ const resolvedPrompt = await resolveCodexPrompt(this.config.prompt, this.skillsDir, (event) => this.emit(event));
1551
+ this.transcript = [`User: ${resolvedPrompt}`];
1552
+ try {
1553
+ let nextPrompt = resolvedPrompt;
1554
+ let queuedPrompt;
1555
+ while (nextPrompt !== undefined && !this.aborted && !terminalError && !sawTurnInterrupted) {
1556
+ if (nextPrompt !== resolvedPrompt)
1557
+ this.transcript.push(`User: ${nextPrompt}`);
1558
+ const streamed = await this.thread.runStreamed(nextPrompt);
1559
+ if (queuedPrompt) {
1560
+ this.queuedPrompts.shift();
1561
+ queuedPrompt.resolve({ delivered: true, mode: "queue" });
1562
+ }
1563
+ for await (const event of streamed.events) {
1564
+ if (event.type === "turn.failed" || event.type === "turn.interrupted" || event.type === "error" && event.willRetry !== true) {
1565
+ this.acceptsSteering = false;
1566
+ }
1567
+ this.handleEvent(event);
1568
+ if (event.type === "turn.completed") {
1569
+ sawTurnCompleted = true;
1570
+ }
1571
+ if (event.type === "turn.interrupted") {
1572
+ sawTurnInterrupted = true;
1573
+ }
1574
+ if (event.type === "turn.failed" && !terminalError) {
1575
+ terminalError = this.formatTerminalError(event.error.message, event.error.codexErrorInfo);
1576
+ this.errorTracker.processCodexUsageLimitMessage(event.error.message);
1577
+ }
1578
+ if (event.type === "error" && event.willRetry !== true && !terminalError) {
1579
+ terminalError = this.formatTerminalError(event.message, event.codexErrorInfo);
1580
+ this.errorTracker.processCodexUsageLimitMessage(event.message);
1581
+ }
1582
+ }
1583
+ queuedPrompt = this.queuedPrompts[0];
1584
+ nextPrompt = queuedPrompt?.text;
1585
+ }
1586
+ } catch (err) {
1587
+ this.acceptsSteering = false;
1588
+ if (this.aborted || err instanceof Error && err.name === "AbortError") {
1589
+ const cost2 = this.buildCostData(this.accumulatedUsage, true);
1590
+ this.emit({ type: "result", cost: cost2, isError: true, errorCategory: "cancelled" });
1591
+ this.settle({
1592
+ exitCode: 130,
1593
+ sessionId: this._sessionId,
1594
+ cost: cost2,
1595
+ isError: true,
1596
+ failureReason: this.abortReason
1597
+ });
1598
+ return;
1599
+ }
1600
+ if (terminalError) {
1601
+ const cost2 = this.buildCostData(this.accumulatedUsage, true);
1602
+ this.emit({
1603
+ type: "result",
1604
+ cost: cost2,
1605
+ isError: true,
1606
+ errorCategory: terminalError.category ?? "turn_failed"
1607
+ });
1608
+ this.settle({
1609
+ exitCode: 1,
1610
+ sessionId: this._sessionId,
1611
+ cost: cost2,
1612
+ isError: true,
1613
+ failureReason: terminalError.message,
1614
+ rateLimitResetAt: this.errorTracker.getRateLimitResetAt(),
1615
+ rateLimitWindows: this.errorTracker.getRateLimitWindows()
1616
+ });
1617
+ return;
1618
+ }
1619
+ throw err;
1620
+ }
1621
+ this.acceptsSteering = false;
1622
+ if (this.aborted || sawTurnInterrupted) {
1623
+ const cost2 = this.buildCostData(this.accumulatedUsage, true);
1624
+ this.emit({ type: "result", cost: cost2, isError: true, errorCategory: "cancelled" });
1625
+ this.settle({
1626
+ exitCode: 130,
1627
+ sessionId: this._sessionId,
1628
+ cost: cost2,
1629
+ isError: true,
1630
+ failureReason: this.aborted ? this.abortReason : "Codex turn interrupted"
1631
+ });
1632
+ return;
1633
+ }
1634
+ const isError = Boolean(terminalError) || !sawTurnCompleted;
1635
+ const cost = this.buildCostData(this.accumulatedUsage, isError);
1636
+ this.emit({
1637
+ type: "result",
1638
+ cost,
1639
+ isError,
1640
+ errorCategory: terminalError ? terminalError.category ?? "turn_failed" : undefined
1641
+ });
1642
+ this.settle({
1643
+ exitCode: isError ? 1 : 0,
1644
+ sessionId: this._sessionId,
1645
+ cost,
1646
+ isError,
1647
+ failureReason: terminalError?.message,
1648
+ rateLimitResetAt: this.errorTracker.getRateLimitResetAt(),
1649
+ rateLimitWindows: this.errorTracker.getRateLimitWindows()
1650
+ });
1651
+ } catch (err) {
1652
+ this.acceptsSteering = false;
1653
+ const message = err instanceof Error ? err.message : String(err);
1654
+ this.emit({ type: "raw_stderr", content: `[codex] Error: ${message}
1655
+ ` });
1656
+ this.emit({ type: "error", message });
1657
+ const cost = this.buildCostData(this.accumulatedUsage, true);
1658
+ this.emit({ type: "result", cost, isError: true, errorCategory: "exception" });
1659
+ this.settle({
1660
+ exitCode: 1,
1661
+ sessionId: this._sessionId,
1662
+ cost,
1663
+ isError: true,
1664
+ failureReason: message,
1665
+ rateLimitResetAt: this.errorTracker.getRateLimitResetAt(),
1666
+ rateLimitWindows: this.errorTracker.getRateLimitWindows()
1667
+ });
1668
+ } finally {
1669
+ const sessionWasAborted = this.aborted || this.abortController?.signal.aborted === true || this.pendingResult?.exitCode === 130;
1670
+ if (process.env.SKIP_SESSION_SUMMARY !== "1" && !sessionWasAborted) {
1671
+ try {
1672
+ await this.summarizeAtEnd();
1673
+ } catch (err) {
1674
+ console.error("session_summary failed (codex):", err);
1675
+ }
1676
+ } else if (sessionWasAborted) {
1677
+ console.debug("[codex] session aborted — skipping session_summary");
1678
+ } else {
1679
+ console.debug("session_summary skipped (codex): SKIP_SESSION_SUMMARY is set");
1680
+ }
1681
+ this.abortRef.current = null;
1682
+ try {
1683
+ await this.thread.close();
1684
+ } catch (error) {
1685
+ this.emit({
1686
+ type: "raw_stderr",
1687
+ content: `[codex] Failed to close app-server: ${String(error)}
1688
+ `
1689
+ });
1690
+ }
1691
+ try {
1692
+ await this.logFileHandle.end();
1693
+ } catch {}
1694
+ await this.agentsMdHandle.cleanup();
1695
+ const finalResult = this.pendingResult ?? {
1696
+ exitCode: 1,
1697
+ sessionId: this._sessionId,
1698
+ cost: this.buildCostData(this.accumulatedUsage, true),
1699
+ isError: true,
1700
+ failureReason: "session did not settle"
1701
+ };
1702
+ this.resolveCompletion(finalResult);
1703
+ }
1704
+ }
1705
+ async summarizeAtEnd() {
1706
+ const transcriptStr = this.transcript.join(`
1707
+ `).slice(-20000);
1708
+ const { agentId, taskId, apiUrl, apiKey } = this.config;
1709
+ if (!agentId || !taskId || !apiUrl || !apiKey) {
1710
+ const missing = [
1711
+ !agentId && "agent id",
1712
+ !taskId && "task id",
1713
+ !apiUrl && "API URL",
1714
+ !apiKey && "API key"
1715
+ ].filter(Boolean);
1716
+ console.warn(`session_summary skipped (codex): missing ${missing.join(", ")}`);
1717
+ return;
1718
+ }
1719
+ if (transcriptStr.length <= 100) {
1720
+ console.warn(`session_summary skipped (codex): transcript too short (${transcriptStr.length} chars)`);
1721
+ return;
1722
+ }
1723
+ const _runSummarize = this.summarizeDeps.runSummarize ?? summarizeSession;
1724
+ const _fetchRetrievals = this.summarizeDeps.fetchRetrievalsForTask ?? fetchRetrievalsForTask;
1725
+ const _postRatings = this.summarizeDeps.postRatings ?? postRatings;
1726
+ const _buildRatings = this.summarizeDeps.buildRatingsFromLlm ?? buildRatingsFromLlm;
1727
+ const wantRatings = getMemoryRaterNames().includes("llm");
1728
+ const retrievals = wantRatings ? await _fetchRetrievals({ apiUrl, apiKey, agentId, taskId }).catch(() => []) : [];
1729
+ const result = await _runSummarize({
1730
+ harness: "codex",
1731
+ transcript: transcriptStr,
1732
+ retrievals,
1733
+ taskContext: {
1734
+ sourceTaskId: taskId,
1735
+ agentId,
1736
+ prompt: this.config.prompt
1737
+ },
1738
+ apiUrl,
1739
+ apiKey,
1740
+ env: this.config.env
1741
+ });
1742
+ if (!result) {
1743
+ console.warn("session_summary skipped (codex): summarizer returned no result");
1744
+ return;
1745
+ }
1746
+ const summary = result.summary.trim();
1747
+ if (summary.length <= 20 || summary.toLowerCase().includes("no significant learnings")) {
1748
+ console.debug(`session_summary skipped (codex): summary failed quality gate (${summary.length} chars)`);
1749
+ return;
1750
+ }
1751
+ const indexResp = await fetch(`${apiUrl}/api/memory/index`, {
1752
+ method: "POST",
1753
+ headers: {
1754
+ "Content-Type": "application/json",
1755
+ Authorization: `Bearer ${apiKey}`,
1756
+ "X-Agent-ID": agentId
1757
+ },
1758
+ body: JSON.stringify({
1759
+ scope: "agent",
1760
+ source: "session_summary",
1761
+ sourceTaskId: taskId,
1762
+ content: summary,
1763
+ name: "session-summary",
1764
+ agentId
1765
+ })
1766
+ });
1767
+ if (!indexResp.ok) {
1768
+ const text = await indexResp.text().catch(() => "");
1769
+ console.error("session_summary: /api/memory/index POST failed (codex):", indexResp.status, text);
1770
+ return;
1771
+ }
1772
+ if (wantRatings && result.ratings && result.ratings.length > 0) {
1773
+ const ratingEvents = _buildRatings(result.ratings, retrievals);
1774
+ if (ratingEvents.length > 0) {
1775
+ await _postRatings({
1776
+ apiUrl,
1777
+ apiKey,
1778
+ agentId,
1779
+ taskId,
1780
+ events: ratingEvents
1781
+ }).catch((err) => console.error("session_summary: postRatings failed (codex):", err));
1782
+ }
1783
+ }
1784
+ }
1785
+ }
1786
+ async function createInProcessCodexSession(config, opts = {}) {
1787
+ const agentsMdHandle = await writeCodexAgentsMd(config.cwd, config.systemPrompt);
1788
+ try {
1789
+ const resolvedModel = resolveCodexModel(config.model);
1790
+ const preSessionEvents = [];
1791
+ const bufferedEmit = (event) => {
1792
+ preSessionEvents.push(event);
1793
+ };
1794
+ const mergedConfig = await buildCodexConfig(config, resolvedModel, bufferedEmit);
1795
+ const authMode = await resolveCodexAuthMode(config, bufferedEmit);
1796
+ const env = {
1797
+ PATH: process.env.PATH ?? "",
1798
+ HOME: process.env.HOME ?? "",
1799
+ ...authMode !== "chatgpt" && process.env.OPENAI_API_KEY ? { OPENAI_API_KEY: process.env.OPENAI_API_KEY } : {},
1800
+ ...process.env.NODE_EXTRA_CA_CERTS ? { NODE_EXTRA_CA_CERTS: process.env.NODE_EXTRA_CA_CERTS } : {},
1801
+ CONTEXT_MODE_EXTERNAL_MCP_NUDGE_EVERY: CTX_MODE_NUDGE_EVERY,
1802
+ ...config.env ?? {},
1803
+ ...buildOtelTraceparentEnv(config.env ?? process.env),
1804
+ SWARM_CODEX_APP_SERVER: "1"
1805
+ };
1806
+ if (authMode === "chatgpt")
1807
+ delete env.OPENAI_API_KEY;
1808
+ const codexPathOverride = process.env.CODEX_PATH_OVERRIDE;
1809
+ if (config.resumeSessionId) {
1810
+ console.warn("[codex-adapter] resumeSessionId ignored — native resume is disabled by deprecation plan");
1811
+ }
1812
+ let thread;
1813
+ let liveSession;
1814
+ if (opts.threadFactory) {
1815
+ thread = await opts.threadFactory({
1816
+ config,
1817
+ resolvedModel,
1818
+ env,
1819
+ codexConfig: mergedConfig
1820
+ });
1821
+ } else {
1822
+ const appServerFactory = opts.appServerFactory ?? ((options) => new CodexAppServer(options));
1823
+ const client = appServerFactory({
1824
+ ...codexPathOverride ? { codexPath: codexPathOverride } : {},
1825
+ env,
1826
+ onStderr: (line) => {
1827
+ if (liveSession)
1828
+ liveSession.emitTransportStderr(line);
1829
+ else
1830
+ bufferedEmit({ type: "raw_stderr", content: `${line}
1831
+ ` });
1832
+ }
1833
+ });
1834
+ try {
1835
+ const response = await client.request("thread/start", {
1836
+ model: resolvedModel,
1837
+ cwd: config.cwd,
1838
+ approvalPolicy: "never",
1839
+ sandbox: "danger-full-access",
1840
+ config: mergedConfig,
1841
+ ephemeral: true
1842
+ });
1843
+ thread = new AppServerCodexThread(client, response.thread.id);
1844
+ } catch (error) {
1845
+ await client.close();
1846
+ throw error;
1847
+ }
1848
+ }
1849
+ const session = new CodexSession(thread, config, agentsMdHandle, resolvedModel, preSessionEvents, opts.skillsDir, opts.summarizeDeps ?? {});
1850
+ liveSession = session;
1851
+ return session;
1852
+ } catch (err) {
1853
+ await agentsMdHandle.cleanup();
1854
+ throw err;
1855
+ }
1856
+ }
1857
+ function resolveCodexRunnerArgv() {
1858
+ const override = process.env.AGENT_SWARM_CODEX_RUNNER_ARGV;
1859
+ if (override) {
1860
+ try {
1861
+ const parsed = JSON.parse(override);
1862
+ if (Array.isArray(parsed) && parsed.every((s) => typeof s === "string")) {
1863
+ return parsed;
1864
+ }
1865
+ } catch {}
1866
+ }
1867
+ const execPath = process.execPath;
1868
+ const scriptArg = process.argv[1];
1869
+ if (scriptArg && /\.(t|j)sx?$/.test(scriptArg)) {
1870
+ return [execPath, scriptArg, "codex-session-runner"];
1871
+ }
1872
+ return [execPath, "codex-session-runner"];
1873
+ }
1874
+
1875
+ class CodexSubprocessSession {
1876
+ proc;
1877
+ listeners = [];
1878
+ eventQueue = [];
1879
+ completionPromise;
1880
+ _sessionId;
1881
+ finished = false;
1882
+ nextControlId = 0;
1883
+ pendingSteering = new Map;
1884
+ constructor(config, skillsDir) {
1885
+ const argv = resolveCodexRunnerArgv();
1886
+ const payload = {
1887
+ config,
1888
+ skillsDir,
1889
+ parentOtelEnv: buildOtelTraceparentEnv(config.env ?? process.env)
1890
+ };
1891
+ const apiKey = getApiKey();
1892
+ this.proc = registerProcessGroup(Bun.spawn(argv, {
1893
+ detached: detachedProcessGroup,
1894
+ env: {
1895
+ PATH: process.env.PATH ?? "",
1896
+ HOME: process.env.HOME ?? "",
1897
+ ...process.env.NODE_EXTRA_CA_CERTS ? { NODE_EXTRA_CA_CERTS: process.env.NODE_EXTRA_CA_CERTS } : {},
1898
+ ...process.env.MCP_BASE_URL ? { MCP_BASE_URL: process.env.MCP_BASE_URL } : {},
1899
+ ...apiKey ? { AGENT_SWARM_API_KEY: apiKey, API_KEY: apiKey } : {},
1900
+ ...process.env.OPENAI_API_KEY ? { OPENAI_API_KEY: process.env.OPENAI_API_KEY } : {},
1901
+ ...process.env.OPENROUTER_API_KEY ? { OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY } : {},
1902
+ ...process.env.ANTHROPIC_API_KEY ? { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY } : {},
1903
+ ...process.env.CODEX_PATH_OVERRIDE ? { CODEX_PATH_OVERRIDE: process.env.CODEX_PATH_OVERRIDE } : {},
1904
+ ...process.env.CODEX_SKILLS_DIR ? { CODEX_SKILLS_DIR: process.env.CODEX_SKILLS_DIR } : {},
1905
+ CONTEXT_MODE_EXTERNAL_MCP_NUDGE_EVERY: CTX_MODE_NUDGE_EVERY,
1906
+ ...process.env.SKIP_SESSION_SUMMARY ? { SKIP_SESSION_SUMMARY: process.env.SKIP_SESSION_SUMMARY } : {},
1907
+ MEMORY_RATERS: getMemoryRaterNames().join(",")
1908
+ },
1909
+ stdin: "pipe",
1910
+ stdout: "pipe",
1911
+ stderr: "pipe"
1912
+ }));
1913
+ const stdin = this.proc.stdin;
1914
+ stdin.write(`${JSON.stringify(payload)}
1915
+ `);
1916
+ this.completionPromise = this.processStreams().finally(() => this.finishSteering());
1917
+ }
1918
+ get sessionId() {
1919
+ return this._sessionId;
1920
+ }
1921
+ onEvent(listener) {
1922
+ this.listeners.push(listener);
1923
+ for (const event of this.eventQueue) {
1924
+ listener(event);
1925
+ }
1926
+ this.eventQueue.length = 0;
1927
+ }
1928
+ async waitForCompletion() {
1929
+ return this.completionPromise;
1930
+ }
1931
+ async abort(reason) {
1932
+ if (this.finished)
1933
+ return;
1934
+ try {
1935
+ this.writeControl({ kind: "abort", reason });
1936
+ let timer;
1937
+ try {
1938
+ await Promise.race([
1939
+ this.completionPromise,
1940
+ new Promise((resolve2) => {
1941
+ timer = setTimeout(resolve2, 2000);
1942
+ })
1943
+ ]);
1944
+ } finally {
1945
+ clearTimeout(timer);
1946
+ }
1947
+ } finally {
1948
+ if (!this.finished)
1949
+ await terminateProcessGroup(this.proc.pid);
1950
+ }
1951
+ }
1952
+ async deliverSteering(delivery) {
1953
+ if (this.finished)
1954
+ return { delivered: false, reason: "Codex session has completed" };
1955
+ const id = ++this.nextControlId;
1956
+ return new Promise((resolve2) => {
1957
+ const timer = delivery.mode === "queue" ? undefined : setTimeout(() => {
1958
+ this.pendingSteering.delete(id);
1959
+ resolve2({ delivered: false, reason: "Codex steering request timed out" });
1960
+ }, 35000);
1961
+ this.pendingSteering.set(id, { resolve: resolve2, timer });
1962
+ try {
1963
+ this.writeControl({ kind: "steer", id, delivery });
1964
+ } catch (error) {
1965
+ clearTimeout(timer);
1966
+ this.pendingSteering.delete(id);
1967
+ resolve2({ delivered: false, reason: scrubSecrets(String(error)) });
1968
+ }
1969
+ });
1970
+ }
1971
+ writeControl(command) {
1972
+ this.proc.stdin.write(`${JSON.stringify(command)}
1973
+ `);
1974
+ }
1975
+ finishSteering() {
1976
+ this.finished = true;
1977
+ for (const { resolve: resolve2, timer } of this.pendingSteering.values()) {
1978
+ clearTimeout(timer);
1979
+ resolve2({ delivered: false, reason: "Codex session has completed" });
1980
+ }
1981
+ this.pendingSteering.clear();
1982
+ }
1983
+ emit(event) {
1984
+ event = JSON.parse(scrubSecrets(JSON.stringify(event)));
1985
+ if (event.type === "session_init" && event.sessionId) {
1986
+ this._sessionId = event.sessionId;
1987
+ }
1988
+ if (this.listeners.length > 0) {
1989
+ for (const listener of this.listeners) {
1990
+ try {
1991
+ listener(event);
1992
+ } catch {}
1993
+ }
1994
+ } else {
1995
+ this.eventQueue.push(event);
1996
+ }
1997
+ }
1998
+ async processStreams() {
1999
+ let result = null;
2000
+ let partial = "";
2001
+ let stderrTail = "";
2002
+ const stdoutPromise = (async () => {
2003
+ const stdout = this.proc.stdout;
2004
+ if (!stdout)
2005
+ return;
2006
+ const decoder = new TextDecoder;
2007
+ for await (const chunk of stdout) {
2008
+ partial += decoder.decode(chunk, { stream: true });
2009
+ const parts = partial.split(`
2010
+ `);
2011
+ partial = parts.pop() ?? "";
2012
+ for (const line of parts) {
2013
+ const trimmed = line.trim();
2014
+ if (!trimmed)
2015
+ continue;
2016
+ this.handleLine(trimmed, (r) => {
2017
+ result = r;
2018
+ });
2019
+ }
2020
+ }
2021
+ partial += decoder.decode();
2022
+ if (partial.trim()) {
2023
+ this.handleLine(partial.trim(), (r) => {
2024
+ result = r;
2025
+ });
2026
+ partial = "";
2027
+ }
2028
+ })();
2029
+ const stderrPromise = (async () => {
2030
+ const stderr = this.proc.stderr;
2031
+ if (!stderr)
2032
+ return;
2033
+ const decoder = new TextDecoder;
2034
+ let pending = "";
2035
+ const emitStderr = (line) => {
2036
+ const text = scrubSecrets(line);
2037
+ stderrTail = (stderrTail + text).slice(-2000);
2038
+ this.emit({ type: "raw_stderr", content: text });
2039
+ };
2040
+ for await (const chunk of stderr) {
2041
+ pending += decoder.decode(chunk, { stream: true });
2042
+ let newline = pending.indexOf(`
2043
+ `);
2044
+ while (newline !== -1) {
2045
+ emitStderr(pending.slice(0, newline + 1));
2046
+ pending = pending.slice(newline + 1);
2047
+ newline = pending.indexOf(`
2048
+ `);
2049
+ }
2050
+ }
2051
+ pending += decoder.decode();
2052
+ if (pending)
2053
+ emitStderr(pending);
2054
+ })();
2055
+ await Promise.all([stdoutPromise, stderrPromise]);
2056
+ const exitCode = await this.proc.exited;
2057
+ this.finishSteering();
2058
+ await terminateProcessGroup(this.proc.pid);
2059
+ if (result) {
2060
+ return result;
2061
+ }
2062
+ const stderrHint = stderrTail.trim() ? `: stderr: ${stderrTail.trim().slice(-500)}` : "";
2063
+ return {
2064
+ exitCode: exitCode ?? 1,
2065
+ sessionId: this._sessionId,
2066
+ isError: true,
2067
+ failureReason: `codex subprocess exited (code=${exitCode ?? "?"}) without a structured result${stderrHint}`
2068
+ };
2069
+ }
2070
+ handleLine(line, setResult) {
2071
+ let msg;
2072
+ try {
2073
+ msg = JSON.parse(scrubSecrets(line));
2074
+ } catch {
2075
+ this.emit({ type: "raw_stderr", content: `${line}
2076
+ ` });
2077
+ return;
2078
+ }
2079
+ if (msg.kind === "event" && msg.event) {
2080
+ this.emit(msg.event);
2081
+ return;
2082
+ }
2083
+ if (msg.kind === "result" && msg.result) {
2084
+ this.finishSteering();
2085
+ setResult(msg.result);
2086
+ return;
2087
+ }
2088
+ if (msg.kind === "steering-result" && msg.id !== undefined && msg.delivery) {
2089
+ const pending = this.pendingSteering.get(msg.id);
2090
+ if (pending) {
2091
+ clearTimeout(pending.timer);
2092
+ this.pendingSteering.delete(msg.id);
2093
+ pending.resolve(msg.delivery);
2094
+ }
2095
+ return;
2096
+ }
2097
+ if (msg.kind === "error" && msg.message) {
2098
+ this.emit({ type: "error", message: msg.message });
2099
+ setResult({
2100
+ exitCode: 1,
2101
+ sessionId: this._sessionId,
2102
+ isError: true,
2103
+ failureReason: msg.message
2104
+ });
2105
+ return;
2106
+ }
2107
+ }
2108
+ }
2109
+
2110
+ class CodexAdapter {
2111
+ name = "codex";
2112
+ traits = {
2113
+ hasMcp: true,
2114
+ nativeSkillDiscovery: false,
2115
+ hasLocalEnvironment: true,
2116
+ steerModes: ["steer", "queue"]
2117
+ };
2118
+ skillsDir;
2119
+ summarizeDeps;
2120
+ appServerFactory;
2121
+ threadFactory;
2122
+ bypassSubprocess;
2123
+ constructor(opts = {}) {
2124
+ this.skillsDir = opts.skillsDir;
2125
+ this.summarizeDeps = opts.summarizeDeps ?? {};
2126
+ this.bypassSubprocess = opts.bypassSubprocess ?? false;
2127
+ this.appServerFactory = opts.appServerFactory;
2128
+ this.threadFactory = opts.threadFactory;
2129
+ }
2130
+ async createSession(config) {
2131
+ if (this.bypassSubprocess) {
2132
+ return createInProcessCodexSession(config, {
2133
+ skillsDir: this.skillsDir,
2134
+ summarizeDeps: this.summarizeDeps,
2135
+ appServerFactory: this.appServerFactory,
2136
+ threadFactory: this.threadFactory
2137
+ });
2138
+ }
2139
+ return new CodexSubprocessSession(config, this.skillsDir);
2140
+ }
2141
+ async canResume(_sessionId) {
2142
+ return false;
2143
+ }
2144
+ formatCommand(commandName) {
2145
+ return `/${commandName}`;
2146
+ }
2147
+ }
2148
+
2149
+ export { codexUserHome, checkCodexCredentials, resolveCodexAuthMode, buildCodexConfig, CodexSession, createInProcessCodexSession, resolveCodexRunnerArgv, CodexAdapter };