factory-factory 0.3.3 → 0.3.4

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 (375) hide show
  1. package/dist/client/assets/{_basePickBy-0_GqMukI.js → _basePickBy-kS1IrKFQ.js} +1 -1
  2. package/dist/client/assets/{_baseUniq-BkDCIs_N.js → _baseUniq-DisTPj-I.js} +1 -1
  3. package/dist/client/assets/{arc-BYBi6T58.js → arc-CWpVTRns.js} +1 -1
  4. package/dist/client/assets/{architectureDiagram-VXUJARFQ-utRnH0EI.js → architectureDiagram-VXUJARFQ-Dp1AYPMt.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-VD42YOAC-ke2uPDpN.js → blockDiagram-VD42YOAC-DXpmez0y.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-YG6GDRKO-DAt0t6eK.js → c4Diagram-YG6GDRKO-C6sDxR49.js} +1 -1
  7. package/dist/client/assets/channel-D5BCeybW.js +1 -0
  8. package/dist/client/assets/{chunk-4BX2VUAB-D1vNGBo6.js → chunk-4BX2VUAB-CQxlRbYu.js} +1 -1
  9. package/dist/client/assets/{chunk-55IACEB6-BDwpH7UI.js → chunk-55IACEB6-DxubFDep.js} +1 -1
  10. package/dist/client/assets/{chunk-B4BG7PRW-DAM5zmSO.js → chunk-B4BG7PRW-BtTQ0im9.js} +1 -1
  11. package/dist/client/assets/{chunk-DI55MBZ5-CgmMeVp9.js → chunk-DI55MBZ5-C2tNYDZj.js} +1 -1
  12. package/dist/client/assets/{chunk-FMBD7UC4-nijXNm9V.js → chunk-FMBD7UC4-kRQRN6vP.js} +1 -1
  13. package/dist/client/assets/{chunk-QN33PNHL-GM5iQLPS.js → chunk-QN33PNHL-DP72lCp6.js} +1 -1
  14. package/dist/client/assets/{chunk-QZHKN3VN-CbOwrlLz.js → chunk-QZHKN3VN-BfxMFZDN.js} +1 -1
  15. package/dist/client/assets/{chunk-TZMSLE5B-D0eFaU6T.js → chunk-TZMSLE5B-G3wPktxK.js} +1 -1
  16. package/dist/client/assets/classDiagram-2ON5EDUG-BUS1JDwr.js +1 -0
  17. package/dist/client/assets/classDiagram-v2-WZHVMYZB-BUS1JDwr.js +1 -0
  18. package/dist/client/assets/clone-C5xhPZw8.js +1 -0
  19. package/dist/client/assets/{cose-bilkent-S5V4N54A-BAHIgVQk.js → cose-bilkent-S5V4N54A-QPsUxyAe.js} +1 -1
  20. package/dist/client/assets/{dagre-6UL2VRFP-CNvHuwOI.js → dagre-6UL2VRFP-BAmsmcnI.js} +1 -1
  21. package/dist/client/assets/{diagram-PSM6KHXK-726LBpxe.js → diagram-PSM6KHXK-QA-3O0wr.js} +1 -1
  22. package/dist/client/assets/{diagram-QEK2KX5R-DQsnVN2A.js → diagram-QEK2KX5R-VZkRJLXU.js} +1 -1
  23. package/dist/client/assets/{diagram-S2PKOQOG-D_T0ifdw.js → diagram-S2PKOQOG-DwLmogjW.js} +1 -1
  24. package/dist/client/assets/{erDiagram-Q2GNP2WA-BmVDGu8M.js → erDiagram-Q2GNP2WA-DMKIVzp4.js} +1 -1
  25. package/dist/client/assets/{flowDiagram-NV44I4VS-Z65JFfSc.js → flowDiagram-NV44I4VS-op3xA_nS.js} +1 -1
  26. package/dist/client/assets/{ganttDiagram-JELNMOA3-BZ3yfDKP.js → ganttDiagram-JELNMOA3-nrnzVCJK.js} +1 -1
  27. package/dist/client/assets/{gitGraphDiagram-NY62KEGX-faBi9oTt.js → gitGraphDiagram-NY62KEGX-CK2ytvyZ.js} +1 -1
  28. package/dist/client/assets/{graph-DMDSMqaB.js → graph-D_lEcPMk.js} +1 -1
  29. package/dist/client/assets/{index-D9ddlgEa.js → index-C2qHRaOH.js} +233 -231
  30. package/dist/client/assets/index-D5ywoSOH.css +1 -0
  31. package/dist/client/assets/{infoDiagram-WHAUD3N6-CJU-Jxtx.js → infoDiagram-WHAUD3N6-DO5X8HkW.js} +1 -1
  32. package/dist/client/assets/{journeyDiagram-XKPGCS4Q-CIFki-iq.js → journeyDiagram-XKPGCS4Q-Bnhe4U1x.js} +1 -1
  33. package/dist/client/assets/{kanban-definition-3W4ZIXB7-DDuM9VeF.js → kanban-definition-3W4ZIXB7-DpTaTXko.js} +1 -1
  34. package/dist/client/assets/{layout-CnYzyA25.js → layout-HT5qr--Q.js} +1 -1
  35. package/dist/client/assets/{linear-BoRrKhGP.js → linear-CDmQ8HG8.js} +1 -1
  36. package/dist/client/assets/{mindmap-definition-VGOIOE7T-QhVqNLyz.js → mindmap-definition-VGOIOE7T-zDr7s4o2.js} +1 -1
  37. package/dist/client/assets/{pieDiagram-ADFJNKIX-BkK0cKA8.js → pieDiagram-ADFJNKIX-BVBJCvYL.js} +1 -1
  38. package/dist/client/assets/{quadrantDiagram-AYHSOK5B-CeFigc3p.js → quadrantDiagram-AYHSOK5B-DzdJzkAJ.js} +1 -1
  39. package/dist/client/assets/{requirementDiagram-UZGBJVZJ-KowHp3BW.js → requirementDiagram-UZGBJVZJ-ChrLzx8S.js} +1 -1
  40. package/dist/client/assets/{sankeyDiagram-TZEHDZUN-D7CevVS2.js → sankeyDiagram-TZEHDZUN-GIzxIQQB.js} +1 -1
  41. package/dist/client/assets/{sequenceDiagram-WL72ISMW-3mHDMyaP.js → sequenceDiagram-WL72ISMW-BHxEpSCU.js} +1 -1
  42. package/dist/client/assets/{stateDiagram-FKZM4ZOC-h-G1xS2P.js → stateDiagram-FKZM4ZOC-ByvdYZKl.js} +1 -1
  43. package/dist/client/assets/stateDiagram-v2-4FDKWEC3-ynQtAOKH.js +1 -0
  44. package/dist/client/assets/{terminal-instance-BbEVZkgm.js → terminal-instance-xdECz0dx.js} +1 -1
  45. package/dist/client/assets/{timeline-definition-IT6M3QCI-Cc2H2fvU.js → timeline-definition-IT6M3QCI-BcCmIxix.js} +1 -1
  46. package/dist/client/assets/{treemap-KMMF4GRG-BAjv-2_R.js → treemap-KMMF4GRG-CVpLLzhu.js} +1 -1
  47. package/dist/client/assets/{xychartDiagram-PRI3JC2R-CqgFJhi0.js → xychartDiagram-PRI3JC2R-DrgKBPUM.js} +1 -1
  48. package/dist/client/index.html +2 -2
  49. package/dist/prisma/generated/commonInputTypes.d.ts +0 -30
  50. package/dist/prisma/generated/commonInputTypes.d.ts.map +1 -1
  51. package/dist/prisma/generated/internal/class.js +2 -2
  52. package/dist/prisma/generated/internal/class.js.map +1 -1
  53. package/dist/prisma/generated/internal/prismaNamespace.d.ts +0 -2
  54. package/dist/prisma/generated/internal/prismaNamespace.d.ts.map +1 -1
  55. package/dist/prisma/generated/internal/prismaNamespace.js +0 -2
  56. package/dist/prisma/generated/internal/prismaNamespace.js.map +1 -1
  57. package/dist/prisma/generated/internal/prismaNamespaceBrowser.d.ts +0 -2
  58. package/dist/prisma/generated/internal/prismaNamespaceBrowser.d.ts.map +1 -1
  59. package/dist/prisma/generated/internal/prismaNamespaceBrowser.js +0 -2
  60. package/dist/prisma/generated/internal/prismaNamespaceBrowser.js.map +1 -1
  61. package/dist/prisma/generated/models/Workspace.d.ts +1 -90
  62. package/dist/prisma/generated/models/Workspace.d.ts.map +1 -1
  63. package/dist/src/backend/domains/github/bridges.d.ts +0 -15
  64. package/dist/src/backend/domains/github/bridges.d.ts.map +1 -1
  65. package/dist/src/backend/domains/github/github-domain-exports.test.js +1 -7
  66. package/dist/src/backend/domains/github/github-domain-exports.test.js.map +1 -1
  67. package/dist/src/backend/domains/github/index.d.ts +1 -3
  68. package/dist/src/backend/domains/github/index.d.ts.map +1 -1
  69. package/dist/src/backend/domains/github/index.js +0 -2
  70. package/dist/src/backend/domains/github/index.js.map +1 -1
  71. package/dist/src/backend/domains/ratchet/bridges.d.ts +1 -0
  72. package/dist/src/backend/domains/ratchet/bridges.d.ts.map +1 -1
  73. package/dist/src/backend/domains/ratchet/fixer-session.service.d.ts.map +1 -1
  74. package/dist/src/backend/domains/ratchet/fixer-session.service.js +2 -0
  75. package/dist/src/backend/domains/ratchet/fixer-session.service.js.map +1 -1
  76. package/dist/src/backend/domains/ratchet/fixer-session.service.test.js +1 -0
  77. package/dist/src/backend/domains/ratchet/fixer-session.service.test.js.map +1 -1
  78. package/dist/src/backend/domains/ratchet/index.d.ts +0 -3
  79. package/dist/src/backend/domains/ratchet/index.d.ts.map +1 -1
  80. package/dist/src/backend/domains/ratchet/index.js +0 -2
  81. package/dist/src/backend/domains/ratchet/index.js.map +1 -1
  82. package/dist/src/backend/domains/ratchet/ratchet-domain-exports.test.js +1 -7
  83. package/dist/src/backend/domains/ratchet/ratchet-domain-exports.test.js.map +1 -1
  84. package/dist/src/backend/domains/session/acp/acp-event-translator.d.ts +3 -0
  85. package/dist/src/backend/domains/session/acp/acp-event-translator.d.ts.map +1 -1
  86. package/dist/src/backend/domains/session/acp/acp-event-translator.js +47 -56
  87. package/dist/src/backend/domains/session/acp/acp-event-translator.js.map +1 -1
  88. package/dist/src/backend/domains/session/acp/acp-permission-bridge.d.ts +3 -1
  89. package/dist/src/backend/domains/session/acp/acp-permission-bridge.d.ts.map +1 -1
  90. package/dist/src/backend/domains/session/acp/acp-permission-bridge.js +10 -1
  91. package/dist/src/backend/domains/session/acp/acp-permission-bridge.js.map +1 -1
  92. package/dist/src/backend/domains/session/acp/acp-permission-bridge.test.js +24 -0
  93. package/dist/src/backend/domains/session/acp/acp-permission-bridge.test.js.map +1 -1
  94. package/dist/src/backend/domains/session/acp/acp-runtime-manager.d.ts.map +1 -1
  95. package/dist/src/backend/domains/session/acp/acp-runtime-manager.js +176 -18
  96. package/dist/src/backend/domains/session/acp/acp-runtime-manager.js.map +1 -1
  97. package/dist/src/backend/domains/session/acp/acp-runtime-manager.manual.integration.test.d.ts +2 -0
  98. package/dist/src/backend/domains/session/acp/acp-runtime-manager.manual.integration.test.d.ts.map +1 -0
  99. package/dist/src/backend/domains/session/acp/acp-runtime-manager.manual.integration.test.js +40 -0
  100. package/dist/src/backend/domains/session/acp/acp-runtime-manager.manual.integration.test.js.map +1 -0
  101. package/dist/src/backend/domains/session/acp/acp-runtime-manager.test.js +88 -1
  102. package/dist/src/backend/domains/session/acp/acp-runtime-manager.test.js.map +1 -1
  103. package/dist/src/backend/domains/session/acp/acp-session-negotiation.integration.test.js +13 -1
  104. package/dist/src/backend/domains/session/acp/acp-session-negotiation.integration.test.js.map +1 -1
  105. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.d.ts +85 -0
  106. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.d.ts.map +1 -0
  107. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.js +1794 -0
  108. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.js.map +1 -0
  109. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.manual.integration.test.d.ts +2 -0
  110. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.manual.integration.test.d.ts.map +1 -0
  111. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.manual.integration.test.js +92 -0
  112. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.manual.integration.test.js.map +1 -0
  113. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.test.d.ts +2 -0
  114. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.test.d.ts.map +1 -0
  115. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.test.js +1407 -0
  116. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.test.js.map +1 -0
  117. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-rpc-client.d.ts +46 -0
  118. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-rpc-client.d.ts.map +1 -0
  119. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-rpc-client.js +238 -0
  120. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-rpc-client.js.map +1 -0
  121. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-rpc-client.test.d.ts +2 -0
  122. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-rpc-client.test.d.ts.map +1 -0
  123. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-rpc-client.test.js +140 -0
  124. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-rpc-client.test.js.map +1 -0
  125. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-schema-snapshot.test.d.ts +2 -0
  126. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-schema-snapshot.test.d.ts.map +1 -0
  127. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-schema-snapshot.test.js +35 -0
  128. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-schema-snapshot.test.js.map +1 -0
  129. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.d.ts +314 -0
  130. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.d.ts.map +1 -0
  131. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.js +308 -0
  132. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.js.map +1 -0
  133. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.test.d.ts +2 -0
  134. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.test.d.ts.map +1 -0
  135. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.test.js +147 -0
  136. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.test.js.map +1 -0
  137. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/index.d.ts +2 -0
  138. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/index.d.ts.map +1 -0
  139. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/index.js +2 -0
  140. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/index.js.map +1 -0
  141. package/dist/src/backend/domains/session/acp/codex-cli-import-resolution.integration.test.d.ts +2 -0
  142. package/dist/src/backend/domains/session/acp/codex-cli-import-resolution.integration.test.d.ts.map +1 -0
  143. package/dist/src/backend/domains/session/acp/codex-cli-import-resolution.integration.test.js +64 -0
  144. package/dist/src/backend/domains/session/acp/codex-cli-import-resolution.integration.test.js.map +1 -0
  145. package/dist/src/backend/domains/session/acp/index.d.ts +1 -0
  146. package/dist/src/backend/domains/session/acp/index.d.ts.map +1 -1
  147. package/dist/src/backend/domains/session/acp/index.js +1 -0
  148. package/dist/src/backend/domains/session/acp/index.js.map +1 -1
  149. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/permission-response.handler.d.ts.map +1 -1
  150. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/permission-response.handler.js +3 -2
  151. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/permission-response.handler.js.map +1 -1
  152. package/dist/src/backend/domains/session/chat/chat-message-handlers.service.d.ts.map +1 -1
  153. package/dist/src/backend/domains/session/chat/chat-message-handlers.service.js +13 -11
  154. package/dist/src/backend/domains/session/chat/chat-message-handlers.service.js.map +1 -1
  155. package/dist/src/backend/domains/session/chat/chat-message-handlers.service.test.js +9 -0
  156. package/dist/src/backend/domains/session/chat/chat-message-handlers.service.test.js.map +1 -1
  157. package/dist/src/backend/domains/session/data/claude-session-history-loader.service.d.ts.map +1 -1
  158. package/dist/src/backend/domains/session/data/claude-session-history-loader.service.js +17 -29
  159. package/dist/src/backend/domains/session/data/claude-session-history-loader.service.js.map +1 -1
  160. package/dist/src/backend/domains/session/data/codex-session-history-loader.service.d.ts +1 -0
  161. package/dist/src/backend/domains/session/data/codex-session-history-loader.service.d.ts.map +1 -1
  162. package/dist/src/backend/domains/session/data/codex-session-history-loader.service.js +152 -74
  163. package/dist/src/backend/domains/session/data/codex-session-history-loader.service.js.map +1 -1
  164. package/dist/src/backend/domains/session/data/codex-session-history-loader.service.test.js +96 -0
  165. package/dist/src/backend/domains/session/data/codex-session-history-loader.service.test.js.map +1 -1
  166. package/dist/src/backend/domains/session/data/session-data.service.d.ts +6 -9
  167. package/dist/src/backend/domains/session/data/session-data.service.d.ts.map +1 -1
  168. package/dist/src/backend/domains/session/data/session-data.service.js +3 -0
  169. package/dist/src/backend/domains/session/data/session-data.service.js.map +1 -1
  170. package/dist/src/backend/domains/session/data/session-history-jsonl-reader.d.ts +6 -0
  171. package/dist/src/backend/domains/session/data/session-history-jsonl-reader.d.ts.map +1 -0
  172. package/dist/src/backend/domains/session/data/session-history-jsonl-reader.js +25 -0
  173. package/dist/src/backend/domains/session/data/session-history-jsonl-reader.js.map +1 -0
  174. package/dist/src/backend/domains/session/data/session-provider-resolver.service.test.js +0 -2
  175. package/dist/src/backend/domains/session/data/session-provider-resolver.service.test.js.map +1 -1
  176. package/dist/src/backend/domains/session/index.d.ts +1 -1
  177. package/dist/src/backend/domains/session/index.d.ts.map +1 -1
  178. package/dist/src/backend/domains/session/index.js +1 -1
  179. package/dist/src/backend/domains/session/index.js.map +1 -1
  180. package/dist/src/backend/domains/session/lifecycle/session.repository.d.ts +2 -2
  181. package/dist/src/backend/domains/session/lifecycle/session.repository.d.ts.map +1 -1
  182. package/dist/src/backend/domains/session/lifecycle/session.repository.js.map +1 -1
  183. package/dist/src/backend/domains/session/lifecycle/session.service.d.ts +22 -3
  184. package/dist/src/backend/domains/session/lifecycle/session.service.d.ts.map +1 -1
  185. package/dist/src/backend/domains/session/lifecycle/session.service.js +279 -50
  186. package/dist/src/backend/domains/session/lifecycle/session.service.js.map +1 -1
  187. package/dist/src/backend/domains/session/lifecycle/session.service.test.js +447 -2
  188. package/dist/src/backend/domains/session/lifecycle/session.service.test.js.map +1 -1
  189. package/dist/src/backend/domains/session/store/session-replay-builder.d.ts.map +1 -1
  190. package/dist/src/backend/domains/session/store/session-replay-builder.js +2 -1
  191. package/dist/src/backend/domains/session/store/session-replay-builder.js.map +1 -1
  192. package/dist/src/backend/domains/session/store/session-replay-builder.test.js +26 -0
  193. package/dist/src/backend/domains/session/store/session-replay-builder.test.js.map +1 -1
  194. package/dist/src/backend/domains/workspace/bridges.d.ts +1 -0
  195. package/dist/src/backend/domains/workspace/bridges.d.ts.map +1 -1
  196. package/dist/src/backend/domains/workspace/index.d.ts +1 -0
  197. package/dist/src/backend/domains/workspace/index.d.ts.map +1 -1
  198. package/dist/src/backend/domains/workspace/index.js +1 -0
  199. package/dist/src/backend/domains/workspace/index.js.map +1 -1
  200. package/dist/src/backend/domains/workspace/lifecycle/creation.service.test.js +0 -2
  201. package/dist/src/backend/domains/workspace/lifecycle/creation.service.test.js.map +1 -1
  202. package/dist/src/backend/domains/workspace/lifecycle/data.service.d.ts +10 -24
  203. package/dist/src/backend/domains/workspace/lifecycle/data.service.d.ts.map +1 -1
  204. package/dist/src/backend/domains/workspace/query/project-management.service.d.ts +2 -6
  205. package/dist/src/backend/domains/workspace/query/project-management.service.d.ts.map +1 -1
  206. package/dist/src/backend/domains/workspace/query/workspace-query.service.d.ts +82 -6
  207. package/dist/src/backend/domains/workspace/query/workspace-query.service.d.ts.map +1 -1
  208. package/dist/src/backend/domains/workspace/query/workspace-query.service.js +15 -15
  209. package/dist/src/backend/domains/workspace/query/workspace-query.service.js.map +1 -1
  210. package/dist/src/backend/domains/workspace/query/workspace-query.service.test.d.ts +2 -0
  211. package/dist/src/backend/domains/workspace/query/workspace-query.service.test.d.ts.map +1 -0
  212. package/dist/src/backend/domains/workspace/query/workspace-query.service.test.js +165 -0
  213. package/dist/src/backend/domains/workspace/query/workspace-query.service.test.js.map +1 -0
  214. package/dist/src/backend/domains/workspace/state/pending-request-type.d.ts +6 -0
  215. package/dist/src/backend/domains/workspace/state/pending-request-type.d.ts.map +1 -0
  216. package/dist/src/backend/domains/workspace/state/pending-request-type.js +27 -0
  217. package/dist/src/backend/domains/workspace/state/pending-request-type.js.map +1 -0
  218. package/dist/src/backend/domains/workspace/state/pending-request-type.test.d.ts +2 -0
  219. package/dist/src/backend/domains/workspace/state/pending-request-type.test.d.ts.map +1 -0
  220. package/dist/src/backend/domains/workspace/state/pending-request-type.test.js +44 -0
  221. package/dist/src/backend/domains/workspace/state/pending-request-type.test.js.map +1 -0
  222. package/dist/src/backend/domains/workspace/worktree/worktree-init.test.js +9 -2
  223. package/dist/src/backend/domains/workspace/worktree/worktree-init.test.js.map +1 -1
  224. package/dist/src/backend/interceptors/pr-detection.interceptor.d.ts.map +1 -1
  225. package/dist/src/backend/interceptors/pr-detection.interceptor.js +53 -8
  226. package/dist/src/backend/interceptors/pr-detection.interceptor.js.map +1 -1
  227. package/dist/src/backend/interceptors/pr-detection.interceptor.test.d.ts +2 -0
  228. package/dist/src/backend/interceptors/pr-detection.interceptor.test.d.ts.map +1 -0
  229. package/dist/src/backend/interceptors/pr-detection.interceptor.test.js +106 -0
  230. package/dist/src/backend/interceptors/pr-detection.interceptor.test.js.map +1 -0
  231. package/dist/src/backend/lib/session-model.js +1 -1
  232. package/dist/src/backend/lib/session-model.js.map +1 -1
  233. package/dist/src/backend/lib/session-model.test.js +1 -1
  234. package/dist/src/backend/lib/session-model.test.js.map +1 -1
  235. package/dist/src/backend/orchestration/data-backup.service.d.ts.map +1 -1
  236. package/dist/src/backend/orchestration/data-backup.service.js +0 -4
  237. package/dist/src/backend/orchestration/data-backup.service.js.map +1 -1
  238. package/dist/src/backend/orchestration/data-backup.service.test.js +0 -4
  239. package/dist/src/backend/orchestration/data-backup.service.test.js.map +1 -1
  240. package/dist/src/backend/orchestration/domain-bridges.orchestrator.d.ts.map +1 -1
  241. package/dist/src/backend/orchestration/domain-bridges.orchestrator.js +3 -19
  242. package/dist/src/backend/orchestration/domain-bridges.orchestrator.js.map +1 -1
  243. package/dist/src/backend/orchestration/domain-bridges.orchestrator.test.js +13 -27
  244. package/dist/src/backend/orchestration/domain-bridges.orchestrator.test.js.map +1 -1
  245. package/dist/src/backend/orchestration/snapshot-reconciliation.orchestrator.d.ts +1 -0
  246. package/dist/src/backend/orchestration/snapshot-reconciliation.orchestrator.d.ts.map +1 -1
  247. package/dist/src/backend/orchestration/snapshot-reconciliation.orchestrator.js +1 -15
  248. package/dist/src/backend/orchestration/snapshot-reconciliation.orchestrator.js.map +1 -1
  249. package/dist/src/backend/orchestration/snapshot-reconciliation.orchestrator.test.js +97 -0
  250. package/dist/src/backend/orchestration/snapshot-reconciliation.orchestrator.test.js.map +1 -1
  251. package/dist/src/backend/orchestration/workspace-archive.orchestrator.d.ts +1 -3
  252. package/dist/src/backend/orchestration/workspace-archive.orchestrator.d.ts.map +1 -1
  253. package/dist/src/backend/orchestration/workspace-init.orchestrator.d.ts.map +1 -1
  254. package/dist/src/backend/orchestration/workspace-init.orchestrator.js +4 -1
  255. package/dist/src/backend/orchestration/workspace-init.orchestrator.js.map +1 -1
  256. package/dist/src/backend/orchestration/workspace-init.orchestrator.test.js +1 -0
  257. package/dist/src/backend/orchestration/workspace-init.orchestrator.test.js.map +1 -1
  258. package/dist/src/backend/resource_accessors/agent-session.accessor.d.ts +1 -0
  259. package/dist/src/backend/resource_accessors/agent-session.accessor.d.ts.map +1 -1
  260. package/dist/src/backend/resource_accessors/agent-session.accessor.js +10 -0
  261. package/dist/src/backend/resource_accessors/agent-session.accessor.js.map +1 -1
  262. package/dist/src/backend/resource_accessors/agent-session.accessor.test.d.ts +2 -0
  263. package/dist/src/backend/resource_accessors/agent-session.accessor.test.d.ts.map +1 -0
  264. package/dist/src/backend/resource_accessors/agent-session.accessor.test.js +148 -0
  265. package/dist/src/backend/resource_accessors/agent-session.accessor.test.js.map +1 -0
  266. package/dist/src/backend/resource_accessors/workspace.accessor.d.ts +0 -15
  267. package/dist/src/backend/resource_accessors/workspace.accessor.d.ts.map +1 -1
  268. package/dist/src/backend/resource_accessors/workspace.accessor.js +0 -33
  269. package/dist/src/backend/resource_accessors/workspace.accessor.js.map +1 -1
  270. package/dist/src/backend/schemas/tool-inputs.schema.d.ts +2 -0
  271. package/dist/src/backend/schemas/tool-inputs.schema.d.ts.map +1 -1
  272. package/dist/src/backend/schemas/tool-inputs.schema.js +1 -0
  273. package/dist/src/backend/schemas/tool-inputs.schema.js.map +1 -1
  274. package/dist/src/backend/services/constants.d.ts +0 -5
  275. package/dist/src/backend/services/constants.d.ts.map +1 -1
  276. package/dist/src/backend/services/constants.js +0 -5
  277. package/dist/src/backend/services/constants.js.map +1 -1
  278. package/dist/src/backend/services/workspace-snapshot-store.service.d.ts +2 -2
  279. package/dist/src/backend/services/workspace-snapshot-store.service.d.ts.map +1 -1
  280. package/dist/src/backend/trpc/admin-active-processes.d.ts +55 -0
  281. package/dist/src/backend/trpc/admin-active-processes.d.ts.map +1 -0
  282. package/dist/src/backend/trpc/admin-active-processes.js +103 -0
  283. package/dist/src/backend/trpc/admin-active-processes.js.map +1 -0
  284. package/dist/src/backend/trpc/admin-active-processes.test.d.ts +2 -0
  285. package/dist/src/backend/trpc/admin-active-processes.test.d.ts.map +1 -0
  286. package/dist/src/backend/trpc/admin-active-processes.test.js +105 -0
  287. package/dist/src/backend/trpc/admin-active-processes.test.js.map +1 -0
  288. package/dist/src/backend/trpc/admin.trpc.d.ts +5 -32
  289. package/dist/src/backend/trpc/admin.trpc.d.ts.map +1 -1
  290. package/dist/src/backend/trpc/admin.trpc.js +29 -128
  291. package/dist/src/backend/trpc/admin.trpc.js.map +1 -1
  292. package/dist/src/backend/trpc/decision-log.trpc.d.ts +3 -3
  293. package/dist/src/backend/trpc/index.d.ts +136 -104
  294. package/dist/src/backend/trpc/index.d.ts.map +1 -1
  295. package/dist/src/backend/trpc/log-file-reader.d.ts +26 -0
  296. package/dist/src/backend/trpc/log-file-reader.d.ts.map +1 -0
  297. package/dist/src/backend/trpc/log-file-reader.js +160 -0
  298. package/dist/src/backend/trpc/log-file-reader.js.map +1 -0
  299. package/dist/src/backend/trpc/log-file-reader.test.d.ts +2 -0
  300. package/dist/src/backend/trpc/log-file-reader.test.d.ts.map +1 -0
  301. package/dist/src/backend/trpc/log-file-reader.test.js +162 -0
  302. package/dist/src/backend/trpc/log-file-reader.test.js.map +1 -0
  303. package/dist/src/backend/trpc/project.trpc.d.ts +2 -6
  304. package/dist/src/backend/trpc/project.trpc.d.ts.map +1 -1
  305. package/dist/src/backend/trpc/session.trpc.d.ts +23 -31
  306. package/dist/src/backend/trpc/session.trpc.d.ts.map +1 -1
  307. package/dist/src/backend/trpc/workspace/init.trpc.d.ts +4 -6
  308. package/dist/src/backend/trpc/workspace/init.trpc.d.ts.map +1 -1
  309. package/dist/src/backend/trpc/workspace/workspace-helpers.d.ts +5 -9
  310. package/dist/src/backend/trpc/workspace/workspace-helpers.d.ts.map +1 -1
  311. package/dist/src/backend/trpc/workspace.trpc.d.ts +103 -39
  312. package/dist/src/backend/trpc/workspace.trpc.d.ts.map +1 -1
  313. package/dist/src/backend/trpc/workspace.trpc.js +8 -0
  314. package/dist/src/backend/trpc/workspace.trpc.js.map +1 -1
  315. package/dist/src/cli/index.js +7 -2
  316. package/dist/src/cli/index.js.map +1 -1
  317. package/dist/src/shared/acp-protocol/protocol/interaction.d.ts +2 -0
  318. package/dist/src/shared/acp-protocol/protocol/interaction.d.ts.map +1 -1
  319. package/dist/src/shared/acp-protocol/protocol/websocket.d.ts +1 -0
  320. package/dist/src/shared/acp-protocol/protocol/websocket.d.ts.map +1 -1
  321. package/dist/src/shared/acp-protocol/protocol/websocket.js.map +1 -1
  322. package/dist/src/shared/pending-request-types.d.ts +4 -0
  323. package/dist/src/shared/pending-request-types.d.ts.map +1 -1
  324. package/dist/src/shared/pending-request-types.js +10 -0
  325. package/dist/src/shared/pending-request-types.js.map +1 -1
  326. package/dist/src/shared/schemas/export-data.schema.d.ts +0 -18
  327. package/dist/src/shared/schemas/export-data.schema.d.ts.map +1 -1
  328. package/dist/src/shared/schemas/export-data.schema.js +0 -2
  329. package/dist/src/shared/schemas/export-data.schema.js.map +1 -1
  330. package/dist/src/shared/websocket/chat-message.schema.d.ts +1 -0
  331. package/dist/src/shared/websocket/chat-message.schema.d.ts.map +1 -1
  332. package/dist/src/shared/websocket/chat-message.schema.js +1 -0
  333. package/dist/src/shared/websocket/chat-message.schema.js.map +1 -1
  334. package/dist/tsconfig.backend.tsbuildinfo +1 -1
  335. package/package.json +4 -2
  336. package/prisma/migrations/20260214232046_remove_ratchet_last_modified/migration.sql +68 -0
  337. package/prisma/schema.prisma +0 -2
  338. package/dist/client/assets/channel-BA4I5_zc.js +0 -1
  339. package/dist/client/assets/classDiagram-2ON5EDUG-DHiY0J8v.js +0 -1
  340. package/dist/client/assets/classDiagram-v2-WZHVMYZB-DHiY0J8v.js +0 -1
  341. package/dist/client/assets/clone-C7QOkM1k.js +0 -1
  342. package/dist/client/assets/index-D_8rf7lP.css +0 -1
  343. package/dist/client/assets/stateDiagram-v2-4FDKWEC3-BGGNxhyR.js +0 -1
  344. package/dist/src/backend/domains/github/pr-review-fixer.service.d.ts +0 -63
  345. package/dist/src/backend/domains/github/pr-review-fixer.service.d.ts.map +0 -1
  346. package/dist/src/backend/domains/github/pr-review-fixer.service.js +0 -141
  347. package/dist/src/backend/domains/github/pr-review-fixer.service.js.map +0 -1
  348. package/dist/src/backend/domains/github/pr-review-fixer.service.test.d.ts +0 -2
  349. package/dist/src/backend/domains/github/pr-review-fixer.service.test.d.ts.map +0 -1
  350. package/dist/src/backend/domains/github/pr-review-fixer.service.test.js +0 -28
  351. package/dist/src/backend/domains/github/pr-review-fixer.service.test.js.map +0 -1
  352. package/dist/src/backend/domains/github/pr-review-monitor.service.d.ts +0 -19
  353. package/dist/src/backend/domains/github/pr-review-monitor.service.d.ts.map +0 -1
  354. package/dist/src/backend/domains/github/pr-review-monitor.service.js +0 -215
  355. package/dist/src/backend/domains/github/pr-review-monitor.service.js.map +0 -1
  356. package/dist/src/backend/domains/ratchet/ci-fixer.service.d.ts +0 -47
  357. package/dist/src/backend/domains/ratchet/ci-fixer.service.d.ts.map +0 -1
  358. package/dist/src/backend/domains/ratchet/ci-fixer.service.js +0 -95
  359. package/dist/src/backend/domains/ratchet/ci-fixer.service.js.map +0 -1
  360. package/dist/src/backend/domains/ratchet/ci-fixer.service.test.d.ts +0 -2
  361. package/dist/src/backend/domains/ratchet/ci-fixer.service.test.d.ts.map +0 -1
  362. package/dist/src/backend/domains/ratchet/ci-fixer.service.test.js +0 -100
  363. package/dist/src/backend/domains/ratchet/ci-fixer.service.test.js.map +0 -1
  364. package/dist/src/backend/domains/ratchet/ci-monitor.service.d.ts +0 -36
  365. package/dist/src/backend/domains/ratchet/ci-monitor.service.d.ts.map +0 -1
  366. package/dist/src/backend/domains/ratchet/ci-monitor.service.js +0 -278
  367. package/dist/src/backend/domains/ratchet/ci-monitor.service.js.map +0 -1
  368. package/dist/src/backend/services/fixer-workflow.service.d.ts +0 -83
  369. package/dist/src/backend/services/fixer-workflow.service.d.ts.map +0 -1
  370. package/dist/src/backend/services/fixer-workflow.service.js +0 -77
  371. package/dist/src/backend/services/fixer-workflow.service.js.map +0 -1
  372. package/dist/src/backend/services/fixer-workflow.service.test.d.ts +0 -2
  373. package/dist/src/backend/services/fixer-workflow.service.test.d.ts.map +0 -1
  374. package/dist/src/backend/services/fixer-workflow.service.test.js +0 -55
  375. package/dist/src/backend/services/fixer-workflow.service.test.js.map +0 -1
