@cloudcli-ai/cloudcli 1.37.2 → 1.37.3

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 (328) hide show
  1. package/README.md +269 -0
  2. package/dist/assets/{abnfDiagram-N423BO3Z-C4UAoHJb.js → abnfDiagram-N423BO3Z-CJntskwA.js} +1 -1
  3. package/dist/assets/{arc-B8jF-ja9.js → arc-CIfH4uMN.js} +1 -1
  4. package/dist/assets/{architectureDiagram-T3A2C74G-C_Wur_CU.js → architectureDiagram-T3A2C74G-_IpEkukg.js} +1 -1
  5. package/dist/assets/{blockDiagram-VBNYF7ZC-Cq4P1i8t.js → blockDiagram-VBNYF7ZC-BttxrpgZ.js} +1 -1
  6. package/dist/assets/{c4Diagram-5PPSVZJV-C1wG4eUj.js → c4Diagram-5PPSVZJV-g51dSRr1.js} +1 -1
  7. package/dist/assets/channel-C5xU9jMw.js +1 -0
  8. package/dist/assets/{chunk-2GRJ4B5K-B8fnBG6E.js → chunk-2GRJ4B5K-D_-LbQUa.js} +1 -1
  9. package/dist/assets/{chunk-2Q5K7J3B-WdfPooPJ.js → chunk-2Q5K7J3B-BtlHzwaO.js} +1 -1
  10. package/dist/assets/{chunk-5RXB4S5H-DhZJuG2E.js → chunk-5RXB4S5H-UzkpvpAT.js} +1 -1
  11. package/dist/assets/{chunk-5VM5RSS4-B0rjslal.js → chunk-5VM5RSS4-BKwx5Gag.js} +1 -1
  12. package/dist/assets/{chunk-6Q2QTUOP-CG2Oj4jL.js → chunk-6Q2QTUOP-D_KEBH5f.js} +1 -1
  13. package/dist/assets/{chunk-GF5L2VYU-rrl2lRxY.js → chunk-GF5L2VYU-rK2DGyto.js} +1 -1
  14. package/dist/assets/{chunk-JWPE2WC7-CjVbrVMA.js → chunk-JWPE2WC7-CeBl_N9i.js} +1 -1
  15. package/dist/assets/{chunk-KBJHAD2P-CRzKgU8S.js → chunk-KBJHAD2P-BiQLWVIr.js} +1 -1
  16. package/dist/assets/{chunk-RYQCIY6F-DBz4GTZF.js → chunk-RYQCIY6F-C1knVGV5.js} +1 -1
  17. package/dist/assets/{chunk-XXDRQBXY-wHxalsXk.js → chunk-XXDRQBXY-CtKbJkvF.js} +1 -1
  18. package/dist/assets/classDiagram-JCYQIIEL-CRrL3vp7.js +1 -0
  19. package/dist/assets/classDiagram-v2-OCEON4UE-CRrL3vp7.js +1 -0
  20. package/dist/assets/{cose-bilkent-JH36ORCC-s1hygY8e.js → cose-bilkent-JH36ORCC-DjX8y40Z.js} +1 -1
  21. package/dist/assets/{cynefin-VYW2F7L2-C4LGV15i.js → cynefin-VYW2F7L2-DM6YeRba.js} +1 -1
  22. package/dist/assets/{cynefinDiagram-MW4NZA55-C5uhu7B3.js → cynefinDiagram-MW4NZA55-wZJ-vy5B.js} +1 -1
  23. package/dist/assets/{dagre-VZM6K2ZE-B-OBTewm.js → dagre-VZM6K2ZE-BzP0ejkH.js} +1 -1
  24. package/dist/assets/{diagram-7IWD3JNH-BjGKQxih.js → diagram-7IWD3JNH-BTL_j23J.js} +1 -1
  25. package/dist/assets/{diagram-B4RE2ZJO-BMKz1X2L.js → diagram-B4RE2ZJO-Cf1Norgu.js} +1 -1
  26. package/dist/assets/{diagram-LBJQPF4R-DNh-GhKC.js → diagram-LBJQPF4R-DPiUEKr-.js} +1 -1
  27. package/dist/assets/{diagram-Q27KOJAE-BaTG9jPT.js → diagram-Q27KOJAE-CTcTpiz5.js} +1 -1
  28. package/dist/assets/{diagram-UB23O5K3-BUwo2OvJ.js → diagram-UB23O5K3-DPMSdYm0.js} +1 -1
  29. package/dist/assets/{ebnfDiagram-BXEA7PRR-DAZgfzX2.js → ebnfDiagram-BXEA7PRR-7yvD6uiF.js} +1 -1
  30. package/dist/assets/{erDiagram-JOGREHBK-1w1CcGEb.js → erDiagram-JOGREHBK-CfgeDoV6.js} +1 -1
  31. package/dist/assets/{flowDiagram-UKHOOZJN-D4EII8Jj.js → flowDiagram-UKHOOZJN-B27ng5zu.js} +1 -1
  32. package/dist/assets/{ganttDiagram-PKOTCBZU-ChvJZAgB.js → ganttDiagram-PKOTCBZU-CcC6b_7t.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-DS77QQ5N-DtOn41S8.js → gitGraphDiagram-DS77QQ5N-BX5kPGrO.js} +1 -1
  34. package/dist/assets/index-BTDHQ6Id.css +32 -0
  35. package/dist/assets/index-sFfz8iBR.js +4931 -0
  36. package/dist/assets/{infoDiagram-6WML65LV-DN_PVEJZ.js → infoDiagram-6WML65LV-CUP-SlN4.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-WSZJBQD7-DbkF1kXb.js → ishikawaDiagram-WSZJBQD7-BTTDhOpX.js} +1 -1
  38. package/dist/assets/{journeyDiagram-NVQOT4AX-pJwmhddE.js → journeyDiagram-NVQOT4AX-ARXcc61m.js} +1 -1
  39. package/dist/assets/{kanban-definition-27J2QSJJ-PPAPgNp5.js → kanban-definition-27J2QSJJ-BieMpqMA.js} +1 -1
  40. package/dist/assets/{linear-iPR-J9f7.js → linear-61vxjRnP.js} +1 -1
  41. package/dist/assets/{mermaid.core-BEUL86Z6.js → mermaid.core-nhnwkfQx.js} +5 -5
  42. package/dist/assets/{mindmap-definition-FAOFIHXS-BvZoBib7.js → mindmap-definition-FAOFIHXS-Cm6x5vT1.js} +1 -1
  43. package/dist/assets/{pegDiagram-VL7TDLO6-DhYreaUd.js → pegDiagram-VL7TDLO6-CcYBq_9H.js} +1 -1
  44. package/dist/assets/{pieDiagram-7S7Q4E2Y-CrOHlyD0.js → pieDiagram-7S7Q4E2Y-CfvIjfzo.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-CIZ2JOQS-D4g5yH3t.js → quadrantDiagram-CIZ2JOQS-SgYoWPNb.js} +1 -1
  46. package/dist/assets/{railroadDiagram-AXF67PYL-Dj39lHlf.js → railroadDiagram-AXF67PYL-D2Mu5E_f.js} +1 -1
  47. package/dist/assets/{requirementDiagram-LRYGKXZP-1AS4Zgm_.js → requirementDiagram-LRYGKXZP-Azwe_k4Q.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-W5VNT64P-CKlMn19P.js → sankeyDiagram-W5VNT64P-DN1Ju3PA.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-SI44F4Z6-B_j3qyvo.js → sequenceDiagram-SI44F4Z6-Bj4qC34S.js} +1 -1
  50. package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
  51. package/dist/assets/{sizeCapture-X5ZJPWSS-Bp2Op16v.js → sizeCapture-X5ZJPWSS-Co4PdklG.js} +1 -1
  52. package/dist/assets/{stateDiagram-OKZ733FA-C0cN6DoY.js → stateDiagram-OKZ733FA-nOvL9dRC.js} +1 -1
  53. package/dist/assets/stateDiagram-v2-UEYNNEHI-DNv8ppw7.js +1 -0
  54. package/dist/assets/{swimlanes-SLNWSIFB-BEvXOV9b.js → swimlanes-SLNWSIFB-CCK3wMid.js} +2 -2
  55. package/dist/assets/swimlanesDiagram-ULZ7WXOC-DnfbNQEp.js +8 -0
  56. package/dist/assets/{timeline-definition-Z64GVDOM-zdJQinvZ.js → timeline-definition-Z64GVDOM-BC29RNFm.js} +1 -1
  57. package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
  58. package/dist/assets/{vennDiagram-T6HMQDX7-DuF1uZMY.js → vennDiagram-T6HMQDX7-pZlZCtEo.js} +1 -1
  59. package/dist/assets/{wardleyDiagram-T6FBY63Y-B9AlLQup.js → wardleyDiagram-T6FBY63Y-De8k2-zM.js} +1 -1
  60. package/dist/assets/{xychartDiagram-ELKLHX3M-DTJX48Qg.js → xychartDiagram-ELKLHX3M-Bk8ykH0w.js} +1 -1
  61. package/dist/index.html +3 -3
  62. package/dist-server/server/index.js +7 -3
  63. package/dist-server/server/index.js.map +1 -1
  64. package/dist-server/server/modules/agent/agent.routes.js +2 -2
  65. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  66. package/dist-server/server/modules/agent/tests/agent.routes.test.js +30 -1
  67. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
  68. package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
  69. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
  70. package/dist-server/server/modules/database/connection.js +1 -1
  71. package/dist-server/server/modules/database/connection.js.map +1 -1
  72. package/dist-server/server/modules/database/index.js +5 -0
  73. package/dist-server/server/modules/database/index.js.map +1 -1
  74. package/dist-server/server/modules/database/migrations.js +32 -1
  75. package/dist-server/server/modules/database/migrations.js.map +1 -1
  76. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
  77. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
  78. package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
  79. package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
  80. package/dist-server/server/modules/database/repositories/sessions.db.js +137 -5
  81. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  82. package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
  83. package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
  84. package/dist-server/server/modules/database/schema.js +92 -0
  85. package/dist-server/server/modules/database/schema.js.map +1 -1
  86. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
  87. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  88. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
  89. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
  90. package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
  91. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
  92. package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
  93. package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
  94. package/dist-server/server/modules/providers/index.js +3 -0
  95. package/dist-server/server/modules/providers/index.js.map +1 -1
  96. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +20 -1
  97. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  98. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
  99. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
  100. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +35 -6
  101. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  102. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +168 -38
  103. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
  104. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +388 -50
  105. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  106. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
  107. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  108. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  109. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  110. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
  111. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
  112. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
  113. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
  114. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +16 -0
  115. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  116. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +60 -49
  117. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
  118. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
  119. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  120. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1565 -432
  121. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  122. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  123. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  124. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
  125. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
  126. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  127. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +258 -6
  128. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  129. package/dist-server/server/modules/providers/provider.routes.js +24 -0
  130. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  131. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +14 -0
  132. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  133. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
  134. package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
  135. package/dist-server/server/modules/providers/services/session-history-cache.service.js +87 -0
  136. package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
  137. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
  138. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  139. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
  140. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  141. package/dist-server/server/modules/providers/services/sessions.service.js +179 -9
  142. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  143. package/dist-server/server/modules/providers/tests/claude-auth.test.js +52 -0
  144. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
  145. package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
  146. package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
  147. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +465 -39
  148. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
  149. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
  150. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
  151. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
  152. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
  153. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
  154. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
  155. package/dist-server/server/modules/providers/tests/codex-runtime.test.js +51 -0
  156. package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
  157. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
  158. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
  159. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +304 -15
  160. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  161. package/dist-server/server/modules/providers/tests/opencode-models.test.js +117 -4
  162. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  163. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
  164. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
  165. package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
  166. package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
  167. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +120 -0
  168. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
  169. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
  170. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
  171. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
  172. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
  173. package/dist-server/server/modules/providers/tests/sessions.service.test.js +45 -1
  174. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
  175. package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
  176. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  177. package/dist-server/server/modules/scheduled-messages/index.js +5 -0
  178. package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
  179. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
  180. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
  181. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +163 -0
  182. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
  183. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
  184. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
  185. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +255 -0
  186. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
  187. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
  188. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
  189. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
  190. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
  191. package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
  192. package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
  193. package/dist-server/server/modules/user/user.module.js +10 -1
  194. package/dist-server/server/modules/user/user.module.js.map +1 -1
  195. package/dist-server/server/modules/user/user.routes.js +44 -0
  196. package/dist-server/server/modules/user/user.routes.js.map +1 -1
  197. package/dist-server/server/modules/user/user.service.js +75 -0
  198. package/dist-server/server/modules/user/user.service.js.map +1 -1
  199. package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
  200. package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
  201. package/dist-server/server/modules/websocket/index.js +7 -0
  202. package/dist-server/server/modules/websocket/index.js.map +1 -1
  203. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +10 -44
  204. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  205. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
  206. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
  207. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +166 -9
  208. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  209. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
  210. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
  211. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -3
  212. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  213. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
  214. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
  215. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
  216. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  217. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
  218. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
  219. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +80 -0
  220. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
  221. package/dist-server/server/shared/claude-cli-path.js +22 -5
  222. package/dist-server/server/shared/claude-cli-path.js.map +1 -1
  223. package/dist-server/server/shared/index.js +3 -0
  224. package/dist-server/server/shared/index.js.map +1 -0
  225. package/dist-server/server/shared/message-unification.js +376 -0
  226. package/dist-server/server/shared/message-unification.js.map +1 -0
  227. package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
  228. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
  229. package/dist-server/server/shared/tests/message-unification.test.js +174 -0
  230. package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
  231. package/dist-server/server/shared/utils.js +34 -0
  232. package/dist-server/server/shared/utils.js.map +1 -1
  233. package/package.json +19 -16
  234. package/server/index.ts +11 -4
  235. package/server/modules/agent/agent.routes.ts +2 -2
  236. package/server/modules/agent/tests/agent.routes.test.ts +33 -1
  237. package/server/modules/browser-use/browser-use.routes.ts +96 -33
  238. package/server/modules/database/connection.ts +2 -1
  239. package/server/modules/database/index.ts +10 -0
  240. package/server/modules/database/migrations.ts +37 -0
  241. package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
  242. package/server/modules/database/repositories/session-drafts.db.ts +171 -0
  243. package/server/modules/database/repositories/sessions.db.ts +196 -3
  244. package/server/modules/database/repositories/user-preferences.db.ts +80 -0
  245. package/server/modules/database/schema.ts +96 -0
  246. package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
  247. package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
  248. package/server/modules/projects/services/project-delete.service.ts +13 -1
  249. package/server/modules/projects/services/project-star.service.ts +2 -2
  250. package/server/modules/providers/index.ts +4 -0
  251. package/server/modules/providers/list/claude/claude-auth.provider.ts +21 -1
  252. package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
  253. package/server/modules/providers/list/claude/claude-models.provider.ts +38 -6
  254. package/server/modules/providers/list/claude/claude-runtime.provider.js +188 -39
  255. package/server/modules/providers/list/claude/claude-sessions.provider.ts +478 -54
  256. package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
  257. package/server/modules/providers/list/claude/claude.provider.ts +3 -0
  258. package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
  259. package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
  260. package/server/modules/providers/list/codex/codex-models.provider.ts +16 -0
  261. package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +93 -66
  262. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
  263. package/server/modules/providers/list/codex/codex-sessions.provider.ts +1806 -477
  264. package/server/modules/providers/list/codex/codex.provider.ts +3 -0
  265. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
  266. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
  267. package/server/modules/providers/list/opencode/opencode-models.provider.ts +279 -6
  268. package/server/modules/providers/provider.routes.ts +31 -0
  269. package/server/modules/providers/services/provider-capabilities.service.ts +23 -0
  270. package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
  271. package/server/modules/providers/services/session-history-cache.service.ts +139 -0
  272. package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
  273. package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
  274. package/server/modules/providers/services/sessions.service.ts +201 -9
  275. package/server/modules/providers/tests/claude-auth.test.ts +58 -0
  276. package/server/modules/providers/tests/claude-models.test.ts +87 -0
  277. package/server/modules/providers/tests/claude-sessions.test.ts +552 -40
  278. package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
  279. package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
  280. package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
  281. package/server/modules/providers/tests/codex-runtime.test.ts +58 -0
  282. package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
  283. package/server/modules/providers/tests/codex-sessions.test.ts +337 -15
  284. package/server/modules/providers/tests/opencode-models.test.ts +166 -8
  285. package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
  286. package/server/modules/providers/tests/session-fork.test.ts +173 -0
  287. package/server/modules/providers/tests/session-history-cache.service.test.ts +143 -0
  288. package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
  289. package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
  290. package/server/modules/providers/tests/sessions.service.test.ts +67 -1
  291. package/server/modules/providers/tests/skills.test.ts +21 -5
  292. package/server/modules/scheduled-messages/index.ts +8 -0
  293. package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
  294. package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +203 -0
  295. package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
  296. package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +310 -0
  297. package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
  298. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
  299. package/server/modules/user/tests/user.service.test.ts +90 -0
  300. package/server/modules/user/user.module.ts +10 -1
  301. package/server/modules/user/user.routes.ts +44 -0
  302. package/server/modules/user/user.service.ts +105 -0
  303. package/server/modules/voice/tests/voice.service.test.ts +3 -2
  304. package/server/modules/websocket/index.ts +8 -0
  305. package/server/modules/websocket/services/chat-run-registry.service.ts +17 -50
  306. package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
  307. package/server/modules/websocket/services/chat-websocket.service.ts +244 -15
  308. package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
  309. package/server/modules/websocket/services/shell-websocket.service.ts +10 -3
  310. package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
  311. package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
  312. package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
  313. package/server/modules/websocket/tests/shell-websocket.service.test.ts +108 -0
  314. package/server/shared/claude-cli-path.ts +24 -6
  315. package/server/shared/index.ts +3 -0
  316. package/server/shared/interfaces.ts +62 -0
  317. package/server/shared/message-unification.ts +449 -0
  318. package/server/shared/tests/claude-cli-path.test.ts +58 -1
  319. package/server/shared/tests/message-unification.test.ts +218 -0
  320. package/server/shared/types.ts +123 -2
  321. package/server/shared/utils.ts +40 -0
  322. package/dist/assets/channel-_falYbU1.js +0 -1
  323. package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +0 -1
  324. package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +0 -1
  325. package/dist/assets/index-CzPLBvXT.css +0 -32
  326. package/dist/assets/index-DFkBBuSA.js +0 -1549
  327. package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +0 -1
  328. package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +0 -8
