elanous 0.2.37 → 0.2.40

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 (727) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/{8kITumG9GAZ1LwT9POrAR → HjeK7zONHfR7op1hKXp4k}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{1746.3563617ea64023a3.js → 1060.014f1f01a17aec26.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/{9151.a2c51e93f9156dae.js → 1281.d81877e35b9068f4.js} +1 -1
  6. package/apps/pwa/out/_next/static/chunks/1350.59f40e39e02d91b1.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1415.79821ec7ae99acdb.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1452.162bb2d9d699e82a.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/147.f92f63bbc5e85fa5.js +286 -0
  10. package/apps/pwa/out/_next/static/chunks/1537-6bb97b5214928ea9.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/1661.fb425a1d8a065b72.js +149 -0
  12. package/apps/pwa/out/_next/static/chunks/1671-1c22b40b50b208f2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/1758.0204d3f48783c49d.js +90 -0
  14. package/apps/pwa/out/_next/static/chunks/1806.027bf78b6a94d340.js +104 -0
  15. package/apps/pwa/out/_next/static/chunks/{3578.4e9f9948a9a6f2f4.js → 2047.8f23e210995d4548.js} +2 -2
  16. package/apps/pwa/out/_next/static/chunks/2061-7f2bbc53ae96c88b.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/{7424.c0fe2a595d4232b5.js → 2066.a77257c8c350770a.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/2096-a938496bc996dcbd.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/{4931.95cc6dbd230774ca.js → 2177.df8547f9d7215c4e.js} +16 -16
  20. package/apps/pwa/out/_next/static/chunks/2200-9a453b8f99687d49.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/{6680.6a693c28aae4cd19.js → 2226.2cf61ec781827219.js} +2 -2
  22. package/apps/pwa/out/_next/static/chunks/2281-b8cede695c6c650e.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/2499.02fbfd18126c10c0.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/{6252.240578f10952e091.js → 2522.f2766b76d22c8bdf.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/{1262.a89bb27a5b2f844e.js → 2566.beaffc3a5e1fcd2e.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/261.0d471e8aded799f3.js +226 -0
  27. package/apps/pwa/out/_next/static/chunks/2615.3c9738153de3ef3f.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2727.e703d99ac414916d.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2752.7904b8df0254d0ae.js +27 -0
  30. package/apps/pwa/out/_next/static/chunks/2762-5051a862dfa40e99.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/2767-31784bfedf8e245d.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/{2318-450e75d07605b8fe.js → 2774-719820e3f5b2a3eb.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/2812-f016aa0dda149287.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{4725-34bed25720cc326b.js → 2829-734fe8bf2eee7347.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/311-32dcac79970985e8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3175-f93f7495cd28253c.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3184.8da993f5502d2140.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/3231-d7c981db0ff8bdcc.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/3250.151309c5176d6b4b.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/3330.7443183b7e6fdbdf.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/3517.a5aa1797a9dbf2ee.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/3567-45f676877ed9e8a5.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/3746-f80a78f220bf37b5.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/3819.8a1de79b2e8c7298.js +24 -0
  45. package/apps/pwa/out/_next/static/chunks/39.9b37b0be4c128a83.js +77 -0
  46. package/apps/pwa/out/_next/static/chunks/{4337e36d.f42c3b391b3d8f20.js → 3b355b3d.6f73c98bf6f9c749.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/4023.a19045970a9ce16a.js +36 -0
  48. package/apps/pwa/out/_next/static/chunks/{58edce7d.f1b0c72acf13b123.js → 409c787b.e48c5bec8ec10456.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/413-24c8284bda08241d.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/4135.e45d6ebc29e20647.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/4283.65502f5d0e6ff6c0.js +287 -0
  52. package/apps/pwa/out/_next/static/chunks/4305-4e28fa92a7b34a5b.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/4335-50efa61fd7613142.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/4351.0f37a6db2b34d50b.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/{5046.7fd5cbd859299448.js → 470.51a72a2348306455.js} +2 -2
  56. package/apps/pwa/out/_next/static/chunks/4720.ecbd1693fa151068.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/{2375.bf364c85dbb6f085.js → 4769.9fd582071301f382.js} +1 -1
  58. package/apps/pwa/out/_next/static/chunks/483.725e4a61be922788.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/{de8446d3.31b73eb239fecd7b.js → 4864a2ca.1c1c280a7772c5e5.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/4969-4b209d7307410c00.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/5068-c7f53862f83c0917.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/5315.bc96711c24dfefe2.js +74 -0
  63. package/apps/pwa/out/_next/static/chunks/{4383.18e5c54db3a22c88.js → 5383.bdfa5d886e39a826.js} +1 -1
  64. package/apps/pwa/out/_next/static/chunks/5436.fdf6fa8ae3e9588d.js +64 -0
  65. package/apps/pwa/out/_next/static/chunks/5542.efbf14f16baed4dd.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/5622.94893f5aea7185d0.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/5644.8f401a25601d05fc.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/5658.35f70eb9871a54bf.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/5721.7bdc0e904c76ab51.js +8 -0
  70. package/apps/pwa/out/_next/static/chunks/5790-eef0ca20884a6bdf.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/5802.ef0d8598ca68fe84.js +62 -0
  72. package/apps/pwa/out/_next/static/chunks/5890.fb974729788dcb71.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/5924.5ef25cddd36d5dbf.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/5979-e59c099ebf8d1f35.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/6020.714cfeada2315e2e.js +94 -0
  76. package/apps/pwa/out/_next/static/chunks/6027-dfe0349d8c50f698.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/{6745-5a63492933959dd9.js → 6051-0ae955eedb10adb9.js} +3 -3
  78. package/apps/pwa/out/_next/static/chunks/6178.bc4730c86cf74536.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/{2283.edd9627b7bdfb546.js → 6257.c0037b8949239f5e.js} +2 -2
  80. package/apps/pwa/out/_next/static/chunks/6273.d808b0dcb3c2b155.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/6292.6ef1df64fa6a4c8a.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/647-af8dd920ba46533c.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/6541-170dc5c59e135c0c.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/6624.981ef7caeb680560.js +32 -0
  85. package/apps/pwa/out/_next/static/chunks/6742-a1ad4a94ba7bc37a.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/{6819.be5c9c4d67f22922.js → 6749.0744ce6eb08d40e9.js} +16 -16
  87. package/apps/pwa/out/_next/static/chunks/6836-493dc55c39187949.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/6856-53ac421c06039a18.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/6908.06008c67f6380ea9.js +148 -0
  90. package/apps/pwa/out/_next/static/chunks/6915.096931864206f522.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/6925.5e181fb7dcf90193.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/6cc1d1d6.9a98dd4622f1afc6.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/7203-800e11037adc41eb.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/7219-15bdd9a80661af8e.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/7279.6170f591105936bb.js +93 -0
  96. package/apps/pwa/out/_next/static/chunks/7349-373165c7fbdcc3df.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/7514-a533ba0ca1cae4f4.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/7515.1ad6efe157fa4448.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/7521.d919cc1fab80c1d9.js +24 -0
  100. package/apps/pwa/out/_next/static/chunks/7538.90e97bbdf9178cff.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/7611.85f045bf232053ac.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/7825-39203196a12579b2.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/7869.5e1a9a12fd70d6c7.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8086.1ab9103c41be884f.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/8099.1cccf31c1baa7e1c.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/8179-e61fffbdb70f1152.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/8265.bdeb991b34db6a06.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/{2002-a43a3f380df873ec.js → 8272-025653884f9645d2.js} +1 -1
  109. package/apps/pwa/out/_next/static/chunks/{7867.e8725745ddd1d940.js → 8357.1aaa713b052a78b1.js} +4 -4
  110. package/apps/pwa/out/_next/static/chunks/8638-e42615f2166165b5.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/8639-92c11d069792429e.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/8691.54e22831d5e8ccf8.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/8d173e38.18a2f7b4e1848bdb.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/9069.657590a84afb8fac.js +41 -0
  115. package/apps/pwa/out/_next/static/chunks/9125-ae588d88a0293b93.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/{4313.76f232c304a15bde.js → 9175.2de5209a5ce6f68a.js} +1 -1
  117. package/apps/pwa/out/_next/static/chunks/9184.45afbf44fec87574.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/{8c58d09b.e8724a299658d86f.js → 9189505a.980427ac048c8c60.js} +1 -1
  119. package/apps/pwa/out/_next/static/chunks/9311.f9f641a46a508611.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/9371-a6fee8378ebfa3ee.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/9421-3aa9ff4fa7f63d8a.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/9992.07ce73c10bd5e6a1.js +79 -0
  123. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-76394dc618d322c8.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/approvals/page-1b948637c37b3abd.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-c63400f1bcf3e47c.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/board/page-95d0957267819586.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/botlab/page-ba957ff875a026a0.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/bots/page-bb6fd0714879cb87.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8f9038520d2acbef.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/chat/page-d4b48397e05fce2c.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/consult/page-72d5744245dc0c27.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/control/page-37625dc8300fa43a.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/decisions/page-fbef9d160272dbc1.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/design-check/page-58424168df84928e.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/editor/page-72c12ba430ac57c0.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/exec/page-d53f3e9ed806d7e0.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/field/page-4d2fbd45feee0829.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/healthz/route-76394dc618d322c8.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/inside/page-8783dc0ed7590a2c.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/intake/page-3706cb390606e930.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/layout-9b0a86598e0111fd.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/live/{page-3337c6a1328c2674.js → page-4edb8df90f4537ce.js} +1 -1
  143. package/apps/pwa/out/_next/static/chunks/app/live-run/page-83d27757e2b8060a.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/loops/page-47398b9ba1cc5e1b.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/market/page-fb86f55cfe2e681d.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e22345fc8fd47b1b.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/morning/{page-1e5da297557d2354.js → page-fca2e59a81744d9e.js} +1 -1
  148. package/apps/pwa/out/_next/static/chunks/app/not-found-6e96bda63100b31e.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/observatory/page-4d17ad8e0db8cea4.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-121f92766c33beaa.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-43f14212c545f92d.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ccec2023e7a974ec.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/outputs/page-b04f33f548b89a94.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/page-c8792823c0917021.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/reflection/page-8c0e32782ceef4cb.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-94bd3922eb230ce8.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/sessions/page-bca565887bafa03c.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-ac1dffafe649fc6c.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/settings/page-52374edb5e23e455.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ad4ebca120d43ca7.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/layout-76394dc618d322c8.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/setup/page-718b89ce4b4ae4e4.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/share/page-204eca33eeeaddec.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/showroom/page-e58ba9bcdb9ac0f9.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/tasks/page-98f6dd50bafd7734.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/term/{page-ac2c1e6169cfce57.js → page-139d722de1dcd6c1.js} +1 -1
  167. package/apps/pwa/out/_next/static/chunks/app/today/page-5a4bdd72bff27057.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/trace/page-6f6ab17d44f984ce.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/vault/page-aabb7bd15fb72da9.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-e4322319164a4436.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workflows/page-e9efc0c8f32e2371.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-c1c80e345fa9d6f5.js → page-ccde99f07aaf71f7.js} +1 -1
  173. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-80464aced9614390.js +1 -0
  174. package/apps/pwa/out/_next/static/chunks/{d9815f84.4918f682a45a8502.js → b84cad00.594027ebe0531fe2.js} +3 -3
  175. package/apps/pwa/out/_next/static/chunks/c696986b.5dc8bb1143af16d1.js +1 -0
  176. package/apps/pwa/out/_next/static/chunks/{709a918c.4fd3b4e2a32cb4e3.js → ca3702f8.2913182e73889fa2.js} +1 -1
  177. package/apps/pwa/out/_next/static/chunks/d5d3cefc-373c4eb722ed848b.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/{5f6af561-f90b7a67f4f75d13.js → d8de0de2-dd0f4ac8e5ab1832.js} +1 -1
  179. package/apps/pwa/out/_next/static/chunks/framework-002b472a66935d31.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/main-49b88d47292c0476.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/main-app-fff2549cb10a133f.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/pages/_app-32abbbba3f3b76b7.js +1 -0
  183. package/apps/pwa/out/_next/static/chunks/pages/_error-9d9cfef60c60dd7a.js +1 -0
  184. package/apps/pwa/out/_next/static/chunks/webpack-cc7f483fa294034a.js +1 -0
  185. package/apps/pwa/out/approvals/index.html +1 -1
  186. package/apps/pwa/out/approvals/index.txt +22 -22
  187. package/apps/pwa/out/autopilot/index.html +1 -1
  188. package/apps/pwa/out/autopilot/index.txt +22 -22
  189. package/apps/pwa/out/board/index.html +1 -1
  190. package/apps/pwa/out/board/index.txt +22 -22
  191. package/apps/pwa/out/botlab/index.html +1 -1
  192. package/apps/pwa/out/botlab/index.txt +21 -21
  193. package/apps/pwa/out/bots/index.html +1 -1
  194. package/apps/pwa/out/bots/index.txt +21 -21
  195. package/apps/pwa/out/ceo/index.html +1 -1
  196. package/apps/pwa/out/ceo/index.txt +22 -22
  197. package/apps/pwa/out/chat/index.html +1 -1
  198. package/apps/pwa/out/chat/index.txt +21 -21
  199. package/apps/pwa/out/consult/index.html +1 -1
  200. package/apps/pwa/out/consult/index.txt +22 -22
  201. package/apps/pwa/out/control/index.html +1 -1
  202. package/apps/pwa/out/control/index.txt +22 -22
  203. package/apps/pwa/out/decisions/index.html +1 -1
  204. package/apps/pwa/out/decisions/index.txt +21 -21
  205. package/apps/pwa/out/design-check/index.html +1 -1
  206. package/apps/pwa/out/design-check/index.txt +22 -22
  207. package/apps/pwa/out/editor/index.html +1 -1
  208. package/apps/pwa/out/editor/index.txt +22 -22
  209. package/apps/pwa/out/exec/index.html +1 -1
  210. package/apps/pwa/out/exec/index.txt +22 -22
  211. package/apps/pwa/out/field/index.html +1 -1
  212. package/apps/pwa/out/field/index.txt +22 -22
  213. package/apps/pwa/out/healthz +1 -1
  214. package/apps/pwa/out/index.html +1 -1
  215. package/apps/pwa/out/index.txt +21 -21
  216. package/apps/pwa/out/inside/index.html +1 -1
  217. package/apps/pwa/out/inside/index.txt +21 -21
  218. package/apps/pwa/out/intake/index.html +1 -1
  219. package/apps/pwa/out/intake/index.txt +22 -22
  220. package/apps/pwa/out/live/index.html +1 -1
  221. package/apps/pwa/out/live/index.txt +22 -22
  222. package/apps/pwa/out/live-run/index.html +1 -1
  223. package/apps/pwa/out/live-run/index.txt +22 -22
  224. package/apps/pwa/out/loops/index.html +1 -1
  225. package/apps/pwa/out/loops/index.txt +21 -21
  226. package/apps/pwa/out/market/index.html +1 -1
  227. package/apps/pwa/out/market/index.txt +21 -21
  228. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  229. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  230. package/apps/pwa/out/morning/index.html +1 -1
  231. package/apps/pwa/out/morning/index.txt +22 -22
  232. package/apps/pwa/out/observatory/index.html +1 -1
  233. package/apps/pwa/out/observatory/index.txt +22 -22
  234. package/apps/pwa/out/ops/checklist/index.html +1 -1
  235. package/apps/pwa/out/ops/checklist/index.txt +21 -21
  236. package/apps/pwa/out/ops/release/index.html +1 -1
  237. package/apps/pwa/out/ops/release/index.txt +21 -21
  238. package/apps/pwa/out/ops/seats/index.html +1 -1
  239. package/apps/pwa/out/ops/seats/index.txt +21 -21
  240. package/apps/pwa/out/outputs/index.html +1 -1
  241. package/apps/pwa/out/outputs/index.txt +21 -21
  242. package/apps/pwa/out/reflection/index.html +1 -1
  243. package/apps/pwa/out/reflection/index.txt +22 -22
  244. package/apps/pwa/out/scheduler/index.html +1 -1
  245. package/apps/pwa/out/scheduler/index.txt +22 -22
  246. package/apps/pwa/out/sessions/index.html +1 -1
  247. package/apps/pwa/out/sessions/index.txt +21 -21
  248. package/apps/pwa/out/settings/devices/index.html +1 -1
  249. package/apps/pwa/out/settings/devices/index.txt +22 -22
  250. package/apps/pwa/out/settings/index.html +1 -1
  251. package/apps/pwa/out/settings/index.txt +22 -22
  252. package/apps/pwa/out/setup/done/index.html +1 -1
  253. package/apps/pwa/out/setup/done/index.txt +22 -22
  254. package/apps/pwa/out/setup/index.html +1 -1
  255. package/apps/pwa/out/setup/index.txt +22 -22
  256. package/apps/pwa/out/share/index.html +1 -1
  257. package/apps/pwa/out/share/index.txt +22 -22
  258. package/apps/pwa/out/showroom/index.html +1 -1
  259. package/apps/pwa/out/showroom/index.txt +21 -21
  260. package/apps/pwa/out/tasks/index.html +1 -1
  261. package/apps/pwa/out/tasks/index.txt +22 -22
  262. package/apps/pwa/out/term/index.html +1 -1
  263. package/apps/pwa/out/term/index.txt +22 -22
  264. package/apps/pwa/out/today/index.html +1 -1
  265. package/apps/pwa/out/today/index.txt +22 -22
  266. package/apps/pwa/out/trace/index.html +1 -1
  267. package/apps/pwa/out/trace/index.txt +22 -22
  268. package/apps/pwa/out/vault/index.html +1 -1
  269. package/apps/pwa/out/vault/index.txt +22 -22
  270. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  271. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  272. package/apps/pwa/out/workflows/index.html +1 -1
  273. package/apps/pwa/out/workflows/index.txt +22 -22
  274. package/apps/pwa/out/workspace/index.html +1 -1
  275. package/apps/pwa/out/workspace/index.txt +22 -22
  276. package/apps/pwa/out/worktrees/index.html +1 -1
  277. package/apps/pwa/out/worktrees/index.txt +22 -22
  278. package/graphs/demo/persona-marketer/fixtures/marketer-scenario.yaml +10 -0
  279. package/graphs/demo/persona-marketer/persona-marketer.yaml +25 -0
  280. package/graphs/demo/persona-marketer/recipes.yaml +19 -0
  281. package/graphs/release/recipes.yaml +9 -0
  282. package/graphs/release/release-loop.yaml +26 -18
  283. package/graphs/video/explainer-line.yaml +4 -1
  284. package/package.json +1 -1
  285. package/scripts/ask-op-inbox-once.ts +17 -0
  286. package/scripts/brand/check.ts +13 -4
  287. package/scripts/dead-code/report.ts +169 -0
  288. package/scripts/gate-0226-accepted-remaining-90.txt +91 -0
  289. package/scripts/gate-0226-failure-census.ts +39 -0
  290. package/scripts/intake-collect-telegram-saved-stage.ts +48 -0
  291. package/scripts/lib/morning-report-options.ts +3 -4
  292. package/scripts/lib/tui-regress-evidence.ts +56 -0
  293. package/scripts/morning-report.ts +5 -1
  294. package/scripts/provision-export-redactions.ts +15 -0
  295. package/scripts/public-copy-lint.ts +122 -0
  296. package/scripts/public-export.ts +77 -2
  297. package/scripts/ratchets/next-tui-entry-budget.ts +124 -0
  298. package/scripts/ratchets/train-side-invariants.sh +7 -1
  299. package/scripts/release-loop/cut-branch.ts +61 -12
  300. package/scripts/release-loop/export-check-node.ts +21 -3
  301. package/scripts/release-loop/gate-node.ts +527 -60
  302. package/scripts/release-loop/gate-partial.ts +82 -3
  303. package/scripts/release-loop/gate-prewarm-plan.ts +35 -0
  304. package/scripts/release-loop/gate-radius-live.ts +4 -2
  305. package/scripts/release-loop/gate-shard-dispatch.ts +128 -0
  306. package/scripts/release-loop/gate-shard-receipts.ts +89 -0
  307. package/scripts/release-loop/gate-wall.ts +5 -5
  308. package/scripts/release-loop/preflight.ts +6 -2
  309. package/scripts/release-loop/resume-cap.ts +2 -1
  310. package/scripts/release-loop/resume-release.ts +317 -13
  311. package/scripts/release-loop/resume.ts +61 -5
  312. package/scripts/release-loop/traffic-node.ts +12 -4
  313. package/scripts/release-loop/traffic-transition.ts +53 -13
  314. package/scripts/release-loop/version-node.ts +35 -5
  315. package/scripts/release-story/draft.ts +1 -1
  316. package/scripts/research-bench/README.md +53 -0
  317. package/scripts/research-bench/questions.json +66 -0
  318. package/scripts/research-bench/rubric.md +33 -0
  319. package/scripts/research-bench/run.ts +523 -0
  320. package/scripts/tui-regress.ts +85 -10
  321. package/scripts/ux-sim.ts +1 -1
  322. package/scripts/video-pipeline.ts +2 -2
  323. package/src/acp/core-turn-bridge.ts +14 -1
  324. package/src/agent/autonomous-tools.ts +10 -3
  325. package/src/agent/monad-agent-turn.ts +4 -1
  326. package/src/agent/registry.ts +26 -2
  327. package/src/agent/run-context.ts +1 -1
  328. package/src/agent/shared-app-tools.ts +1 -1
  329. package/src/agent/surface-ux/build.ts +12 -3
  330. package/src/agent/surface-ux/types.ts +1 -1
  331. package/src/agent/task-notification.ts +78 -7
  332. package/src/agent/types.ts +3 -0
  333. package/src/agent-mission/driver.ts +29 -3
  334. package/src/agent-mission/mission-cli.ts +2 -1
  335. package/src/agent-substrate/review-context-pack.ts +365 -0
  336. package/src/agent-substrate/review-observation.ts +10 -0
  337. package/src/agent-substrate/self-review-cli.ts +419 -42
  338. package/src/ask-user-question/index.ts +6 -0
  339. package/src/ask-user-question/inline-resolver.ts +49 -0
  340. package/src/auto-research/question-ladder-contract.ts +156 -0
  341. package/src/auto-research/research-turn.ts +467 -0
  342. package/src/autopilot/agent-loop.ts +1 -1
  343. package/src/autopilot/discovery/doc-lint.ts +1 -1
  344. package/src/autopilot/pty-control-loop.ts +8 -1
  345. package/src/boot/daemon-tools/index.ts +4 -3
  346. package/src/boot/daemon-tools/self-implement.ts +270 -2
  347. package/src/boot/daemon-tools/types.ts +2 -0
  348. package/src/bots/screen-probe.ts +1 -1
  349. package/src/budget/llm-cost.ts +44 -5
  350. package/src/budget/unified-usage.ts +17 -1
  351. package/src/capture/tui-self-report.ts +24 -2
  352. package/src/chat/index.ts +34 -7
  353. package/src/chat/tool-render/dispatch.ts +8 -1
  354. package/src/cli/agent-cli.ts +43 -8
  355. package/src/cli/guardian-cli.ts +3 -1
  356. package/src/cli/intake-cli.ts +70 -13
  357. package/src/cli/lesson-cli.ts +86 -10
  358. package/src/cli/log-event-names.ts +1 -0
  359. package/src/cli/pod-cli.ts +17 -2
  360. package/src/cli/pr-land-overlap-evidence.ts +38 -5
  361. package/src/cli/pr-land-refusal.ts +1 -1
  362. package/src/cli/pty-takeover-cli.ts +125 -12
  363. package/src/cli/release-checklist-read-cli.ts +5 -2
  364. package/src/cli/release-cli.ts +99 -15
  365. package/src/cli/repo-cli.ts +2 -1
  366. package/src/cli/review-carry.ts +77 -0
  367. package/src/cli/self-update.ts +1 -1
  368. package/src/cli/tasks-card-show.ts +31 -1
  369. package/src/cli/tasks-cli.ts +98 -32
  370. package/src/cli/tasks-read-cli.ts +13 -5
  371. package/src/context-bus/context-now.ts +86 -2
  372. package/src/control-plane/ledger.ts +21 -4
  373. package/src/core-turn/run-core-turn.ts +51 -3
  374. package/src/core-turn/run-goal-loop.ts +152 -8
  375. package/src/core-turn/types.ts +12 -0
  376. package/src/dashboard/chat/acp-chat.ts +2 -2
  377. package/src/dashboard/classic-sigquit.ts +16 -0
  378. package/src/dashboard/index.ts +339 -44
  379. package/src/dashboard/input/chat-main-interaction-opts.ts +3 -0
  380. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +7 -2
  381. package/src/dashboard/input/chat-main-slash-command.ts +7 -0
  382. package/src/dashboard/input/dashboard-plain-turn-stream.ts +4 -0
  383. package/src/dashboard/input/dashboard-priority-key-route.ts +21 -5
  384. package/src/dashboard/input/external-input-host.ts +2 -0
  385. package/src/dashboard/input/terminal-modal-router.ts +8 -159
  386. package/src/dashboard/side-basic-runtime.ts +61 -0
  387. package/src/dashboard/sidetab-runtime.ts +228 -0
  388. package/src/dashboard/slash-runtime/ask-clarification.ts +7 -1
  389. package/src/dashboard/slash-runtime/dashboard-handlers.ts +22 -8
  390. package/src/dashboard/slash-runtime/help-from-registry.ts +2 -2
  391. package/src/dashboard/term-slash-runtime.ts +18 -18
  392. package/src/dashboard/turn-preamble.ts +30 -4
  393. package/src/domains/guardian-tick.ts +9 -0
  394. package/src/domains/knowledge.ts +1 -1
  395. package/src/esc-abort-gate.ts +33 -1
  396. package/src/execution-loop/launch-gate.ts +206 -1
  397. package/src/git-fs/watcher.ts +27 -22
  398. package/src/git-fs/worktree.ts +19 -3
  399. package/src/graph-runner/graph-cli.ts +6 -4
  400. package/src/graph-runner/graph-grow.ts +37 -9
  401. package/src/graph-runner/runner.ts +48 -11
  402. package/src/graph-wizard/generate.ts +16 -7
  403. package/src/graph-wizard/mission-compose.ts +201 -0
  404. package/src/grok/agent-search.ts +8 -2
  405. package/src/grok/credential.ts +112 -36
  406. package/src/grounding/ref-index.ts +11 -0
  407. package/src/grounding/ref-vector.ts +61 -0
  408. package/src/grounding/sources-cli.ts +2 -2
  409. package/src/grounding/sources.ts +15 -1
  410. package/src/harness/harness-cli-command.ts +38 -9
  411. package/src/harness/harness-pod-dispatch.ts +7 -1
  412. package/src/hq/fence-wrapper.ts +1 -1
  413. package/src/hq/transfer.ts +1 -1
  414. package/src/index.ts +60 -16
  415. package/src/input/external-input-queue.ts +10 -0
  416. package/src/intake-plane/collect-telegram-saved.ts +90 -6
  417. package/src/intake-plane/intake-to-tasks.ts +246 -3
  418. package/src/interactive-terminal-modal.ts +3 -0
  419. package/src/keybindings.ts +17 -22
  420. package/src/lessons/friction-ledger.ts +154 -0
  421. package/src/live-supply/collector.ts +149 -0
  422. package/src/live-supply/probes.ts +52 -0
  423. package/src/live-supply/snapshot.ts +40 -0
  424. package/src/llm/provider-credentials.ts +1 -1
  425. package/src/llm-tool-schema.ts +20 -0
  426. package/src/llm.ts +486 -61
  427. package/src/loops/orchestrator/inbox-once.ts +28 -0
  428. package/src/loops/orchestrator/inbox.ts +130 -1
  429. package/src/loops/orchestrator/tick.ts +14 -3
  430. package/src/maturity/feature-maturity.ts +1 -0
  431. package/src/nexus/api/channel-ask-op.ts +96 -0
  432. package/src/nexus/api/discord-trigger-bot.ts +28 -1
  433. package/src/nexus/api/harness-api.ts +2 -1
  434. package/src/nexus/api/telegram-trigger-bot.ts +24 -0
  435. package/src/nexus/api/terminals.ts +28 -6
  436. package/src/patents/ledger.ts +18 -1
  437. package/src/preview/terminal.ts +4 -2
  438. package/src/prompt-cache/anthropic.ts +2 -0
  439. package/src/prompt-enhance/enhance.ts +5 -0
  440. package/src/pty/snapshot.py +132 -0
  441. package/src/pty-shell/pty-control-ipc.ts +29 -2
  442. package/src/pty-shell/pty-manifest.ts +75 -5
  443. package/src/pty-shell/registry.ts +2 -0
  444. package/src/pty-shell/tui-run-state.ts +80 -3
  445. package/src/release-loop/gate-host-reservation.ts +13 -4
  446. package/src/release-loop/gate-shard-ledger.ts +68 -0
  447. package/src/release-loop/gate-shards.ts +61 -0
  448. package/src/release-loop/light-rc.ts +7 -4
  449. package/src/release-loop/merged-pr-checklist.ts +25 -2
  450. package/src/release-loop/rehearsal-shard-plan.ts +6 -1
  451. package/src/release-loop/rehearsal.ts +28 -1
  452. package/src/repl/index.ts +10 -0
  453. package/src/research/axis-fanout.ts +133 -0
  454. package/src/research/edge-decision.ts +197 -0
  455. package/src/research/gap-followup.ts +249 -0
  456. package/src/research/lenses/counter-evidence.ts +100 -0
  457. package/src/research/lenses/working-backwards.ts +89 -0
  458. package/src/research/output-router.ts +127 -0
  459. package/src/research/plugin-discovery.ts +242 -0
  460. package/src/research-bridge/format.ts +16 -1
  461. package/src/research-bridge/index.ts +1 -0
  462. package/src/research-bridge/invoke.ts +45 -7
  463. package/src/research-bridge/types.ts +2 -0
  464. package/src/self-dev/ask-launch-flow.ts +34 -106
  465. package/src/self-dev/decompose.ts +45 -9
  466. package/src/self-dev/dev-cli.ts +2 -0
  467. package/src/self-dev/dev-pipeline.ts +8 -4
  468. package/src/self-dev/draft-residue.ts +221 -2
  469. package/src/self-dev/draft-sweep.ts +29 -16
  470. package/src/self-dev/graph-journey-route.ts +10 -2
  471. package/src/self-dev/launch-preflight-text.ts +2 -2
  472. package/src/self-dev/launch-preflight.ts +38 -7
  473. package/src/self-dev/orchestrate-cli.ts +1 -0
  474. package/src/self-dev/orchestrate.ts +1 -0
  475. package/src/self-dev/run-supervisor.ts +41 -7
  476. package/src/self-dev/shard-run.ts +260 -3
  477. package/src/self-implement/comment-only-source.ts +54 -0
  478. package/src/self-implement/device-first.ts +8 -2
  479. package/src/self-implement/doctor-visit.ts +100 -57
  480. package/src/self-implement/gate-artifacts.ts +5 -2
  481. package/src/self-implement/gate-baseline.ts +119 -13
  482. package/src/self-implement/gate-cli.ts +466 -93
  483. package/src/self-implement/gate-failure-signature.ts +44 -0
  484. package/src/self-implement/gate-receipt-registry.ts +180 -0
  485. package/src/self-implement/gate-remote.ts +147 -22
  486. package/src/self-implement/goal-author-clarification.ts +178 -24
  487. package/src/self-implement/goal-author-decompose.ts +24 -15
  488. package/src/self-implement/goal-author.ts +82 -7
  489. package/src/self-implement/goal-doc/section.ts +22 -0
  490. package/src/self-implement/goal-supervisor-fill.ts +38 -4
  491. package/src/self-implement/host-regate.ts +120 -45
  492. package/src/self-implement/impl-findings-feedback.ts +188 -0
  493. package/src/self-implement/knowhow-pack.ts +301 -9
  494. package/src/self-implement/l8-merge-queue.ts +198 -32
  495. package/src/self-implement/off-diff-evidence.ts +147 -0
  496. package/src/self-implement/orchestrator.ts +137 -16
  497. package/src/self-implement/pod-inplace-worktree.ts +82 -0
  498. package/src/self-implement/pod-warm-pool-profile.ts +2 -2
  499. package/src/self-implement/premise-review.ts +7 -5
  500. package/src/self-implement/run-ledger.ts +304 -8
  501. package/src/self-implement/run-stop.ts +12 -0
  502. package/src/self-implement/seams.ts +39 -13
  503. package/src/self-implement/self-implement-cli.ts +30 -4
  504. package/src/session/channel-ask-op-envelope.ts +36 -0
  505. package/src/session/channel-op-reply-route.ts +200 -0
  506. package/src/session/chat.ts +92 -15
  507. package/src/session/index.ts +2 -0
  508. package/src/session-runtime/index.ts +9 -22
  509. package/src/session-runtime/provider-circuit.ts +140 -0
  510. package/src/shell-runner/file-engine.ts +1 -0
  511. package/src/shell-runner/types.ts +4 -0
  512. package/src/sidetab-core/model.ts +176 -0
  513. package/src/skills/runner.ts +7 -3
  514. package/src/skills/tools/dev-harness.ts +2 -2
  515. package/src/skills/tools/schedule-wakeup.ts +24 -7
  516. package/src/skills/tools/shell.ts +51 -5
  517. package/src/status/bar.ts +3 -3
  518. package/src/steward/directive-audit.ts +354 -0
  519. package/src/storage/file-lock.ts +17 -4
  520. package/src/surface/child-process.ts +31 -0
  521. package/src/surface/tui-stderr-owner.ts +48 -0
  522. package/src/task-agent/adopt-pr.ts +63 -2
  523. package/src/task-agent/cover.ts +30 -12
  524. package/src/task-agent/evidence-command-normalize.ts +42 -0
  525. package/src/task-agent/export-redactions-provision.ts +43 -0
  526. package/src/task-agent/judge.ts +56 -0
  527. package/src/task-agent/parent-host.ts +59 -12
  528. package/src/task-agent/postland-card.ts +35 -2
  529. package/src/task-agent/rejudge-on-head.ts +46 -2
  530. package/src/task-agent/repair-cycle.ts +68 -9
  531. package/src/task-agent/repair-worker.ts +37 -1
  532. package/src/task-agent/task-hand.ts +226 -33
  533. package/src/task-agent/tier-split.ts +142 -24
  534. package/src/task-orchestrator/surfaces/pod-command-job.ts +330 -2
  535. package/src/task-orchestrator/surfaces/pod-config-snapshot.ts +123 -0
  536. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +57 -5
  537. package/src/task-orchestrator/surfaces/pod-mirror-seed.ts +11 -8
  538. package/src/task-orchestrator/surfaces/pod-pool.ts +27 -4
  539. package/src/task-orchestrator/surfaces/pod-si-warm-protocol.ts +1 -1
  540. package/src/task-orchestrator/surfaces/pod-source-receive.ts +74 -6
  541. package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +2 -1
  542. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +56 -15
  543. package/src/task-orchestrator/surfaces/self-implement-pod.ts +310 -79
  544. package/src/task-orchestrator/surfaces/self-implement.ts +33 -2
  545. package/src/terminal/session-registry.ts +1 -0
  546. package/src/terminal/terminal-modal-router.ts +156 -0
  547. package/src/tool-hints/gate.ts +6 -4
  548. package/src/tool-hints/signals.ts +33 -1
  549. package/src/tool-runtime/skill-exec-runtime.ts +95 -5
  550. package/src/tui/chat/background-job-status.ts +8 -2
  551. package/src/tui/entry.ts +483 -32
  552. package/src/tui/inline-choice.ts +188 -0
  553. package/src/tui/sidetab/attach.ts +304 -0
  554. package/src/tui/sidetab/list.ts +2 -0
  555. package/src/tui/sidetab/nl-tool.ts +136 -0
  556. package/src/tui/slash/resume-command.ts +23 -0
  557. package/src/tui/slash/resume.ts +3 -23
  558. package/src/tui/slash/term-next.ts +2 -1
  559. package/src/tui/slash/term.ts +12 -1
  560. package/src/tui-client/dashboard-session.ts +7 -1
  561. package/src/tui-client/local-chat-turn.ts +29 -8
  562. package/src/user-config.ts +42 -3
  563. package/src/ux-sim/classic-tui-seat-scenarios.ts +1 -7
  564. package/src/ux-sim/fake-land-queue.ts +88 -0
  565. package/src/ux-sim/fake-llm.ts +11 -5
  566. package/src/ux-sim/gate-shard-host-reroute.ts +82 -0
  567. package/src/ux-sim/goal-interim-trace.ts +24 -0
  568. package/src/ux-sim/next-tui-seat-scenarios.ts +61 -14
  569. package/src/ux-sim/task-orchestrator-surfaces-self-implement-pod-device.ts +4 -1
  570. package/src/version/packed-revision.json +1 -1
  571. package/src/video-pipeline/recipes/explainer.ts +54 -2
  572. package/src/web-search/grok.ts +32 -6
  573. package/src/web-search/index.ts +8 -7
  574. package/src/workflow-runtime/node-catalog.ts +53 -4
  575. package/apps/pwa/out/_next/static/chunks/1041-5841aae848fb6403.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/1087.3177e54f672dc526.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/1105.efe1e13503ac7a33.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/1224.2ceca6c180d5130b.js +0 -62
  579. package/apps/pwa/out/_next/static/chunks/1417-1768233ac92f8a5d.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/1612.81bbc19af06af16c.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/1766.728d42053070bb2d.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/1773.d38e945481e04125.js +0 -1
  583. package/apps/pwa/out/_next/static/chunks/1974-c12f9600da3d3c26.js +0 -1
  584. package/apps/pwa/out/_next/static/chunks/1ed5e8af.cf424815c7c0efad.js +0 -1
  585. package/apps/pwa/out/_next/static/chunks/2061.7232dc9dc058417f.js +0 -1
  586. package/apps/pwa/out/_next/static/chunks/210.90b2cbd64926cf34.js +0 -32
  587. package/apps/pwa/out/_next/static/chunks/244-a810d282a20f1783.js +0 -1
  588. package/apps/pwa/out/_next/static/chunks/2479-fe7e188bd9402d14.js +0 -1
  589. package/apps/pwa/out/_next/static/chunks/2514.03b60f266531a015.js +0 -148
  590. package/apps/pwa/out/_next/static/chunks/2526-a657538869888604.js +0 -1
  591. package/apps/pwa/out/_next/static/chunks/2533-6f0341ff075809e9.js +0 -1
  592. package/apps/pwa/out/_next/static/chunks/2676.e7994c695ef0f30d.js +0 -1
  593. package/apps/pwa/out/_next/static/chunks/2734.3ffdf54254fb6f99.js +0 -27
  594. package/apps/pwa/out/_next/static/chunks/2770-23252bf3a37880b9.js +0 -1
  595. package/apps/pwa/out/_next/static/chunks/2969.66b8b545139d1544.js +0 -1
  596. package/apps/pwa/out/_next/static/chunks/3128.a9210d08800ea6a8.js +0 -1
  597. package/apps/pwa/out/_next/static/chunks/3199.6a1a9b0e6cd01dc6.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/3212.12179fd3eee8cef1.js +0 -1
  599. package/apps/pwa/out/_next/static/chunks/3760-2909ddb9279281cd.js +0 -1
  600. package/apps/pwa/out/_next/static/chunks/3825.1921b5901bf33dce.js +0 -36
  601. package/apps/pwa/out/_next/static/chunks/3994-63eb884ed0547011.js +0 -1
  602. package/apps/pwa/out/_next/static/chunks/4036.d54e55e1ebf5acdc.js +0 -90
  603. package/apps/pwa/out/_next/static/chunks/4115.5b438b99f0ae729f.js +0 -226
  604. package/apps/pwa/out/_next/static/chunks/4269.d8f9f34bb0fc6c43.js +0 -287
  605. package/apps/pwa/out/_next/static/chunks/431-4eed5bd2c5fe1719.js +0 -1
  606. package/apps/pwa/out/_next/static/chunks/4324.c99ba3e83eb1b691.js +0 -1
  607. package/apps/pwa/out/_next/static/chunks/4599-96eff3f2a9c242c1.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/4825-a9d9d4e9f5892309.js +0 -1
  609. package/apps/pwa/out/_next/static/chunks/4833-7934c592c5569f00.js +0 -1
  610. package/apps/pwa/out/_next/static/chunks/4879-68518f547a207aff.js +0 -1
  611. package/apps/pwa/out/_next/static/chunks/4896.f9b70285363b27b2.js +0 -24
  612. package/apps/pwa/out/_next/static/chunks/4937-28d437999b77c9dd.js +0 -1
  613. package/apps/pwa/out/_next/static/chunks/49aa54ab.80f39398e0ed0f30.js +0 -1
  614. package/apps/pwa/out/_next/static/chunks/5056.482687319b82638e.js +0 -104
  615. package/apps/pwa/out/_next/static/chunks/506.16b83d56a437a9ee.js +0 -1
  616. package/apps/pwa/out/_next/static/chunks/5260-20593b0a25d0afbc.js +0 -1
  617. package/apps/pwa/out/_next/static/chunks/5404.cc4e9fec26fd5925.js +0 -1
  618. package/apps/pwa/out/_next/static/chunks/576.485e83e3f3dec19a.js +0 -1
  619. package/apps/pwa/out/_next/static/chunks/5832-a6be14e663f65726.js +0 -1
  620. package/apps/pwa/out/_next/static/chunks/5854-56648a9fe646b9a0.js +0 -1
  621. package/apps/pwa/out/_next/static/chunks/5925.3b1f8b135710249b.js +0 -93
  622. package/apps/pwa/out/_next/static/chunks/5952.294fc574a8625184.js +0 -1
  623. package/apps/pwa/out/_next/static/chunks/6168.17749aa3905fa073.js +0 -74
  624. package/apps/pwa/out/_next/static/chunks/6416.5d50b8ec9986ca98.js +0 -1
  625. package/apps/pwa/out/_next/static/chunks/6758.e7fbf532953c0783.js +0 -1
  626. package/apps/pwa/out/_next/static/chunks/6795.0cb8a59730d99703.js +0 -149
  627. package/apps/pwa/out/_next/static/chunks/6877-84d47b7e85d0c41e.js +0 -1
  628. package/apps/pwa/out/_next/static/chunks/6971-fb5f9dc0a2cb5d2d.js +0 -1
  629. package/apps/pwa/out/_next/static/chunks/717.522b08685979b814.js +0 -1
  630. package/apps/pwa/out/_next/static/chunks/7191.e41eb8f455c7f089.js +0 -41
  631. package/apps/pwa/out/_next/static/chunks/7233.8e744b7513119409.js +0 -1
  632. package/apps/pwa/out/_next/static/chunks/7238-97c2226f80504bd8.js +0 -1
  633. package/apps/pwa/out/_next/static/chunks/7285.307963afcf7e7660.js +0 -24
  634. package/apps/pwa/out/_next/static/chunks/7299-fbe4b8c47a87614a.js +0 -1
  635. package/apps/pwa/out/_next/static/chunks/7314.df0d95930b0c3f50.js +0 -1
  636. package/apps/pwa/out/_next/static/chunks/7406.c0589fdf2ee66629.js +0 -1
  637. package/apps/pwa/out/_next/static/chunks/752-90e3e7daad3d8e39.js +0 -1
  638. package/apps/pwa/out/_next/static/chunks/7524-bcd3d9928c6842d9.js +0 -1
  639. package/apps/pwa/out/_next/static/chunks/7549.9a4de3bf231cfc52.js +0 -1
  640. package/apps/pwa/out/_next/static/chunks/757-4fbaf50942b8c424.js +0 -1
  641. package/apps/pwa/out/_next/static/chunks/765.76ae970ceb597b5b.js +0 -1
  642. package/apps/pwa/out/_next/static/chunks/7893-e444a1b0809cd1e4.js +0 -1
  643. package/apps/pwa/out/_next/static/chunks/7956-33748cabb3a05562.js +0 -1
  644. package/apps/pwa/out/_next/static/chunks/7c5ed21f-f286708e57cf88f7.js +0 -1
  645. package/apps/pwa/out/_next/static/chunks/8030-aa66b478b53dbfca.js +0 -1
  646. package/apps/pwa/out/_next/static/chunks/8051.c88c07dacbbc5f21.js +0 -8
  647. package/apps/pwa/out/_next/static/chunks/8080-40d781e7704d3671.js +0 -1
  648. package/apps/pwa/out/_next/static/chunks/8149.80e402d4345e8dae.js +0 -77
  649. package/apps/pwa/out/_next/static/chunks/8205.50a5268f4e12ba0b.js +0 -1
  650. package/apps/pwa/out/_next/static/chunks/8231.a128feaa6f8b6079.js +0 -1
  651. package/apps/pwa/out/_next/static/chunks/85.4ddb1dda2ea3ffa2.js +0 -286
  652. package/apps/pwa/out/_next/static/chunks/8537.26517a09e8247beb.js +0 -1
  653. package/apps/pwa/out/_next/static/chunks/8595-e9b7081ab773a61f.js +0 -1
  654. package/apps/pwa/out/_next/static/chunks/8821-30cc65a61a60951f.js +0 -1
  655. package/apps/pwa/out/_next/static/chunks/8905.b3385aadb7b133e6.js +0 -79
  656. package/apps/pwa/out/_next/static/chunks/8983.246bb3a1ac38f50f.js +0 -1
  657. package/apps/pwa/out/_next/static/chunks/9072.18342f5941a1f3ec.js +0 -1
  658. package/apps/pwa/out/_next/static/chunks/9123.9df04866b2d6e00f.js +0 -1
  659. package/apps/pwa/out/_next/static/chunks/9175-7d383df15a82e2be.js +0 -1
  660. package/apps/pwa/out/_next/static/chunks/9181-bf89c07ce5b8f599.js +0 -1
  661. package/apps/pwa/out/_next/static/chunks/920-429bbc4504b45f71.js +0 -1
  662. package/apps/pwa/out/_next/static/chunks/9207.625edf45d656151c.js +0 -1
  663. package/apps/pwa/out/_next/static/chunks/9378.d2575c6ffd6dd756.js +0 -94
  664. package/apps/pwa/out/_next/static/chunks/957.db5b5c4d32a35240.js +0 -1
  665. package/apps/pwa/out/_next/static/chunks/958.53a8dcf4729e6997.js +0 -64
  666. package/apps/pwa/out/_next/static/chunks/9647.8f0cd0898c592a29.js +0 -1
  667. package/apps/pwa/out/_next/static/chunks/9665-26abb02665704c2c.js +0 -1
  668. package/apps/pwa/out/_next/static/chunks/9703.e7d81b6faef1050a.js +0 -1
  669. package/apps/pwa/out/_next/static/chunks/9882-7a9164b3c2dacbe5.js +0 -1
  670. package/apps/pwa/out/_next/static/chunks/9998.b74e033efadecd10.js +0 -1
  671. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c604fb980a634d88.js +0 -1
  672. package/apps/pwa/out/_next/static/chunks/app/approvals/page-ec2d8a4979ca9075.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cb6add08ad3c7660.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/app/board/page-e2eaa321ec223fe6.js +0 -1
  675. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5b8a09c475b5f844.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/app/bots/page-91a77daedfbac191.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/app/ceo/page-f37fc5e974bda39b.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/app/chat/page-91429a0b78415a94.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/app/consult/page-b95798b58263b910.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/app/control/page-763d53e175e17aa1.js +0 -1
  681. package/apps/pwa/out/_next/static/chunks/app/decisions/page-d55023204055b12f.js +0 -1
  682. package/apps/pwa/out/_next/static/chunks/app/design-check/page-89d2140d78abc810.js +0 -1
  683. package/apps/pwa/out/_next/static/chunks/app/editor/page-82dc069abfe27f55.js +0 -1
  684. package/apps/pwa/out/_next/static/chunks/app/exec/page-25d9eee7beb552c0.js +0 -1
  685. package/apps/pwa/out/_next/static/chunks/app/field/page-770b4594c713ee97.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c604fb980a634d88.js +0 -1
  687. package/apps/pwa/out/_next/static/chunks/app/inside/page-ba8e0dcae4f83985.js +0 -1
  688. package/apps/pwa/out/_next/static/chunks/app/intake/page-5c112c6d7c33656e.js +0 -1
  689. package/apps/pwa/out/_next/static/chunks/app/layout-09788e1aa9a87db6.js +0 -1
  690. package/apps/pwa/out/_next/static/chunks/app/live-run/page-89acd5d68c0bc560.js +0 -1
  691. package/apps/pwa/out/_next/static/chunks/app/loops/page-5c13e2c76371d797.js +0 -1
  692. package/apps/pwa/out/_next/static/chunks/app/market/page-653a9f18642825c6.js +0 -1
  693. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-fe986fe79b107546.js +0 -1
  694. package/apps/pwa/out/_next/static/chunks/app/not-found-91ea9f5869a40943.js +0 -1
  695. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7824e21b999962e2.js +0 -1
  696. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-53d00a60cc75a922.js +0 -1
  697. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-88a1edbff62eaf1d.js +0 -1
  698. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4e931248b3f9574b.js +0 -1
  699. package/apps/pwa/out/_next/static/chunks/app/outputs/page-443364c9e4a23b31.js +0 -1
  700. package/apps/pwa/out/_next/static/chunks/app/page-b9af2ac1c4737ce8.js +0 -1
  701. package/apps/pwa/out/_next/static/chunks/app/reflection/page-25ab9c614c14129b.js +0 -1
  702. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-dbcdcfac0a395a8a.js +0 -1
  703. package/apps/pwa/out/_next/static/chunks/app/sessions/page-bf1de6645089ec58.js +0 -1
  704. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c4201761a5e839c9.js +0 -1
  705. package/apps/pwa/out/_next/static/chunks/app/settings/page-c304f469ea5d70e4.js +0 -1
  706. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8e5a238cf5e50c9c.js +0 -1
  707. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c604fb980a634d88.js +0 -1
  708. package/apps/pwa/out/_next/static/chunks/app/setup/page-32b526069ee50b3c.js +0 -1
  709. package/apps/pwa/out/_next/static/chunks/app/share/page-fb9e639c107f5f72.js +0 -1
  710. package/apps/pwa/out/_next/static/chunks/app/showroom/page-84220c776608cedc.js +0 -1
  711. package/apps/pwa/out/_next/static/chunks/app/tasks/page-e475618cbfd5ca2b.js +0 -1
  712. package/apps/pwa/out/_next/static/chunks/app/today/page-fccd3020af2f8806.js +0 -1
  713. package/apps/pwa/out/_next/static/chunks/app/trace/page-9c0d7198ff7b5a91.js +0 -1
  714. package/apps/pwa/out/_next/static/chunks/app/vault/page-d54b1ec9b9c6430d.js +0 -1
  715. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8c4bef1791d732ca.js +0 -1
  716. package/apps/pwa/out/_next/static/chunks/app/workflows/page-46fcc0b0bbc6fbfe.js +0 -1
  717. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-7dfda00afd172f5b.js +0 -1
  718. package/apps/pwa/out/_next/static/chunks/f21fd540.63ac75328926ed30.js +0 -1
  719. package/apps/pwa/out/_next/static/chunks/framework-77fdcc873519a6cd.js +0 -1
  720. package/apps/pwa/out/_next/static/chunks/main-app-d0d620d2ce235713.js +0 -1
  721. package/apps/pwa/out/_next/static/chunks/main-e0ef79c79337ba90.js +0 -1
  722. package/apps/pwa/out/_next/static/chunks/pages/_app-84b1bc71e3f9ce6a.js +0 -1
  723. package/apps/pwa/out/_next/static/chunks/pages/_error-2acb61f1363128cb.js +0 -1
  724. package/apps/pwa/out/_next/static/chunks/webpack-462ff5727ceb7824.js +0 -1
  725. package/scripts/migrate-monad-to-elanous.ts +0 -195
  726. package/scripts/rebrand-elanous.ts +0 -242
  727. /package/apps/pwa/out/_next/static/{8kITumG9GAZ1LwT9POrAR → HjeK7zONHfR7op1hKXp4k}/_ssgManifest.js +0 -0
package/src/llm.ts CHANGED
@@ -62,7 +62,7 @@ import { BUILTIN_CATALOG, reasoningEffortCeiling as reasoningEffortCeilingOf } f
62
62
  import { inferProviderFromModel as registryInferProviderFromModel } from './registry/normalize.js';
63
63
  import { closestMatches } from './tool-name-suggest.js';
64
64
  import { createProviderToolNameMap } from './llm-tool-name.js';
65
- import { isOpenRouterGrokModel, sanitizeToolSchemaForGrok } from './llm-tool-schema.js';
65
+ import { assertNoTopLevelToolSchemaCombinators, isOpenRouterGrokModel, sanitizeToolSchemaForGrok } from './llm-tool-schema.js';
66
66
  import { undoTurnRuntime } from './tool-runtime/undo-turn-runtime.js';
67
67
  import { listSnapshots } from './undo-turn/index.js';
68
68
  // Zero-dep spec module (no tool-runtime registry) — safe for this hot path.
@@ -269,6 +269,10 @@ export interface LLMMessage {
269
269
  role: 'system' | 'user' | 'assistant';
270
270
  /** Plain string for text-only messages; ContentBlock[] when images are mixed in. */
271
271
  content: string | ContentBlock[];
272
+ /** Codex Responses message phase, preserved when replaying a stateless turn. */
273
+ phase?: 'commentary' | 'partial_answer' | 'final_answer';
274
+ /** Opaque Codex stateless reasoning items, before this assistant message. */
275
+ codexReasoningItems?: Array<{ type: 'reasoning'; encrypted_content: string; summary: unknown[]; id?: string }>;
272
276
  }
273
277
 
274
278
  /** JSONSchema-shaped tool definition sent to the provider (no handler). */
@@ -284,6 +288,21 @@ function toolUseNames(messages: readonly LLMMessage[]): string[] {
284
288
  : []);
285
289
  }
286
290
 
291
+ /** Return paths for legacy tuple-form `items` schemas without logging schema
292
+ * values. Provider 400s often omit the rejected tool name, so these paths let
293
+ * the next TUI recurrence identify the source contract without logging args. */
294
+ function collectTupleItemsSchemaPaths(schema: unknown, path = 'parameters'): string[] {
295
+ if (Array.isArray(schema)) {
296
+ return schema.flatMap((value, index) => collectTupleItemsSchemaPaths(value, `${path}[${index}]`));
297
+ }
298
+ if (!schema || typeof schema !== 'object') return [];
299
+ return Object.entries(schema as Record<string, unknown>).flatMap(([key, value]) => {
300
+ const childPath = `${path}.${key}`;
301
+ const tuplePath = key === 'items' && Array.isArray(value) ? [childPath] : [];
302
+ return [...tuplePath, ...collectTupleItemsSchemaPaths(value, childPath)];
303
+ });
304
+ }
305
+
287
306
  function mapProviderToolNamesInMessages(
288
307
  messages: readonly LLMMessage[],
289
308
  mapName: (name: string) => string,
@@ -340,7 +359,10 @@ function restoreProviderToolCallNames(
340
359
  * (currently Anthropic only) — consumers that don't care should
341
360
  * ignore it; `textOnly()` filters it out. */
342
361
  export type LLMStreamEvent =
343
- | { type: 'text'; delta: string }
362
+ | { type: 'text'; delta: string; phase?: 'commentary' | 'final' }
363
+ | { type: 'text_phase'; phase: 'commentary' | 'final' }
364
+ | { type: 'turn_end'; endTurn: boolean }
365
+ | { type: 'codex_reasoning_item'; item: { type: 'reasoning'; encrypted_content: string; summary: unknown[]; id?: string } }
344
366
  | {
345
367
  type: 'tool_call';
346
368
  id: string;
@@ -403,6 +425,9 @@ export type ToolChoice = 'auto' | 'required' | 'none' | { name: string };
403
425
 
404
426
  export interface LLMOpts {
405
427
  model?: string;
428
+ /** Explicit research surface verdict from the turn router. Relaxes early
429
+ * exploration guards while retaining a finite tool-loop hard stop. */
430
+ researchTurn?: boolean;
406
431
  /** Reports the provider and model actually attempted, including fallbacks. */
407
432
  onResolvedProvider?(provider: string, model: string): void;
408
433
  temperature?: number;
@@ -412,6 +437,24 @@ export interface LLMOpts {
412
437
  streamIdleTimeoutMs?: number;
413
438
  /** Optional per-request usage telemetry from the provider stream. */
414
439
  onUsage?(usage: LLMUsage): void;
440
+ /** Optional request-size policy. Providers that expose a serialized request
441
+ * can compact the normalized conversation before sending it. The callback
442
+ * is opt-in; providers without a serializer hook preserve their wire path. */
443
+ requestSizeGuard?: {
444
+ maxChars: number;
445
+ compactMessages: (
446
+ messages: LLMMessage[],
447
+ info: { provider: string; requestChars: number; maxChars: number },
448
+ ) => Promise<{ messages: LLMMessage[]; reason: string } | null>;
449
+ onMeasured?: (info: {
450
+ provider: string;
451
+ requestChars: number;
452
+ maxChars: number;
453
+ compacted: boolean;
454
+ reason: string;
455
+ previousChars?: number;
456
+ }) => void;
457
+ };
415
458
  /** Optional model role attached to this request's usage observation. */
416
459
  usageRole?: ModelRole;
417
460
  /** Optional per-call metering phase; absent calls keep the existing ledger shape. */
@@ -473,6 +516,9 @@ export interface LLMOpts {
473
516
  * 그러면 종결 신호 뒤 빈 턴은 이 스트림에 쓴 답이 없어도 재촉·`[NO FINAL SYNTHESIS]` 없이 빈 답으로 끝낸다.
474
517
  * 비우면(기본) 종전대로 — 답이 «어디에도» 없을 때의 경고는 그대로 보인다. */
475
518
  terminalPriorAnswerVisible?: boolean;
519
+ /** Goal-loop only: a validated completion ends the provider loop after tool results settle.
520
+ * Omitted for ordinary terminal tools, which may need a further answer turn. */
521
+ terminalCompletionAccepted?: (name: string, args: Record<string, unknown>) => boolean;
476
522
  /** Prompt-caching toggle. Honored by Anthropic (attaches
477
523
  * cache_control markers on system + tools + 2nd-to-last history
478
524
  * message) and OpenAI (passes stream_options.include_usage so
@@ -1625,7 +1671,8 @@ type ResponsesContentBlock =
1625
1671
  * codex answer began with "tool_result 가 없다" because, from the
1626
1672
  * model's perspective, no tool result ever arrived. */
1627
1673
  type ResponsesInputItem =
1628
- | { role: string; content: ResponsesContentBlock[] }
1674
+ | { role: string; content: ResponsesContentBlock[]; phase?: 'commentary' | 'partial_answer' | 'final_answer' }
1675
+ | { type: 'reasoning'; encrypted_content: string; summary: unknown[]; id?: string }
1629
1676
  | { type: 'function_call'; name: string; arguments: string; call_id: string }
1630
1677
  // Image-pipeline P3 (2026-05-05) — `output` accepts string OR a
1631
1678
  // ContentItem[] mirroring the user-message content block shape.
@@ -1652,6 +1699,8 @@ type ResponsesInputItem =
1652
1699
  * Codex variants — current gpt-5 family all true per allowlist). */
1653
1700
  export interface MessagesToResponsesInputOpts {
1654
1701
  acceptToolImages?: boolean;
1702
+ /** Server-stored continuation already contains prior reasoning items. */
1703
+ replayCodexReasoning?: boolean;
1655
1704
  acceptUserMessageImages?: boolean;
1656
1705
  onOrphanToolResult?: (callId: string) => void;
1657
1706
  }
@@ -1824,6 +1873,9 @@ export function messagesToResponsesInput(
1824
1873
  continue;
1825
1874
  }
1826
1875
 
1876
+ // Stateless Codex requires the opaque reasoning item in its original
1877
+ // position before the corresponding assistant message/tool calls.
1878
+ if (m.role === 'assistant' && opts.replayCodexReasoning !== false && m.codexReasoningItems) input.push(...m.codexReasoningItems);
1827
1879
  // Assemble the final content block list: text first (matches
1828
1880
  // how users usually write prompts — "describe this image" then
1829
1881
  // attachment), images second. Responses API is order-sensitive
@@ -1841,7 +1893,7 @@ export function messagesToResponsesInput(
1841
1893
  // representation — emitting an empty Message wrapper would just
1842
1894
  // add a stray empty-text block to the conversation history.
1843
1895
  if (content.length > 0) {
1844
- input.push({ role: m.role, content });
1896
+ input.push({ role: m.role, content, ...(m.role === 'assistant' && m.phase ? { phase: m.phase } : {}) });
1845
1897
  }
1846
1898
  input.push(...trailingItems);
1847
1899
  }
@@ -2172,6 +2224,41 @@ function mapReasoningLevelToGeminiThinking(
2172
2224
  /** Stream against /responses (Codex, OpenAI Responses API). Exported
2173
2225
  * so tests can exercise header/body shape without spinning up the
2174
2226
  * full makeCodexProvider path. */
2227
+ function buildCodexResponsesWireBody(
2228
+ body: {
2229
+ model: string;
2230
+ instructions: string;
2231
+ input: unknown[];
2232
+ tools?: unknown[];
2233
+ tool_choice?: unknown;
2234
+ reasoning?: { effort?: 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'; summary?: 'auto' | 'concise' | 'detailed' };
2235
+ store?: boolean;
2236
+ previousResponseId?: string;
2237
+ },
2238
+ promptCache: boolean,
2239
+ ): Record<string, unknown> {
2240
+ const cacheKey = promptCache ? computePromptCacheKey(body.model, body.instructions) : undefined;
2241
+ return {
2242
+ ...body,
2243
+ stream: true,
2244
+ store: body.store === true,
2245
+ ...(body.tool_choice ? { tool_choice: body.tool_choice } : {}),
2246
+ ...(body.previousResponseId ? { previous_response_id: body.previousResponseId } : {}),
2247
+ ...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
2248
+ ...(body.reasoning ? {
2249
+ reasoning: body.reasoning,
2250
+ include: ['reasoning.encrypted_content'],
2251
+ } : {}),
2252
+ };
2253
+ }
2254
+
2255
+ function serializeCodexResponsesWireBody(
2256
+ body: Parameters<typeof buildCodexResponsesWireBody>[0],
2257
+ promptCache: boolean,
2258
+ ): string {
2259
+ return JSON.stringify(buildCodexResponsesWireBody(body, promptCache));
2260
+ }
2261
+
2175
2262
  export async function* streamCodexResponsesEvents(
2176
2263
  url: string,
2177
2264
  bearer: string,
@@ -2218,6 +2305,9 @@ export async function* streamCodexResponsesEvents(
2218
2305
  /** Opt-out hook (tests). When false, skips the
2219
2306
  * `prompt_cache_key` body field; production always passes true. */
2220
2307
  promptCache?: boolean;
2308
+ /** Exact body string already serialized by the request-size preflight.
2309
+ * Passing it through ensures the measured bytes are the bytes fetched. */
2310
+ serializedWireBody?: string;
2221
2311
  /** Callback fired when the SSE stream emits `response.created`
2222
2312
  * carrying the new response's id. Used by makeCodexProvider to
2223
2313
  * capture the id and thread `previousResponseId` on the next
@@ -2271,32 +2361,16 @@ export async function* streamCodexResponsesEvents(
2271
2361
  // value codex_cli_rs ships) so the backend
2272
2362
  // applies the same tool/SSE conventions
2273
2363
  const promptCache = extra.promptCache !== false;
2274
- const cacheKey = promptCache ? computePromptCacheKey(body.model, body.instructions) : undefined;
2275
- // TUI-OBS-400-SHAPE(#69) — 전송 객체·문자열을 한 번 만들어 둔다: 4xx 거절 때 «실제로 보낸 것» 의 모양을 잰다.
2276
- const wireBody: Record<string, unknown> = {
2277
- ...body,
2278
- stream: true,
2279
- // store=true wave: when caller opts in, the backend persists the
2280
- // response and the next request can thread `previous_response_id`
2281
- // to send only the delta input items. Default still false so any
2282
- // caller that doesn't opt in (tests building bodies directly)
2283
- // sees the legacy wire shape.
2364
+ // TUI-REQUEST-SIZE-COMPACT — the exact serializer is shared with the
2365
+ // preflight guard, so the size decision and fetch body cannot drift.
2366
+ const wireBody = buildCodexResponsesWireBody(body, promptCache);
2367
+ const wireJson = extra.serializedWireBody ?? JSON.stringify(wireBody);
2368
+ debug.log('llm.request', 'request-size', {
2369
+ provider: 'openai-codex',
2370
+ requestChars: wireJson.length,
2284
2371
  store: body.store === true,
2285
- ...(body.tool_choice ? { tool_choice: body.tool_choice } : {}),
2286
- ...(body.previousResponseId ? { previous_response_id: body.previousResponseId } : {}),
2287
- ...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
2288
- // Reasoning opt-in (gpt-5 family). When the caller sets
2289
- // body.reasoning we mirror it onto the wire AND append the
2290
- // include directive that lets multi-turn requests thread the
2291
- // model's encrypted reasoning context. Omit both when absent
2292
- // so non-reasoning models / opt-out users see the legacy
2293
- // wire shape unchanged.
2294
- ...(body.reasoning ? {
2295
- reasoning: body.reasoning,
2296
- include: ['reasoning.encrypted_content'],
2297
- } : {}),
2298
- };
2299
- const wireJson = JSON.stringify(wireBody);
2372
+ previousResponseId: body.previousResponseId ? `${body.previousResponseId.slice(0, 12)}…` : null,
2373
+ });
2300
2374
  const response = await fetchApiWithRetry(
2301
2375
  url,
2302
2376
  {
@@ -2325,6 +2399,10 @@ export async function* streamCodexResponsesEvents(
2325
2399
  debug.log('llm.response.status', `codex ${response.status}`, {
2326
2400
  url, status: response.status, elapsedMs: Date.now() - startedAt,
2327
2401
  });
2402
+ let messagePhase: 'commentary' | 'final' | undefined;
2403
+ const codexTextPhase = (value: unknown): 'commentary' | 'final' | undefined =>
2404
+ value === 'commentary' || value === 'partial_answer' ? 'commentary'
2405
+ : value === 'final_answer' ? 'final' : undefined;
2328
2406
  try {
2329
2407
  for await (const line of sseLineStream(response.body!)) {
2330
2408
  if (!line.startsWith('data: ')) continue;
@@ -2344,15 +2422,32 @@ export async function* streamCodexResponsesEvents(
2344
2422
  extra.onResponseCreated(id);
2345
2423
  }
2346
2424
  }
2425
+ if (type === 'response.output_item.added' || type === 'response.output_item.done') {
2426
+ if (parsed.item?.type === 'message') {
2427
+ const phase = codexTextPhase(parsed.item.phase);
2428
+ if (type === 'response.output_item.added') messagePhase = phase;
2429
+ if (phase) {
2430
+ messagePhase = phase;
2431
+ yield { type: 'text_phase', phase };
2432
+ }
2433
+ }
2434
+ }
2347
2435
  // Text tokens — both `response.output_text.delta` (new naming) and
2348
2436
  // raw `output_text.delta` (older streams).
2349
2437
  if (type.endsWith('output_text.delta')) {
2350
2438
  const delta = parsed.delta || '';
2351
- if (delta) yield { type: 'text', delta };
2439
+ if (delta) yield { type: 'text', delta, ...(messagePhase ? { phase: messagePhase } : {}) };
2352
2440
  }
2353
2441
  // Tool calls come in as `response.output_item.done` with item.type = 'function_call'.
2354
2442
  if (type === 'response.output_item.done') {
2355
2443
  const item = parsed.item;
2444
+ if (item?.type === 'reasoning' && typeof item.encrypted_content === 'string' && item.encrypted_content) {
2445
+ yield { type: 'codex_reasoning_item', item: {
2446
+ type: 'reasoning', encrypted_content: item.encrypted_content,
2447
+ summary: Array.isArray(item.summary) ? item.summary : [],
2448
+ ...(typeof item.id === 'string' ? { id: item.id } : {}),
2449
+ } };
2450
+ }
2356
2451
  if (item && item.type === 'function_call') {
2357
2452
  let args: Record<string, unknown> = {};
2358
2453
  try { args = item.arguments ? JSON.parse(item.arguments) : {}; } catch { /* empty */ }
@@ -2407,6 +2502,9 @@ export async function* streamCodexResponsesEvents(
2407
2502
  // hit rate for codex regardless. See store=true wave (next+1)
2408
2503
  // for the volume-side counterpart.
2409
2504
  if (type === 'response.completed') {
2505
+ if (typeof parsed.response?.end_turn === 'boolean') {
2506
+ yield { type: 'turn_end', endTurn: parsed.response.end_turn };
2507
+ }
2410
2508
  const u = parsed.response?.usage;
2411
2509
  if (u && typeof u === 'object') {
2412
2510
  const cached = u.input_tokens_details?.cached_tokens
@@ -4471,6 +4569,14 @@ function explainAndLogFallbackCandidates(
4471
4569
 
4472
4570
  /** 스트림 중 끊김 재시도 상한 — codex `DEFAULT_STREAM_MAX_RETRIES`(5) 와 같은 값. */
4473
4571
  const STREAM_DISCONNECT_MAX_RETRIES = 5;
4572
+ class StreamIdleTimeoutError extends Error {
4573
+ constructor(readonly idleMs: number) {
4574
+ super(`stream idle timeout after ${idleMs}ms`);
4575
+ this.name = 'StreamIdleTimeoutError';
4576
+ Object.assign(this, { code: 'ETIMEDOUT' });
4577
+ }
4578
+ }
4579
+ const STREAM_IDLE_TIMEOUT_MAX_RETRIES = 1;
4474
4580
  let streamDisconnectRetryDelayMs: (attempt: number) => number = backoffMs;
4475
4581
 
4476
4582
  /** @internal 시험 seam — 재시도 대기(기본 `backoffMs`)를 바꾼다. `undefined` 면 원복. */
@@ -4795,6 +4901,20 @@ type ProviderTextResult =
4795
4901
  | { ok: true; full: string; usage: ObservedUsage | 'unmeasured' }
4796
4902
  | { ok: false; full: string; error: unknown };
4797
4903
 
4904
+ function resolveStreamIdleTimeout(opts: LLMOpts, provider: LLMProvider, model: string): { ms: number; source: 'call' | 'config' | 'default' } {
4905
+ if (opts.streamIdleTimeoutMs !== undefined
4906
+ && Number.isFinite(opts.streamIdleTimeoutMs) && opts.streamIdleTimeoutMs > 0) {
4907
+ return { ms: opts.streamIdleTimeoutMs, source: 'call' };
4908
+ }
4909
+ let configured: number | undefined;
4910
+ try {
4911
+ configured = (require('./user-config.js') as typeof import('./user-config.js')).getUserConfig().llm.streamIdleTimeoutMs;
4912
+ } catch { /* config lookup is fail-soft; preserve provider defaults */ }
4913
+ if (configured !== undefined) return { ms: configured, source: 'config' };
4914
+ const longIdle = usesLongReasoningIdle(getModelFamily(model), provider.name);
4915
+ return { ms: longIdle ? STREAM_IDLE_TIMEOUT_MS_REASONING : STREAM_IDLE_TIMEOUT_MS, source: 'default' };
4916
+ }
4917
+
4798
4918
  function accumulateUsage(usage: ObservedUsage | undefined, received: LLMUsage): ObservedUsage {
4799
4919
  const total = { ...usage };
4800
4920
  for (const field of ['inputTokens', 'outputTokens', 'cacheReadInputTokens', 'cacheCreationInputTokens', 'reportedCostUsd'] as const) {
@@ -4832,10 +4952,8 @@ async function consumeProviderText(
4832
4952
  // Preserve the provider or callback error that caused this close.
4833
4953
  }
4834
4954
  };
4835
- const idleMs = opts.streamIdleTimeoutMs ?? (
4836
- usesLongReasoningIdle(getModelFamily(opts.model ?? provider.defaultModel), provider.name)
4837
- ? STREAM_IDLE_TIMEOUT_MS_REASONING : STREAM_IDLE_TIMEOUT_MS
4838
- );
4955
+ const idleTimeout = resolveStreamIdleTimeout(opts, provider, opts.model ?? provider.defaultModel);
4956
+ const idleMs = idleTimeout.ms;
4839
4957
 
4840
4958
  while (true) {
4841
4959
  let idleTimer: ReturnType<typeof setTimeout> | undefined;
@@ -4856,10 +4974,9 @@ async function consumeProviderText(
4856
4974
  if (step.idle) {
4857
4975
  debug.log('llm', 'stream-idle-timeout', {
4858
4976
  provider: provider.name, model: opts.model ?? provider.defaultModel,
4859
- idleMs, receivedChars: full.length, site: 'stream-llm',
4977
+ idleMs, idleMsSource: idleTimeout.source, receivedChars: full.length, site: 'stream-llm',
4860
4978
  });
4861
- const error = new Error(`stream idle timeout after ${idleMs}ms`);
4862
- error.name = 'StreamIdleTimeoutError';
4979
+ const error = new StreamIdleTimeoutError(idleMs);
4863
4980
  // An async generator can leave return() pending behind a stalled next().
4864
4981
  void closeIterator();
4865
4982
  return { ok: false, full, error };
@@ -4904,6 +5021,7 @@ export async function streamLLM(
4904
5021
  );
4905
5022
  activeProvider = finalized.provider;
4906
5023
  const activeModel = finalized.model;
5024
+ if (getModelFamily(activeModel) === 'claude') assertNoTopLevelToolSchemaCombinators(opts.tools);
4907
5025
  opts.onResolvedProvider?.(activeProvider.name, activeModel);
4908
5026
  debug.log('llm.router', 'streamLLM', {
4909
5027
  provider: activeProvider.name,
@@ -4940,6 +5058,7 @@ export async function streamLLM(
4940
5058
  textChars: full.length,
4941
5059
  usage: consumed.usage,
4942
5060
  });
5061
+ (await import('./session-runtime/provider-circuit.js')).recordProviderSuccess(activeProvider.name, Date.now());
4943
5062
  return full;
4944
5063
  }
4945
5064
 
@@ -4957,6 +5076,7 @@ export async function streamLLM(
4957
5076
  status: err.status,
4958
5077
  kind: err.kind,
4959
5078
  }, { level: 'warn' });
5079
+ (await import('./session-runtime/provider-circuit.js')).recordProviderFailure(activeProvider.name, 'overload-failover', Date.now());
4960
5080
  activeProvider = switched;
4961
5081
  modelOverride = undefined;
4962
5082
  attemptedProviders.add(activeProvider.name);
@@ -4993,6 +5113,7 @@ export async function streamLLM(
4993
5113
  tools: opts.tools?.map((tool) => tool.name) ?? [],
4994
5114
  ...(opts.model ? { modelOverrideDropped: opts.model } : {}),
4995
5115
  }, { level: 'warn' });
5116
+ (await import('./session-runtime/provider-circuit.js')).recordProviderFailure(activeProvider.name, verdict.category, Date.now());
4996
5117
  activeProvider = fallback;
4997
5118
  modelOverride = undefined;
4998
5119
  attemptedProviders.add(activeProvider.name);
@@ -5118,6 +5239,8 @@ export function resolveDefaultProvider(model?: string): LLMProvider {
5118
5239
  export interface StreamWithToolsHandlers {
5119
5240
  /** Streaming text delta — invoked for every text chunk across all turns. */
5120
5241
  onText(delta: string, fullAcrossTurns: string): void;
5242
+ /** Transient user-visible status emitted by stream recovery (not model text). */
5243
+ onStatus?(line: string): void;
5121
5244
  /** Dispatch a tool call. Returning undefined/null throws in the loop so
5122
5245
  * callers should always return a JSON-serializable value (can be an
5123
5246
  * error object — the model will see it as a tool_result either way).
@@ -7382,6 +7505,16 @@ function resultText(result: unknown): string {
7382
7505
  return '';
7383
7506
  }
7384
7507
 
7508
+ /** skill_exec returns structured host metadata, but its model-facing tool result is the answer body. */
7509
+ function toolResultContentForCall(toolName: string, result: unknown): string {
7510
+ if (toolName === 'skill_exec' && result && typeof result === 'object'
7511
+ && typeof (result as { output?: unknown }).output === 'string') {
7512
+ if ((result as { ok?: unknown }).ok === false) return JSON.stringify(result);
7513
+ return (result as { output: string }).output;
7514
+ }
7515
+ return typeof result === 'string' ? result : JSON.stringify(result);
7516
+ }
7517
+
7385
7518
  function detectExecutionFailure(result: unknown): boolean {
7386
7519
  if (!result || typeof result !== 'object') return false;
7387
7520
  const typed = result as Record<string, unknown>;
@@ -8319,12 +8452,42 @@ export async function streamLLMWithTools(
8319
8452
  skipProviders?: readonly string[];
8320
8453
  } = {},
8321
8454
  ): Promise<string> {
8455
+ // Fixed providers keep the original synchronous launch path. Load the circuit before
8456
+ // selection only when it can change this turn's initial provider.
8457
+ const circuit = !opts.provider && !opts.initialProvider
8458
+ ? await import('./session-runtime/provider-circuit.js')
8459
+ : undefined;
8322
8460
  const provider = opts.provider ?? opts.initialProvider ?? resolveDefaultProvider(opts.model);
8323
8461
  const initial = finalizeStreamingProviderModel(provider, opts.model ?? provider.defaultModel);
8324
8462
  let activeProvider = initial.provider;
8325
8463
  let activeModel = initial.model;
8464
+ let circuitBlockedInitialProvider: string | undefined;
8465
+ // SURVIVAL-PROVIDER-CIRCUIT (S2): 명시·고정 공급자(`provider`·`initialProvider`)가 없고
8466
+ // 회로가 열려(open) 있으면, 체인 해석(`remainingFallbackProviderNames` 순서)의 첫
8467
+ // closed(없으면 half-open) 공급자로 이번 턴을 연다. 모두 open 이면 원래 1순위를 그대로
8468
+ // 쓴다(«갈 곳 없음 = 멈춤»이 아니다) — `circuit-all-open` 관측만 남긴다.
8469
+ if (circuit && circuit.circuitState(activeProvider.name, Date.now()) === 'open') {
8470
+ const chainNames = [activeProvider.name, ...remainingFallbackProviderNames(new Set(opts.skipProviders ?? []), opts)]
8471
+ .filter((name) => providerByName(name)?.available());
8472
+ const pickedName = circuit.pickInitialProvider(chainNames, Date.now());
8473
+ const picked = pickedName && pickedName !== activeProvider.name ? providerByName(pickedName) : undefined;
8474
+ if (picked && picked.available()) {
8475
+ circuitBlockedInitialProvider = activeProvider.name;
8476
+ debug.log('tui.survival', 'turn-opened-on-fallback', { blocked: activeProvider.name, opened: picked.name });
8477
+ handlers.onStatus?.(`⚡ ${activeProvider.name} 회로 열림 — 이번 턴은 ${picked.name}`);
8478
+ const pickedInitial = finalizeStreamingProviderModel(picked, picked.defaultModel);
8479
+ activeProvider = pickedInitial.provider;
8480
+ activeModel = pickedInitial.model;
8481
+ } else {
8482
+ debug.log('tui.survival', 'circuit-all-open', { blocked: activeProvider.name });
8483
+ }
8484
+ }
8326
8485
  opts.onResolvedProvider?.(activeProvider.name, activeModel);
8327
- const attemptedProviders = new Set<string>([activeProvider.name, ...(opts.skipProviders ?? [])]);
8486
+ const attemptedProviders = new Set<string>([
8487
+ activeProvider.name,
8488
+ ...(circuitBlockedInitialProvider ? [circuitBlockedInitialProvider] : []),
8489
+ ...(opts.skipProviders ?? []),
8490
+ ]);
8328
8491
  let blockedProviders: ProviderFallbackAttempt[] = [];
8329
8492
  const effectiveModel = activeModel;
8330
8493
  const modelFamily = getModelFamily(effectiveModel);
@@ -8344,7 +8507,7 @@ export async function streamLLMWithTools(
8344
8507
  tools: opts.tools?.map(t => t.name),
8345
8508
  toolsAvailable: !!provider.streamChat && (opts.tools?.length ?? 0) > 0,
8346
8509
  }, { level: 'info' });
8347
- if (!provider.streamChat || !opts.tools || opts.tools.length === 0) {
8510
+ if (!provider.streamChat || ((opts.tools?.length ?? 0) === 0 && provider.name !== 'openai-codex')) {
8348
8511
  // No tool support or no tools requested — delegate to plain streamLLM.
8349
8512
  debug.log('llm.router', 'streamLLMWithTools.fallback', {
8350
8513
  reason: !provider.streamChat ? 'provider-no-streamChat' : 'no-tools',
@@ -8409,7 +8572,9 @@ export async function streamLLMWithTools(
8409
8572
  const familyDefaultMaxTurns = resolved === 0 ? Number.POSITIVE_INFINITY : (resolved || baselineForFamily);
8410
8573
  // `let` (not const) — the conditional budget-grant hook (opts.budgetGrant)
8411
8574
  // may extend this mid-loop when a designated multi-round tool is called.
8412
- let maxTurns = opts.maxTurns ?? familyDefaultMaxTurns;
8575
+ let maxTurns = opts.maxTurns ?? (opts.researchTurn
8576
+ ? Math.min(32, Math.max(16, familyDefaultMaxTurns))
8577
+ : familyDefaultMaxTurns);
8413
8578
  const budgetGrant = opts.budgetGrant;
8414
8579
  // ⭐ Hydration-capable tool list (F2 gap④ · 2026-07-26). The provider tool
8415
8580
  // array used to be `opts.tools`, FIXED for the whole loop. That made
@@ -8428,7 +8593,9 @@ export async function streamLLMWithTools(
8428
8593
  } catch { /* config unreadable — default 비-면제·hot-path 는 config 손상에 안 막힌다 */ }
8429
8594
  const inspectBudgetThreshold = getInspectBudgetThreshold(history, modelFamily, codexInspectExemptFlag);
8430
8595
  const inspectFollowupMode = getInspectFollowupMode(history);
8431
- const explorationThresholdForTurn = getExplorationThreshold(history, maxTurns, modelFamily);
8596
+ const explorationThresholdForTurn = opts.researchTurn
8597
+ ? Number.POSITIVE_INFINITY
8598
+ : getExplorationThreshold(history, maxTurns, modelFamily);
8432
8599
  const maxTurnsUnbounded = maxTurns === Infinity;
8433
8600
  const familyDefaultMaxTurnsUnbounded = familyDefaultMaxTurns === Infinity;
8434
8601
  const explorationUnbounded = !Number.isFinite(explorationThresholdForTurn);
@@ -8443,6 +8610,7 @@ export async function streamLLMWithTools(
8443
8610
  inspectBudgetThreshold,
8444
8611
  inspectFollowupMode,
8445
8612
  explorationThresholdForTurn,
8613
+ researchTurn: opts.researchTurn === true,
8446
8614
  // ★ Infinity 는 JSON 직렬화 시 null 이 되므로 boolean 으로도 남긴다(관측·제1원칙).
8447
8615
  // true = 프론티어 면제로 read/search 스트릭 컷 없음(대표 방침 2026-07-17).
8448
8616
  explorationUnbounded,
@@ -8500,6 +8668,8 @@ export async function streamLLMWithTools(
8500
8668
  }
8501
8669
  // OR-DEEPSEEK-TUNE — 마지막 턴의 «도구 없는» 추론(deepseek/* 만 채워진다). 매 턴 시작에 비운다.
8502
8670
  let turnReasoningDetails: Array<Record<string, unknown>> | undefined;
8671
+ let turnCodexReasoningItems: LLMMessage['codexReasoningItems'];
8672
+ let turnCodexTextPhase: LLMMessage['phase'];
8503
8673
  const emitTurnComplete = (finalAssistantText?: string): void => {
8504
8674
  if (finalAssistantText && finalAssistantText.length > 0) {
8505
8675
  history.push({
@@ -8508,18 +8678,23 @@ export async function streamLLMWithTools(
8508
8678
  type: 'text', text: finalAssistantText,
8509
8679
  ...(turnReasoningDetails ? { providerMeta: { reasoningDetails: turnReasoningDetails } } : {}),
8510
8680
  }],
8681
+ ...(turnCodexReasoningItems?.length ? { codexReasoningItems: turnCodexReasoningItems } : {}),
8682
+ ...(turnCodexTextPhase ? { phase: turnCodexTextPhase } : {}),
8511
8683
  });
8512
8684
  }
8513
8685
  handlers.onTurnComplete?.(history.slice(initialHistoryLen));
8514
8686
  };
8515
8687
  let fullText = '';
8516
8688
  let sawToolRound = false;
8689
+ let codexContinuationNeeded = false;
8517
8690
  // TUI-GOAL-LOOP-FRICTION — 종결 신호 도구(LLMOpts.terminalToolNames). 비면 아래 분기는 전부 꺼진다.
8518
8691
  const terminalToolNames: ReadonlySet<string> = new Set(opts.terminalToolNames ?? []);
8519
8692
  /** 이 스트림에서 종결 신호를 한 번이라도 디스패치했나. */
8520
8693
  let terminalSignalDispatched = false;
8694
+ let completionNeedsFinalTurn = false;
8521
8695
  /** 도구 라운드에 실린 마지막 «비지 않은» 답 텍스트(종결 신호 턴 마감·빈 턴 포기에서 쓴다). */
8522
8696
  let lastToolRoundText = '';
8697
+ let lastToolRoundTextPhase: 'commentary' | 'final' | undefined;
8523
8698
  const agentBatchTickIntervalMs = opts.agentBatchTickIntervalMs ?? BATCH_TICK_INTERVAL_MS;
8524
8699
 
8525
8700
  // Empty-turn guard: smaller models (gpt-5.4-mini, haiku) sometimes
@@ -8693,7 +8868,7 @@ export async function streamLLMWithTools(
8693
8868
  let loopTermination: 'budget-exhausted' | 'aborted' = 'budget-exhausted';
8694
8869
  let terminalTurn = maxTurns;
8695
8870
 
8696
- for (let turn = 0; turn < maxTurns; turn++) {
8871
+ for (let turn = 0; turn < maxTurns || ((completionNeedsFinalTurn || codexContinuationNeeded) && turn === maxTurns); turn++) {
8697
8872
  // ★ /cancel 범용화(대표 2026-07-12) — abort 신호가 오면 tool 루프를 즉시 중단한다. 이게 없으면
8698
8873
  // fetch 는 abort 돼도 루프가 다음 turn 으로 계속 돌아 tool 반복(grep 폭주 등)이 멈추지 않는다.
8699
8874
  if (opts.signal?.aborted) {
@@ -8726,6 +8901,11 @@ export async function streamLLMWithTools(
8726
8901
  }
8727
8902
  }
8728
8903
  let turnText = '';
8904
+ let turnTextPhase: 'commentary' | 'final' | undefined;
8905
+ let turnEnd: boolean | undefined;
8906
+ codexContinuationNeeded = false;
8907
+ turnCodexReasoningItems = undefined;
8908
+ turnCodexTextPhase = undefined;
8729
8909
  // TUI-TURN-NARRATION — 이 라운드에 받은 추론 요약 글자 수(내용은 남기지 않는다).
8730
8910
  let turnReasoningChars = 0;
8731
8911
  turnReasoningDetails = undefined;
@@ -8825,13 +9005,14 @@ export async function streamLLMWithTools(
8825
9005
  // revert to auto (see above).
8826
9006
  const buildTurnOpts = () => {
8827
9007
  const remainingFallbacks = opts.provider ? 0 : remainingFallbackProviderNames(attemptedProviders, opts).length;
8828
- const base = { ...opts, model: activeModel, tools: activeTools, remainingFallbacks };
9008
+ const base = { ...opts, model: activeModel, tools: completionNeedsFinalTurn ? [] : activeTools, remainingFallbacks };
8829
9009
  return turn === 0 ? base : { ...base, toolChoice: undefined };
8830
9010
  };
8831
9011
  let turnOpts = buildTurnOpts();
8832
9012
  // reasoning-heavy family(codex/claude/gemini/grok/local)는 침묵 추론 구간이 길어 idle watchdog 를
8833
9013
  // family-aware 로 상향(2026-07-19 goal-exec → 아크4 전-family). fast/mini(gpt·other)만 45s 유지.
8834
- const streamIdleTimeoutMs = usesLongReasoningIdle(modelFamily, activeProvider.name) ? STREAM_IDLE_TIMEOUT_MS_REASONING : STREAM_IDLE_TIMEOUT_MS;
9014
+ const idleTimeout = resolveStreamIdleTimeout(opts, activeProvider, activeModel);
9015
+ const streamIdleTimeoutMs = idleTimeout.ms;
8835
9016
  // ⚠️ hang 진단(2026-07-17) — codex SSE 스트림이 완료 신호 없이 안 끝나는 케이스 확정용.
8836
9017
  // consume-start 후 consume-end 가 없으면 = for-await(streamChat SSE) hang. lastEvType 로 어느
8837
9018
  // 이벤트 뒤에서 멈추는지(usage/text/tool_call) 특정. 무장할 상한도 남겨 실험 런을 구별한다.
@@ -8840,6 +9021,7 @@ export async function streamLLMWithTools(
8840
9021
  provider: activeProvider.name,
8841
9022
  model: activeModel,
8842
9023
  idleMs: streamIdleTimeoutMs,
9024
+ idleMsSource: idleTimeout.source,
8843
9025
  ...(modelFamily !== 'other' ? { modelFamily } : {}),
8844
9026
  });
8845
9027
  let evCount = 0;
@@ -8847,6 +9029,8 @@ export async function streamLLMWithTools(
8847
9029
  // ⭐ TUI-STREAM-RETRY(2026-10-14 실측) — 200 OK 로 흐르던 스트림이 소켓 끊김으로 죽으면 같은 공급자로
8848
9030
  // 다시 건다(codex `stream_max_retries` 동형). 이 턴에서 받은 tool_call·image 가 있으면 재시도하지 않는다.
8849
9031
  let streamDisconnectRetries = 0;
9032
+ let streamIdleTimeoutRetries = 0;
9033
+ let streamIdleRetryPartial = '';
8850
9034
  let streamEmittedImage = false;
8851
9035
  // 끊긴 조각을 이력(turnText·fullText)에서 «조용히» 되감는다 — 화면엔 지우라고 알리지 않는다
8852
9036
  // (TUI 의 빈 델타는 «커밋»이다). 이미 보인 글과 겹치는 재시도 본문은 `retryDisplay` 가 막는다.
@@ -8864,6 +9048,7 @@ export async function streamLLMWithTools(
8864
9048
  if (turnText.length > 0) {
8865
9049
  fullText = fullText.slice(0, fullText.length - turnText.length);
8866
9050
  turnText = '';
9051
+ turnTextPhase = undefined;
8867
9052
  }
8868
9053
  turnReasoningChars = 0;
8869
9054
  retryDisplay.arm();
@@ -8875,9 +9060,25 @@ export async function streamLLMWithTools(
8875
9060
  // 이벤트 대기를 idle timeout 과 race — STREAM_IDLE_TIMEOUT_MS 무이벤트면 스트림 정리·탈출(받은
8876
9061
  // 텍스트로 진행). 35분 무한 hang → 자동 복구. 원인 규명은 consume-end 부재/idle-timeout 로그가.
8877
9062
  let streamIter: AsyncIterator<LLMStreamEvent> | undefined;
9063
+ let streamAttemptAbort: AbortController | undefined;
9064
+ let streamAttemptAbortCleanup: (() => void) | undefined;
9065
+ const releaseStreamAttempt = (abort = false, reason?: unknown): void => {
9066
+ if (abort) streamAttemptAbort?.abort(reason);
9067
+ streamAttemptAbortCleanup?.();
9068
+ streamAttemptAbortCleanup = undefined;
9069
+ streamAttemptAbort = undefined;
9070
+ };
9071
+ let providerToolSchemaDiagnostics: Record<string, {
9072
+ originalName: string;
9073
+ providerName: string;
9074
+ tupleItemsPaths: Record<string, string>;
9075
+ }> | undefined;
8878
9076
  while (true) {
8879
9077
  let idleTimer: ReturnType<typeof setTimeout> | undefined;
8880
9078
  let step: { idle: true } | { idle: false; r: IteratorResult<LLMStreamEvent, void> };
9079
+ // Validate before entering the provider stream's catch/fallback boundary.
9080
+ // An incompatible request is deterministic and must not be retried elsewhere.
9081
+ if (getModelFamily(activeModel) === 'claude') assertNoTopLevelToolSchemaCombinators(turnOpts.tools);
8881
9082
  try {
8882
9083
  if (!streamIter) {
8883
9084
  const finalized = finalizeStreamingProviderModel(activeProvider, activeModel);
@@ -8901,8 +9102,22 @@ export async function streamLLMWithTools(
8901
9102
  ? [turnOpts.toolChoice.name]
8902
9103
  : []),
8903
9104
  ]);
9105
+ const attemptAbort = new AbortController();
9106
+ const forwardExternalAbort = () => attemptAbort.abort(opts.signal?.reason);
9107
+ if (opts.signal?.aborted) forwardExternalAbort();
9108
+ else opts.signal?.addEventListener('abort', forwardExternalAbort, { once: true });
9109
+ streamAttemptAbort = attemptAbort;
9110
+ streamAttemptAbortCleanup = () => opts.signal?.removeEventListener('abort', forwardExternalAbort);
9111
+ providerToolSchemaDiagnostics = Object.fromEntries((turnOpts.tools ?? []).map((tool, index) => [String(index), {
9112
+ originalName: tool.name,
9113
+ providerName: nameMap.toProviderName(tool.name),
9114
+ tupleItemsPaths: Object.fromEntries(
9115
+ collectTupleItemsSchemaPaths(tool.parameters).map((path, pathIndex) => [String(pathIndex), path]),
9116
+ ),
9117
+ }]));
8904
9118
  const providerOpts: LLMOpts = {
8905
9119
  ...turnOpts,
9120
+ signal: attemptAbort.signal,
8906
9121
  tools: turnOpts.tools?.map((tool) => ({ ...tool, name: nameMap.toProviderName(tool.name) })),
8907
9122
  toolChoice: turnOpts.toolChoice && typeof turnOpts.toolChoice === 'object'
8908
9123
  ? { name: nameMap.toProviderName(turnOpts.toolChoice.name) }
@@ -8918,14 +9133,68 @@ export async function streamLLMWithTools(
8918
9133
  streamIter.next().then((r) => ({ idle: false as const, r })),
8919
9134
  new Promise<{ idle: true }>((res) => { idleTimer = setTimeout(() => res({ idle: true }), streamIdleTimeoutMs); }),
8920
9135
  ]);
9136
+ if (step.idle) throw new StreamIdleTimeoutError(streamIdleTimeoutMs);
8921
9137
  } catch (err) {
8922
9138
  if (idleTimer) clearTimeout(idleTimer);
9139
+ releaseStreamAttempt(true, err);
9140
+ if (err instanceof ApiHttpError && err.status === 400 && providerToolSchemaDiagnostics) {
9141
+ debug.log('llm.tool-schema', 'provider-request-rejected', {
9142
+ provider: activeProvider.name,
9143
+ model: activeModel,
9144
+ turn,
9145
+ status: err.status,
9146
+ tools: providerToolSchemaDiagnostics,
9147
+ }, { level: 'warn' });
9148
+ }
9149
+ if (err instanceof StreamIdleTimeoutError) {
9150
+ debug.log('llm.stream', 'idle-timeout', {
9151
+ turn, evCount, lastEvType, idleMs: err.idleMs, idleMsSource: idleTimeout.source,
9152
+ textChars: turnText.length, pendingCalls: pendingCalls.length,
9153
+ }, { level: 'warn' });
9154
+ // Provider iterators can queue return() behind a pending next(). Detach cleanup.
9155
+ const timedOutIterator = streamIter;
9156
+ if (timedOutIterator?.return) {
9157
+ try { void Promise.resolve(timedOutIterator.return(undefined)).catch(() => {}); } catch { /* fail-soft cleanup */ }
9158
+ }
9159
+ if (streamIdleTimeoutRetries < STREAM_IDLE_TIMEOUT_MAX_RETRIES
9160
+ && pendingCalls.length === 0 && !streamEmittedImage && !opts.signal?.aborted) {
9161
+ streamIdleTimeoutRetries++;
9162
+ debug.log('llm.retry', 'idle-timeout-retry', {
9163
+ provider: activeProvider.name, model: activeModel, turn,
9164
+ attempt: streamIdleTimeoutRetries, maxRetries: STREAM_IDLE_TIMEOUT_MAX_RETRIES,
9165
+ delayMs: 0, statusLine: '스트림 무응답 · 재시도',
9166
+ }, { level: 'warn' });
9167
+ try { handlers.onStatus?.(`스트림 무응답 · 재시도 ${streamIdleTimeoutRetries}/${STREAM_IDLE_TIMEOUT_MAX_RETRIES}`); } catch { /* UI notification must not block recovery */ }
9168
+ streamIdleRetryPartial = turnText;
9169
+ rewindDisconnectedStream();
9170
+ streamIter = undefined;
9171
+ continue;
9172
+ }
9173
+ // Do not replay or advance providers after a tool side effect or image escaped.
9174
+ if (pendingCalls.length > 0 || streamEmittedImage) break;
9175
+ if (opts.signal?.aborted) throw err;
9176
+ if (opts.provider && (turnText.length > 0 || streamIdleRetryPartial.length > 0)) {
9177
+ // A pinned provider cannot advance to fallback. Keep already shown partial text
9178
+ // and complete the turn instead of rewinding it and failing the whole response.
9179
+ if (turnText.length === 0 && streamIdleRetryPartial.length > 0) {
9180
+ turnText = streamIdleRetryPartial;
9181
+ fullText += streamIdleRetryPartial;
9182
+ }
9183
+ try { handlers.onStatus?.('스트림 무응답 · 재시도 소진 (부분 응답 유지)'); } catch { /* status is fail-soft */ }
9184
+ debug.log('llm.stream', 'idle-timeout-pinned-partial-preserved', { turn, evCount, lastEvType, textChars: turnText.length }, { level: 'warn' });
9185
+ break;
9186
+ }
9187
+ if (opts.provider) throw err;
9188
+ rewindDisconnectedStream();
9189
+ streamIter = undefined;
9190
+ }
8923
9191
  if (err instanceof OverloadFailoverError && !opts.provider) {
8924
9192
  const switched = activeProvider.name === 'openai-codex'
8925
9193
  ? firstAvailableFallbackProvider(attemptedProviders, opts)
8926
9194
  : providerByName('grok');
8927
9195
  if (switched && !attemptedProviders.has(switched.name)
8928
9196
  && (activeProvider.name === 'openai-codex' || switched.available()) && evCount === 0) {
9197
+ (circuit ?? await import('./session-runtime/provider-circuit.js')).recordProviderFailure(activeProvider.name, 'overload-failover', Date.now());
8929
9198
  debug.log('llm.router', 'overload-failover', {
8930
9199
  turn,
8931
9200
  blockedProvider: activeProvider.name,
@@ -8934,17 +9203,19 @@ export async function streamLLMWithTools(
8934
9203
  status: err.status,
8935
9204
  kind: err.kind,
8936
9205
  }, { level: 'warn' });
9206
+ handlers.onStatus?.(`⚠ 공급자 전환: ${activeProvider.name} → ${switched.name} (${err.kind ?? err.name ?? 'unknown'})`);
8937
9207
  const finalized = finalizeStreamingProviderModel(switched, switched.defaultModel);
8938
9208
  activeProvider = finalized.provider;
8939
9209
  activeModel = finalized.model;
8940
9210
  attemptedProviders.add(activeProvider.name);
8941
9211
  streamDisconnectRetries = 0;
9212
+ streamIdleTimeoutRetries = 0;
8942
9213
  turnOpts = buildTurnOpts();
8943
9214
  streamIter = undefined;
8944
9215
  continue;
8945
9216
  }
8946
9217
  }
8947
- if (evCount > 0 && isStreamDisconnectRetryable(err, pendingCalls.length, streamEmittedImage) && !opts.signal?.aborted) {
9218
+ if (!(err instanceof StreamIdleTimeoutError) && evCount > 0 && isStreamDisconnectRetryable(err, pendingCalls.length, streamEmittedImage) && !opts.signal?.aborted) {
8948
9219
  const retryData = {
8949
9220
  provider: activeProvider.name,
8950
9221
  model: activeModel,
@@ -8988,6 +9259,7 @@ export async function streamLLMWithTools(
8988
9259
  remainingProviders: remaining,
8989
9260
  attempted: blockedProviders,
8990
9261
  });
9262
+ (circuit ?? await import('./session-runtime/provider-circuit.js')).recordProviderFailure(activeProvider.name, verdict.category, Date.now());
8991
9263
  blockedProviders = [...verdict.attempts];
8992
9264
  const fallback = verdict.action === 'advance' ? providerByName(verdict.nextProvider) : undefined;
8993
9265
  if (!fallback) {
@@ -9013,30 +9285,43 @@ export async function streamLLMWithTools(
9013
9285
  tools: activeTools.map((tool) => tool.name),
9014
9286
  ...(opts.model ? { modelOverrideDropped: opts.model } : {}),
9015
9287
  }, { level: 'warn' });
9288
+ handlers.onStatus?.(`⚠ 공급자 전환: ${activeProvider.name} → ${fallback.name} (${verdict.category})`);
9016
9289
  const finalized = finalizeStreamingProviderModel(fallback, fallback.defaultModel);
9017
9290
  activeProvider = finalized.provider;
9018
9291
  activeModel = finalized.model;
9019
9292
  attemptedProviders.add(activeProvider.name);
9020
9293
  // 재시도 상한은 «공급자마다» — 넘어간 공급자도 스트림 도중 끊김이면 다시 5회까지 건다.
9021
9294
  streamDisconnectRetries = 0;
9295
+ streamIdleTimeoutRetries = 0;
9022
9296
  turnOpts = buildTurnOpts();
9023
9297
  streamIter = undefined;
9024
9298
  continue;
9025
9299
  }
9026
9300
  if (idleTimer) clearTimeout(idleTimer);
9027
- if (step.idle) {
9028
- debug.log('llm.stream', 'idle-timeout', { turn, evCount, lastEvType, idleMs: streamIdleTimeoutMs, textChars: turnText.length, pendingCalls: pendingCalls.length }, { level: 'warn' });
9029
- try { await streamIter.return?.(undefined); } catch { /* fail-soft — 스트림 정리 실패 무시 */ }
9301
+ if (step.r.done) {
9302
+ (circuit ?? await import('./session-runtime/provider-circuit.js')).recordProviderSuccess(activeProvider.name, Date.now());
9303
+ releaseStreamAttempt();
9030
9304
  break;
9031
9305
  }
9032
- if (step.r.done) break;
9033
9306
  const ev = step.r.value;
9034
9307
  evCount++;
9035
9308
  lastEvType = ev.type;
9309
+ // One same-provider idle retry is allowed per turn; progress clears the stale partial marker,
9310
+ // but does not reset that retry budget.
9311
+ if (streamIdleTimeoutRetries > 0) streamIdleRetryPartial = '';
9036
9312
  if (ev.type === 'text') {
9037
9313
  turnText += ev.delta;
9314
+ if (ev.phase) turnTextPhase = ev.phase;
9315
+ if (ev.phase) turnCodexTextPhase = ev.phase === 'final' ? 'final_answer' : 'commentary';
9038
9316
  fullText += ev.delta;
9039
9317
  emitRetryDisplayStep(retryDisplay.onText(ev.delta, turnText));
9318
+ } else if (ev.type === 'text_phase') {
9319
+ turnTextPhase = ev.phase;
9320
+ turnCodexTextPhase = ev.phase === 'final' ? 'final_answer' : 'commentary';
9321
+ } else if (ev.type === 'turn_end') {
9322
+ turnEnd = ev.endTurn;
9323
+ } else if (ev.type === 'codex_reasoning_item') {
9324
+ (turnCodexReasoningItems ??= []).push(ev.item);
9040
9325
  } else if (ev.type === 'reasoning_details') {
9041
9326
  turnReasoningDetails = ev.details;
9042
9327
  } else if (ev.type === 'tool_call') {
@@ -9163,6 +9448,26 @@ export async function streamLLMWithTools(
9163
9448
  }
9164
9449
  }
9165
9450
 
9451
+ // Codex can explicitly mark a response as unfinished even when it emitted
9452
+ // text and no tools. Keep that text in history with its phase, then ask
9453
+ // again. The extra turn is bounded to one past maxTurns.
9454
+ if (pendingCalls.length === 0 && turnEnd === false) {
9455
+ if (turnText || turnCodexReasoningItems?.length) history.push({
9456
+ role: 'assistant', content: turnText,
9457
+ ...(turnTextPhase ? { phase: turnTextPhase === 'final' ? 'final_answer' as const : 'commentary' as const } : {}),
9458
+ ...(turnCodexReasoningItems?.length ? { codexReasoningItems: turnCodexReasoningItems } : {}),
9459
+ });
9460
+ if (turnText) {
9461
+ clearVisibleAssistantTextForToolRound(handlers, sawToolRound);
9462
+ sawToolRound = true;
9463
+ }
9464
+ codexContinuationNeeded = true;
9465
+ debug.log('llm.router', 'codex.end-turn-false-continue', { turn, phase: turnTextPhase ?? 'unknown', textChars: turnText.length });
9466
+ // The one grace turn is also unfinished. Fall through to the existing
9467
+ // budget notice; never present a phase-marked progress item as final.
9468
+ if (turn >= maxTurns) break;
9469
+ continue;
9470
+ }
9166
9471
  // No tool calls + non-empty text = the model gave its final answer.
9167
9472
  if (pendingCalls.length === 0 && turnText.length > 0) {
9168
9473
  const visibleText = finalizeVisibleAssistantText(fullText, turnText, sawToolRound);
@@ -9200,7 +9505,7 @@ export async function streamLLMWithTools(
9200
9505
  // TUI-H1-GOALLOOP-EMPTY-TAIL — 답이 이 스트림 «앞»에 이미 보였어도 같다(opts.terminalPriorAnswerVisible —
9201
9506
  // 예: Bash → «끝» → 재주입 → update_goal 만 → 빈 턴). 이 스트림에 답이 없으면 빈 문자열로 끝내고 화면은
9202
9507
  // 건드리지 않는다(onText 생략 · emitTurnComplete 는 빈 답을 이력에 안 싣는다). 마감은 호출자(goal loop) 몫.
9203
- if (terminalSignalDispatched && (lastToolRoundText.trim().length > 0 || opts.terminalPriorAnswerVisible === true)) {
9508
+ if (!completionNeedsFinalTurn && terminalSignalDispatched && (lastToolRoundText.trim().length > 0 || opts.terminalPriorAnswerVisible === true)) {
9204
9509
  const answer = lastToolRoundText.trim().length > 0 ? lastToolRoundText : '';
9205
9510
  debug.log('goal.loop', 'empty-turn-after-terminal-signal', {
9206
9511
  turn, emptyRetries, answerChars: answer.length,
@@ -9244,7 +9549,10 @@ export async function streamLLMWithTools(
9244
9549
  clearVisibleAssistantTextForToolRound(handlers, sawToolRound);
9245
9550
  }
9246
9551
  sawToolRound = true;
9247
- if (turnText.trim().length > 0) lastToolRoundText = turnText;
9552
+ if (turnText.trim().length > 0) {
9553
+ lastToolRoundText = turnText;
9554
+ lastToolRoundTextPhase = turnTextPhase;
9555
+ }
9248
9556
  // TUI-GOAL-LOOP-FRICTION(#46) — 이 턴의 호출이 «종결 신호뿐»이면 예산·후속 단계 거부를 걸지 않는다.
9249
9557
  const terminalOnlyTurn = terminalToolNames.size > 0
9250
9558
  && pendingCalls.length > 0
@@ -9293,7 +9601,10 @@ export async function streamLLMWithTools(
9293
9601
  ...(call.providerMeta !== undefined ? { providerMeta: call.providerMeta } : {}),
9294
9602
  });
9295
9603
  }
9296
- history.push({ role: 'assistant', content: assistantBlocks });
9604
+ history.push({ role: 'assistant', content: assistantBlocks,
9605
+ ...(turnTextPhase ? { phase: turnTextPhase === 'final' ? 'final_answer' as const : 'commentary' as const } : {}),
9606
+ ...(turnCodexReasoningItems?.length ? { codexReasoningItems: turnCodexReasoningItems } : {}),
9607
+ });
9297
9608
 
9298
9609
  // Final-synthesis window: in the last FINAL_SYNTHESIS_TURNS of the
9299
9610
  // budget, refuse to execute tool calls. Each pending call gets a
@@ -10289,7 +10600,8 @@ export async function streamLLMWithTools(
10289
10600
  // rejection without colliding with the auto-narrow path)
10290
10601
  // - call is in SEARCH_ONLY_TOOLS
10291
10602
  if (
10292
- dispatchedReadCount === 0
10603
+ !opts.researchTurn
10604
+ && dispatchedReadCount === 0
10293
10605
  && autoNarrowedReadCount === 0
10294
10606
  && searchOnlyTurnStreak >= 3
10295
10607
  && SEARCH_ONLY_TOOLS.has(call.name)
@@ -11066,7 +11378,7 @@ export async function streamLLMWithTools(
11066
11378
  resultBlocks.push({
11067
11379
  type: 'tool_result',
11068
11380
  tool_use_id: call.id,
11069
- content: typeof result === 'string' ? result : JSON.stringify(result),
11381
+ content: toolResultContentForCall(call.name, result),
11070
11382
  ...(isError ? { isError: true } : {}),
11071
11383
  });
11072
11384
  }
@@ -11115,10 +11427,43 @@ export async function streamLLMWithTools(
11115
11427
  }
11116
11428
  // 신호뿐인 턴 · 섞인 턴(정상 디스패치 · 부분 거부 · 통째 거부의 수신 확인) 모두 — 종결 신호가 이 턴에 «받아들여졌다».
11117
11429
  if (!hardStopText && pendingCalls.some(isTerminalCall)) terminalSignalDispatched = true;
11430
+ // A goal completion with evidence has already been acknowledged by dispatch. The
11431
+ // across-turn goal loop owns final acceptance (test evidence / rejected calls),
11432
+ // but no further provider request is needed to make that decision. Keep the
11433
+ // tool-result blocks in onTurnComplete so it can inspect the original call.
11434
+ const hasAcceptedCompletion = !hardStopText && opts.terminalCompletionAccepted && pendingCalls.some((call) =>
11435
+ isTerminalCall(call) && resultBlocks.some((block) => block.type === 'tool_result'
11436
+ && block.tool_use_id === call.id && !block.isError)
11437
+ && opts.terminalCompletionAccepted!(call.name, call.args));
11438
+ const hasVisibleAnswer = lastToolRoundText.trim().length > 0 || opts.terminalPriorAnswerVisible === true;
11439
+ // A completion tool can follow a commentary/progress segment on any provider.
11440
+ // Its successful ack does not turn that segment into a final user answer.
11441
+ const completionTextIsProgress = lastToolRoundTextPhase === 'commentary'
11442
+ || (lastToolRoundTextPhase !== 'final' && lastToolRoundText.trim().length <= 240
11443
+ && /(?:하겠습니다|드리겠습니다|할게요|할 예정입니다|확인하겠|살펴보겠|작성하겠|정리하겠|I(?:'ll| will)\b|\bnext[,]?\s+I\b)/i.test(lastToolRoundText));
11444
+ if (hasAcceptedCompletion && completionTextIsProgress) {
11445
+ completionNeedsFinalTurn = true;
11446
+ history.push({ role: LOOP_SELF_NOTE_ROLE, content: 'The goal completion was accepted. Write your final answer to the user now as plain text. Do not call tools or describe what you will do next.' });
11447
+ debug.log('goal.loop', 'completion-await-final', { turn, progressChars: lastToolRoundText.length, phase: lastToolRoundTextPhase ?? 'unknown' }, { level: 'info' });
11448
+ }
11449
+ if (hasAcceptedCompletion && hasVisibleAnswer && !completionNeedsFinalTurn) {
11450
+ const answer = lastToolRoundText.trim().length > 0 ? lastToolRoundText : '';
11451
+ const visiblePriorAnswer = opts.terminalPriorAnswerVisible === true;
11452
+ debug.log('goal.loop', 'accepted-terminal-completion-finish', {
11453
+ turn, answerChars: answer.length, priorAnswerVisible: visiblePriorAnswer,
11454
+ tools: pendingCalls.map((call) => call.name),
11455
+ }, { level: 'info' });
11456
+ // Every text delta was already streamed above (and fullText accumulates
11457
+ // all rounds). A second onText here would violate the cumulative-full
11458
+ // contract and duplicate text shown before this terminal tool call.
11459
+ const finalAnswer = answer || (opts.terminalPriorAnswerVisible ? fullText.trim() : '');
11460
+ emitTurnComplete(finalAnswer);
11461
+ return finalAnswer;
11462
+ }
11118
11463
  // TUI-GOAL-LOOP-FRICTION(#63) — 종결 신호뿐인 턴 ⊕ 같은 턴에 쓴 답 = 이 턴이 최종 답이다.
11119
11464
  // 다음 모델 턴을 열면 할 말이 없는 모델이 빈 턴을 되풀이하고 `[NO FINAL SYNTHESIS]` 가 붙는다(10-15 22:36Z 표본).
11120
11465
  // 이 턴에 답이 없으면 종전처럼 다음 턴을 연다(앞 라운드 글은 «하겠습니다» 류 서술일 수 있다 — 모델이 답을 쓰게).
11121
- if (terminalOnlyTurn && !hardStopText && !postToolResultUserNote && turnText.trim().length > 0) {
11466
+ if (terminalOnlyTurn && !completionNeedsFinalTurn && !hardStopText && !postToolResultUserNote && turnText.trim().length > 0) {
11122
11467
  const answer = turnText;
11123
11468
  debug.log('goal.loop', 'terminal-signal-finish', {
11124
11469
  turn, maxTurns, tools: pendingCalls.map((call) => call.name), answerChars: answer.length,
@@ -11245,7 +11590,8 @@ export async function streamLLMWithTools(
11245
11590
  // synthesis attempt. Extended to gemini 2026-05-04 alongside
11246
11591
  // W5-E/W5-F for the same pathology shape.
11247
11592
  if (
11248
- !forceSynthesisAttempted
11593
+ !opts.researchTurn
11594
+ && !forceSynthesisAttempted
11249
11595
  && noContentReadTurnStreak >= 4
11250
11596
  && (modelFamily === 'gemini'
11251
11597
  || applyCodexBehaviorGuard('force-synthesis', effectiveModel, codexGuardCandidate, true))
@@ -11331,6 +11677,7 @@ export async function streamLLMWithTools(
11331
11677
  let synthesizedText: string | null = null;
11332
11678
  const shouldForceSynthesisOnBudget =
11333
11679
  loopTermination === 'budget-exhausted'
11680
+ && !codexContinuationNeeded
11334
11681
  && !forceSynthesisAttempted
11335
11682
  && (modelFamily === 'gemini'
11336
11683
  || applyCodexBehaviorGuard('force-synthesis', effectiveModel, codexGuardCandidate, true));
@@ -11500,6 +11847,7 @@ function makeOpenAICompatProvider(
11500
11847
  throw new Error(`${name} unavailable: configure apiKey via \`elanous setup\``);
11501
11848
  }
11502
11849
  const activeModel = opts.model || model;
11850
+ if (getModelFamily(activeModel) === 'claude') assertNoTopLevelToolSchemaCombinators(opts.tools);
11503
11851
  const wireModel = name === 'openrouter' ? openRouterWireModel(activeModel) : activeModel;
11504
11852
  const tools = toOpenAITools(
11505
11853
  opts.tools,
@@ -11797,6 +12145,8 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
11797
12145
  const responsesUrl = cfgBase
11798
12146
  ? (cfgBase.endsWith('/responses') ? cfgBase : `${cfgBase.replace(/\/$/, '')}/responses`)
11799
12147
  : `${CODEX_API_BASE_URL}/responses`;
12148
+ const endpointSupportsStore = !responsesUrl.includes('chatgpt.com/backend-api');
12149
+ const useStore = endpointSupportsStore && cfg.codexStore !== false;
11800
12150
  // Image-pipeline P3 (2026-05-05) — gate the function_call_output
11801
12151
  // ContentItem[] wire on the active model. gpt-5 family supports
11802
12152
  // image_url in tool results; legacy gpt-4 / non-vision routes
@@ -11816,6 +12166,7 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
11816
12166
  const { instructions, input } = messagesToResponsesInput(messages, {
11817
12167
  acceptToolImages: codexAcceptToolImages,
11818
12168
  acceptUserMessageImages: codexAcceptUserImages,
12169
+ replayCodexReasoning: !useStore,
11819
12170
  onOrphanToolResult: (callId) => {
11820
12171
  try { debug.log('llm.codex', 'orphan-tool-output-dropped', { callId }); }
11821
12172
  catch { /* observation must not block the request */ }
@@ -11865,12 +12216,17 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
11865
12216
  // wave for ChatGPT regardless of cfg.codexStore — the
11866
12217
  // infrastructure stays in place for Azure/API-key paths and
11867
12218
  // for any future ChatGPT-side policy change.
11868
- const endpointSupportsStore = !responsesUrl.includes('chatgpt.com/backend-api');
11869
- const useStore = endpointSupportsStore && cfg.codexStore !== false;
11870
12219
  const delta = useStore ? getIncrementalItems(lastInput, input) : null;
11871
- const wireInput = delta ?? input;
12220
+ // A stored continuation already has old reasoning. A full rebase has
12221
+ // no previous_response_id, so it must replay those opaque items.
12222
+ const wireInput = delta ?? (useStore
12223
+ ? messagesToResponsesInput(messages, {
12224
+ acceptToolImages: codexAcceptToolImages,
12225
+ acceptUserMessageImages: codexAcceptUserImages,
12226
+ }).input
12227
+ : input);
11872
12228
  const wirePrevId = (delta && lastResponseId) ? lastResponseId : undefined;
11873
- yield* streamCodexResponsesEvents(responsesUrl, tokens.accessToken, {
12229
+ let requestBody = {
11874
12230
  model: opts.model || model,
11875
12231
  instructions,
11876
12232
  input: wireInput,
@@ -11884,8 +12240,77 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
11884
12240
  // can disable via llm.codexStore=false.
11885
12241
  store: useStore,
11886
12242
  ...(wirePrevId ? { previousResponseId: wirePrevId } : {}),
11887
- }, opts.signal, {
12243
+ };
12244
+ const requestGuard = opts.requestSizeGuard;
12245
+ const promptCache = opts.promptCache !== false;
12246
+ let serializedWireBody: string | undefined;
12247
+ if (requestGuard) {
12248
+ serializedWireBody = serializeCodexResponsesWireBody(requestBody, promptCache);
12249
+ let requestChars = serializedWireBody.length;
12250
+ const initialRequestChars = requestChars;
12251
+ let requestCompacted = false;
12252
+ let requestCompactReason = requestChars > requestGuard.maxChars
12253
+ ? 'store-threading-skipped'
12254
+ : 'under-cap';
12255
+ if (requestChars > requestGuard.maxChars && !useStore) {
12256
+ try {
12257
+ const compacted = await requestGuard.compactMessages(messages, {
12258
+ provider: 'openai-codex', requestChars, maxChars: requestGuard.maxChars,
12259
+ });
12260
+ if (compacted) {
12261
+ const compactInput = messagesToResponsesInput(compacted.messages, {
12262
+ acceptToolImages: codexAcceptToolImages,
12263
+ acceptUserMessageImages: codexAcceptUserImages,
12264
+ // Compaction runs only when store threading is off, matching
12265
+ // the original non-threaded wire input policy.
12266
+ replayCodexReasoning: !useStore,
12267
+ });
12268
+ const compactBody = { ...requestBody, instructions: compactInput.instructions, input: compactInput.input };
12269
+ const compactWireBody = serializeCodexResponsesWireBody(compactBody, promptCache);
12270
+ const compactChars = compactWireBody.length;
12271
+ if (compactChars < requestChars) {
12272
+ messages.splice(0, messages.length, ...compacted.messages);
12273
+ requestBody = compactBody;
12274
+ serializedWireBody = compactWireBody;
12275
+ requestChars = compactChars;
12276
+ requestCompacted = true;
12277
+ requestCompactReason = compactChars > requestGuard.maxChars
12278
+ ? `compaction-still-over-cap:${compacted.reason}`
12279
+ : compacted.reason;
12280
+ } else {
12281
+ requestCompactReason = 'compaction-no-reduction';
12282
+ }
12283
+ } else {
12284
+ requestCompactReason = 'compaction-no-op';
12285
+ }
12286
+ } catch (error) {
12287
+ requestCompactReason = 'compaction-error';
12288
+ debug.log('llm.request', 'request-size-compact-error', {
12289
+ provider: 'openai-codex',
12290
+ requestChars,
12291
+ maxChars: requestGuard.maxChars,
12292
+ message: error instanceof Error ? error.message : String(error),
12293
+ }, { level: 'warn' });
12294
+ }
12295
+ }
12296
+ requestGuard.onMeasured?.({
12297
+ provider: 'openai-codex', requestChars, maxChars: requestGuard.maxChars,
12298
+ compacted: requestCompacted, reason: requestCompactReason,
12299
+ ...(requestCompacted ? { previousChars: initialRequestChars } : {}),
12300
+ });
12301
+ debug.log('llm.request', 'request-size-compact', {
12302
+ provider: 'openai-codex',
12303
+ beforeChars: initialRequestChars,
12304
+ afterChars: requestChars,
12305
+ maxChars: requestGuard.maxChars,
12306
+ compacted: requestCompacted,
12307
+ reason: requestCompactReason,
12308
+ });
12309
+ }
12310
+ yield* streamCodexResponsesEvents(responsesUrl, tokens.accessToken, requestBody, opts.signal, {
11888
12311
  accountId,
12312
+ promptCache,
12313
+ ...(serializedWireBody ? { serializedWireBody } : {}),
11889
12314
  // Capture the new response id from the SSE stream so the
11890
12315
  // next call can thread it. If the backend skips the event
11891
12316
  // lastResponseId stays undefined and we fall back to full