@@ -0,0 +1,1794 @@
1
+ import { Readable, Writable } from 'node:stream';
2
+ import { AgentSideConnection, ndJsonStream, PROTOCOL_VERSION, RequestError, } from '@agentclientprotocol/sdk';
3
+ import { CodexRequestError, CodexRpcClient } from './codex-rpc-client.js';
4
+ import { collaborationModeListResponseSchema, configRequirementsReadResponseSchema, knownCodexNotificationSchema, knownCodexServerRequestSchema, modelListResponseSchema, threadReadResponseSchema, threadResumeResponseSchema, threadStartResponseSchema, turnStartResponseSchema, } from './codex-zod.js';
5
+ const PENDING_TURN_ID = '__pending_turn__';
6
+ const MAX_CLOSE_WATCHER_ATTACH_RETRIES = 50;
7
+ const DEFAULT_APPROVAL_POLICIES = ['on-failure', 'on-request', 'never'];
8
+ const DEFAULT_SANDBOX_MODES = ['read-only', 'workspace-write', 'danger-full-access'];
9
+ function isNonEmptyString(value) {
10
+ return typeof value === 'string' && value.trim().length > 0;
11
+ }
12
+ function dedupeStrings(values) {
13
+ return Array.from(new Set(values));
14
+ }
15
+ function humanizeToken(value) {
16
+ return value
17
+ .split(/[-_]/)
18
+ .filter((part) => part.length > 0)
19
+ .map((part) => part.slice(0, 1).toUpperCase() + part.slice(1))
20
+ .join(' ');
21
+ }
22
+ function sanitizeModeName(mode) {
23
+ return mode
24
+ .split('_')
25
+ .map((part) => part.slice(0, 1).toUpperCase() + part.slice(1))
26
+ .join(' ');
27
+ }
28
+ function formatApprovalPolicyLabel(policy) {
29
+ if (policy === 'on-request') {
30
+ return 'On Request';
31
+ }
32
+ if (policy === 'on-failure') {
33
+ return 'On Failure';
34
+ }
35
+ if (policy === 'never') {
36
+ return 'Never Ask';
37
+ }
38
+ if (policy === 'untrusted') {
39
+ return 'Untrusted';
40
+ }
41
+ return humanizeToken(policy);
42
+ }
43
+ function formatSandboxModeLabel(mode) {
44
+ if (mode === 'workspace-write') {
45
+ return 'Workspace Write';
46
+ }
47
+ if (mode === 'read-only') {
48
+ return 'Read-only';
49
+ }
50
+ if (mode === 'danger-full-access') {
51
+ return 'Full Access';
52
+ }
53
+ return humanizeToken(mode);
54
+ }
55
+ function formatExecutionPresetName(approvalPolicy, sandboxMode) {
56
+ if (approvalPolicy === 'never' && sandboxMode === 'danger-full-access') {
57
+ return 'YOLO (Full Access)';
58
+ }
59
+ return `${formatApprovalPolicyLabel(approvalPolicy)} (${formatSandboxModeLabel(sandboxMode)})`;
60
+ }
61
+ function createWorkspaceWriteSandboxPolicy(cwd) {
62
+ return {
63
+ type: 'workspaceWrite',
64
+ writableRoots: [cwd],
65
+ readOnlyAccess: { type: 'fullAccess' },
66
+ networkAccess: true,
67
+ excludeTmpdirEnvVar: false,
68
+ excludeSlashTmp: false,
69
+ };
70
+ }
71
+ function createDangerFullAccessSandboxPolicy() {
72
+ return {
73
+ type: 'dangerFullAccess',
74
+ };
75
+ }
76
+ function parseSandboxModeFromPolicy(policy) {
77
+ if (!isRecord(policy)) {
78
+ return null;
79
+ }
80
+ const policyType = asString(policy.type);
81
+ if (policyType === 'workspaceWrite') {
82
+ return 'workspace-write';
83
+ }
84
+ if (policyType === 'readOnly') {
85
+ return 'read-only';
86
+ }
87
+ if (policyType === 'dangerFullAccess') {
88
+ return 'danger-full-access';
89
+ }
90
+ return null;
91
+ }
92
+ function createSandboxPolicyFromMode(mode, cwd) {
93
+ if (mode === 'danger-full-access') {
94
+ return createDangerFullAccessSandboxPolicy();
95
+ }
96
+ if (mode === 'read-only') {
97
+ return {
98
+ type: 'readOnly',
99
+ access: { type: 'fullAccess' },
100
+ };
101
+ }
102
+ return createWorkspaceWriteSandboxPolicy(cwd);
103
+ }
104
+ function toSessionId(threadId) {
105
+ return `sess_${threadId}`;
106
+ }
107
+ function toThreadId(sessionId) {
108
+ return sessionId.startsWith('sess_') ? sessionId.slice('sess_'.length) : sessionId;
109
+ }
110
+ function createToolCallId(threadId, turnId, itemId) {
111
+ return `codex:${threadId}:${turnId}:${itemId}`;
112
+ }
113
+ function isPlanLikeMode(mode) {
114
+ return /plan/i.test(mode);
115
+ }
116
+ const PLAN_EXIT_MODE_PREFERENCE = ['default', 'code', 'acceptEdits', 'ask'];
117
+ const PLAN_TEXT_MAX_DEPTH = 8;
118
+ const PLAN_TEXT_PREFERRED_KEYS = [
119
+ 'plan',
120
+ 'text',
121
+ 'content',
122
+ 'markdown',
123
+ 'value',
124
+ 'message',
125
+ ];
126
+ function extractFirstPlanText(values, depth) {
127
+ for (const entry of values) {
128
+ const extracted = extractPlanTextLocal(entry, depth + 1);
129
+ if (extracted) {
130
+ return extracted;
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+ function extractPlanTextFromRecord(value, depth) {
136
+ const preferred = extractFirstPlanText(PLAN_TEXT_PREFERRED_KEYS.map((key) => value[key]), depth);
137
+ if (preferred) {
138
+ return preferred;
139
+ }
140
+ return extractFirstPlanText(Object.values(value), depth);
141
+ }
142
+ function extractPlanTextLocal(value, depth = 0) {
143
+ if (depth > PLAN_TEXT_MAX_DEPTH) {
144
+ return null;
145
+ }
146
+ if (typeof value === 'string') {
147
+ const trimmed = value.trim();
148
+ return trimmed.length > 0 ? value : null;
149
+ }
150
+ if (Array.isArray(value)) {
151
+ return extractFirstPlanText(value, depth);
152
+ }
153
+ if (!isRecord(value)) {
154
+ return null;
155
+ }
156
+ return extractPlanTextFromRecord(value, depth);
157
+ }
158
+ function toMcpEnvRecord(envVars) {
159
+ const env = {};
160
+ for (const entry of envVars) {
161
+ env[entry.name] = entry.value;
162
+ }
163
+ return env;
164
+ }
165
+ function toMcpHeadersRecord(headers) {
166
+ const result = {};
167
+ for (const header of headers) {
168
+ result[header.name] = header.value;
169
+ }
170
+ return result;
171
+ }
172
+ function sanitizeMcpServerName(name, index) {
173
+ const trimmed = name.trim();
174
+ return trimmed.length > 0 ? trimmed : `mcp_server_${index + 1}`;
175
+ }
176
+ function toCodexMcpServerConfig(server) {
177
+ if ('command' in server) {
178
+ const env = toMcpEnvRecord(server.env);
179
+ return {
180
+ enabled: true,
181
+ command: server.command,
182
+ args: [...server.args],
183
+ ...(Object.keys(env).length > 0 ? { env } : {}),
184
+ };
185
+ }
186
+ const httpHeaders = toMcpHeadersRecord(server.headers);
187
+ return {
188
+ enabled: true,
189
+ url: server.url,
190
+ ...(Object.keys(httpHeaders).length > 0 ? { http_headers: httpHeaders } : {}),
191
+ ...(server.type === 'sse' ? { transport: 'sse' } : {}),
192
+ };
193
+ }
194
+ function toCodexMcpConfigMap(mcpServers) {
195
+ const mcpServersByName = {};
196
+ const usedNames = new Set();
197
+ for (const [index, server] of mcpServers.entries()) {
198
+ const baseName = sanitizeMcpServerName(server.name, index);
199
+ let nextName = baseName;
200
+ let suffix = 2;
201
+ while (usedNames.has(nextName)) {
202
+ nextName = `${baseName}_${suffix}`;
203
+ suffix += 1;
204
+ }
205
+ usedNames.add(nextName);
206
+ mcpServersByName[nextName] = toCodexMcpServerConfig(server);
207
+ }
208
+ return mcpServersByName;
209
+ }
210
+ function buildToolUserInputPermissionOptions(questions) {
211
+ if (questions.length !== 1) {
212
+ return [
213
+ { optionId: 'allow_once', name: 'Submit', kind: 'allow_once' },
214
+ { optionId: 'reject_once', name: 'Cancel', kind: 'reject_once' },
215
+ ];
216
+ }
217
+ const firstQuestionWithOptions = questions.find((question) => Array.isArray(question.options) && question.options.length > 0);
218
+ if (!firstQuestionWithOptions?.options) {
219
+ return [
220
+ { optionId: 'allow_once', name: 'Submit', kind: 'allow_once' },
221
+ { optionId: 'reject_once', name: 'Cancel', kind: 'reject_once' },
222
+ ];
223
+ }
224
+ const mappedOptions = firstQuestionWithOptions.options.slice(0, 6).map((option, index) => ({
225
+ optionId: `answer_${index}`,
226
+ name: option.label,
227
+ kind: 'allow_once',
228
+ }));
229
+ mappedOptions.push({ optionId: 'reject_once', name: 'Cancel', kind: 'reject_once' });
230
+ return mappedOptions;
231
+ }
232
+ function buildToolUserInputAnswers(params) {
233
+ if (params.selectedOptionId === null || params.selectedOptionId === 'reject_once') {
234
+ return {};
235
+ }
236
+ const parsedAnswers = parseToolUserInputAnswersFromPermissionMeta({
237
+ questions: params.questions,
238
+ permission: params.permission,
239
+ });
240
+ if (parsedAnswers) {
241
+ return parsedAnswers;
242
+ }
243
+ if (params.questions.length !== 1) {
244
+ throw new Error('Missing structured answers for multi-question requestUserInput');
245
+ }
246
+ const selectedIndex = params.selectedOptionId.startsWith('answer_')
247
+ ? Number.parseInt(params.selectedOptionId.slice('answer_'.length), 10)
248
+ : Number.NaN;
249
+ const answers = {};
250
+ const [question] = params.questions;
251
+ if (!(question && Array.isArray(question.options)) || question.options.length === 0) {
252
+ return answers;
253
+ }
254
+ const selectedOption = Number.isNaN(selectedIndex)
255
+ ? question.options[0]
256
+ : (question.options[selectedIndex] ?? question.options[0]);
257
+ if (!selectedOption) {
258
+ return answers;
259
+ }
260
+ answers[question.id] = { answers: [selectedOption.label] };
261
+ return answers;
262
+ }
263
+ function isRecord(value) {
264
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
265
+ }
266
+ function parseToolUserInputAnswersFromPermissionMeta(params) {
267
+ const meta = params.permission._meta;
268
+ if (!isRecord(meta)) {
269
+ return null;
270
+ }
271
+ const factoryFactoryMeta = isRecord(meta.factoryFactory) ? meta.factoryFactory : null;
272
+ const answersRaw = (factoryFactoryMeta?.toolUserInputAnswers ?? meta.toolUserInputAnswers);
273
+ if (!isRecord(answersRaw)) {
274
+ return null;
275
+ }
276
+ const knownQuestionIds = new Set(params.questions.map((question) => question.id));
277
+ const answers = {};
278
+ for (const [questionId, value] of Object.entries(answersRaw)) {
279
+ if (!knownQuestionIds.has(questionId)) {
280
+ continue;
281
+ }
282
+ const values = (Array.isArray(value) ? value : [value])
283
+ .filter((entry) => typeof entry === 'string')
284
+ .map((entry) => entry.trim())
285
+ .filter((entry) => entry.length > 0);
286
+ if (values.length === 0) {
287
+ continue;
288
+ }
289
+ answers[questionId] = { answers: values };
290
+ }
291
+ return answers;
292
+ }
293
+ function asString(value) {
294
+ return typeof value === 'string' ? value : null;
295
+ }
296
+ function parseTextFromPromptBlock(block) {
297
+ if (block.type === 'text') {
298
+ return block.text;
299
+ }
300
+ if (block.type === 'resource_link') {
301
+ return `[ACP_RESOURCE_LINK uri="${block.uri}" name="${block.name}"]\n[/ACP_RESOURCE_LINK]`;
302
+ }
303
+ if (block.type === 'resource') {
304
+ const resource = block.resource;
305
+ const mime = resource.mimeType ?? 'unknown';
306
+ const payload = 'text' in resource && typeof resource.text === 'string'
307
+ ? resource.text
308
+ : 'blob' in resource && typeof resource.blob === 'string'
309
+ ? resource.blob
310
+ : JSON.stringify(resource);
311
+ return `[ACP_RESOURCE uri="${resource.uri}" mime="${mime}"]\n${payload}\n[/ACP_RESOURCE]`;
312
+ }
313
+ if (block.type === 'image') {
314
+ const mime = block.mimeType ?? 'application/octet-stream';
315
+ return `[ACP_IMAGE mime="${mime}" bytes=${block.data.length}]`;
316
+ }
317
+ return JSON.stringify(block);
318
+ }
319
+ function toToolStatus(status) {
320
+ if (status === 'inProgress') {
321
+ return 'in_progress';
322
+ }
323
+ if (status === 'completed') {
324
+ return 'completed';
325
+ }
326
+ if (status === 'failed' || status === 'declined') {
327
+ return 'failed';
328
+ }
329
+ return undefined;
330
+ }
331
+ function createCollaborationModeConfigOption(currentMode, collaborationModes) {
332
+ const options = collaborationModes
333
+ .filter((entry) => isNonEmptyString(entry.mode))
334
+ .map((entry) => ({
335
+ value: entry.mode,
336
+ name: entry.name,
337
+ }));
338
+ if (!options.some((option) => option.value === currentMode)) {
339
+ options.unshift({ value: currentMode, name: sanitizeModeName(currentMode) });
340
+ }
341
+ return {
342
+ id: 'mode',
343
+ category: 'mode',
344
+ name: 'Collaboration Mode',
345
+ type: 'select',
346
+ currentValue: currentMode,
347
+ options,
348
+ };
349
+ }
350
+ function createModelConfigOption(currentModel, modelCatalog) {
351
+ const options = modelCatalog.map((model) => ({
352
+ value: model.id,
353
+ name: model.displayName,
354
+ description: model.description,
355
+ }));
356
+ if (!options.some((option) => option.value === currentModel)) {
357
+ options.unshift({ value: currentModel, name: currentModel });
358
+ }
359
+ return {
360
+ id: 'model',
361
+ category: 'model',
362
+ name: 'Model',
363
+ type: 'select',
364
+ currentValue: currentModel,
365
+ options,
366
+ };
367
+ }
368
+ function createReasoningEffortConfigOption(currentReasoningEffort, modelCatalog, currentModel) {
369
+ const modelEntry = modelCatalog.find((model) => model.id === currentModel);
370
+ if (!modelEntry) {
371
+ return null;
372
+ }
373
+ const supportedEntries = modelEntry.supportedReasoningEfforts.filter((entry) => isNonEmptyString(entry.reasoningEffort));
374
+ if (supportedEntries.length === 0) {
375
+ return null;
376
+ }
377
+ const supportedByValue = new Map();
378
+ for (const entry of supportedEntries) {
379
+ if (!supportedByValue.has(entry.reasoningEffort)) {
380
+ supportedByValue.set(entry.reasoningEffort, entry.description);
381
+ }
382
+ }
383
+ const supportedValues = [...supportedByValue.keys()];
384
+ const resolvedCurrent = currentReasoningEffort && supportedByValue.has(currentReasoningEffort)
385
+ ? currentReasoningEffort
386
+ : supportedByValue.has(modelEntry.defaultReasoningEffort)
387
+ ? modelEntry.defaultReasoningEffort
388
+ : supportedValues[0];
389
+ if (!resolvedCurrent) {
390
+ return null;
391
+ }
392
+ return {
393
+ id: 'reasoning_effort',
394
+ category: 'thought_level',
395
+ name: 'Reasoning Effort',
396
+ type: 'select',
397
+ currentValue: resolvedCurrent,
398
+ options: supportedValues.map((value) => ({
399
+ value,
400
+ name: value,
401
+ ...(supportedByValue.get(value) ? { description: supportedByValue.get(value) } : {}),
402
+ })),
403
+ };
404
+ }
405
+ function createExecutionModeConfigOption(currentPresetId, presets) {
406
+ return {
407
+ id: 'execution_mode',
408
+ category: 'permission',
409
+ name: 'Execution Mode',
410
+ type: 'select',
411
+ currentValue: currentPresetId,
412
+ options: presets.map((preset) => ({
413
+ value: preset.id,
414
+ name: preset.name,
415
+ ...(preset.description ? { description: preset.description } : {}),
416
+ })),
417
+ };
418
+ }
419
+ function toExecutionPresetId(approvalPolicy, sandboxMode) {
420
+ return JSON.stringify([approvalPolicy, sandboxMode]);
421
+ }
422
+ export class CodexAppServerAcpAdapter {
423
+ connection;
424
+ codex;
425
+ sessions = new Map();
426
+ sessionIdByThreadId = new Map();
427
+ modelCatalog = [];
428
+ allowedApprovalPolicies = [];
429
+ allowedSandboxModes = [];
430
+ collaborationModes = [];
431
+ mcpServersByThreadId = new Map();
432
+ appliedMcpServerConfigJson = '{}';
433
+ constructor(connection, codexClient) {
434
+ this.connection = connection;
435
+ this.codex =
436
+ codexClient ??
437
+ new CodexRpcClient({
438
+ cwd: process.cwd(),
439
+ env: { ...process.env },
440
+ onStderr: (line) => {
441
+ process.stderr.write(line);
442
+ },
443
+ onNotification: (notification) => {
444
+ void this.handleCodexNotification(notification.method, notification.params);
445
+ },
446
+ onRequest: (request) => {
447
+ void this.handleCodexServerRequest(request).catch((error) => {
448
+ const message = error instanceof Error ? error.message : String(error);
449
+ this.codex.respondError(request.id, {
450
+ code: -32_600,
451
+ message: 'Failed to process codex approval request',
452
+ data: { error: message },
453
+ });
454
+ });
455
+ },
456
+ onProtocolError: (error) => {
457
+ process.stderr.write(`[codex-app-server-acp] protocol-error: ${error.reason}\n`);
458
+ },
459
+ });
460
+ this.monitorConnectionClose();
461
+ }
462
+ monitorConnectionClose() {
463
+ let attachAttempts = 0;
464
+ const attachCloseWatcher = () => {
465
+ try {
466
+ void this.connection.closed
467
+ .finally(async () => {
468
+ await this.codex.stop();
469
+ })
470
+ .catch(() => {
471
+ });
472
+ }
473
+ catch {
474
+ attachAttempts += 1;
475
+ if (attachAttempts >= MAX_CLOSE_WATCHER_ATTACH_RETRIES) {
476
+ process.stderr.write(`[codex-app-server-acp] failed to attach close watcher after ${MAX_CLOSE_WATCHER_ATTACH_RETRIES} attempts\n`);
477
+ void this.codex.stop();
478
+ return;
479
+ }
480
+ const retryTimer = setTimeout(attachCloseWatcher, 0);
481
+ retryTimer.unref?.();
482
+ }
483
+ };
484
+ queueMicrotask(attachCloseWatcher);
485
+ }
486
+ async initialize(_params) {
487
+ this.codex.start();
488
+ await this.codex.request('initialize', {
489
+ clientInfo: {
490
+ name: 'factory-factory-codex-app-server-acp',
491
+ version: '0.1.0',
492
+ },
493
+ capabilities: {
494
+ experimentalApi: true,
495
+ },
496
+ });
497
+ this.codex.notify('initialized');
498
+ const configRequirements = await this.loadConfigRequirements();
499
+ this.allowedApprovalPolicies = configRequirements.allowedApprovalPolicies;
500
+ this.allowedSandboxModes = configRequirements.allowedSandboxModes;
501
+ this.collaborationModes = await this.loadCollaborationModes();
502
+ this.modelCatalog = await this.loadModelCatalog();
503
+ return {
504
+ protocolVersion: PROTOCOL_VERSION,
505
+ agentInfo: {
506
+ name: 'factory-factory-codex-app-server-acp',
507
+ version: '0.1.0',
508
+ },
509
+ agentCapabilities: {
510
+ loadSession: true,
511
+ promptCapabilities: {
512
+ image: false,
513
+ embeddedContext: true,
514
+ },
515
+ },
516
+ authMethods: [],
517
+ };
518
+ }
519
+ async newSession(params) {
520
+ const defaultModel = this.resolveDefaultModel();
521
+ const responseRaw = await this.codex.request('thread/start', {
522
+ cwd: params.cwd,
523
+ model: defaultModel,
524
+ });
525
+ const response = threadStartResponseSchema.parse(responseRaw);
526
+ const sessionId = toSessionId(response.thread.id);
527
+ const approvalPolicy = this.requireApprovalPolicy(response.approvalPolicy, 'thread/start');
528
+ const model = this.resolveSessionModel(response.model, defaultModel);
529
+ const sandboxPolicy = this.resolveSandboxPolicy(response.sandbox, params.cwd);
530
+ const reasoningEffort = this.resolveReasoningEffortForModel(model, response.reasoningEffort);
531
+ const collaborationMode = this.resolveDefaultCollaborationMode();
532
+ const session = {
533
+ sessionId,
534
+ threadId: response.thread.id,
535
+ cwd: params.cwd,
536
+ defaults: {
537
+ model,
538
+ approvalPolicy,
539
+ sandboxPolicy,
540
+ reasoningEffort,
541
+ collaborationMode,
542
+ },
543
+ activeTurn: null,
544
+ toolCallsByItemId: new Map(),
545
+ planTextByItemId: new Map(),
546
+ planApprovalRequestedByTurnId: new Set(),
547
+ pendingPlanApprovalsByTurnId: new Map(),
548
+ pendingTurnCompletionsByTurnId: new Map(),
549
+ };
550
+ this.sessions.set(sessionId, session);
551
+ this.sessionIdByThreadId.set(session.threadId, sessionId);
552
+ try {
553
+ await this.applyMcpServers(session.threadId, params.mcpServers);
554
+ }
555
+ catch (error) {
556
+ this.sessions.delete(sessionId);
557
+ this.sessionIdByThreadId.delete(session.threadId);
558
+ throw error;
559
+ }
560
+ return {
561
+ sessionId,
562
+ configOptions: this.buildConfigOptions(session),
563
+ };
564
+ }
565
+ async loadSession(params) {
566
+ const threadId = toThreadId(params.sessionId);
567
+ const responseRaw = await this.codex.request('thread/resume', {
568
+ threadId,
569
+ cwd: params.cwd,
570
+ });
571
+ const response = threadResumeResponseSchema.parse(responseRaw);
572
+ const approvalPolicy = this.requireApprovalPolicy(response.approvalPolicy, 'thread/resume');
573
+ const model = this.resolveSessionModel(response.model, this.resolveDefaultModel());
574
+ const sandboxPolicy = this.resolveSandboxPolicy(response.sandbox, params.cwd);
575
+ const reasoningEffort = this.resolveReasoningEffortForModel(model, response.reasoningEffort);
576
+ const collaborationMode = this.resolveDefaultCollaborationMode();
577
+ const session = {
578
+ sessionId: params.sessionId,
579
+ threadId: response.thread.id,
580
+ cwd: params.cwd,
581
+ defaults: {
582
+ model,
583
+ approvalPolicy,
584
+ sandboxPolicy,
585
+ reasoningEffort,
586
+ collaborationMode,
587
+ },
588
+ activeTurn: null,
589
+ toolCallsByItemId: new Map(),
590
+ planTextByItemId: new Map(),
591
+ planApprovalRequestedByTurnId: new Set(),
592
+ pendingPlanApprovalsByTurnId: new Map(),
593
+ pendingTurnCompletionsByTurnId: new Map(),
594
+ };
595
+ this.sessions.set(session.sessionId, session);
596
+ this.sessionIdByThreadId.set(session.threadId, session.sessionId);
597
+ try {
598
+ await this.applyMcpServers(session.threadId, params.mcpServers);
599
+ await this.replayThreadHistory(session.sessionId, session.threadId);
600
+ }
601
+ catch (error) {
602
+ try {
603
+ await this.removeMcpServersForThread(session.threadId);
604
+ }
605
+ catch {
606
+ }
607
+ this.sessions.delete(session.sessionId);
608
+ this.sessionIdByThreadId.delete(session.threadId);
609
+ throw error;
610
+ }
611
+ return {
612
+ configOptions: this.buildConfigOptions(session),
613
+ };
614
+ }
615
+ async authenticate(_params) {
616
+ await Promise.resolve();
617
+ return {};
618
+ }
619
+ async setSessionMode(params) {
620
+ await Promise.resolve();
621
+ const session = this.requireSession(params.sessionId);
622
+ const availableModes = this.getCollaborationModeValues(session.defaults.collaborationMode);
623
+ if (!availableModes.includes(params.modeId)) {
624
+ throw RequestError.invalidParams({
625
+ modeId: params.modeId,
626
+ });
627
+ }
628
+ session.defaults.collaborationMode = params.modeId;
629
+ return {};
630
+ }
631
+ async setSessionConfigOption(params) {
632
+ await Promise.resolve();
633
+ const session = this.requireSession(params.sessionId);
634
+ switch (params.configId) {
635
+ case 'mode': {
636
+ const availableModes = this.getCollaborationModeValues(session.defaults.collaborationMode);
637
+ if (!availableModes.includes(params.value)) {
638
+ throw RequestError.invalidParams({
639
+ configId: params.configId,
640
+ value: params.value,
641
+ });
642
+ }
643
+ session.defaults.collaborationMode = params.value;
644
+ break;
645
+ }
646
+ case 'execution_mode': {
647
+ const presets = this.getExecutionPresets(session);
648
+ const selectedPreset = presets.find((preset) => preset.id === params.value);
649
+ if (!selectedPreset) {
650
+ throw RequestError.invalidParams({
651
+ configId: params.configId,
652
+ value: params.value,
653
+ });
654
+ }
655
+ session.defaults.approvalPolicy = selectedPreset.approvalPolicy;
656
+ session.defaults.sandboxPolicy = createSandboxPolicyFromMode(selectedPreset.sandboxMode, session.cwd);
657
+ break;
658
+ }
659
+ case 'model': {
660
+ if (!this.modelCatalog.some((model) => model.id === params.value)) {
661
+ throw RequestError.invalidParams({
662
+ configId: params.configId,
663
+ value: params.value,
664
+ });
665
+ }
666
+ session.defaults.model = params.value;
667
+ session.defaults.reasoningEffort = this.resolveReasoningEffortForModel(session.defaults.model, session.defaults.reasoningEffort);
668
+ break;
669
+ }
670
+ case 'reasoning_effort':
671
+ case 'thought_level': {
672
+ const modelEntry = this.modelCatalog.find((model) => model.id === session.defaults.model);
673
+ const supportedReasoningEfforts = modelEntry?.supportedReasoningEfforts ?? [];
674
+ const supportedValues = new Set(supportedReasoningEfforts
675
+ .map((entry) => entry.reasoningEffort)
676
+ .filter((effort) => isNonEmptyString(effort)));
677
+ if (!supportedValues.has(params.value)) {
678
+ throw RequestError.invalidParams({
679
+ configId: params.configId,
680
+ value: params.value,
681
+ });
682
+ }
683
+ session.defaults.reasoningEffort = params.value;
684
+ break;
685
+ }
686
+ default:
687
+ throw RequestError.invalidParams({ configId: params.configId });
688
+ }
689
+ return {
690
+ configOptions: this.buildConfigOptions(session),
691
+ };
692
+ }
693
+ async prompt(params) {
694
+ const session = this.requireSession(params.sessionId);
695
+ if (session.activeTurn) {
696
+ throw RequestError.invalidRequest({
697
+ reason: 'A turn is already in progress for this session',
698
+ });
699
+ }
700
+ const input = params.prompt
701
+ .map((block) => parseTextFromPromptBlock(block))
702
+ .filter((text) => text.trim().length > 0)
703
+ .map((text) => ({ type: 'text', text, text_elements: [] }));
704
+ if (input.length === 0) {
705
+ throw RequestError.invalidParams({
706
+ reason: 'Prompt must include at least one non-empty content block',
707
+ });
708
+ }
709
+ const stopReasonPromise = this.createPendingTurnPromise(session);
710
+ try {
711
+ const turnStartParams = {
712
+ threadId: session.threadId,
713
+ input,
714
+ cwd: session.cwd,
715
+ approvalPolicy: session.defaults.approvalPolicy,
716
+ sandboxPolicy: session.defaults.sandboxPolicy,
717
+ model: session.defaults.model,
718
+ effort: session.defaults.reasoningEffort,
719
+ };
720
+ const collaborationModeParams = this.resolveTurnCollaborationMode(session);
721
+ if (collaborationModeParams) {
722
+ turnStartParams.collaborationMode = collaborationModeParams;
723
+ }
724
+ const turnStartRaw = await this.codex.request('turn/start', turnStartParams);
725
+ const turnStart = turnStartResponseSchema.parse(turnStartRaw);
726
+ await this.bindTurnToActivePrompt(session, turnStart.turn.id);
727
+ const immediateStopReason = await this.resolveImmediateTurnStopReason(session, turnStart);
728
+ if (immediateStopReason) {
729
+ this.settleTurn(session, immediateStopReason);
730
+ return { stopReason: await stopReasonPromise };
731
+ }
732
+ if (this.isActiveTurnCancelRequested(session)) {
733
+ await this.requestTurnInterrupt(session);
734
+ }
735
+ return { stopReason: await stopReasonPromise };
736
+ }
737
+ catch (error) {
738
+ if (error instanceof CodexRequestError && error.code === -32_001) {
739
+ await this.emitTurnFailureMessage(session.sessionId, error.message || 'Codex app-server request failed with overload response.');
740
+ this.settleTurn(session, 'end_turn');
741
+ return { stopReason: await stopReasonPromise };
742
+ }
743
+ this.settleTurn(session, 'end_turn');
744
+ throw error;
745
+ }
746
+ }
747
+ async resolveImmediateTurnStopReason(session, turnStart) {
748
+ const status = turnStart.turn.status;
749
+ if (status === 'interrupted') {
750
+ return 'cancelled';
751
+ }
752
+ if (status === 'failed') {
753
+ const failureMessage = asString(isRecord(turnStart.turn.error) ? turnStart.turn.error.message : null);
754
+ if (failureMessage) {
755
+ await this.emitTurnFailureMessage(session.sessionId, failureMessage);
756
+ }
757
+ return 'end_turn';
758
+ }
759
+ if (status === 'completed') {
760
+ return 'end_turn';
761
+ }
762
+ return null;
763
+ }
764
+ createPendingTurnPromise(session) {
765
+ return new Promise((resolve) => {
766
+ session.activeTurn = {
767
+ turnId: PENDING_TURN_ID,
768
+ cancelRequested: false,
769
+ settled: false,
770
+ resolve,
771
+ };
772
+ });
773
+ }
774
+ async bindTurnToActivePrompt(session, turnId) {
775
+ if (!session.activeTurn || session.activeTurn.settled) {
776
+ return;
777
+ }
778
+ session.activeTurn.turnId = turnId;
779
+ const pendingCompletion = session.pendingTurnCompletionsByTurnId.get(turnId);
780
+ if (!pendingCompletion) {
781
+ return;
782
+ }
783
+ session.pendingTurnCompletionsByTurnId.delete(turnId);
784
+ if (pendingCompletion.errorMessage) {
785
+ await this.emitTurnFailureMessage(session.sessionId, pendingCompletion.errorMessage);
786
+ }
787
+ this.settleTurn(session, pendingCompletion.stopReason);
788
+ }
789
+ async requestTurnInterrupt(session) {
790
+ if (!session.activeTurn || session.activeTurn.turnId === PENDING_TURN_ID) {
791
+ return;
792
+ }
793
+ await this.codex.request('turn/interrupt', {
794
+ threadId: session.threadId,
795
+ turnId: session.activeTurn.turnId,
796
+ });
797
+ }
798
+ isActiveTurnCancelRequested(session) {
799
+ return Boolean(session.activeTurn?.cancelRequested);
800
+ }
801
+ async emitTurnFailureMessage(sessionId, errorMessage) {
802
+ await this.emitSessionUpdate(sessionId, {
803
+ sessionUpdate: 'agent_message_chunk',
804
+ content: {
805
+ type: 'text',
806
+ text: errorMessage,
807
+ },
808
+ });
809
+ }
810
+ async cancel(params) {
811
+ const session = this.sessions.get(params.sessionId);
812
+ if (!session?.activeTurn) {
813
+ return;
814
+ }
815
+ session.activeTurn.cancelRequested = true;
816
+ if (session.activeTurn.turnId === PENDING_TURN_ID) {
817
+ return;
818
+ }
819
+ try {
820
+ await this.requestTurnInterrupt(session);
821
+ }
822
+ catch {
823
+ this.settleTurn(session, 'cancelled');
824
+ }
825
+ }
826
+ resolveDefaultModel() {
827
+ const preferred = this.modelCatalog.find((model) => model.isDefault);
828
+ const fallback = this.modelCatalog[0]?.id;
829
+ if (preferred?.id) {
830
+ return preferred.id;
831
+ }
832
+ if (fallback) {
833
+ return fallback;
834
+ }
835
+ throw new Error('Codex app-server model/list returned no models');
836
+ }
837
+ buildMergedMcpServersConfig() {
838
+ const merged = {};
839
+ const threadIds = [...this.mcpServersByThreadId.keys()].sort();
840
+ for (const threadId of threadIds) {
841
+ const threadConfig = this.mcpServersByThreadId.get(threadId);
842
+ if (!threadConfig) {
843
+ continue;
844
+ }
845
+ for (const serverName of Object.keys(threadConfig).sort()) {
846
+ const nextServerConfig = threadConfig[serverName];
847
+ if (!nextServerConfig) {
848
+ continue;
849
+ }
850
+ const existingConfig = merged[serverName];
851
+ if (!existingConfig) {
852
+ merged[serverName] = nextServerConfig;
853
+ continue;
854
+ }
855
+ if (JSON.stringify(existingConfig) === JSON.stringify(nextServerConfig)) {
856
+ continue;
857
+ }
858
+ merged[`${serverName}__${threadId}`] = nextServerConfig;
859
+ }
860
+ }
861
+ return merged;
862
+ }
863
+ async writeMergedMcpServersConfig() {
864
+ const mergedConfig = this.buildMergedMcpServersConfig();
865
+ const nextConfigJson = JSON.stringify(mergedConfig);
866
+ if (nextConfigJson === this.appliedMcpServerConfigJson) {
867
+ return;
868
+ }
869
+ await this.codex.request('config/value/write', {
870
+ keyPath: 'mcp_servers',
871
+ value: mergedConfig,
872
+ mergeStrategy: 'replace',
873
+ });
874
+ await this.codex.request('config/mcpServer/reload', {});
875
+ this.appliedMcpServerConfigJson = nextConfigJson;
876
+ }
877
+ async removeMcpServersForThread(threadId) {
878
+ if (!this.mcpServersByThreadId.has(threadId)) {
879
+ return;
880
+ }
881
+ this.mcpServersByThreadId.delete(threadId);
882
+ await this.writeMergedMcpServersConfig();
883
+ }
884
+ async applyMcpServers(threadId, mcpServers) {
885
+ const previousConfig = this.mcpServersByThreadId.get(threadId);
886
+ const hadPreviousConfig = this.mcpServersByThreadId.has(threadId);
887
+ const threadConfig = toCodexMcpConfigMap(mcpServers);
888
+ if (Object.keys(threadConfig).length === 0) {
889
+ this.mcpServersByThreadId.delete(threadId);
890
+ }
891
+ else {
892
+ this.mcpServersByThreadId.set(threadId, threadConfig);
893
+ }
894
+ try {
895
+ await this.writeMergedMcpServersConfig();
896
+ }
897
+ catch (error) {
898
+ if (hadPreviousConfig && previousConfig) {
899
+ this.mcpServersByThreadId.set(threadId, previousConfig);
900
+ }
901
+ else {
902
+ this.mcpServersByThreadId.delete(threadId);
903
+ }
904
+ throw error;
905
+ }
906
+ }
907
+ buildConfigOptions(session) {
908
+ const executionPresets = this.getExecutionPresets(session);
909
+ const currentExecutionPreset = this.resolveExecutionPresetId(session, executionPresets);
910
+ const configOptions = [
911
+ createModelConfigOption(session.defaults.model, this.modelCatalog),
912
+ createCollaborationModeConfigOption(session.defaults.collaborationMode, this.collaborationModes),
913
+ createExecutionModeConfigOption(currentExecutionPreset, executionPresets),
914
+ ];
915
+ const reasoningOption = createReasoningEffortConfigOption(session.defaults.reasoningEffort, this.modelCatalog, session.defaults.model);
916
+ if (reasoningOption) {
917
+ configOptions.push(reasoningOption);
918
+ }
919
+ return configOptions;
920
+ }
921
+ requireApprovalPolicy(approvalPolicy, source) {
922
+ if (!isNonEmptyString(approvalPolicy)) {
923
+ throw new Error(`Codex ${source} response did not include approvalPolicy`);
924
+ }
925
+ return approvalPolicy;
926
+ }
927
+ resolveSessionModel(model, fallbackModel) {
928
+ return isNonEmptyString(model) ? model : fallbackModel;
929
+ }
930
+ resolveSandboxPolicy(sandbox, cwd) {
931
+ return isRecord(sandbox) ? sandbox : createWorkspaceWriteSandboxPolicy(cwd);
932
+ }
933
+ resolveDefaultCollaborationMode() {
934
+ if (this.collaborationModes.length === 0) {
935
+ throw new Error('Codex collaborationMode/list returned no modes');
936
+ }
937
+ const preferredDefault = this.collaborationModes.find((entry) => entry.mode === 'default');
938
+ const firstMode = this.collaborationModes[0]?.mode;
939
+ if (!firstMode) {
940
+ throw new Error('Codex collaborationMode/list returned an invalid mode entry');
941
+ }
942
+ return preferredDefault?.mode ?? firstMode;
943
+ }
944
+ resolveTurnCollaborationMode(session) {
945
+ const modeEntry = this.collaborationModes.find((entry) => entry.mode === session.defaults.collaborationMode);
946
+ return {
947
+ mode: modeEntry?.mode ?? session.defaults.collaborationMode,
948
+ settings: {
949
+ model: modeEntry?.model ?? session.defaults.model,
950
+ reasoning_effort: modeEntry?.reasoningEffort ?? session.defaults.reasoningEffort,
951
+ developer_instructions: modeEntry?.developerInstructions ?? null,
952
+ },
953
+ };
954
+ }
955
+ resolveReasoningEffortForModel(modelId, candidateReasoningEffort) {
956
+ const modelEntry = this.modelCatalog.find((entry) => entry.id === modelId);
957
+ if (!modelEntry) {
958
+ return null;
959
+ }
960
+ const supportedEntries = modelEntry.supportedReasoningEfforts.filter((entry) => isNonEmptyString(entry.reasoningEffort));
961
+ if (supportedEntries.length === 0) {
962
+ return null;
963
+ }
964
+ const supportedValues = dedupeStrings(supportedEntries.map((entry) => entry.reasoningEffort));
965
+ if (isNonEmptyString(candidateReasoningEffort) &&
966
+ supportedValues.includes(candidateReasoningEffort)) {
967
+ return candidateReasoningEffort;
968
+ }
969
+ if (supportedValues.includes(modelEntry.defaultReasoningEffort)) {
970
+ return modelEntry.defaultReasoningEffort;
971
+ }
972
+ return supportedValues[0] ?? null;
973
+ }
974
+ getCollaborationModeValues(currentMode) {
975
+ const values = this.collaborationModes
976
+ .map((entry) => entry.mode)
977
+ .filter((mode) => isNonEmptyString(mode));
978
+ if (!values.includes(currentMode)) {
979
+ values.unshift(currentMode);
980
+ }
981
+ return dedupeStrings(values);
982
+ }
983
+ resolveCurrentSandboxMode(session) {
984
+ const currentSandboxMode = parseSandboxModeFromPolicy(session.defaults.sandboxPolicy);
985
+ if (currentSandboxMode) {
986
+ return currentSandboxMode;
987
+ }
988
+ const allowedSandboxMode = this.allowedSandboxModes[0];
989
+ if (allowedSandboxMode) {
990
+ return allowedSandboxMode;
991
+ }
992
+ throw new Error('Unable to resolve current sandbox mode for execution-mode options');
993
+ }
994
+ getExecutionPresets(session) {
995
+ const currentSandboxMode = this.resolveCurrentSandboxMode(session);
996
+ const policies = this.allowedApprovalPolicies.length > 0
997
+ ? this.allowedApprovalPolicies
998
+ : [session.defaults.approvalPolicy];
999
+ const sandboxModes = this.allowedSandboxModes.length > 0 ? this.allowedSandboxModes : [currentSandboxMode];
1000
+ const presets = [];
1001
+ const seen = new Set();
1002
+ const addPreset = (approvalPolicy, sandboxMode, description) => {
1003
+ const id = toExecutionPresetId(approvalPolicy, sandboxMode);
1004
+ if (seen.has(id)) {
1005
+ return;
1006
+ }
1007
+ seen.add(id);
1008
+ presets.push({
1009
+ id,
1010
+ name: formatExecutionPresetName(approvalPolicy, sandboxMode),
1011
+ ...(description ? { description } : {}),
1012
+ approvalPolicy,
1013
+ sandboxMode,
1014
+ });
1015
+ };
1016
+ addPreset(session.defaults.approvalPolicy, currentSandboxMode, `Current session: ${formatExecutionPresetName(session.defaults.approvalPolicy, currentSandboxMode)}`);
1017
+ for (const policy of policies) {
1018
+ for (const sandboxMode of sandboxModes) {
1019
+ addPreset(policy, sandboxMode);
1020
+ }
1021
+ }
1022
+ return presets;
1023
+ }
1024
+ resolveExecutionPresetId(session, presets) {
1025
+ const currentSandboxMode = this.resolveCurrentSandboxMode(session);
1026
+ const currentPresetId = toExecutionPresetId(session.defaults.approvalPolicy, currentSandboxMode);
1027
+ const fallbackPresetId = presets[0]?.id;
1028
+ if (!fallbackPresetId) {
1029
+ throw new Error('Execution-mode presets are unavailable for this session');
1030
+ }
1031
+ return presets.some((preset) => preset.id === currentPresetId)
1032
+ ? currentPresetId
1033
+ : fallbackPresetId;
1034
+ }
1035
+ requireSession(sessionId) {
1036
+ const session = this.sessions.get(sessionId);
1037
+ if (!session) {
1038
+ throw RequestError.invalidParams({ sessionId });
1039
+ }
1040
+ return session;
1041
+ }
1042
+ async loadConfigRequirements() {
1043
+ const raw = await this.codex.request('configRequirements/read', undefined);
1044
+ const parsed = configRequirementsReadResponseSchema.parse(raw);
1045
+ const requirements = parsed.requirements;
1046
+ const allowedApprovalPolicies = dedupeStrings((requirements?.allowedApprovalPolicies ?? []).filter(isNonEmptyString));
1047
+ const allowedSandboxModes = dedupeStrings((requirements?.allowedSandboxModes ?? []).filter((mode) => mode === 'read-only' || mode === 'workspace-write' || mode === 'danger-full-access'));
1048
+ const hasExplicitApprovalPolicies = Array.isArray(requirements?.allowedApprovalPolicies);
1049
+ const hasExplicitSandboxModes = Array.isArray(requirements?.allowedSandboxModes);
1050
+ return {
1051
+ allowedApprovalPolicies: allowedApprovalPolicies.length > 0
1052
+ ? allowedApprovalPolicies
1053
+ : hasExplicitApprovalPolicies
1054
+ ? []
1055
+ : DEFAULT_APPROVAL_POLICIES,
1056
+ allowedSandboxModes: allowedSandboxModes.length > 0
1057
+ ? allowedSandboxModes
1058
+ : hasExplicitSandboxModes
1059
+ ? []
1060
+ : DEFAULT_SANDBOX_MODES,
1061
+ };
1062
+ }
1063
+ async loadCollaborationModes() {
1064
+ const entries = [];
1065
+ let cursor = null;
1066
+ for (;;) {
1067
+ const response = await this.requestCollaborationModeListWithRetry(cursor);
1068
+ entries.push(...response.data.flatMap((entry) => isNonEmptyString(entry.mode)
1069
+ ? [
1070
+ {
1071
+ mode: entry.mode,
1072
+ name: entry.name,
1073
+ model: entry.model ?? null,
1074
+ reasoningEffort: entry.reasoning_effort ?? null,
1075
+ developerInstructions: entry.developer_instructions ?? null,
1076
+ },
1077
+ ]
1078
+ : []));
1079
+ cursor = response.nextCursor ?? null;
1080
+ if (!cursor) {
1081
+ break;
1082
+ }
1083
+ }
1084
+ if (entries.length === 0) {
1085
+ throw new Error('Codex collaborationMode/list returned no modes');
1086
+ }
1087
+ return entries;
1088
+ }
1089
+ async requestCollaborationModeListWithRetry(cursor, maxAttempts = 3) {
1090
+ let attempt = 0;
1091
+ let lastError;
1092
+ while (attempt < maxAttempts) {
1093
+ attempt += 1;
1094
+ try {
1095
+ const raw = await this.codex.request('collaborationMode/list', cursor ? { cursor } : {});
1096
+ return collaborationModeListResponseSchema.parse(raw);
1097
+ }
1098
+ catch (error) {
1099
+ lastError = error;
1100
+ if (!(error instanceof CodexRequestError) ||
1101
+ error.code !== -32_001 ||
1102
+ attempt >= maxAttempts) {
1103
+ throw error;
1104
+ }
1105
+ const delayMs = Math.round(2 ** attempt * 100 + Math.random() * 120);
1106
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1107
+ }
1108
+ }
1109
+ throw lastError;
1110
+ }
1111
+ async loadModelCatalog() {
1112
+ const models = [];
1113
+ let cursor = null;
1114
+ for (;;) {
1115
+ const response = await this.requestModelListWithRetry(cursor);
1116
+ models.push(...response.data.map((model) => ({
1117
+ id: model.id,
1118
+ displayName: model.displayName,
1119
+ description: model.description,
1120
+ defaultReasoningEffort: model.defaultReasoningEffort,
1121
+ supportedReasoningEfforts: (model.supportedReasoningEfforts ?? [])
1122
+ .filter((entry) => isNonEmptyString(entry.reasoningEffort))
1123
+ .map((entry) => ({
1124
+ reasoningEffort: entry.reasoningEffort,
1125
+ description: entry.description,
1126
+ })),
1127
+ inputModalities: model.inputModalities ?? [],
1128
+ isDefault: model.isDefault ?? false,
1129
+ })));
1130
+ cursor = response.nextCursor ?? null;
1131
+ if (!cursor) {
1132
+ return models;
1133
+ }
1134
+ }
1135
+ }
1136
+ async requestModelListWithRetry(cursor, maxAttempts = 3) {
1137
+ let attempt = 0;
1138
+ let lastError;
1139
+ while (attempt < maxAttempts) {
1140
+ attempt += 1;
1141
+ try {
1142
+ const raw = await this.codex.request('model/list', cursor ? { cursor } : {});
1143
+ return modelListResponseSchema.parse(raw);
1144
+ }
1145
+ catch (error) {
1146
+ lastError = error;
1147
+ if (!(error instanceof CodexRequestError) ||
1148
+ error.code !== -32_001 ||
1149
+ attempt >= maxAttempts) {
1150
+ throw error;
1151
+ }
1152
+ const delayMs = Math.round(2 ** attempt * 100 + Math.random() * 120);
1153
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1154
+ }
1155
+ }
1156
+ throw lastError;
1157
+ }
1158
+ async replayThreadHistory(sessionId, threadId) {
1159
+ const session = this.requireSession(sessionId);
1160
+ const threadReadRaw = await this.codex.request('thread/read', {
1161
+ threadId,
1162
+ includeTurns: true,
1163
+ });
1164
+ const threadRead = threadReadResponseSchema.parse(threadReadRaw);
1165
+ for (const turn of threadRead.thread.turns) {
1166
+ for (const item of turn.items) {
1167
+ await this.replayThreadHistoryItem(session, sessionId, turn.id, item);
1168
+ }
1169
+ }
1170
+ }
1171
+ async replayThreadHistoryItem(session, sessionId, turnId, item) {
1172
+ if (item.type === 'userMessage') {
1173
+ await this.replayUserMessageHistoryItem(sessionId, item);
1174
+ return;
1175
+ }
1176
+ if (item.type === 'agentMessage') {
1177
+ await this.replayAgentMessageHistoryItem(sessionId, item);
1178
+ return;
1179
+ }
1180
+ await this.replayToolLikeHistoryItem(session, sessionId, turnId, item);
1181
+ }
1182
+ async replayUserMessageHistoryItem(sessionId, item) {
1183
+ const contentBlocks = Array.isArray(item.content) ? item.content : [];
1184
+ for (const content of contentBlocks) {
1185
+ if (!isRecord(content) || content.type !== 'text' || typeof content.text !== 'string') {
1186
+ continue;
1187
+ }
1188
+ await this.emitSessionUpdate(sessionId, {
1189
+ sessionUpdate: 'user_message_chunk',
1190
+ content: { type: 'text', text: content.text },
1191
+ });
1192
+ }
1193
+ }
1194
+ async replayAgentMessageHistoryItem(sessionId, item) {
1195
+ const text = asString(item.text);
1196
+ if (!text) {
1197
+ return;
1198
+ }
1199
+ await this.emitSessionUpdate(sessionId, {
1200
+ sessionUpdate: 'agent_message_chunk',
1201
+ content: { type: 'text', text },
1202
+ });
1203
+ }
1204
+ async replayToolLikeHistoryItem(session, sessionId, turnId, item) {
1205
+ const toolInfo = this.buildToolCallState(session, item, turnId);
1206
+ if (!toolInfo) {
1207
+ return;
1208
+ }
1209
+ await this.emitSessionUpdate(sessionId, {
1210
+ sessionUpdate: 'tool_call',
1211
+ toolCallId: toolInfo.toolCallId,
1212
+ title: toolInfo.title,
1213
+ kind: toolInfo.kind,
1214
+ status: 'completed',
1215
+ rawInput: item,
1216
+ rawOutput: item,
1217
+ });
1218
+ }
1219
+ async handleCodexNotification(method, params) {
1220
+ const parsed = knownCodexNotificationSchema.safeParse({ method, params });
1221
+ if (!parsed.success) {
1222
+ process.stderr.write(`[codex-app-server-acp] dropped malformed notification: ${method}\n`);
1223
+ return;
1224
+ }
1225
+ const notification = parsed.data;
1226
+ if (notification.method === 'error' || notification.method === 'turn/started') {
1227
+ return;
1228
+ }
1229
+ const sessionId = this.sessionIdByThreadId.get(notification.params.threadId);
1230
+ if (!sessionId) {
1231
+ return;
1232
+ }
1233
+ const session = this.sessions.get(sessionId);
1234
+ if (!session) {
1235
+ return;
1236
+ }
1237
+ if (notification.method === 'item/agentMessage/delta') {
1238
+ await this.emitSessionUpdate(sessionId, {
1239
+ sessionUpdate: 'agent_message_chunk',
1240
+ content: {
1241
+ type: 'text',
1242
+ text: notification.params.delta,
1243
+ },
1244
+ });
1245
+ return;
1246
+ }
1247
+ if (notification.method === 'item/plan/delta') {
1248
+ await this.handlePlanDelta(sessionId, session, notification.params.itemId, notification.params.delta);
1249
+ return;
1250
+ }
1251
+ if (notification.method === 'item/commandExecution/outputDelta') {
1252
+ await this.emitToolCallProgress(sessionId, session, notification.params.itemId, notification.params.delta);
1253
+ return;
1254
+ }
1255
+ if (notification.method === 'item/fileChange/outputDelta') {
1256
+ await this.emitToolCallProgress(sessionId, session, notification.params.itemId, notification.params.delta);
1257
+ return;
1258
+ }
1259
+ if (notification.method === 'item/mcpToolCall/progress') {
1260
+ await this.emitToolCallProgress(sessionId, session, notification.params.itemId, notification.params.message);
1261
+ return;
1262
+ }
1263
+ if (notification.method === 'item/started') {
1264
+ await this.handleItemStarted(session, notification.params.item, notification.params.turnId);
1265
+ return;
1266
+ }
1267
+ if (notification.method === 'item/completed') {
1268
+ await this.handleItemCompleted(session, notification.params.item, notification.params.turnId);
1269
+ return;
1270
+ }
1271
+ if (notification.method === 'turn/completed') {
1272
+ await this.handleTurnCompletedNotification(session, notification.params.turn.id, notification.params.turn.status, notification.params.turn.error?.message);
1273
+ }
1274
+ }
1275
+ async handlePlanDelta(sessionId, session, itemId, delta) {
1276
+ const previous = session.planTextByItemId.get(itemId) ?? '';
1277
+ const next = `${previous}${delta}`;
1278
+ session.planTextByItemId.set(itemId, next);
1279
+ await this.emitSessionUpdate(sessionId, {
1280
+ sessionUpdate: 'plan',
1281
+ entries: [
1282
+ {
1283
+ content: next,
1284
+ priority: 'medium',
1285
+ status: 'in_progress',
1286
+ },
1287
+ ],
1288
+ });
1289
+ }
1290
+ async emitToolCallProgress(sessionId, session, itemId, output) {
1291
+ const toolCall = session.toolCallsByItemId.get(itemId);
1292
+ if (!toolCall) {
1293
+ return;
1294
+ }
1295
+ await this.emitSessionUpdate(sessionId, {
1296
+ sessionUpdate: 'tool_call_update',
1297
+ toolCallId: toolCall.toolCallId,
1298
+ status: 'in_progress',
1299
+ rawOutput: output,
1300
+ });
1301
+ }
1302
+ async handleTurnCompletedNotification(session, turnId, status, errorMessage) {
1303
+ if (status === 'inProgress') {
1304
+ return;
1305
+ }
1306
+ const completionStatus = status;
1307
+ if (this.shouldDeferTurnCompletion(session, turnId, completionStatus)) {
1308
+ session.pendingTurnCompletionsByTurnId.set(turnId, this.buildPendingTurnCompletion(completionStatus, errorMessage));
1309
+ return;
1310
+ }
1311
+ await this.finalizeTurnCompletion(session, completionStatus, errorMessage);
1312
+ }
1313
+ shouldDeferTurnCompletion(session, turnId, status) {
1314
+ if (!session.activeTurn || session.activeTurn.turnId !== turnId) {
1315
+ return true;
1316
+ }
1317
+ return status !== 'interrupted' && this.hasPendingPlanApprovals(session, turnId);
1318
+ }
1319
+ buildPendingTurnCompletion(status, errorMessage) {
1320
+ const stopReason = status === 'interrupted' ? 'cancelled' : 'end_turn';
1321
+ if (status === 'failed' && errorMessage) {
1322
+ return { stopReason, errorMessage };
1323
+ }
1324
+ return { stopReason };
1325
+ }
1326
+ async finalizeTurnCompletion(session, status, errorMessage) {
1327
+ if (status === 'interrupted') {
1328
+ this.settleTurn(session, 'cancelled');
1329
+ return;
1330
+ }
1331
+ if (status === 'failed' && errorMessage) {
1332
+ await this.emitTurnFailureMessage(session.sessionId, errorMessage);
1333
+ }
1334
+ if (status === 'failed') {
1335
+ this.settleTurn(session, 'end_turn');
1336
+ return;
1337
+ }
1338
+ this.settleTurn(session, session.activeTurn?.cancelRequested ? 'cancelled' : 'end_turn');
1339
+ }
1340
+ async handleItemStarted(session, item, turnId) {
1341
+ const toolInfo = this.buildToolCallState(session, item, turnId);
1342
+ if (!toolInfo) {
1343
+ return;
1344
+ }
1345
+ session.toolCallsByItemId.set(item.id, toolInfo);
1346
+ await this.emitSessionUpdate(session.sessionId, {
1347
+ sessionUpdate: 'tool_call',
1348
+ toolCallId: toolInfo.toolCallId,
1349
+ title: toolInfo.title,
1350
+ kind: toolInfo.kind,
1351
+ status: 'pending',
1352
+ rawInput: item,
1353
+ });
1354
+ const itemStatus = toToolStatus(item.status);
1355
+ if (itemStatus === 'in_progress') {
1356
+ await this.emitSessionUpdate(session.sessionId, {
1357
+ sessionUpdate: 'tool_call_update',
1358
+ toolCallId: toolInfo.toolCallId,
1359
+ status: 'in_progress',
1360
+ });
1361
+ }
1362
+ }
1363
+ async handleItemCompleted(session, item, turnId) {
1364
+ const existing = session.toolCallsByItemId.get(item.id);
1365
+ if (!existing) {
1366
+ return;
1367
+ }
1368
+ const shouldHoldTurnForPlanApproval = item.type === 'plan' &&
1369
+ isPlanLikeMode(session.defaults.collaborationMode) &&
1370
+ !session.planApprovalRequestedByTurnId.has(turnId) &&
1371
+ this.extractPlanApprovalText(session, item) !== null;
1372
+ if (shouldHoldTurnForPlanApproval) {
1373
+ this.holdTurnUntilPlanApprovalResolves(session, turnId);
1374
+ }
1375
+ const statusFromItem = toToolStatus(item.status);
1376
+ const status = statusFromItem ?? 'completed';
1377
+ const locations = this.extractLocations(item);
1378
+ await this.emitSessionUpdate(session.sessionId, {
1379
+ sessionUpdate: 'tool_call_update',
1380
+ toolCallId: existing.toolCallId,
1381
+ status,
1382
+ kind: existing.kind,
1383
+ title: existing.title,
1384
+ ...(locations.length > 0 ? { locations } : {}),
1385
+ rawOutput: item,
1386
+ });
1387
+ session.toolCallsByItemId.delete(item.id);
1388
+ await this.maybeRequestPlanApproval(session, item, turnId, existing);
1389
+ }
1390
+ buildPlanApprovalInput(planText, sourceItemId) {
1391
+ return {
1392
+ type: 'ExitPlanMode',
1393
+ plan: { type: 'text', text: planText },
1394
+ reason: 'Plan proposed. Approve to exit plan mode and continue implementation.',
1395
+ source: 'codex_plan_completion',
1396
+ sourceItemId,
1397
+ };
1398
+ }
1399
+ extractPlanApprovalText(session, item) {
1400
+ const bufferedText = session.planTextByItemId.get(item.id);
1401
+ if (bufferedText && bufferedText.trim().length > 0) {
1402
+ return bufferedText;
1403
+ }
1404
+ const fromPlanField = extractPlanTextLocal(item.plan);
1405
+ if (fromPlanField && fromPlanField.trim().length > 0) {
1406
+ return fromPlanField;
1407
+ }
1408
+ const fromTextField = extractPlanTextLocal(item.text);
1409
+ if (fromTextField && fromTextField.trim().length > 0) {
1410
+ return fromTextField;
1411
+ }
1412
+ return null;
1413
+ }
1414
+ isPlanApprovalSelected(optionId) {
1415
+ if (!optionId) {
1416
+ return false;
1417
+ }
1418
+ return optionId.startsWith('allow') || optionId === 'default';
1419
+ }
1420
+ resolvePostPlanApprovalMode(currentMode) {
1421
+ if (!isPlanLikeMode(currentMode)) {
1422
+ return null;
1423
+ }
1424
+ const availableModes = this.getCollaborationModeValues(currentMode);
1425
+ for (const preferred of PLAN_EXIT_MODE_PREFERENCE) {
1426
+ const match = availableModes.find((mode) => mode.toLowerCase() === preferred.toLowerCase());
1427
+ if (match) {
1428
+ return match;
1429
+ }
1430
+ }
1431
+ const firstNonPlan = availableModes.find((mode) => !isPlanLikeMode(mode));
1432
+ if (firstNonPlan) {
1433
+ return firstNonPlan;
1434
+ }
1435
+ return currentMode.toLowerCase() === 'default' ? null : 'default';
1436
+ }
1437
+ holdTurnUntilPlanApprovalResolves(session, turnId) {
1438
+ if (this.hasPendingPlanApprovals(session, turnId)) {
1439
+ return;
1440
+ }
1441
+ session.pendingPlanApprovalsByTurnId.set(turnId, 1);
1442
+ }
1443
+ hasPendingPlanApprovals(session, turnId) {
1444
+ return (session.pendingPlanApprovalsByTurnId.get(turnId) ?? 0) > 0;
1445
+ }
1446
+ async releaseTurnHoldForPlanApproval(session, turnId) {
1447
+ const pendingCount = session.pendingPlanApprovalsByTurnId.get(turnId) ?? 0;
1448
+ if (pendingCount <= 1) {
1449
+ session.pendingPlanApprovalsByTurnId.delete(turnId);
1450
+ }
1451
+ else {
1452
+ session.pendingPlanApprovalsByTurnId.set(turnId, pendingCount - 1);
1453
+ }
1454
+ if (this.hasPendingPlanApprovals(session, turnId)) {
1455
+ return;
1456
+ }
1457
+ if (!session.activeTurn || session.activeTurn.settled || session.activeTurn.turnId !== turnId) {
1458
+ return;
1459
+ }
1460
+ const deferredCompletion = session.pendingTurnCompletionsByTurnId.get(turnId);
1461
+ if (!deferredCompletion) {
1462
+ return;
1463
+ }
1464
+ session.pendingTurnCompletionsByTurnId.delete(turnId);
1465
+ if (deferredCompletion.errorMessage) {
1466
+ await this.emitTurnFailureMessage(session.sessionId, deferredCompletion.errorMessage);
1467
+ }
1468
+ this.settleTurn(session, deferredCompletion.stopReason);
1469
+ }
1470
+ async maybeRequestPlanApproval(session, item, turnId, completedPlanToolCall) {
1471
+ if (item.type !== 'plan') {
1472
+ return;
1473
+ }
1474
+ if (!isPlanLikeMode(session.defaults.collaborationMode)) {
1475
+ return;
1476
+ }
1477
+ if (session.planApprovalRequestedByTurnId.has(turnId)) {
1478
+ return;
1479
+ }
1480
+ const planText = this.extractPlanApprovalText(session, item);
1481
+ if (!planText) {
1482
+ return;
1483
+ }
1484
+ session.planApprovalRequestedByTurnId.add(turnId);
1485
+ this.holdTurnUntilPlanApprovalResolves(session, turnId);
1486
+ const approvalToolCallId = `${completedPlanToolCall.toolCallId}:exit-plan`;
1487
+ const approvalInput = this.buildPlanApprovalInput(planText, item.id);
1488
+ await this.emitSessionUpdate(session.sessionId, {
1489
+ sessionUpdate: 'tool_call',
1490
+ toolCallId: approvalToolCallId,
1491
+ title: 'ExitPlanMode',
1492
+ kind: 'switch_mode',
1493
+ status: 'pending',
1494
+ rawInput: approvalInput,
1495
+ });
1496
+ try {
1497
+ const permissionResult = await this.connection.requestPermission({
1498
+ sessionId: session.sessionId,
1499
+ toolCall: {
1500
+ toolCallId: approvalToolCallId,
1501
+ title: 'ExitPlanMode',
1502
+ kind: 'switch_mode',
1503
+ status: 'pending',
1504
+ rawInput: approvalInput,
1505
+ },
1506
+ options: [
1507
+ {
1508
+ optionId: 'allow_once',
1509
+ name: 'Approve plan',
1510
+ kind: 'allow_once',
1511
+ },
1512
+ {
1513
+ optionId: 'reject_once',
1514
+ name: 'Request changes',
1515
+ kind: 'reject_once',
1516
+ },
1517
+ ],
1518
+ });
1519
+ const selectedOptionId = permissionResult.outcome.outcome === 'selected' ? permissionResult.outcome.optionId : null;
1520
+ const approved = this.isPlanApprovalSelected(selectedOptionId);
1521
+ if (approved) {
1522
+ const postApprovalMode = this.resolvePostPlanApprovalMode(session.defaults.collaborationMode);
1523
+ if (postApprovalMode) {
1524
+ session.defaults.collaborationMode = postApprovalMode;
1525
+ await this.emitSessionUpdate(session.sessionId, {
1526
+ sessionUpdate: 'config_option_update',
1527
+ configOptions: this.buildConfigOptions(session),
1528
+ });
1529
+ }
1530
+ }
1531
+ await this.emitSessionUpdate(session.sessionId, {
1532
+ sessionUpdate: 'tool_call_update',
1533
+ toolCallId: approvalToolCallId,
1534
+ kind: 'switch_mode',
1535
+ title: 'ExitPlanMode',
1536
+ status: approved ? 'completed' : 'failed',
1537
+ rawOutput: approved ? 'Plan approved' : 'Plan approval rejected',
1538
+ });
1539
+ }
1540
+ catch (error) {
1541
+ const message = error instanceof Error ? error.message : String(error);
1542
+ await this.emitSessionUpdate(session.sessionId, {
1543
+ sessionUpdate: 'tool_call_update',
1544
+ toolCallId: approvalToolCallId,
1545
+ kind: 'switch_mode',
1546
+ title: 'ExitPlanMode',
1547
+ status: 'failed',
1548
+ rawOutput: `Plan approval failed: ${message}`,
1549
+ });
1550
+ }
1551
+ finally {
1552
+ await this.releaseTurnHoldForPlanApproval(session, turnId);
1553
+ }
1554
+ }
1555
+ extractLocations(item) {
1556
+ if (!isRecord(item) || item.type !== 'fileChange') {
1557
+ return [];
1558
+ }
1559
+ const changes = item.changes;
1560
+ if (!Array.isArray(changes)) {
1561
+ return [];
1562
+ }
1563
+ const locations = [];
1564
+ for (const change of changes) {
1565
+ if (!isRecord(change)) {
1566
+ continue;
1567
+ }
1568
+ const path = asString(change.path);
1569
+ if (!path) {
1570
+ continue;
1571
+ }
1572
+ locations.push({ path });
1573
+ }
1574
+ return locations;
1575
+ }
1576
+ buildToolCallState(session, item, turnId) {
1577
+ const kindByType = {
1578
+ commandExecution: 'execute',
1579
+ fileChange: 'edit',
1580
+ mcpToolCall: 'fetch',
1581
+ webSearch: 'search',
1582
+ plan: 'think',
1583
+ reasoning: 'think',
1584
+ };
1585
+ const kind = kindByType[item.type];
1586
+ if (!kind) {
1587
+ return null;
1588
+ }
1589
+ let title = item.type;
1590
+ if (item.type === 'commandExecution') {
1591
+ const command = asString(item.command);
1592
+ title = command ?? 'commandExecution';
1593
+ }
1594
+ else if (item.type === 'fileChange') {
1595
+ title = 'fileChange';
1596
+ }
1597
+ else if (item.type === 'mcpToolCall') {
1598
+ const server = asString(item.server) ?? 'mcp';
1599
+ const tool = asString(item.tool) ?? 'tool';
1600
+ title = `mcpToolCall:${server}/${tool}`;
1601
+ }
1602
+ else if (item.type === 'webSearch') {
1603
+ const query = asString(item.query);
1604
+ title = query ? `webSearch:${query}` : 'webSearch';
1605
+ }
1606
+ return {
1607
+ toolCallId: createToolCallId(session.threadId, turnId, item.id),
1608
+ kind,
1609
+ title,
1610
+ };
1611
+ }
1612
+ async handleCodexServerRequest(request) {
1613
+ try {
1614
+ const parsed = knownCodexServerRequestSchema.safeParse(request);
1615
+ if (!parsed.success) {
1616
+ this.codex.respondError(request.id, {
1617
+ code: -32_602,
1618
+ message: 'Unsupported codex server request payload',
1619
+ });
1620
+ return;
1621
+ }
1622
+ const typed = parsed.data;
1623
+ const sessionId = this.sessionIdByThreadId.get(typed.params.threadId);
1624
+ if (!sessionId) {
1625
+ this.codex.respondError(typed.id, {
1626
+ code: -32_603,
1627
+ message: 'No ACP session mapped for this thread',
1628
+ });
1629
+ return;
1630
+ }
1631
+ const session = this.sessions.get(sessionId);
1632
+ if (!session) {
1633
+ this.codex.respondError(typed.id, {
1634
+ code: -32_603,
1635
+ message: 'No ACP session mapped for this thread',
1636
+ });
1637
+ return;
1638
+ }
1639
+ const existingTool = session.toolCallsByItemId.get(typed.params.itemId);
1640
+ const toolCall = existingTool ??
1641
+ this.buildApprovalToolCallState(session, typed.method, typed.params.itemId, typed.params.turnId, typed.params);
1642
+ session.toolCallsByItemId.set(typed.params.itemId, toolCall);
1643
+ if (!existingTool) {
1644
+ await this.emitSessionUpdate(sessionId, {
1645
+ sessionUpdate: 'tool_call',
1646
+ toolCallId: toolCall.toolCallId,
1647
+ title: toolCall.title,
1648
+ kind: toolCall.kind,
1649
+ status: 'pending',
1650
+ rawInput: typed.params,
1651
+ });
1652
+ }
1653
+ const questions = typed.method === 'item/tool/requestUserInput' ? typed.params.questions : [];
1654
+ const permissionOptions = typed.method === 'item/tool/requestUserInput'
1655
+ ? buildToolUserInputPermissionOptions(questions)
1656
+ : [
1657
+ {
1658
+ optionId: 'allow_once',
1659
+ name: 'Allow once',
1660
+ kind: 'allow_once',
1661
+ },
1662
+ {
1663
+ optionId: 'reject_once',
1664
+ name: 'Reject',
1665
+ kind: 'reject_once',
1666
+ },
1667
+ ];
1668
+ const permissionResult = await this.connection.requestPermission({
1669
+ sessionId,
1670
+ toolCall: {
1671
+ toolCallId: toolCall.toolCallId,
1672
+ title: toolCall.title,
1673
+ kind: toolCall.kind,
1674
+ status: 'pending',
1675
+ rawInput: typed.params,
1676
+ },
1677
+ options: permissionOptions,
1678
+ });
1679
+ await this.respondToCodexPermissionRequest({
1680
+ request: typed,
1681
+ permission: permissionResult,
1682
+ session,
1683
+ toolCallId: toolCall.toolCallId,
1684
+ questions,
1685
+ });
1686
+ }
1687
+ catch (error) {
1688
+ const message = error instanceof Error ? error.message : String(error);
1689
+ this.codex.respondError(request.id, {
1690
+ code: -32_600,
1691
+ message: 'Failed to process codex approval request',
1692
+ data: { error: message },
1693
+ });
1694
+ }
1695
+ }
1696
+ buildApprovalToolCallState(session, method, itemId, turnId, params) {
1697
+ if (method === 'item/commandExecution/requestApproval') {
1698
+ const command = asString(params.command);
1699
+ return {
1700
+ toolCallId: createToolCallId(session.threadId, turnId, itemId),
1701
+ title: command ? command : 'commandExecution',
1702
+ kind: 'execute',
1703
+ };
1704
+ }
1705
+ if (method === 'item/fileChange/requestApproval') {
1706
+ return {
1707
+ toolCallId: createToolCallId(session.threadId, turnId, itemId),
1708
+ title: 'fileChange',
1709
+ kind: 'edit',
1710
+ };
1711
+ }
1712
+ return {
1713
+ toolCallId: createToolCallId(session.threadId, turnId, itemId),
1714
+ title: 'item/tool/requestUserInput',
1715
+ kind: 'other',
1716
+ };
1717
+ }
1718
+ async respondToCodexPermissionRequest(params) {
1719
+ const selected = params.permission.outcome.outcome === 'selected' ? params.permission.outcome.optionId : null;
1720
+ const allow = selected === 'allow_once';
1721
+ if (params.request.method === 'item/tool/requestUserInput') {
1722
+ const rejected = selected === null || selected === 'reject_once';
1723
+ let answers;
1724
+ try {
1725
+ answers = buildToolUserInputAnswers({
1726
+ questions: params.questions,
1727
+ permission: params.permission,
1728
+ selectedOptionId: selected,
1729
+ });
1730
+ }
1731
+ catch (error) {
1732
+ const message = error instanceof Error ? error.message : String(error);
1733
+ this.codex.respondError(params.request.id, {
1734
+ code: -32_602,
1735
+ message: 'Failed to map requestUserInput answers',
1736
+ data: { error: message },
1737
+ });
1738
+ await this.emitSessionUpdate(params.session.sessionId, {
1739
+ sessionUpdate: 'tool_call_update',
1740
+ toolCallId: params.toolCallId,
1741
+ status: 'failed',
1742
+ rawOutput: { error: message },
1743
+ });
1744
+ return;
1745
+ }
1746
+ this.codex.respondSuccess(params.request.id, {
1747
+ answers,
1748
+ });
1749
+ await this.emitSessionUpdate(params.session.sessionId, {
1750
+ sessionUpdate: 'tool_call_update',
1751
+ toolCallId: params.toolCallId,
1752
+ status: rejected ? 'failed' : 'completed',
1753
+ rawOutput: rejected ? { outcome: 'rejected' } : { answers },
1754
+ });
1755
+ return;
1756
+ }
1757
+ this.codex.respondSuccess(params.request.id, {
1758
+ decision: allow ? 'accept' : 'decline',
1759
+ });
1760
+ await this.emitSessionUpdate(params.session.sessionId, {
1761
+ sessionUpdate: 'tool_call_update',
1762
+ toolCallId: params.toolCallId,
1763
+ status: allow ? 'in_progress' : 'failed',
1764
+ rawOutput: { decision: allow ? 'accept' : 'decline' },
1765
+ });
1766
+ }
1767
+ settleTurn(session, stopReason) {
1768
+ if (!session.activeTurn || session.activeTurn.settled) {
1769
+ return;
1770
+ }
1771
+ session.activeTurn.settled = true;
1772
+ session.activeTurn.resolve(stopReason);
1773
+ session.activeTurn = null;
1774
+ session.planTextByItemId.clear();
1775
+ session.planApprovalRequestedByTurnId.clear();
1776
+ session.pendingPlanApprovalsByTurnId.clear();
1777
+ session.toolCallsByItemId.clear();
1778
+ session.pendingTurnCompletionsByTurnId.clear();
1779
+ }
1780
+ async emitSessionUpdate(sessionId, update) {
1781
+ try {
1782
+ await this.connection.sessionUpdate({ sessionId, update });
1783
+ }
1784
+ catch {
1785
+ }
1786
+ }
1787
+ }
1788
+ export function runCodexAppServerAcpAdapter() {
1789
+ const output = Writable.toWeb(process.stdout);
1790
+ const input = Readable.toWeb(process.stdin);
1791
+ const stream = ndJsonStream(output, input);
1792
+ new AgentSideConnection((connection) => new CodexAppServerAcpAdapter(connection), stream);
1793
+ }
1794
+ //# sourceMappingURL=codex-app-server-acp-adapter.js.map