@@ -6,7 +6,7 @@ import test from 'node:test';
6
6
 
7
7
  import { closeConnection, initializeDatabase, sessionsDb } from '@/modules/database/index.js';
8
8
  import { CodexSessionSynchronizer } from '@/modules/providers/list/codex/codex-session-synchronizer.provider.js';
9
- import { CodexSessionsProvider } from '@/modules/providers/list/codex/codex-sessions.provider.js';
9
+ import { CodexSessionsProvider, parseCodexExecScript, readCodexMemoryCitations } from '@/modules/providers/list/codex/codex-sessions.provider.js';
10
10
 
11
11
  const patchHomeDir = (nextHomeDir: string) => {
12
12
  const original = os.homedir;
@@ -152,7 +152,7 @@ test('Codex synchronizer leaves indexed sessions untitled when no name is availa
152
152
  }
153
153
  });
154
154
 
155
- test('Codex history preserves wrapped exec tool calls and results', { concurrency: false }, async () => {
155
+ test('Codex history translates wrapped exec scripts into the tools they ran', { concurrency: false }, async () => {
156
156
  const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-exec-history-'));
157
157
  const workspacePath = path.join(tempRoot, 'workspace');
158
158
  await mkdir(workspacePath, { recursive: true });
@@ -161,6 +161,8 @@ test('Codex history preserves wrapped exec tool calls and results', { concurrenc
161
161
  try {
162
162
  const providerSessionId = 'codex-exec-1';
163
163
  const transcriptPath = await writeCodexTranscript(tempRoot, providerSessionId, workspacePath);
164
+ // Every shape Codex's "code mode" emits. Only a wrapper nothing recognizes
165
+ // may stay labelled `exec`; the rest must name the work they performed.
164
166
  const wrappedCalls = [
165
167
  {
166
168
  callId: 'shell-command-1',
@@ -176,14 +178,22 @@ test('Codex history preserves wrapped exec tool calls and results', { concurrenc
176
178
  },
177
179
  {
178
180
  callId: 'exec-command-1',
179
- input: 'await tools.exec_command({"command":"echo current"});',
181
+ input: 'await tools.exec_command({"cmd":"npm test","workdir":"/repo","yield_time_ms":10000});',
180
182
  expectedToolName: 'Bash',
181
- expectedToolInput: JSON.stringify({ command: 'echo current' }),
183
+ expectedToolInput: JSON.stringify({ command: 'npm test' }),
184
+ },
185
+ {
186
+ callId: 'web-run-1',
187
+ input: 'await tools.web__run({ search_query: [{ q: "Codex" }, { q: "rollout format" }] });',
188
+ expectedToolName: 'WebSearch',
189
+ expectedToolInput: JSON.stringify({ query: 'Codex | rollout format' }),
190
+ },
191
+ {
192
+ callId: 'unknown-1',
193
+ input: 'await tools.unknown_wrapper({ value: true });',
194
+ expectedToolName: 'exec',
195
+ expectedToolInput: 'await tools.unknown_wrapper({ value: true });',
182
196
  },
183
- { callId: 'apply-patch-1', input: 'await tools.apply_patch("*** Begin Patch\\n*** End Patch");' },
184
- { callId: 'web-run-1', input: 'await tools.web__run({ search_query: [{ q: "Codex" }] });' },
185
- { callId: 'update-plan-1', input: 'await tools.update_plan({ plan: [] });' },
186
- { callId: 'unknown-1', input: 'await tools.unknown_wrapper({ value: true });' },
187
197
  ];
188
198
  const transcriptLines = [
189
199
  JSON.stringify({ type: 'session_meta', payload: { id: providerSessionId, cwd: workspacePath } }),
@@ -209,19 +219,15 @@ test('Codex history preserves wrapped exec tool calls and results', { concurrenc
209
219
 
210
220
  const history = await new CodexSessionsProvider().fetchHistory('app-exec-1');
211
221
  const toolUses = history.messages.filter((message) => message.kind === 'tool_use');
212
- const toolResults = history.messages.filter((message) => message.kind === 'tool_result');
213
222
  const toolUsesById = new Map(toolUses.map((message) => [message.toolId, message]));
214
- const toolResultsById = new Map(toolResults.map((message) => [message.toolId, message]));
215
223
 
216
224
  assert.equal(toolUses.length, wrappedCalls.length);
217
- assert.equal(toolResults.length, wrappedCalls.length);
218
225
  for (const call of wrappedCalls) {
219
226
  const toolUse = toolUsesById.get(call.callId);
220
- assert.ok(toolUse);
221
- assert.equal(toolUse.toolName, call.expectedToolName || 'exec');
222
- assert.equal(toolUse.toolInput, call.expectedToolInput || call.input);
227
+ assert.ok(toolUse, `missing row for ${call.callId}`);
228
+ assert.equal(toolUse.toolName, call.expectedToolName);
229
+ assert.equal(toolUse.toolInput, call.expectedToolInput);
223
230
  assert.equal(toolUse.toolResult?.content, `result:${call.callId}`);
224
- assert.equal(toolResultsById.get(call.callId)?.content, `result:${call.callId}`);
225
231
  }
226
232
  });
227
233
  } finally {
@@ -229,3 +235,319 @@ test('Codex history preserves wrapped exec tool calls and results', { concurrenc
229
235
  await rm(tempRoot, { recursive: true, force: true });
230
236
  }
231
237
  });
238
+
239
+ test('Codex history strips the sandbox envelopes from shell output', { concurrency: false }, async () => {
240
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-exec-output-'));
241
+ const workspacePath = path.join(tempRoot, 'workspace');
242
+ await mkdir(workspacePath, { recursive: true });
243
+ const restoreHomeDir = patchHomeDir(tempRoot);
244
+
245
+ try {
246
+ const providerSessionId = 'codex-output-1';
247
+ const transcriptPath = await writeCodexTranscript(tempRoot, providerSessionId, workspacePath);
248
+ await writeFile(transcriptPath, `${[
249
+ JSON.stringify({ type: 'session_meta', payload: { id: providerSessionId, cwd: workspacePath } }),
250
+ JSON.stringify({
251
+ type: 'response_item',
252
+ payload: {
253
+ type: 'custom_tool_call',
254
+ name: 'exec',
255
+ call_id: 'failing-1',
256
+ input: 'await tools.shell_command({ command: "npm test" });',
257
+ },
258
+ }),
259
+ // Codex nests two report headers: one for the sandbox script and one for
260
+ // the command it ran. Neither belongs in a chat transcript.
261
+ JSON.stringify({
262
+ type: 'response_item',
263
+ payload: {
264
+ type: 'custom_tool_call_output',
265
+ call_id: 'failing-1',
266
+ output: [
267
+ { type: 'input_text', text: 'Script failed\nWall time 1.2 seconds\nOutput:\n' },
268
+ { type: 'input_text', text: 'Script error:\nExit code: 1\nWall time: 1.1 seconds\nOutput:\n1 test failed\n' },
269
+ ],
270
+ },
271
+ }),
272
+ ].join('\n')}\n`, 'utf8');
273
+
274
+ await withIsolatedDatabase(async () => {
275
+ sessionsDb.createAppSession('app-output-1', 'codex', workspacePath);
276
+ sessionsDb.assignProviderSessionId('app-output-1', providerSessionId);
277
+ await new CodexSessionSynchronizer().synchronize();
278
+
279
+ const history = await new CodexSessionsProvider().fetchHistory('app-output-1');
280
+ const bash = history.messages.find((message) => message.kind === 'tool_use');
281
+
282
+ assert.equal(bash?.toolName, 'Bash');
283
+ assert.equal(bash?.toolResult?.content, '1 test failed\n');
284
+ assert.equal(bash?.toolResult?.isError, true, 'a non-zero exit code must mark the row failed');
285
+ });
286
+ } finally {
287
+ restoreHomeDir();
288
+ await rm(tempRoot, { recursive: true, force: true });
289
+ }
290
+ });
291
+
292
+ test('Codex history renders one file row per patched file, from the applied diff', { concurrency: false }, async () => {
293
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-patch-history-'));
294
+ const workspacePath = path.join(tempRoot, 'workspace');
295
+ await mkdir(workspacePath, { recursive: true });
296
+ const restoreHomeDir = patchHomeDir(tempRoot);
297
+
298
+ try {
299
+ const providerSessionId = 'codex-patch-1';
300
+ const transcriptPath = await writeCodexTranscript(tempRoot, providerSessionId, workspacePath);
301
+ const patch = [
302
+ '*** Begin Patch',
303
+ '*** Update File: /repo/a.ts',
304
+ '@@',
305
+ '-const a = 1;',
306
+ '+const a = 2;',
307
+ '*** Update File: /repo/b.ts',
308
+ '@@',
309
+ '-const b = 1;',
310
+ '+const b = 2;',
311
+ '*** End Patch',
312
+ ].join('\n');
313
+
314
+ await writeFile(transcriptPath, `${[
315
+ JSON.stringify({ type: 'session_meta', payload: { id: providerSessionId, cwd: workspacePath } }),
316
+ JSON.stringify({
317
+ type: 'response_item',
318
+ payload: { type: 'custom_tool_call', name: 'apply_patch', call_id: 'patch-1', input: patch },
319
+ }),
320
+ // The out-of-band report carries the real diffs, keyed by path in an
321
+ // order that does not follow the patch body.
322
+ JSON.stringify({
323
+ type: 'event_msg',
324
+ payload: {
325
+ type: 'patch_apply_end',
326
+ call_id: 'patch-1',
327
+ success: true,
328
+ changes: {
329
+ '/repo/b.ts': { type: 'update', unified_diff: '@@\n-const b = 1;\n+const b = 22;\n' },
330
+ '/repo/a.ts': { type: 'update', unified_diff: '@@\n-const a = 1;\n+const a = 22;\n' },
331
+ },
332
+ },
333
+ }),
334
+ JSON.stringify({
335
+ type: 'response_item',
336
+ payload: { type: 'custom_tool_call_output', call_id: 'patch-1', output: '{}' },
337
+ }),
338
+ ].join('\n')}\n`, 'utf8');
339
+
340
+ await withIsolatedDatabase(async () => {
341
+ sessionsDb.createAppSession('app-patch-1', 'codex', workspacePath);
342
+ sessionsDb.assignProviderSessionId('app-patch-1', providerSessionId);
343
+ await new CodexSessionSynchronizer().synchronize();
344
+
345
+ const history = await new CodexSessionsProvider().fetchHistory('app-patch-1');
346
+ const edits = history.messages.filter((message) => message.kind === 'tool_use');
347
+
348
+ assert.equal(edits.length, 2, 'the patch must not be rendered twice');
349
+ const byPath = new Map(edits.map((edit) => {
350
+ const input = JSON.parse(String(edit.toolInput)) as { file_path: string; new_string: string };
351
+ return [input.file_path, { edit, input }];
352
+ }));
353
+
354
+ assert.equal(byPath.get('/repo/a.ts')?.edit.toolName, 'Edit');
355
+ // The applied diff wins over the one reconstructed from the call input,
356
+ // and it must land on the row for its own file.
357
+ assert.equal(byPath.get('/repo/a.ts')?.input.new_string, 'const a = 22;');
358
+ assert.equal(byPath.get('/repo/b.ts')?.input.new_string, 'const b = 22;');
359
+ for (const edit of edits) {
360
+ assert.equal(edit.toolResult?.isError, false, 'a successful patch must resolve its row');
361
+ }
362
+ });
363
+ } finally {
364
+ restoreHomeDir();
365
+ await rm(tempRoot, { recursive: true, force: true });
366
+ }
367
+ });
368
+
369
+ test('Codex history attaches a spawned agent\'s own transcript to the Task row', { concurrency: false }, async () => {
370
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-subagent-history-'));
371
+ const workspacePath = path.join(tempRoot, 'workspace');
372
+ await mkdir(workspacePath, { recursive: true });
373
+ const restoreHomeDir = patchHomeDir(tempRoot);
374
+
375
+ try {
376
+ const providerSessionId = 'codex-parent-2';
377
+ const agentThreadId = 'codex-agent-thread-2';
378
+ const transcriptPath = await writeCodexTranscript(tempRoot, providerSessionId, workspacePath);
379
+
380
+ await writeFile(transcriptPath, `${[
381
+ JSON.stringify({ type: 'session_meta', payload: { id: providerSessionId, cwd: workspacePath } }),
382
+ JSON.stringify({
383
+ type: 'response_item',
384
+ payload: {
385
+ type: 'function_call',
386
+ name: 'spawn_agent',
387
+ call_id: 'spawn-1',
388
+ arguments: JSON.stringify({ task_name: 'test_agent_1' }),
389
+ },
390
+ }),
391
+ JSON.stringify({
392
+ type: 'event_msg',
393
+ payload: {
394
+ type: 'sub_agent_activity',
395
+ kind: 'started',
396
+ event_id: 'spawn-1',
397
+ agent_thread_id: agentThreadId,
398
+ agent_path: '/root/test_agent_1',
399
+ },
400
+ }),
401
+ JSON.stringify({
402
+ type: 'response_item',
403
+ payload: {
404
+ type: 'agent_message',
405
+ author: '/root/test_agent_1',
406
+ content: [{ type: 'input_text', text: 'Message Type: FINAL_ANSWER\nSender: /root/test_agent_1\nPayload:\nAll done.' }],
407
+ },
408
+ }),
409
+ ].join('\n')}\n`, 'utf8');
410
+
411
+ // Codex writes a spawned agent's rollout next to its parent's, named after
412
+ // the agent thread id.
413
+ await writeFile(
414
+ path.join(path.dirname(transcriptPath), `rollout-2026-07-07T00-00-00-${agentThreadId}.jsonl`),
415
+ `${[
416
+ JSON.stringify({
417
+ type: 'session_meta',
418
+ payload: { id: agentThreadId, cwd: workspacePath, thread_source: 'subagent', agent_nickname: 'Hegel' },
419
+ }),
420
+ JSON.stringify({
421
+ type: 'response_item',
422
+ payload: {
423
+ type: 'custom_tool_call',
424
+ name: 'exec',
425
+ call_id: 'agent-call-1',
426
+ input: 'await tools.shell_command({ command: "ls" });',
427
+ },
428
+ }),
429
+ JSON.stringify({
430
+ type: 'response_item',
431
+ payload: { type: 'custom_tool_call_output', call_id: 'agent-call-1', output: 'Exit code: 0\nOutput:\nREADME.md\n' },
432
+ }),
433
+ ].join('\n')}\n`,
434
+ 'utf8',
435
+ );
436
+
437
+ await withIsolatedDatabase(async () => {
438
+ sessionsDb.createAppSession('app-parent-2', 'codex', workspacePath);
439
+ sessionsDb.assignProviderSessionId('app-parent-2', providerSessionId);
440
+ await new CodexSessionSynchronizer().synchronize();
441
+
442
+ const history = await new CodexSessionsProvider().fetchHistory('app-parent-2');
443
+ const task = history.messages.find((message) => message.kind === 'tool_use' && message.toolName === 'Task');
444
+
445
+ assert.ok(task, 'a spawned agent must produce a Task row');
446
+ assert.equal(task.subagent?.id, agentThreadId);
447
+ assert.equal(task.subagent?.name, 'Hegel');
448
+ assert.equal(task.subagent?.status, 'completed');
449
+ assert.equal(task.toolResult?.content, 'All done.');
450
+
451
+ // The agent's own shell call is translated exactly like the parent's.
452
+ assert.equal(task.subagentTools?.length, 1);
453
+ assert.equal(task.subagentTools?.[0].kind, 'tool');
454
+ assert.equal(task.subagentTools?.[0].toolName, 'Bash');
455
+ assert.equal(task.subagentTools?.[0].toolResult?.content, 'README.md\n');
456
+ });
457
+ } finally {
458
+ restoreHomeDir();
459
+ await rm(tempRoot, { recursive: true, force: true });
460
+ }
461
+ });
462
+
463
+ test('Codex memory citations are lifted out of the reply they trail', () => {
464
+ const reply = [
465
+ 'Here is the answer.',
466
+ '',
467
+ '<oai-mem-citation>',
468
+ '<citation_entries>',
469
+ 'MEMORY.md:137-142|note=[used verified container provisioning details]',
470
+ 'notes/deploy.md:4-9',
471
+ '</citation_entries>',
472
+ '<rollout_ids>',
473
+ '019eda6d-c2f7-70c1-8b42-bf03c44a1f35',
474
+ '</rollout_ids>',
475
+ '</oai-mem-citation>',
476
+ ].join('\n');
477
+
478
+ const { text, memoryCitations } = readCodexMemoryCitations(reply);
479
+
480
+ assert.equal(text, 'Here is the answer.');
481
+ assert.deepEqual(memoryCitations, [
482
+ { source: 'MEMORY.md:137-142', note: 'used verified container provisioning details' },
483
+ { source: 'notes/deploy.md:4-9' },
484
+ ]);
485
+ });
486
+
487
+ test('a plan followed by a memory citation is still recognized as a plan', async () => {
488
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-plan-citation-'));
489
+ const workspacePath = path.join(tempRoot, 'workspace');
490
+ await mkdir(workspacePath, { recursive: true });
491
+ const restoreHomeDir = patchHomeDir(tempRoot);
492
+
493
+ try {
494
+ const providerSessionId = 'codex-plan-citation-1';
495
+ const sessionsDir = path.join(tempRoot, '.codex', 'sessions', '2026', '07', '07');
496
+ await mkdir(sessionsDir, { recursive: true });
497
+ await writeFile(path.join(sessionsDir, `rollout-${providerSessionId}.jsonl`), `${[
498
+ JSON.stringify({ type: 'session_meta', payload: { id: providerSessionId, cwd: workspacePath } }),
499
+ JSON.stringify({
500
+ type: 'response_item',
501
+ payload: {
502
+ type: 'message',
503
+ role: 'assistant',
504
+ id: 'plan-1',
505
+ content: [{
506
+ type: 'output_text',
507
+ text: '<proposed_plan>\n# Ship it\n</proposed_plan>\n\n<oai-mem-citation>\n<citation_entries>\nMEMORY.md:1-2|note=[prior deploy steps]\n</citation_entries>\n</oai-mem-citation>',
508
+ }],
509
+ },
510
+ }),
511
+ ].join('\n')}\n`, 'utf8');
512
+
513
+ await withIsolatedDatabase(async () => {
514
+ sessionsDb.createAppSession('app-plan-citation-1', 'codex', workspacePath);
515
+ sessionsDb.assignProviderSessionId('app-plan-citation-1', providerSessionId);
516
+ await new CodexSessionSynchronizer().synchronize();
517
+
518
+ const history = await new CodexSessionsProvider().fetchHistory('app-plan-citation-1');
519
+ const plan = history.messages.find((message) => message.toolName === 'ExitPlanMode');
520
+
521
+ assert.ok(plan, 'the trailing citation block must not hide the plan envelope');
522
+ assert.equal(JSON.parse(String(plan.toolInput)).plan, '# Ship it');
523
+ assert.deepEqual(plan.memoryCitations, [{ source: 'MEMORY.md:1-2', note: 'prior deploy steps' }]);
524
+ assert.ok(
525
+ !history.messages.some((message) => JSON.stringify(message).includes('oai-mem-citation')),
526
+ 'no transcript row may still carry the raw citation markup',
527
+ );
528
+ });
529
+ } finally {
530
+ restoreHomeDir();
531
+ await rm(tempRoot, { recursive: true, force: true });
532
+ }
533
+ });
534
+
535
+ test('an exec script that updates the plan yields the steps it set', () => {
536
+ const operations = parseCodexExecScript([
537
+ 'const p = await tools.update_plan({plan:[',
538
+ ' {step:"Confirm the current workspace",status:"completed"},',
539
+ ' {step:"Check the Git working-tree status",status:"in_progress"},',
540
+ ' {step:"Identify the project",status:"pending"}',
541
+ ']});',
542
+ 'text(p);',
543
+ ].join('\n'));
544
+
545
+ assert.deepEqual(operations, [{
546
+ kind: 'plan',
547
+ todos: [
548
+ { content: 'Confirm the current workspace', status: 'completed' },
549
+ { content: 'Check the Git working-tree status', status: 'in_progress' },
550
+ { content: 'Identify the project', status: 'pending' },
551
+ ],
552
+ }]);
553
+ });
@@ -1,4 +1,7 @@
1
1
  import assert from 'node:assert/strict';
2
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
2
5
  import test from 'node:test';
3
6
 
4
7
  import {
@@ -6,20 +9,64 @@ import {
6
9
  OPENCODE_PREDEFINED_MODELS,
7
10
  } from '@/modules/providers/list/opencode/opencode-models.provider.js';
8
11
 
9
- test('OpenCode exposes only the curated predefined catalog', async () => {
10
- const adapter = new OpenCodeProviderModels();
12
+ const OPENCODE_ENV_KEYS = ['OPENCODE_API_KEY', 'ANTHROPIC_API_KEY', 'OPENAI_API_KEY'];
11
13
 
12
- assert.deepEqual(await adapter.getSupportedModels(), OPENCODE_PREDEFINED_MODELS);
13
- assert.equal(
14
- (await adapter.getCurrentActiveModel()).model,
15
- OPENCODE_PREDEFINED_MODELS.DEFAULT,
16
- );
14
+ /**
15
+ * Runs one case against a throwaway OpenCode home, so the catalog the adapter
16
+ * reports depends on the fixture rather than on the providers the machine
17
+ * running the suite happens to be logged into.
18
+ */
19
+ const withOpenCodeHome = async (
20
+ setUp: (homeDir: string) => Promise<void>,
21
+ runTest: (adapter: OpenCodeProviderModels) => Promise<void>,
22
+ ): Promise<void> => {
23
+ const homeDir = await mkdtemp(path.join(os.tmpdir(), 'opencode-catalog-'));
24
+ const originalHomedir = os.homedir;
25
+ const originalEnv = OPENCODE_ENV_KEYS.map((key) => [key, process.env[key]] as const);
26
+
27
+ (os as any).homedir = () => homeDir;
28
+ for (const key of OPENCODE_ENV_KEYS) {
29
+ delete process.env[key];
30
+ }
31
+
32
+ try {
33
+ await setUp(homeDir);
34
+ await runTest(new OpenCodeProviderModels());
35
+ } finally {
36
+ (os as any).homedir = originalHomedir;
37
+ for (const [key, value] of originalEnv) {
38
+ if (value === undefined) {
39
+ delete process.env[key];
40
+ } else {
41
+ process.env[key] = value;
42
+ }
43
+ }
44
+ await rm(homeDir, { recursive: true, force: true });
45
+ }
46
+ };
47
+
48
+ const writeOpenCodeAuth = async (homeDir: string, auth: Record<string, unknown>): Promise<void> => {
49
+ const authDir = path.join(homeDir, '.local', 'share', 'opencode');
50
+ await mkdir(authDir, { recursive: true });
51
+ await writeFile(path.join(authDir, 'auth.json'), JSON.stringify(auth), 'utf8');
52
+ };
53
+
54
+ test('OpenCode exposes only the curated predefined catalog', async () => {
55
+ await withOpenCodeHome(async () => {}, async (adapter) => {
56
+ // Nothing readable about this install, so the picker keeps every option
57
+ // rather than coming up empty.
58
+ assert.deepEqual(await adapter.getSupportedModels(), OPENCODE_PREDEFINED_MODELS);
59
+ assert.equal(
60
+ (await adapter.getCurrentActiveModel()).model,
61
+ OPENCODE_PREDEFINED_MODELS.DEFAULT,
62
+ );
63
+ });
17
64
  // OpenCode routes by `<providerID>/<modelID>`, so every option has to carry a
18
65
  // provider prefix that `opencode models --verbose` reports.
19
66
  const providerIds = new Set(
20
67
  OPENCODE_PREDEFINED_MODELS.OPTIONS.map((option) => option.value.split('/')[0]),
21
68
  );
22
- assert.deepEqual([...providerIds].sort(), ['anthropic', 'opencode', 'openai'].sort());
69
+ assert.deepEqual([...providerIds].sort(), ['anthropic', 'opencode', 'opencode-go', 'openai'].sort());
23
70
  assert.equal(
24
71
  OPENCODE_PREDEFINED_MODELS.OPTIONS.every((option) => /^[a-z0-9-]+\/.+/.test(option.value)),
25
72
  true,
@@ -38,4 +85,115 @@ test('OpenCode exposes only the curated predefined catalog', async () => {
38
85
  assert.ok(
39
86
  OPENCODE_PREDEFINED_MODELS.OPTIONS.some((option) => option.value === 'openai/gpt-5.6'),
40
87
  );
88
+ // The Go gateway carries its own provider id, so its models must be curated
89
+ // too - a Go subscriber otherwise authenticates while the picker offers
90
+ // nothing they can run.
91
+ const opencodeGoOptions = OPENCODE_PREDEFINED_MODELS.OPTIONS.filter(
92
+ (option) => option.value.startsWith('opencode-go/'),
93
+ );
94
+ assert.equal(opencodeGoOptions.length, 27);
95
+ assert.ok(opencodeGoOptions.every((option) => option.description === 'OpenCode Go'));
96
+ const glmFlash = opencodeGoOptions.find(
97
+ (option) => option.value === 'opencode-go/glm-5.3-flash',
98
+ );
99
+ assert.ok(glmFlash);
100
+ // Effort choices come from `opencode models --verbose` variants; the runtime
101
+ // turns a selected value into `--variant`, so the values have to match the
102
+ // CLI's exactly.
103
+ assert.deepEqual(
104
+ glmFlash?.effort?.values.map((value) => value.value),
105
+ ['low', 'high', 'max'],
106
+ );
107
+ assert.ok(
108
+ opencodeGoOptions
109
+ .filter((option) => !option.effort)
110
+ .every((option) =>
111
+ ['glm-5.1', 'kimi-k2.6', 'kimi-k2.7-code', 'mimo-v2.5', 'mimo-v2.5-pro',
112
+ 'minimax-m2.7', 'qwen3.6-plus', 'qwen3.7-max', 'qwen3.7-plus']
113
+ .includes(option.value.slice('opencode-go/'.length)),
114
+ ),
115
+ );
116
+ });
117
+
118
+ test('OpenCode offers only models the install can route to', async () => {
119
+ // Asking for a provider the user never connected fails the whole run with
120
+ // "Model <id> is not valid", so an OpenCode Zen model must not be offered -
121
+ // or defaulted to - on a machine that only holds an Anthropic key.
122
+ await withOpenCodeHome(
123
+ (homeDir) => writeOpenCodeAuth(homeDir, { anthropic: { type: 'api', key: 'test' } }),
124
+ async (adapter) => {
125
+ const catalog = await adapter.getSupportedModels();
126
+ const providerIds = new Set(catalog.OPTIONS.map((option) => option.value.split('/')[0]));
127
+
128
+ assert.deepEqual([...providerIds], ['anthropic']);
129
+ assert.ok(catalog.OPTIONS.length > 0);
130
+ assert.equal(catalog.DEFAULT.startsWith('anthropic/'), true);
131
+ assert.ok(catalog.OPTIONS.some((option) => option.value === catalog.DEFAULT));
132
+ assert.equal((await adapter.getCurrentActiveModel()).model, catalog.DEFAULT);
133
+ },
134
+ );
135
+
136
+ // A Go subscriber's auth store holds only `opencode-go`, so the whole catalog
137
+ // has to resolve to Go models and the default has to move onto one of them
138
+ // instead of the unreachable Zen default.
139
+ await withOpenCodeHome(
140
+ (homeDir) => writeOpenCodeAuth(homeDir, { 'opencode-go': { type: 'api', key: 'test' } }),
141
+ async (adapter) => {
142
+ const catalog = await adapter.getSupportedModels();
143
+ const providerIds = new Set(catalog.OPTIONS.map((option) => option.value.split('/')[0]));
144
+
145
+ assert.deepEqual([...providerIds], ['opencode-go']);
146
+ assert.equal(catalog.OPTIONS.length, 27);
147
+ assert.equal(catalog.DEFAULT, 'opencode-go/grok-4.6');
148
+ assert.ok(catalog.OPTIONS.some((option) => option.value === catalog.DEFAULT));
149
+ assert.equal((await adapter.getCurrentActiveModel()).model, catalog.DEFAULT);
150
+ },
151
+ );
152
+
153
+ // The catalog default survives whenever its own provider is connected.
154
+ await withOpenCodeHome(
155
+ (homeDir) => writeOpenCodeAuth(homeDir, {
156
+ opencode: { type: 'api', key: 'test' },
157
+ openai: { type: 'oauth' },
158
+ }),
159
+ async (adapter) => {
160
+ const catalog = await adapter.getSupportedModels();
161
+ const providerIds = new Set(catalog.OPTIONS.map((option) => option.value.split('/')[0]));
162
+
163
+ assert.deepEqual([...providerIds].sort(), ['opencode', 'openai'].sort());
164
+ assert.equal(catalog.DEFAULT, OPENCODE_PREDEFINED_MODELS.DEFAULT);
165
+ },
166
+ );
167
+
168
+ // Providers configured rather than logged into count too.
169
+ await withOpenCodeHome(
170
+ async (homeDir) => {
171
+ const configDir = path.join(homeDir, '.config', 'opencode');
172
+ await mkdir(configDir, { recursive: true });
173
+ await writeFile(
174
+ path.join(configDir, 'opencode.json'),
175
+ JSON.stringify({ provider: { anthropic: { options: {} } } }),
176
+ 'utf8',
177
+ );
178
+ },
179
+ async (adapter) => {
180
+ const catalog = await adapter.getSupportedModels();
181
+ const providerIds = new Set(catalog.OPTIONS.map((option) => option.value.split('/')[0]));
182
+
183
+ assert.deepEqual([...providerIds], ['anthropic']);
184
+ },
185
+ );
186
+
187
+ // An API key in the environment is enough on its own.
188
+ await withOpenCodeHome(
189
+ async () => {
190
+ process.env.OPENAI_API_KEY = 'test';
191
+ },
192
+ async (adapter) => {
193
+ const catalog = await adapter.getSupportedModels();
194
+ const providerIds = new Set(catalog.OPTIONS.map((option) => option.value.split('/')[0]));
195
+
196
+ assert.deepEqual([...providerIds], ['openai']);
197
+ },
198
+ );
41
199
  });