elanous 0.2.38 → 0.2.41

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 (712) hide show
  1. package/README.md +7 -7
  2. package/apps/pwa/out/404/index.html +1 -1
  3. package/apps/pwa/out/404.html +1 -1
  4. package/apps/pwa/out/_next/static/{RrjXTwz-urRAbChP1bX02 → GiOLWmt4MqDZePubBjA4Q}/_buildManifest.js +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1115.434ae2ecb0ad986d.js +149 -0
  6. package/apps/pwa/out/_next/static/chunks/1121.667cec0bcdbb3fdb.js +36 -0
  7. package/apps/pwa/out/_next/static/chunks/1178.1aa9cad0e7695427.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1197-54da59d250a6e476.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/{5203.51e5d8b5f1dcfe53.js → 1219.7e17964e0e7cd8e5.js} +3 -3
  10. package/apps/pwa/out/_next/static/chunks/1245.1cfd13b26dbd71d6.js +287 -0
  11. package/apps/pwa/out/_next/static/chunks/1494-f93046ff5c5a124d.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/1609.2b312797599b8e45.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/1661.0dd330f3d89f73f3.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/167-0c121e4b47e0f9da.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/1700.cdc2bf8cc685e221.js +90 -0
  16. package/apps/pwa/out/_next/static/chunks/1703-78e46acad29f3cfc.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/{7573.2376e13c9c21f1f8.js → 1765.679b29625c8e88d4.js} +2 -2
  18. package/apps/pwa/out/_next/static/chunks/{3528.e4b8ab70d339f965.js → 1800.f7ff51dc4978a958.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/1971-7efb628c471dc5a0.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/1999-8de7b7bb36801913.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/2073.689dcb2680113d76.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2198-557d49184104d165.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/221-440ce67fa9f5c839.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/{8879.ac06755bcf3e44a1.js → 2263.e09b6edea33bad2d.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/2427-8ed8e98624cbd664.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/2457-725c5827bc003795.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/2491-2718b15a5ccb75a5.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2527-5c636a43c7cc5dde.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2692-1027cb16fe4ccf49.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2743.5d3f76948be32b34.js +41 -0
  31. package/apps/pwa/out/_next/static/chunks/2773.0e80b9d325fb5635.js +32 -0
  32. package/apps/pwa/out/_next/static/chunks/2873-0f3cb56c8e3528ea.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/{9fe1b809.738da85c8b17e3fb.js → 2fdf2931.b431eee4fb468bc3.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/{eaad0cba.771200940c529df7.js → 2fe2c302.41b517f88d6fe502.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/3097.864e92bdaa2f4d15.js +79 -0
  36. package/apps/pwa/out/_next/static/chunks/3136-d6870ba63300a93e.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3173-0289113d434e83b7.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/3230-49298c3b73ef2268.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/325-dd9208a1a0ab7ce4.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/3283.528f430dd043fb4e.js +74 -0
  41. package/apps/pwa/out/_next/static/chunks/3295.cc8cb13b8f0dae1a.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/3688-86e3b88abf18cb3b.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/3804-7220467a5473a8df.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/3808.3e1e247ce7bdf889.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{b7478fef.5917716286213fcc.js → 385e4574.e9886ab32759eda3.js} +3 -3
  46. package/apps/pwa/out/_next/static/chunks/3927.b5473ad109da44c5.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/3934-98ea94832e194796.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/4133.2056bb4d78f621af.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/4156.ba4de4a3664f04b4.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/4297-239ca82f293faab0.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/4320-dcc5a7734126f4c9.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/4434.94b4c552a2d91e69.js +148 -0
  53. package/apps/pwa/out/_next/static/chunks/{8911.bf5e712acb25fb6c.js → 4527.a69f2894d1bc9f4a.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/4579.03e4092f73950965.js +226 -0
  55. package/apps/pwa/out/_next/static/chunks/{5307.d7328020227880c2.js → 4667.97e31aa38c95c475.js} +4 -4
  56. package/apps/pwa/out/_next/static/chunks/{6142.913b44e260a3f925.js → 4702.4a4adc1f16d7c93b.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/4815.481501c57b3568d9.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/487-d16e698ef14662d9.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/4888-7a38bf522c9cc6fe.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/5034-30e429d6bb086881.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/5040-35c494e60dadd97d.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/50c6ab1e.81a96e419350909d.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/5154.4b6a836179356f30.js +94 -0
  64. package/apps/pwa/out/_next/static/chunks/{8487.291666583bebe640.js → 5159.115f1c2ca471386c.js} +1 -1
  65. package/apps/pwa/out/_next/static/chunks/5228-a8200df486278f2d.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/530.d26346e18089640a.js +115 -0
  67. package/apps/pwa/out/_next/static/chunks/5379.97af106d09566f69.js +8 -0
  68. package/apps/pwa/out/_next/static/chunks/{89d6c04f-6b8ab341748e468a.js → 53aeb4ee-51c92f8d683f5220.js} +1 -1
  69. package/apps/pwa/out/_next/static/chunks/{3291.f6c1005387d514bd.js → 5471.1e611302ed3a7c53.js} +2 -2
  70. package/apps/pwa/out/_next/static/chunks/5479-b6372e762719f92f.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/5534-699c5cbcbc96c5d7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/{3086.fb14b60ce98b53d3.js → 5574.f35accf4f132b29d.js} +1 -1
  73. package/apps/pwa/out/_next/static/chunks/5701.9c4e8f250312eeee.js +24 -0
  74. package/apps/pwa/out/_next/static/chunks/{1632.7d87f998e714bede.js → 5744.b9415e18ad623100.js} +1 -1
  75. package/apps/pwa/out/_next/static/chunks/{5300.677c97e0393742e6.js → 5892.2526d962339662d8.js} +1 -1
  76. package/apps/pwa/out/_next/static/chunks/5937.c2df9e3447e1f667.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/5941.8210cc93c9202c0a.js +286 -0
  78. package/apps/pwa/out/_next/static/chunks/{7161-8f22db3ec5ed005e.js → 5947-135e26537d80a6b9.js} +3 -3
  79. package/apps/pwa/out/_next/static/chunks/{3124-561f7f037943cde2.js → 5964-7e21164a8f2cf0ce.js} +1 -1
  80. package/apps/pwa/out/_next/static/chunks/5975.4004f820ae3cecd5.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/6142-7ccd48795d7abd55.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/6160.bf9177b8c1bfb243.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/6398.71ba0690c8027555.js +27 -0
  84. package/apps/pwa/out/_next/static/chunks/6492.2bd5c10edc1a064e.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/6552.a3840aa7da35ad68.js +62 -0
  86. package/apps/pwa/out/_next/static/chunks/6736.1e36d3ac5fa5e41d.js +24 -0
  87. package/apps/pwa/out/_next/static/chunks/6874.d5cf7eb8edb2cabe.js +2 -0
  88. package/apps/pwa/out/_next/static/chunks/6900.3ccbe069bdd4556a.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/71327eb4.add50b365f94bf3a.js +122 -0
  90. package/apps/pwa/out/_next/static/chunks/7136.deca3e3a2ddaa374.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/7139-ee9f8ed2677a40b3.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/{2415.738b777642a02233.js → 7191.fdc955f7d96064a4.js} +1 -1
  93. package/apps/pwa/out/_next/static/chunks/7423-e0aa5d9e2962120e.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/7453.afc1ea7e05d56a8c.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/7503-5e6a3d7fe67e4a0c.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/7761-476fd7bf768abd34.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/7975.eafb12097a0a9e62.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{1026-44010f405704050c.js → 8050-9b364bdef8f71927.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/8152.64e253c35cccfaae.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/8190.620f0231b3924bcb.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/83184dcd-0ee6247ff41eb3f2.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8448.45b6b1f1a99eeb35.js +104 -0
  103. package/apps/pwa/out/_next/static/chunks/8464-e24f10451b658aac.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8477.e4c944bb3aa5d2f3.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/8710.ca385257d5ca422e.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/8830.627ccf294ec2465c.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/8880.64c445592f04acaf.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/8981.c706964c8ba27aff.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/{5638.107314dc479e2f37.js → 9048.212083de42d25be3.js} +2 -2
  110. package/apps/pwa/out/_next/static/chunks/9053.bd0fffaff8d3f836.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/9161-c66e723d7ec9d682.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/9185.1ecdf9b65c0bb684.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/9356.fccd5f58086b4ff5.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/9446.dc0406109371d329.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/9506.dcf25d467a42627e.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/{8019.21ca554a821dad01.js → 9507.7026657a2f47eb64.js} +16 -16
  117. package/apps/pwa/out/_next/static/chunks/9541.eb8e2f8480c7feca.js +93 -0
  118. package/apps/pwa/out/_next/static/chunks/9577.ed7582186efa5482.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/9579.4a8169600fff2058.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/9642-ff2e039eb62e3b92.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/9659-8f2323348113ad80.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/9724.4c5623af2db43153.js +34 -0
  123. package/apps/pwa/out/_next/static/chunks/{6493.27ad77de7aac7966.js → 9834.63ab2bbc3153172e.js} +1 -1
  124. package/apps/pwa/out/_next/static/chunks/{3389.012c70767a872f9b.js → 9869.81a1e24ca682d5a0.js} +1 -1
  125. package/apps/pwa/out/_next/static/chunks/{8994d93e.33ba491f95345fc2.js → a0a01613.6d1cca322238d7fe.js} +1 -1
  126. package/apps/pwa/out/_next/static/chunks/{de8b930d.d94c0f42acf807fc.js → addc2b32.8c4ff8f0aefe987d.js} +1 -1
  127. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-8d9d76ea322d855c.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/approvals/page-ed2c4e59f9f1fe21.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-2dc99e03c5d76ca0.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/board/page-b5a3a788330e920f.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-866aac5ff226a7f3.js → page-14949db884bfa672.js} +1 -1
  132. package/apps/pwa/out/_next/static/chunks/app/bots/page-ea76c85b36388a97.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/ceo/page-01716d20af62d325.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/chat/page-62baf6881a64b1e9.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/consult/page-f89ba590c2af8afa.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/control/page-22f8b0f78d9637e5.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/decisions/page-817f9d7e6820be80.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/design-check/page-ffb9bc0d2650249a.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/editor/page-f6d0e64873c0f629.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/exec/page-8bfa0d4ba23a209d.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/field/page-f5060462249e32c7.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/healthz/route-8d9d76ea322d855c.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/inside/page-b2049f8b1ef79117.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/intake/page-1446c5fa93907f11.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/layout-b48a7a07c5204156.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/live/page-e974b8c4ca47614c.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/live-run/page-8a919b869bd28594.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/loops/page-d31b8de6bfcd72ed.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/market/page-64765f817909db29.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-d128c8569a36ee9b.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/morning/page-b2679d357e9b9270.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/not-found-6fabec98a9e7b269.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/observatory/page-048f30149dcf1349.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-9113181d6b5a2725.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-40f9e19149024298.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-1e7a755a13227611.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3c8f8b67a203cf07.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/page-18032f5437215062.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/reflection/page-da3b739192fd0ee3.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d332545571bd6fd1.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/sessions/page-5237aa5b3f3b8ec5.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a142dfb0331a05f5.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/settings/page-d16e56c3989c147e.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-6b6593600869e2cd.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/setup/layout-8d9d76ea322d855c.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/setup/page-29e8b8a8396b55cb.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/share/page-b3414d856a1a7e2f.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/showroom/page-c3affa52b7ce961b.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f21dddf9f95b6fab.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/term/page-f5a30a24745bf088.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/today/page-8c8569a09b4d4e34.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/trace/page-2ce32722c0243483.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/app/vault/page-4767b0d9c0a98c6c.js +1 -0
  174. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-116ee1339c387a75.js +1 -0
  175. package/apps/pwa/out/_next/static/chunks/app/workflows/page-35e8c50ca4ef7ffc.js +1 -0
  176. package/apps/pwa/out/_next/static/chunks/app/workspace/page-52e17113356bb891.js +1 -0
  177. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-7458ae05ed2ac1a0.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/b2271e54.c1de3347976e90b0.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/{b7459770.6539b11860eb8a9c.js → c0c1a699.0ba79f97005965d4.js} +7 -7
  180. package/apps/pwa/out/_next/static/chunks/framework-ad6c9dd524ad6ef8.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/main-app-2ddd1f2b44d6aa59.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/main-e3d82bdc358d20c5.js +1 -0
  183. package/apps/pwa/out/_next/static/chunks/pages/_app-71d5aa3896e1655f.js +1 -0
  184. package/apps/pwa/out/_next/static/chunks/pages/_error-03ffc54e00438546.js +1 -0
  185. package/apps/pwa/out/_next/static/chunks/webpack-56bb89af5a611d0b.js +1 -0
  186. package/apps/pwa/out/approvals/index.html +1 -1
  187. package/apps/pwa/out/approvals/index.txt +22 -22
  188. package/apps/pwa/out/autopilot/index.html +1 -1
  189. package/apps/pwa/out/autopilot/index.txt +22 -22
  190. package/apps/pwa/out/board/index.html +1 -1
  191. package/apps/pwa/out/board/index.txt +22 -22
  192. package/apps/pwa/out/botlab/index.html +1 -1
  193. package/apps/pwa/out/botlab/index.txt +21 -21
  194. package/apps/pwa/out/bots/index.html +1 -1
  195. package/apps/pwa/out/bots/index.txt +21 -21
  196. package/apps/pwa/out/ceo/index.html +1 -1
  197. package/apps/pwa/out/ceo/index.txt +22 -22
  198. package/apps/pwa/out/chat/index.html +1 -1
  199. package/apps/pwa/out/chat/index.txt +21 -21
  200. package/apps/pwa/out/consult/index.html +1 -1
  201. package/apps/pwa/out/consult/index.txt +22 -22
  202. package/apps/pwa/out/control/index.html +1 -1
  203. package/apps/pwa/out/control/index.txt +22 -22
  204. package/apps/pwa/out/decisions/index.html +1 -1
  205. package/apps/pwa/out/decisions/index.txt +21 -21
  206. package/apps/pwa/out/design-check/index.html +1 -1
  207. package/apps/pwa/out/design-check/index.txt +22 -22
  208. package/apps/pwa/out/editor/index.html +1 -1
  209. package/apps/pwa/out/editor/index.txt +22 -22
  210. package/apps/pwa/out/exec/index.html +1 -1
  211. package/apps/pwa/out/exec/index.txt +22 -22
  212. package/apps/pwa/out/field/index.html +1 -1
  213. package/apps/pwa/out/field/index.txt +22 -22
  214. package/apps/pwa/out/healthz +1 -1
  215. package/apps/pwa/out/index.html +1 -1
  216. package/apps/pwa/out/index.txt +21 -21
  217. package/apps/pwa/out/inside/index.html +1 -1
  218. package/apps/pwa/out/inside/index.txt +21 -21
  219. package/apps/pwa/out/intake/index.html +1 -1
  220. package/apps/pwa/out/intake/index.txt +22 -22
  221. package/apps/pwa/out/live/index.html +1 -1
  222. package/apps/pwa/out/live/index.txt +22 -22
  223. package/apps/pwa/out/live-run/index.html +1 -1
  224. package/apps/pwa/out/live-run/index.txt +22 -22
  225. package/apps/pwa/out/loops/index.html +1 -1
  226. package/apps/pwa/out/loops/index.txt +21 -21
  227. package/apps/pwa/out/market/index.html +1 -1
  228. package/apps/pwa/out/market/index.txt +21 -21
  229. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  230. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  231. package/apps/pwa/out/morning/index.html +1 -1
  232. package/apps/pwa/out/morning/index.txt +22 -22
  233. package/apps/pwa/out/observatory/index.html +1 -1
  234. package/apps/pwa/out/observatory/index.txt +22 -22
  235. package/apps/pwa/out/ops/checklist/index.html +1 -1
  236. package/apps/pwa/out/ops/checklist/index.txt +21 -21
  237. package/apps/pwa/out/ops/release/index.html +1 -1
  238. package/apps/pwa/out/ops/release/index.txt +21 -21
  239. package/apps/pwa/out/ops/seats/index.html +1 -1
  240. package/apps/pwa/out/ops/seats/index.txt +21 -21
  241. package/apps/pwa/out/outputs/index.html +1 -1
  242. package/apps/pwa/out/outputs/index.txt +21 -21
  243. package/apps/pwa/out/reflection/index.html +1 -1
  244. package/apps/pwa/out/reflection/index.txt +22 -22
  245. package/apps/pwa/out/scheduler/index.html +1 -1
  246. package/apps/pwa/out/scheduler/index.txt +22 -22
  247. package/apps/pwa/out/sessions/index.html +1 -1
  248. package/apps/pwa/out/sessions/index.txt +21 -21
  249. package/apps/pwa/out/settings/devices/index.html +1 -1
  250. package/apps/pwa/out/settings/devices/index.txt +22 -22
  251. package/apps/pwa/out/settings/index.html +1 -1
  252. package/apps/pwa/out/settings/index.txt +22 -22
  253. package/apps/pwa/out/setup/done/index.html +1 -1
  254. package/apps/pwa/out/setup/done/index.txt +22 -22
  255. package/apps/pwa/out/setup/index.html +1 -1
  256. package/apps/pwa/out/setup/index.txt +22 -22
  257. package/apps/pwa/out/share/index.html +1 -1
  258. package/apps/pwa/out/share/index.txt +22 -22
  259. package/apps/pwa/out/showroom/index.html +1 -1
  260. package/apps/pwa/out/showroom/index.txt +21 -21
  261. package/apps/pwa/out/tasks/index.html +1 -1
  262. package/apps/pwa/out/tasks/index.txt +22 -22
  263. package/apps/pwa/out/term/index.html +1 -1
  264. package/apps/pwa/out/term/index.txt +22 -22
  265. package/apps/pwa/out/today/index.html +1 -1
  266. package/apps/pwa/out/today/index.txt +22 -22
  267. package/apps/pwa/out/trace/index.html +1 -1
  268. package/apps/pwa/out/trace/index.txt +22 -22
  269. package/apps/pwa/out/vault/index.html +1 -1
  270. package/apps/pwa/out/vault/index.txt +22 -22
  271. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  272. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  273. package/apps/pwa/out/workflows/index.html +1 -1
  274. package/apps/pwa/out/workflows/index.txt +22 -22
  275. package/apps/pwa/out/workspace/index.html +1 -1
  276. package/apps/pwa/out/workspace/index.txt +22 -22
  277. package/apps/pwa/out/worktrees/index.html +1 -1
  278. package/apps/pwa/out/worktrees/index.txt +22 -22
  279. package/bin/elanous.mjs +6 -1
  280. package/catalog/models/openai/gpt-6.1-sol.yaml +16 -0
  281. package/catalog/models/openai/gpt-6.1-terra.yaml +16 -0
  282. package/graphs/demo/persona-marketer/fixtures/marketer-scenario.yaml +10 -0
  283. package/graphs/demo/persona-marketer/persona-marketer.yaml +25 -0
  284. package/graphs/demo/persona-marketer/recipes.yaml +19 -0
  285. package/graphs/demo/persona-teacher/lesson-pack.yaml +16 -0
  286. package/graphs/demo/persona-teacher/recipes.yaml +17 -0
  287. package/graphs/demo/persona-teacher/weekly-lesson.yaml +36 -0
  288. package/graphs/overlays/stop-heal-ladder.yaml +23 -2
  289. package/graphs/release/release-loop.yaml +3 -1
  290. package/graphs/units/gate/gate.yaml +20 -0
  291. package/graphs/units/git/git.yaml +26 -0
  292. package/graphs/units/implement/implement.yaml +19 -0
  293. package/graphs/units/landing/landing.yaml +18 -0
  294. package/graphs/units/review/review.yaml +20 -0
  295. package/graphs/units/verbs/recipes.yaml +3 -0
  296. package/graphs/units/verbs/research.yaml +20 -0
  297. package/graphs/video/explainer-line.yaml +4 -1
  298. package/graphs/work/research-report.yaml +11 -1
  299. package/package.json +1 -1
  300. package/scripts/dead-code/report.ts +169 -0
  301. package/scripts/disk-steward.ts +46 -0
  302. package/scripts/fleet/node-setup.sh +50 -0
  303. package/scripts/graph-units/verbs/research.ts +55 -0
  304. package/scripts/lib/morning-report-options.ts +3 -4
  305. package/scripts/lib/tui-regress-evidence.ts +56 -0
  306. package/scripts/llm-cost-ledger.ts +179 -8
  307. package/scripts/measure-nl-routing-corpus.ts +10 -2
  308. package/scripts/morning-report.ts +5 -1
  309. package/scripts/persona-teacher/weekly-lesson-node.ts +172 -0
  310. package/scripts/public-copy-lint.ts +163 -19
  311. package/scripts/public-export.ts +15 -7
  312. package/scripts/ratchets/train-side-invariants.sh +0 -1
  313. package/scripts/release-loop/export-check-node.ts +21 -3
  314. package/scripts/release-loop/gate-autoheal.ts +117 -0
  315. package/scripts/release-loop/gate-baseline-receipt.ts +8 -1
  316. package/scripts/release-loop/gate-diff.ts +22 -3
  317. package/scripts/release-loop/gate-node.ts +583 -64
  318. package/scripts/release-loop/gate-shard-dispatch.ts +128 -0
  319. package/scripts/release-loop/gate-shard-load-flaky.ts +256 -0
  320. package/scripts/release-loop/gate-shard-receipts.ts +89 -0
  321. package/scripts/release-loop/gate-triage-node.ts +70 -10
  322. package/scripts/release-loop/gate-wall.ts +5 -5
  323. package/scripts/release-loop/preflight.ts +6 -2
  324. package/scripts/release-loop/resume-release.ts +267 -20
  325. package/scripts/release-loop/resume.ts +48 -0
  326. package/scripts/release-loop/traffic-node.ts +6 -3
  327. package/scripts/release-loop/unattended-release.ts +4 -2
  328. package/scripts/release-loop/version-node.ts +7 -2
  329. package/scripts/release-story/draft.ts +1 -1
  330. package/scripts/research-bench/README.md +53 -0
  331. package/scripts/research-bench/questions.json +66 -0
  332. package/scripts/research-bench/rubric.md +33 -0
  333. package/scripts/research-bench/run.ts +523 -0
  334. package/scripts/shadow-authoring-probe.ts +20 -0
  335. package/scripts/tui-regress.ts +85 -10
  336. package/scripts/ux-sim.ts +19 -2
  337. package/src/acp/core-turn-bridge.ts +94 -4
  338. package/src/agent/autonomous-tools.ts +10 -3
  339. package/src/agent/monad-agent-turn.ts +4 -1
  340. package/src/agent/registry.ts +26 -2
  341. package/src/agent/shared-app-tools.ts +1 -1
  342. package/src/agent/surface-ux/build.ts +12 -3
  343. package/src/agent/surface-ux/types.ts +1 -1
  344. package/src/agent/task-notification.ts +75 -6
  345. package/src/agent/types.ts +3 -0
  346. package/src/agent-mission/driver.ts +29 -3
  347. package/src/agent-mission/mission-cli.ts +2 -1
  348. package/src/agent-substrate/review-context-pack.ts +365 -0
  349. package/src/agent-substrate/self-review-cli.ts +291 -20
  350. package/src/ask-user-question/index.ts +6 -0
  351. package/src/ask-user-question/inline-resolver.ts +49 -0
  352. package/src/auto-research/question-ladder-contract.ts +22 -3
  353. package/src/auto-research/research-turn.ts +359 -17
  354. package/src/autopilot/agent-loop.ts +1 -1
  355. package/src/autopilot/discovery/wiki-claim-proposer.ts +22 -1
  356. package/src/autopilot/pty-control-loop.ts +8 -1
  357. package/src/boot/daemon-tools/index.ts +4 -3
  358. package/src/boot/daemon-tools/self-implement.ts +312 -7
  359. package/src/boot/daemon-tools/types.ts +2 -0
  360. package/src/budget/llm-cost.ts +44 -5
  361. package/src/budget/shadow-arms.ts +52 -0
  362. package/src/budget/unified-usage.ts +17 -1
  363. package/src/capture/tui-self-report.ts +14 -0
  364. package/src/chat/index.ts +33 -7
  365. package/src/chat/pickers/state.ts +7 -1
  366. package/src/cli/agent-cli.ts +43 -8
  367. package/src/cli/intake-cli.ts +22 -1
  368. package/src/cli/lesson-cli.ts +19 -3
  369. package/src/cli/log-event-names.ts +6 -0
  370. package/src/cli/mcp-login.ts +8 -18
  371. package/src/cli/msg-cli.ts +57 -1
  372. package/src/cli/pod-cli.ts +17 -2
  373. package/src/cli/pr-land-heavy-remote.ts +15 -9
  374. package/src/cli/pty-takeover-cli.ts +67 -7
  375. package/src/cli/release-cli.ts +79 -7
  376. package/src/cli/repo-cli.ts +2 -1
  377. package/src/cli/sim-cli.ts +7 -1
  378. package/src/cli/tasks-cli.ts +41 -13
  379. package/src/codex/models.ts +30 -15
  380. package/src/control-plane/ledger.ts +21 -4
  381. package/src/core-turn/run-core-turn.ts +47 -3
  382. package/src/core-turn/run-goal-loop.ts +240 -10
  383. package/src/core-turn/types.ts +9 -0
  384. package/src/dashboard/acp-boot.ts +72 -3
  385. package/src/dashboard/chat/acp-chat.ts +2 -2
  386. package/src/dashboard/classic-sigquit.ts +16 -0
  387. package/src/dashboard/index.ts +327 -27
  388. package/src/dashboard/input/chat-main-interaction-opts.ts +3 -0
  389. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +7 -2
  390. package/src/dashboard/input/chat-main-slash-command.ts +7 -0
  391. package/src/dashboard/input/dashboard-plain-turn-stream.ts +4 -0
  392. package/src/dashboard/input/dashboard-priority-key-route.ts +21 -5
  393. package/src/dashboard/input/external-input-host.ts +2 -0
  394. package/src/dashboard/sidetab-runtime.ts +228 -0
  395. package/src/dashboard/slash-runtime/ask-clarification.ts +7 -1
  396. package/src/dashboard/slash-runtime/dashboard-handlers.ts +20 -8
  397. package/src/dashboard/slash-runtime/help-from-registry.ts +2 -2
  398. package/src/dashboard/term-slash-runtime.ts +16 -18
  399. package/src/dashboard/turn-preamble.ts +14 -1
  400. package/src/disk-steward/judge.ts +73 -0
  401. package/src/disk-steward/ledger.ts +122 -0
  402. package/src/disk-steward/steward.ts +373 -0
  403. package/src/domains/guardian-tick.ts +191 -2
  404. package/src/domains/knowledge.ts +1 -1
  405. package/src/esc-abort-gate.ts +33 -1
  406. package/src/execution-loop/launch-gate.ts +206 -1
  407. package/src/graph-runner/graph-cli.ts +27 -4
  408. package/src/graph-runner/in-process-registry.ts +37 -0
  409. package/src/graph-runner/runner.ts +63 -11
  410. package/src/graph-wizard/generate.ts +16 -7
  411. package/src/graph-wizard/mission-compose.ts +201 -0
  412. package/src/grok/agent-search.ts +8 -2
  413. package/src/grok/credential.ts +5 -0
  414. package/src/grounding/ref-index.ts +30 -0
  415. package/src/grounding/ref-vector.ts +61 -0
  416. package/src/grounding/sources-cli.ts +11 -3
  417. package/src/grounding/sources.ts +15 -1
  418. package/src/harness/harness-cli-command.ts +305 -23
  419. package/src/harness/harness-incidents.ts +312 -7
  420. package/src/harness/harness-pod-dispatch.ts +7 -1
  421. package/src/harness/harness-space.ts +15 -1
  422. package/src/harness/progress-worker-entry.ts +59 -0
  423. package/src/harness/progress-worker-runtime.ts +11 -0
  424. package/src/index.ts +35 -5
  425. package/src/input/external-input-queue.ts +10 -0
  426. package/src/instance/dev-project.ts +25 -0
  427. package/src/intake-plane/collect-telegram-saved.ts +36 -1
  428. package/src/intake-plane/intake-to-tasks.ts +246 -3
  429. package/src/intelligence-map/model-alias.ts +6 -5
  430. package/src/intelligence-map/model-catalog.ts +59 -2
  431. package/src/lessons/friction-ledger.ts +154 -0
  432. package/src/live-supply/collector.ts +149 -0
  433. package/src/live-supply/probes.ts +52 -0
  434. package/src/live-supply/snapshot.ts +40 -0
  435. package/src/llm/model-defaults.ts +4 -4
  436. package/src/llm.ts +473 -87
  437. package/src/loops/orchestrator/tick.ts +56 -0
  438. package/src/mcp/mcp-oauth.ts +3 -1
  439. package/src/model-tier/llm-tier-map.ts +9 -9
  440. package/src/model-tier/turn-tier.ts +1 -1
  441. package/src/msg/msg-mirror-github.ts +80 -0
  442. package/src/msg/msg-mirror-policy.ts +61 -0
  443. package/src/msg/msg-mirror-runner.ts +219 -0
  444. package/src/msg/msg-mirror-state.ts +33 -0
  445. package/src/msg/msg-store.ts +11 -0
  446. package/src/nexus/api/harness-api.ts +2 -1
  447. package/src/output-truncation.ts +41 -12
  448. package/src/project/project-store.ts +48 -1
  449. package/src/pty/snapshot.py +132 -0
  450. package/src/pty-shell/pty-control-ipc.ts +29 -2
  451. package/src/pty-shell/pty-manifest.ts +59 -3
  452. package/src/pty-shell/registry.ts +2 -0
  453. package/src/pty-shell/tui-run-state.ts +80 -3
  454. package/src/release-loop/gate-host-reservation.ts +157 -4
  455. package/src/release-loop/gate-shard-ledger.ts +68 -0
  456. package/src/release-loop/merged-pr-checklist.ts +25 -2
  457. package/src/release-loop/rehearsal-shard-plan.ts +6 -1
  458. package/src/release-loop/rehearsal.ts +27 -0
  459. package/src/release-loop/release-schedule.ts +90 -1
  460. package/src/research/bloom-exemplars.ts +135 -0
  461. package/src/research/edge-decision.ts +124 -15
  462. package/src/research/gap-followup.ts +249 -0
  463. package/src/research/lenses/counter-evidence.ts +100 -0
  464. package/src/research/lenses/working-backwards.ts +89 -0
  465. package/src/research/plugin-discovery.ts +242 -0
  466. package/src/research-bridge/invoke.ts +4 -2
  467. package/src/research-bridge/types.ts +2 -0
  468. package/src/self-dev/ask-launch-flow.ts +73 -131
  469. package/src/self-dev/dev-cli.ts +19 -17
  470. package/src/self-dev/dev-pipeline.ts +8 -4
  471. package/src/self-dev/graph-front-nodes.ts +23 -0
  472. package/src/self-dev/graph-journey-route.ts +10 -2
  473. package/src/self-dev/launch-preflight-text.ts +2 -2
  474. package/src/self-dev/launch-preflight.ts +47 -11
  475. package/src/self-dev/orchestrate-cli.ts +1 -0
  476. package/src/self-dev/orchestrate.ts +15 -0
  477. package/src/self-dev/run-supervisor.ts +204 -3
  478. package/src/self-dev/run-types.ts +3 -0
  479. package/src/self-implement/device-first.ts +8 -2
  480. package/src/self-implement/doctor-visit.ts +76 -58
  481. package/src/self-implement/gate-artifacts.ts +16 -0
  482. package/src/self-implement/gate-baseline.ts +75 -4
  483. package/src/self-implement/gate-cli.ts +368 -30
  484. package/src/self-implement/gate-receipt-registry.ts +180 -0
  485. package/src/self-implement/gate-remote.ts +165 -24
  486. package/src/self-implement/goal-author-clarification.ts +205 -32
  487. package/src/self-implement/goal-author-decompose.ts +24 -15
  488. package/src/self-implement/goal-author.ts +86 -10
  489. package/src/self-implement/graph-stage-units.ts +172 -0
  490. package/src/self-implement/graph-templates.ts +162 -1
  491. package/src/self-implement/graph-variant.ts +37 -4
  492. package/src/self-implement/host-regate.ts +124 -49
  493. package/src/self-implement/impl-findings-feedback.ts +188 -0
  494. package/src/self-implement/knowhow-pack.ts +227 -10
  495. package/src/self-implement/off-diff-evidence.ts +124 -0
  496. package/src/self-implement/orchestrator.ts +320 -28
  497. package/src/self-implement/run-ledger.ts +110 -14
  498. package/src/self-implement/run-stop.ts +33 -2
  499. package/src/self-implement/running-runs.ts +35 -12
  500. package/src/self-implement/seams.ts +6 -3
  501. package/src/self-implement/self-implement-cli.ts +40 -6
  502. package/src/self-implement/stop-autoheal-operational.ts +43 -0
  503. package/src/self-implement/stop-autoheal.ts +15 -6
  504. package/src/session/chat.ts +66 -15
  505. package/src/session/request-size-guard.ts +123 -0
  506. package/src/session-runtime/provider-circuit.ts +140 -0
  507. package/src/shell-runner/file-engine.ts +1 -0
  508. package/src/shell-runner/types.ts +4 -0
  509. package/src/sidetab-core/model.ts +312 -0
  510. package/src/skills/tools/schedule-wakeup.ts +23 -1
  511. package/src/skills/tools/shell.ts +51 -5
  512. package/src/steward/directive-audit.ts +354 -0
  513. package/src/surface/child-process.ts +31 -0
  514. package/src/surface/tui-stderr-owner.ts +48 -0
  515. package/src/task-agent/cover.ts +30 -12
  516. package/src/task-agent/judge.ts +56 -0
  517. package/src/task-agent/parent-host.ts +14 -1
  518. package/src/task-agent/postland-card.ts +35 -2
  519. package/src/task-agent/rejudge-on-head.ts +46 -2
  520. package/src/task-agent/repair-cycle.ts +1 -1
  521. package/src/task-agent/task-hand.ts +133 -25
  522. package/src/task-agent/tier-split.ts +105 -21
  523. package/src/task-orchestrator/surfaces/pod-command-job.ts +330 -2
  524. package/src/task-orchestrator/surfaces/pod-config-snapshot.ts +123 -0
  525. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +70 -2
  526. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +29 -4
  527. package/src/task-orchestrator/surfaces/pod-mirror-seed.ts +11 -8
  528. package/src/task-orchestrator/surfaces/pod-si-warm-protocol.ts +1 -1
  529. package/src/task-orchestrator/surfaces/pod-source-receive.ts +74 -6
  530. package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +2 -1
  531. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +56 -15
  532. package/src/task-orchestrator/surfaces/self-implement-pod.ts +298 -45
  533. package/src/task-orchestrator/surfaces/self-implement.ts +18 -0
  534. package/src/tool-runtime/skill-exec-runtime.ts +95 -5
  535. package/src/tui/entry.ts +25 -5
  536. package/src/tui/inline-choice.ts +188 -0
  537. package/src/tui/sidetab/list.ts +36 -165
  538. package/src/tui/sidetab/nl-tool.ts +2 -136
  539. package/src/tui/sidetab/peek.ts +52 -0
  540. package/src/tui/slash/term.ts +12 -1
  541. package/src/tui-client/dashboard-session.ts +10 -1
  542. package/src/user-config.ts +45 -9
  543. package/src/ux-sim/classic-tui-seat-scenarios.ts +1 -7
  544. package/src/ux-sim/fake-land-queue.ts +88 -0
  545. package/src/ux-sim/fault-needs-human-reason-repair.ts +107 -0
  546. package/src/ux-sim/fault-pod-child-result.ts +42 -0
  547. package/src/ux-sim/gate-shard-host-reroute.ts +82 -0
  548. package/src/ux-sim/goal-interim-trace.ts +24 -0
  549. package/src/ux-sim/next-tui-seat-scenarios.ts +8 -13
  550. package/src/ux-sim/release-sim-rig.ts +262 -0
  551. package/src/ux-sim/seminar-rehearsal.ts +109 -0
  552. package/src/ux-sim/subagent-tools-device.ts +2 -2
  553. package/src/ux-sim/task-orchestrator-surfaces-self-implement-pod-device.ts +4 -1
  554. package/src/version/packed-revision.json +1 -1
  555. package/src/video-pipeline/recipes/explainer.ts +54 -2
  556. package/src/web-search/grok.ts +32 -6
  557. package/src/web-search/index.ts +8 -7
  558. package/src/workflow-runtime/node-catalog.ts +53 -4
  559. package/apps/pwa/out/_next/static/chunks/1118.a8a74ea29aac3e46.js +0 -27
  560. package/apps/pwa/out/_next/static/chunks/1204-2aa830479adf80f4.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/1341-43b7d2dc77d45c9d.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/1440-c3ea8882badfd9bf.js +0 -1
  563. package/apps/pwa/out/_next/static/chunks/1698-9c11cbf97f13717e.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/1770-3b1fc92253ef9f2f.js +0 -1
  565. package/apps/pwa/out/_next/static/chunks/1923-5c1695332240246a.js +0 -1
  566. package/apps/pwa/out/_next/static/chunks/1938.0e6c60ee91c4d7f5.js +0 -148
  567. package/apps/pwa/out/_next/static/chunks/2104.70e086d5174a4736.js +0 -1
  568. package/apps/pwa/out/_next/static/chunks/213-20675f20673ba22a.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/214.186a430da28338f1.js +0 -1
  570. package/apps/pwa/out/_next/static/chunks/2277.3bd4c0ac7f1b5996.js +0 -93
  571. package/apps/pwa/out/_next/static/chunks/240.e09f433f71c78ff4.js +0 -104
  572. package/apps/pwa/out/_next/static/chunks/2417.02151393605a7cd0.js +0 -1
  573. package/apps/pwa/out/_next/static/chunks/2463.cd2641a76daaa9ff.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/2576.b9e9c629c4b84c52.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/265-c021de53f0865bd9.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/2998-b1cc13956cd6a6e7.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/2e4782d3.ba2f0ab2b63a53e7.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/3130.626b54382b4dc379.js +0 -1
  579. package/apps/pwa/out/_next/static/chunks/3221-1bb3f99a55088613.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/3280.24edb1ffd03a7f3f.js +0 -24
  581. package/apps/pwa/out/_next/static/chunks/3288-eb3c22a1cbf2ae68.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/33.ef99e8374f96c6c4.js +0 -36
  583. package/apps/pwa/out/_next/static/chunks/3341-f09b9efbd7a82f70.js +0 -1
  584. package/apps/pwa/out/_next/static/chunks/3410-d7c0f14b329d6fda.js +0 -1
  585. package/apps/pwa/out/_next/static/chunks/3421.c87d6313a7d670f7.js +0 -287
  586. package/apps/pwa/out/_next/static/chunks/3422-83a3953f9bd67e26.js +0 -1
  587. package/apps/pwa/out/_next/static/chunks/3587-002e24e368c515a9.js +0 -1
  588. package/apps/pwa/out/_next/static/chunks/3644.f12a7d821ab940af.js +0 -1
  589. package/apps/pwa/out/_next/static/chunks/3660.d7516588e2d875c3.js +0 -1
  590. package/apps/pwa/out/_next/static/chunks/3695.b82e2610f4cd4ea9.js +0 -1
  591. package/apps/pwa/out/_next/static/chunks/3858.ca1f5bc0f577eac9.js +0 -94
  592. package/apps/pwa/out/_next/static/chunks/3959-e6ac4a139d9d4bcc.js +0 -1
  593. package/apps/pwa/out/_next/static/chunks/3f0de3de-2340c532c00395db.js +0 -1
  594. package/apps/pwa/out/_next/static/chunks/4032.53f62176514ec0ea.js +0 -1
  595. package/apps/pwa/out/_next/static/chunks/4199-f7c6eb40cd0322f8.js +0 -1
  596. package/apps/pwa/out/_next/static/chunks/4204-ef8484cbecd5d62f.js +0 -1
  597. package/apps/pwa/out/_next/static/chunks/4259-163129ca79913c4b.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/428-eaa77df3d0c85969.js +0 -1
  599. package/apps/pwa/out/_next/static/chunks/4432-97e400ffa1e161f5.js +0 -1
  600. package/apps/pwa/out/_next/static/chunks/449-aa427cf2cbb75f50.js +0 -1
  601. package/apps/pwa/out/_next/static/chunks/4491-39a0fd32bbaf3840.js +0 -1
  602. package/apps/pwa/out/_next/static/chunks/4588.b7cf06d382953979.js +0 -1
  603. package/apps/pwa/out/_next/static/chunks/4690.70ec8060825366ab.js +0 -32
  604. package/apps/pwa/out/_next/static/chunks/4804.6012b5cd33c22792.js +0 -90
  605. package/apps/pwa/out/_next/static/chunks/4881.e4d22d730e3b9df5.js +0 -1
  606. package/apps/pwa/out/_next/static/chunks/4937.ce2e340dc29cad3e.js +0 -79
  607. package/apps/pwa/out/_next/static/chunks/5-bb1c08d84bd71df3.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/5005.de98df23b37cdf3c.js +0 -1
  609. package/apps/pwa/out/_next/static/chunks/5035.dc64a89cad5573cc.js +0 -74
  610. package/apps/pwa/out/_next/static/chunks/5041-5aada92ca215ffdd.js +0 -1
  611. package/apps/pwa/out/_next/static/chunks/5207.1a224035455790fd.js +0 -1
  612. package/apps/pwa/out/_next/static/chunks/5255-1b60d50eabe9f057.js +0 -1
  613. package/apps/pwa/out/_next/static/chunks/5269.d0436ee6d773382a.js +0 -286
  614. package/apps/pwa/out/_next/static/chunks/5309.3367c4d50f52b489.js +0 -1
  615. package/apps/pwa/out/_next/static/chunks/552-689827d49780fa00.js +0 -1
  616. package/apps/pwa/out/_next/static/chunks/5539.34cc9d60622cb69b.js +0 -8
  617. package/apps/pwa/out/_next/static/chunks/5568.218dea0c00ca2ec7.js +0 -1
  618. package/apps/pwa/out/_next/static/chunks/5645.b7e3b055455a04ea.js +0 -1
  619. package/apps/pwa/out/_next/static/chunks/5662.18cc0f30353cbd96.js +0 -1
  620. package/apps/pwa/out/_next/static/chunks/5671.da24fc9dc5e98325.js +0 -1
  621. package/apps/pwa/out/_next/static/chunks/5b141171.324c40fd49cb6821.js +0 -1
  622. package/apps/pwa/out/_next/static/chunks/6175-d4621f6a10b0cfab.js +0 -1
  623. package/apps/pwa/out/_next/static/chunks/6290.3eb53a20746b72ce.js +0 -1
  624. package/apps/pwa/out/_next/static/chunks/6415-9307f3eafa90837f.js +0 -1
  625. package/apps/pwa/out/_next/static/chunks/6579-3de35d00aacd71b5.js +0 -1
  626. package/apps/pwa/out/_next/static/chunks/680-2f324c4e039a5b67.js +0 -1
  627. package/apps/pwa/out/_next/static/chunks/6845-331b3c7481da3ba3.js +0 -1
  628. package/apps/pwa/out/_next/static/chunks/7000.28157393caaf5343.js +0 -62
  629. package/apps/pwa/out/_next/static/chunks/7311-60a7ea80d23c7521.js +0 -1
  630. package/apps/pwa/out/_next/static/chunks/7401.525d0a12f7d02c06.js +0 -1
  631. package/apps/pwa/out/_next/static/chunks/7438.c2cf72b66dca6aa4.js +0 -1
  632. package/apps/pwa/out/_next/static/chunks/7488.966b116789f3d41a.js +0 -1
  633. package/apps/pwa/out/_next/static/chunks/7524.ce973525d30b3d52.js +0 -1
  634. package/apps/pwa/out/_next/static/chunks/7577.ea6e9ac72e035520.js +0 -1
  635. package/apps/pwa/out/_next/static/chunks/7628.6b16863ac3972333.js +0 -34
  636. package/apps/pwa/out/_next/static/chunks/7657.8d1fb3d2f7ebe273.js +0 -1
  637. package/apps/pwa/out/_next/static/chunks/7903-1435d5d2f0dd7807.js +0 -1
  638. package/apps/pwa/out/_next/static/chunks/7954.4f7a8832663ee979.js +0 -115
  639. package/apps/pwa/out/_next/static/chunks/7978-30a91192c5dbe3c2.js +0 -1
  640. package/apps/pwa/out/_next/static/chunks/8025-8795821525cf1a07.js +0 -1
  641. package/apps/pwa/out/_next/static/chunks/8191.3674a00f0b565c23.js +0 -1
  642. package/apps/pwa/out/_next/static/chunks/8217-604955d8885c5ef2.js +0 -1
  643. package/apps/pwa/out/_next/static/chunks/8298-eed6f5f2585a63f4.js +0 -1
  644. package/apps/pwa/out/_next/static/chunks/8509.3a54d4e61893d7cb.js +0 -1
  645. package/apps/pwa/out/_next/static/chunks/8758.ac421f726a189125.js +0 -1
  646. package/apps/pwa/out/_next/static/chunks/891.d6c723a51a067b36.js +0 -149
  647. package/apps/pwa/out/_next/static/chunks/9223.cbcc88e0e9950426.js +0 -41
  648. package/apps/pwa/out/_next/static/chunks/9299.24e2a7d9b6d1f91b.js +0 -226
  649. package/apps/pwa/out/_next/static/chunks/9317.6a9869039c231bb3.js +0 -24
  650. package/apps/pwa/out/_next/static/chunks/9383.e4755f275b3c11ff.js +0 -1
  651. package/apps/pwa/out/_next/static/chunks/9581.da63ddf41acfad46.js +0 -1
  652. package/apps/pwa/out/_next/static/chunks/9715.8c5529a0a5c41a36.js +0 -1
  653. package/apps/pwa/out/_next/static/chunks/9817.3009d65f0889ef1f.js +0 -2
  654. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-76873dd80da4f715.js +0 -1
  655. package/apps/pwa/out/_next/static/chunks/app/approvals/page-ed32b6ec5dea8f23.js +0 -1
  656. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-fdd40e15babf3d25.js +0 -1
  657. package/apps/pwa/out/_next/static/chunks/app/board/page-384efc78e85e400b.js +0 -1
  658. package/apps/pwa/out/_next/static/chunks/app/bots/page-d91809c5aeed319a.js +0 -1
  659. package/apps/pwa/out/_next/static/chunks/app/ceo/page-128f3522f89a81ab.js +0 -1
  660. package/apps/pwa/out/_next/static/chunks/app/chat/page-595961abf3b43ad1.js +0 -1
  661. package/apps/pwa/out/_next/static/chunks/app/consult/page-f41dccb76d8c0263.js +0 -1
  662. package/apps/pwa/out/_next/static/chunks/app/control/page-a3205c2e28b7d0d9.js +0 -1
  663. package/apps/pwa/out/_next/static/chunks/app/decisions/page-c39a80277814a3f4.js +0 -1
  664. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1ab3f98194513195.js +0 -1
  665. package/apps/pwa/out/_next/static/chunks/app/editor/page-a5246f7e254d77d0.js +0 -1
  666. package/apps/pwa/out/_next/static/chunks/app/exec/page-0f92287bdb81217d.js +0 -1
  667. package/apps/pwa/out/_next/static/chunks/app/field/page-5a37131ff63793ef.js +0 -1
  668. package/apps/pwa/out/_next/static/chunks/app/healthz/route-76873dd80da4f715.js +0 -1
  669. package/apps/pwa/out/_next/static/chunks/app/inside/page-ac4bd14bb2348bfd.js +0 -1
  670. package/apps/pwa/out/_next/static/chunks/app/intake/page-c348a3959c5b5fc6.js +0 -1
  671. package/apps/pwa/out/_next/static/chunks/app/layout-2addd81c0d3e2906.js +0 -1
  672. package/apps/pwa/out/_next/static/chunks/app/live/page-da6b765f58a3eefb.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/app/live-run/page-0661e40f8780c32c.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/app/loops/page-b89af1b1c8e4875b.js +0 -1
  675. package/apps/pwa/out/_next/static/chunks/app/market/page-da8aeb82ba751cdb.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-cc71317b239439db.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/app/morning/page-bde7fc1d64ffe369.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/app/not-found-e2797214f713fa58.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/app/observatory/page-2a35bcda70defaf6.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-3af03deedde45032.js +0 -1
  681. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d1d1210f99f620ae.js +0 -1
  682. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-46d1b5500885861d.js +0 -1
  683. package/apps/pwa/out/_next/static/chunks/app/outputs/page-291ab19fe2acb173.js +0 -1
  684. package/apps/pwa/out/_next/static/chunks/app/page-7f569e533ddeecba.js +0 -1
  685. package/apps/pwa/out/_next/static/chunks/app/reflection/page-990129c9fee46d8f.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-7737f785ebb1cdaf.js +0 -1
  687. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f3c73a4f7ffd2f8.js +0 -1
  688. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-03be7fb347f01058.js +0 -1
  689. package/apps/pwa/out/_next/static/chunks/app/settings/page-96b0280625e4df2b.js +0 -1
  690. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-56855545049eb56d.js +0 -1
  691. package/apps/pwa/out/_next/static/chunks/app/setup/layout-76873dd80da4f715.js +0 -1
  692. package/apps/pwa/out/_next/static/chunks/app/setup/page-79a909c9981f0338.js +0 -1
  693. package/apps/pwa/out/_next/static/chunks/app/share/page-a9d9a3e66459c1e0.js +0 -1
  694. package/apps/pwa/out/_next/static/chunks/app/showroom/page-c383cbf2013f7069.js +0 -1
  695. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f57c6a2e54dbdca4.js +0 -1
  696. package/apps/pwa/out/_next/static/chunks/app/term/page-6b140f77593576eb.js +0 -1
  697. package/apps/pwa/out/_next/static/chunks/app/today/page-6b767ae2d9d0adfc.js +0 -1
  698. package/apps/pwa/out/_next/static/chunks/app/trace/page-116d6198d5da0e3d.js +0 -1
  699. package/apps/pwa/out/_next/static/chunks/app/vault/page-b9c9b778c7501370.js +0 -1
  700. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-a6c78392ab8f6335.js +0 -1
  701. package/apps/pwa/out/_next/static/chunks/app/workflows/page-455183deb4ed0cfe.js +0 -1
  702. package/apps/pwa/out/_next/static/chunks/app/workspace/page-6a63e99526bd63d0.js +0 -1
  703. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31c22caafe788c08.js +0 -1
  704. package/apps/pwa/out/_next/static/chunks/bb6987f9.3647e1a23b0e3217.js +0 -122
  705. package/apps/pwa/out/_next/static/chunks/framework-4b12a946f352cee2.js +0 -1
  706. package/apps/pwa/out/_next/static/chunks/main-941155250ed2644b.js +0 -1
  707. package/apps/pwa/out/_next/static/chunks/main-app-f8b6765abed6d149.js +0 -1
  708. package/apps/pwa/out/_next/static/chunks/pages/_app-78432194c85388f9.js +0 -1
  709. package/apps/pwa/out/_next/static/chunks/pages/_error-cdf7390829512b1b.js +0 -1
  710. package/apps/pwa/out/_next/static/chunks/webpack-d5f0964a9c2e92d2.js +0 -1
  711. package/scripts/ratchets/next-tui-entry-budget.ts +0 -124
  712. /package/apps/pwa/out/_next/static/{RrjXTwz-urRAbChP1bX02 → GiOLWmt4MqDZePubBjA4Q}/_ssgManifest.js +0 -0
package/src/llm.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  // Auto-routes based on model name prefix, or picks the first available provider.
4
4
 
5
5
  import { wellFormedLLMMessages } from './llm-wellformed.js';
6
+ import { truncateOutput } from './output-truncation.js';
6
7
  import { createHash, randomUUID } from 'node:crypto';
7
8
  import { testFakeLlmEnabled, testFakeLlmProvider } from './ux-sim/fake-llm.js';
8
9
  import { realpathSync } from 'node:fs';
@@ -269,6 +270,10 @@ export interface LLMMessage {
269
270
  role: 'system' | 'user' | 'assistant';
270
271
  /** Plain string for text-only messages; ContentBlock[] when images are mixed in. */
271
272
  content: string | ContentBlock[];
273
+ /** Codex Responses message phase, preserved when replaying a stateless turn. */
274
+ phase?: 'commentary' | 'partial_answer' | 'final_answer';
275
+ /** Opaque Codex stateless reasoning items, before this assistant message. */
276
+ codexReasoningItems?: Array<{ type: 'reasoning'; encrypted_content: string; summary: unknown[]; id?: string }>;
272
277
  }
273
278
 
274
279
  /** JSONSchema-shaped tool definition sent to the provider (no handler). */
@@ -284,6 +289,21 @@ function toolUseNames(messages: readonly LLMMessage[]): string[] {
284
289
  : []);
285
290
  }
286
291
 
292
+ /** Return paths for legacy tuple-form `items` schemas without logging schema
293
+ * values. Provider 400s often omit the rejected tool name, so these paths let
294
+ * the next TUI recurrence identify the source contract without logging args. */
295
+ function collectTupleItemsSchemaPaths(schema: unknown, path = 'parameters'): string[] {
296
+ if (Array.isArray(schema)) {
297
+ return schema.flatMap((value, index) => collectTupleItemsSchemaPaths(value, `${path}[${index}]`));
298
+ }
299
+ if (!schema || typeof schema !== 'object') return [];
300
+ return Object.entries(schema as Record<string, unknown>).flatMap(([key, value]) => {
301
+ const childPath = `${path}.${key}`;
302
+ const tuplePath = key === 'items' && Array.isArray(value) ? [childPath] : [];
303
+ return [...tuplePath, ...collectTupleItemsSchemaPaths(value, childPath)];
304
+ });
305
+ }
306
+
287
307
  function mapProviderToolNamesInMessages(
288
308
  messages: readonly LLMMessage[],
289
309
  mapName: (name: string) => string,
@@ -340,7 +360,10 @@ function restoreProviderToolCallNames(
340
360
  * (currently Anthropic only) — consumers that don't care should
341
361
  * ignore it; `textOnly()` filters it out. */
342
362
  export type LLMStreamEvent =
343
- | { type: 'text'; delta: string }
363
+ | { type: 'text'; delta: string; phase?: 'commentary' | 'final' }
364
+ | { type: 'text_phase'; phase: 'commentary' | 'final' }
365
+ | { type: 'turn_end'; endTurn: boolean }
366
+ | { type: 'codex_reasoning_item'; item: { type: 'reasoning'; encrypted_content: string; summary: unknown[]; id?: string } }
344
367
  | {
345
368
  type: 'tool_call';
346
369
  id: string;
@@ -415,6 +438,27 @@ export interface LLMOpts {
415
438
  streamIdleTimeoutMs?: number;
416
439
  /** Optional per-request usage telemetry from the provider stream. */
417
440
  onUsage?(usage: LLMUsage): void;
441
+ /** Optional request-size policy. Providers that expose a serialized request
442
+ * can compact the normalized conversation before sending it. The callback
443
+ * is opt-in; providers without a serializer hook preserve their wire path. */
444
+ requestSizeGuard?: {
445
+ maxChars: number;
446
+ compactMessages: (
447
+ messages: LLMMessage[],
448
+ info: { provider: string; requestChars: number; maxChars: number },
449
+ ) => Promise<{ messages: LLMMessage[]; reason: string } | null>;
450
+ onMeasured?: (info: {
451
+ provider: string;
452
+ requestChars: number;
453
+ maxChars: number;
454
+ compacted: boolean;
455
+ reason: string;
456
+ previousChars?: number;
457
+ }) => void;
458
+ };
459
+ /** Optional cap for text tool results accumulated into the conversation history.
460
+ * Oversized results are spilled to a local file and represented by head/tail. */
461
+ toolResultInlineLimitBytes?: number;
418
462
  /** Optional model role attached to this request's usage observation. */
419
463
  usageRole?: ModelRole;
420
464
  /** Optional per-call metering phase; absent calls keep the existing ledger shape. */
@@ -476,6 +520,9 @@ export interface LLMOpts {
476
520
  * 그러면 종결 신호 뒤 빈 턴은 이 스트림에 쓴 답이 없어도 재촉·`[NO FINAL SYNTHESIS]` 없이 빈 답으로 끝낸다.
477
521
  * 비우면(기본) 종전대로 — 답이 «어디에도» 없을 때의 경고는 그대로 보인다. */
478
522
  terminalPriorAnswerVisible?: boolean;
523
+ /** Goal-loop only: a validated completion ends the provider loop after tool results settle.
524
+ * Omitted for ordinary terminal tools, which may need a further answer turn. */
525
+ terminalCompletionAccepted?: (name: string, args: Record<string, unknown>) => boolean;
479
526
  /** Prompt-caching toggle. Honored by Anthropic (attaches
480
527
  * cache_control markers on system + tools + 2nd-to-last history
481
528
  * message) and OpenAI (passes stream_options.include_usage so
@@ -1628,7 +1675,8 @@ type ResponsesContentBlock =
1628
1675
  * codex answer began with "tool_result 가 없다" because, from the
1629
1676
  * model's perspective, no tool result ever arrived. */
1630
1677
  type ResponsesInputItem =
1631
- | { role: string; content: ResponsesContentBlock[] }
1678
+ | { role: string; content: ResponsesContentBlock[]; phase?: 'commentary' | 'partial_answer' | 'final_answer' }
1679
+ | { type: 'reasoning'; encrypted_content: string; summary: unknown[]; id?: string }
1632
1680
  | { type: 'function_call'; name: string; arguments: string; call_id: string }
1633
1681
  // Image-pipeline P3 (2026-05-05) — `output` accepts string OR a
1634
1682
  // ContentItem[] mirroring the user-message content block shape.
@@ -1655,6 +1703,8 @@ type ResponsesInputItem =
1655
1703
  * Codex variants — current gpt-5 family all true per allowlist). */
1656
1704
  export interface MessagesToResponsesInputOpts {
1657
1705
  acceptToolImages?: boolean;
1706
+ /** Server-stored continuation already contains prior reasoning items. */
1707
+ replayCodexReasoning?: boolean;
1658
1708
  acceptUserMessageImages?: boolean;
1659
1709
  onOrphanToolResult?: (callId: string) => void;
1660
1710
  }
@@ -1827,6 +1877,9 @@ export function messagesToResponsesInput(
1827
1877
  continue;
1828
1878
  }
1829
1879
 
1880
+ // Stateless Codex requires the opaque reasoning item in its original
1881
+ // position before the corresponding assistant message/tool calls.
1882
+ if (m.role === 'assistant' && opts.replayCodexReasoning !== false && m.codexReasoningItems) input.push(...m.codexReasoningItems);
1830
1883
  // Assemble the final content block list: text first (matches
1831
1884
  // how users usually write prompts — "describe this image" then
1832
1885
  // attachment), images second. Responses API is order-sensitive
@@ -1844,7 +1897,7 @@ export function messagesToResponsesInput(
1844
1897
  // representation — emitting an empty Message wrapper would just
1845
1898
  // add a stray empty-text block to the conversation history.
1846
1899
  if (content.length > 0) {
1847
- input.push({ role: m.role, content });
1900
+ input.push({ role: m.role, content, ...(m.role === 'assistant' && m.phase ? { phase: m.phase } : {}) });
1848
1901
  }
1849
1902
  input.push(...trailingItems);
1850
1903
  }
@@ -2175,6 +2228,41 @@ function mapReasoningLevelToGeminiThinking(
2175
2228
  /** Stream against /responses (Codex, OpenAI Responses API). Exported
2176
2229
  * so tests can exercise header/body shape without spinning up the
2177
2230
  * full makeCodexProvider path. */
2231
+ function buildCodexResponsesWireBody(
2232
+ body: {
2233
+ model: string;
2234
+ instructions: string;
2235
+ input: unknown[];
2236
+ tools?: unknown[];
2237
+ tool_choice?: unknown;
2238
+ reasoning?: { effort?: 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'; summary?: 'auto' | 'concise' | 'detailed' };
2239
+ store?: boolean;
2240
+ previousResponseId?: string;
2241
+ },
2242
+ promptCache: boolean,
2243
+ ): Record<string, unknown> {
2244
+ const cacheKey = promptCache ? computePromptCacheKey(body.model, body.instructions) : undefined;
2245
+ return {
2246
+ ...body,
2247
+ stream: true,
2248
+ store: body.store === true,
2249
+ ...(body.tool_choice ? { tool_choice: body.tool_choice } : {}),
2250
+ ...(body.previousResponseId ? { previous_response_id: body.previousResponseId } : {}),
2251
+ ...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
2252
+ ...(body.reasoning ? {
2253
+ reasoning: body.reasoning,
2254
+ include: ['reasoning.encrypted_content'],
2255
+ } : {}),
2256
+ };
2257
+ }
2258
+
2259
+ function serializeCodexResponsesWireBody(
2260
+ body: Parameters<typeof buildCodexResponsesWireBody>[0],
2261
+ promptCache: boolean,
2262
+ ): string {
2263
+ return JSON.stringify(buildCodexResponsesWireBody(body, promptCache));
2264
+ }
2265
+
2178
2266
  export async function* streamCodexResponsesEvents(
2179
2267
  url: string,
2180
2268
  bearer: string,
@@ -2221,6 +2309,9 @@ export async function* streamCodexResponsesEvents(
2221
2309
  /** Opt-out hook (tests). When false, skips the
2222
2310
  * `prompt_cache_key` body field; production always passes true. */
2223
2311
  promptCache?: boolean;
2312
+ /** Exact body string already serialized by the request-size preflight.
2313
+ * Passing it through ensures the measured bytes are the bytes fetched. */
2314
+ serializedWireBody?: string;
2224
2315
  /** Callback fired when the SSE stream emits `response.created`
2225
2316
  * carrying the new response's id. Used by makeCodexProvider to
2226
2317
  * capture the id and thread `previousResponseId` on the next
@@ -2274,32 +2365,16 @@ export async function* streamCodexResponsesEvents(
2274
2365
  // value codex_cli_rs ships) so the backend
2275
2366
  // applies the same tool/SSE conventions
2276
2367
  const promptCache = extra.promptCache !== false;
2277
- const cacheKey = promptCache ? computePromptCacheKey(body.model, body.instructions) : undefined;
2278
- // TUI-OBS-400-SHAPE(#69) — 전송 객체·문자열을 한 번 만들어 둔다: 4xx 거절 때 «실제로 보낸 것» 의 모양을 잰다.
2279
- const wireBody: Record<string, unknown> = {
2280
- ...body,
2281
- stream: true,
2282
- // store=true wave: when caller opts in, the backend persists the
2283
- // response and the next request can thread `previous_response_id`
2284
- // to send only the delta input items. Default still false so any
2285
- // caller that doesn't opt in (tests building bodies directly)
2286
- // sees the legacy wire shape.
2368
+ // TUI-REQUEST-SIZE-COMPACT — the exact serializer is shared with the
2369
+ // preflight guard, so the size decision and fetch body cannot drift.
2370
+ const wireBody = buildCodexResponsesWireBody(body, promptCache);
2371
+ const wireJson = extra.serializedWireBody ?? JSON.stringify(wireBody);
2372
+ debug.log('llm.request', 'request-size', {
2373
+ provider: 'openai-codex',
2374
+ requestChars: wireJson.length,
2287
2375
  store: body.store === true,
2288
- ...(body.tool_choice ? { tool_choice: body.tool_choice } : {}),
2289
- ...(body.previousResponseId ? { previous_response_id: body.previousResponseId } : {}),
2290
- ...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
2291
- // Reasoning opt-in (gpt-5 family). When the caller sets
2292
- // body.reasoning we mirror it onto the wire AND append the
2293
- // include directive that lets multi-turn requests thread the
2294
- // model's encrypted reasoning context. Omit both when absent
2295
- // so non-reasoning models / opt-out users see the legacy
2296
- // wire shape unchanged.
2297
- ...(body.reasoning ? {
2298
- reasoning: body.reasoning,
2299
- include: ['reasoning.encrypted_content'],
2300
- } : {}),
2301
- };
2302
- const wireJson = JSON.stringify(wireBody);
2376
+ previousResponseId: body.previousResponseId ? `${body.previousResponseId.slice(0, 12)}…` : null,
2377
+ });
2303
2378
  const response = await fetchApiWithRetry(
2304
2379
  url,
2305
2380
  {
@@ -2328,6 +2403,10 @@ export async function* streamCodexResponsesEvents(
2328
2403
  debug.log('llm.response.status', `codex ${response.status}`, {
2329
2404
  url, status: response.status, elapsedMs: Date.now() - startedAt,
2330
2405
  });
2406
+ let messagePhase: 'commentary' | 'final' | undefined;
2407
+ const codexTextPhase = (value: unknown): 'commentary' | 'final' | undefined =>
2408
+ value === 'commentary' || value === 'partial_answer' ? 'commentary'
2409
+ : value === 'final_answer' ? 'final' : undefined;
2331
2410
  try {
2332
2411
  for await (const line of sseLineStream(response.body!)) {
2333
2412
  if (!line.startsWith('data: ')) continue;
@@ -2347,15 +2426,32 @@ export async function* streamCodexResponsesEvents(
2347
2426
  extra.onResponseCreated(id);
2348
2427
  }
2349
2428
  }
2429
+ if (type === 'response.output_item.added' || type === 'response.output_item.done') {
2430
+ if (parsed.item?.type === 'message') {
2431
+ const phase = codexTextPhase(parsed.item.phase);
2432
+ if (type === 'response.output_item.added') messagePhase = phase;
2433
+ if (phase) {
2434
+ messagePhase = phase;
2435
+ yield { type: 'text_phase', phase };
2436
+ }
2437
+ }
2438
+ }
2350
2439
  // Text tokens — both `response.output_text.delta` (new naming) and
2351
2440
  // raw `output_text.delta` (older streams).
2352
2441
  if (type.endsWith('output_text.delta')) {
2353
2442
  const delta = parsed.delta || '';
2354
- if (delta) yield { type: 'text', delta };
2443
+ if (delta) yield { type: 'text', delta, ...(messagePhase ? { phase: messagePhase } : {}) };
2355
2444
  }
2356
2445
  // Tool calls come in as `response.output_item.done` with item.type = 'function_call'.
2357
2446
  if (type === 'response.output_item.done') {
2358
2447
  const item = parsed.item;
2448
+ if (item?.type === 'reasoning' && typeof item.encrypted_content === 'string' && item.encrypted_content) {
2449
+ yield { type: 'codex_reasoning_item', item: {
2450
+ type: 'reasoning', encrypted_content: item.encrypted_content,
2451
+ summary: Array.isArray(item.summary) ? item.summary : [],
2452
+ ...(typeof item.id === 'string' ? { id: item.id } : {}),
2453
+ } };
2454
+ }
2359
2455
  if (item && item.type === 'function_call') {
2360
2456
  let args: Record<string, unknown> = {};
2361
2457
  try { args = item.arguments ? JSON.parse(item.arguments) : {}; } catch { /* empty */ }
@@ -2410,6 +2506,9 @@ export async function* streamCodexResponsesEvents(
2410
2506
  // hit rate for codex regardless. See store=true wave (next+1)
2411
2507
  // for the volume-side counterpart.
2412
2508
  if (type === 'response.completed') {
2509
+ if (typeof parsed.response?.end_turn === 'boolean') {
2510
+ yield { type: 'turn_end', endTurn: parsed.response.end_turn };
2511
+ }
2413
2512
  const u = parsed.response?.usage;
2414
2513
  if (u && typeof u === 'object') {
2415
2514
  const cached = u.input_tokens_details?.cached_tokens
@@ -4474,6 +4573,13 @@ function explainAndLogFallbackCandidates(
4474
4573
 
4475
4574
  /** 스트림 중 끊김 재시도 상한 — codex `DEFAULT_STREAM_MAX_RETRIES`(5) 와 같은 값. */
4476
4575
  const STREAM_DISCONNECT_MAX_RETRIES = 5;
4576
+ class StreamIdleTimeoutError extends Error {
4577
+ constructor(readonly idleMs: number) {
4578
+ super(`stream idle timeout after ${idleMs}ms`);
4579
+ this.name = 'StreamIdleTimeoutError';
4580
+ Object.assign(this, { code: 'ETIMEDOUT' });
4581
+ }
4582
+ }
4477
4583
  const STREAM_IDLE_TIMEOUT_MAX_RETRIES = 1;
4478
4584
  let streamDisconnectRetryDelayMs: (attempt: number) => number = backoffMs;
4479
4585
 
@@ -4799,6 +4905,20 @@ type ProviderTextResult =
4799
4905
  | { ok: true; full: string; usage: ObservedUsage | 'unmeasured' }
4800
4906
  | { ok: false; full: string; error: unknown };
4801
4907
 
4908
+ function resolveStreamIdleTimeout(opts: LLMOpts, provider: LLMProvider, model: string): { ms: number; source: 'call' | 'config' | 'default' } {
4909
+ if (opts.streamIdleTimeoutMs !== undefined
4910
+ && Number.isFinite(opts.streamIdleTimeoutMs) && opts.streamIdleTimeoutMs > 0) {
4911
+ return { ms: opts.streamIdleTimeoutMs, source: 'call' };
4912
+ }
4913
+ let configured: number | undefined;
4914
+ try {
4915
+ configured = (require('./user-config.js') as typeof import('./user-config.js')).getUserConfig().llm.streamIdleTimeoutMs;
4916
+ } catch { /* config lookup is fail-soft; preserve provider defaults */ }
4917
+ if (configured !== undefined) return { ms: configured, source: 'config' };
4918
+ const longIdle = usesLongReasoningIdle(getModelFamily(model), provider.name);
4919
+ return { ms: longIdle ? STREAM_IDLE_TIMEOUT_MS_REASONING : STREAM_IDLE_TIMEOUT_MS, source: 'default' };
4920
+ }
4921
+
4802
4922
  function accumulateUsage(usage: ObservedUsage | undefined, received: LLMUsage): ObservedUsage {
4803
4923
  const total = { ...usage };
4804
4924
  for (const field of ['inputTokens', 'outputTokens', 'cacheReadInputTokens', 'cacheCreationInputTokens', 'reportedCostUsd'] as const) {
@@ -4836,10 +4956,8 @@ async function consumeProviderText(
4836
4956
  // Preserve the provider or callback error that caused this close.
4837
4957
  }
4838
4958
  };
4839
- const idleMs = opts.streamIdleTimeoutMs ?? (
4840
- usesLongReasoningIdle(getModelFamily(opts.model ?? provider.defaultModel), provider.name)
4841
- ? STREAM_IDLE_TIMEOUT_MS_REASONING : STREAM_IDLE_TIMEOUT_MS
4842
- );
4959
+ const idleTimeout = resolveStreamIdleTimeout(opts, provider, opts.model ?? provider.defaultModel);
4960
+ const idleMs = idleTimeout.ms;
4843
4961
 
4844
4962
  while (true) {
4845
4963
  let idleTimer: ReturnType<typeof setTimeout> | undefined;
@@ -4860,10 +4978,9 @@ async function consumeProviderText(
4860
4978
  if (step.idle) {
4861
4979
  debug.log('llm', 'stream-idle-timeout', {
4862
4980
  provider: provider.name, model: opts.model ?? provider.defaultModel,
4863
- idleMs, receivedChars: full.length, site: 'stream-llm',
4981
+ idleMs, idleMsSource: idleTimeout.source, receivedChars: full.length, site: 'stream-llm',
4864
4982
  });
4865
- const error = new Error(`stream idle timeout after ${idleMs}ms`);
4866
- error.name = 'StreamIdleTimeoutError';
4983
+ const error = new StreamIdleTimeoutError(idleMs);
4867
4984
  // An async generator can leave return() pending behind a stalled next().
4868
4985
  void closeIterator();
4869
4986
  return { ok: false, full, error };
@@ -4945,6 +5062,7 @@ export async function streamLLM(
4945
5062
  textChars: full.length,
4946
5063
  usage: consumed.usage,
4947
5064
  });
5065
+ (await import('./session-runtime/provider-circuit.js')).recordProviderSuccess(activeProvider.name, Date.now());
4948
5066
  return full;
4949
5067
  }
4950
5068
 
@@ -4962,6 +5080,7 @@ export async function streamLLM(
4962
5080
  status: err.status,
4963
5081
  kind: err.kind,
4964
5082
  }, { level: 'warn' });
5083
+ (await import('./session-runtime/provider-circuit.js')).recordProviderFailure(activeProvider.name, 'overload-failover', Date.now());
4965
5084
  activeProvider = switched;
4966
5085
  modelOverride = undefined;
4967
5086
  attemptedProviders.add(activeProvider.name);
@@ -4998,6 +5117,7 @@ export async function streamLLM(
4998
5117
  tools: opts.tools?.map((tool) => tool.name) ?? [],
4999
5118
  ...(opts.model ? { modelOverrideDropped: opts.model } : {}),
5000
5119
  }, { level: 'warn' });
5120
+ (await import('./session-runtime/provider-circuit.js')).recordProviderFailure(activeProvider.name, verdict.category, Date.now());
5001
5121
  activeProvider = fallback;
5002
5122
  modelOverride = undefined;
5003
5123
  attemptedProviders.add(activeProvider.name);
@@ -5123,7 +5243,7 @@ export function resolveDefaultProvider(model?: string): LLMProvider {
5123
5243
  export interface StreamWithToolsHandlers {
5124
5244
  /** Streaming text delta — invoked for every text chunk across all turns. */
5125
5245
  onText(delta: string, fullAcrossTurns: string): void;
5126
- /** Optional status line for hosts that surface transient stream recovery. */
5246
+ /** Transient user-visible status emitted by stream recovery (not model text). */
5127
5247
  onStatus?(line: string): void;
5128
5248
  /** Dispatch a tool call. Returning undefined/null throws in the loop so
5129
5249
  * callers should always return a JSON-serializable value (can be an
@@ -7390,13 +7510,33 @@ function resultText(result: unknown): string {
7390
7510
  }
7391
7511
 
7392
7512
  /** skill_exec returns structured host metadata, but its model-facing tool result is the answer body. */
7393
- function toolResultContentForCall(toolName: string, result: unknown): string {
7513
+ function toolResultContentForCall(toolName: string, result: unknown, inlineLimitBytes?: number): string {
7514
+ let content: string;
7394
7515
  if (toolName === 'skill_exec' && result && typeof result === 'object'
7395
7516
  && typeof (result as { output?: unknown }).output === 'string') {
7396
- if ((result as { ok?: unknown }).ok === false) return JSON.stringify(result);
7397
- return (result as { output: string }).output;
7517
+ content = (result as { ok?: unknown }).ok === false
7518
+ ? JSON.stringify(result)
7519
+ : (result as { output: string }).output;
7520
+ } else {
7521
+ content = typeof result === 'string' ? result : JSON.stringify(result);
7522
+ }
7523
+ if (!Number.isFinite(inlineLimitBytes) || inlineLimitBytes === undefined || inlineLimitBytes <= 0) return content;
7524
+ const safeToolName = toolName.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 48) || 'tool';
7525
+ const capped = truncateOutput(content, {
7526
+ toolName: `tool-result-${safeToolName}`,
7527
+ inlineLimit: inlineLimitBytes,
7528
+ headBytes: Math.floor(inlineLimitBytes * 0.6),
7529
+ tailBytes: Math.floor(inlineLimitBytes * 0.2),
7530
+ });
7531
+ if (capped.spilled) {
7532
+ debug.log('llm.router', 'tool-result.output-spilled', {
7533
+ tool: safeToolName,
7534
+ originalBytes: capped.originalBytes,
7535
+ inlineLimitBytes,
7536
+ savedPath: capped.savedPath,
7537
+ });
7398
7538
  }
7399
- return typeof result === 'string' ? result : JSON.stringify(result);
7539
+ return capped.output;
7400
7540
  }
7401
7541
 
7402
7542
  function detectExecutionFailure(result: unknown): boolean {
@@ -8336,12 +8476,42 @@ export async function streamLLMWithTools(
8336
8476
  skipProviders?: readonly string[];
8337
8477
  } = {},
8338
8478
  ): Promise<string> {
8479
+ // Fixed providers keep the original synchronous launch path. Load the circuit before
8480
+ // selection only when it can change this turn's initial provider.
8481
+ const circuit = !opts.provider && !opts.initialProvider
8482
+ ? await import('./session-runtime/provider-circuit.js')
8483
+ : undefined;
8339
8484
  const provider = opts.provider ?? opts.initialProvider ?? resolveDefaultProvider(opts.model);
8340
8485
  const initial = finalizeStreamingProviderModel(provider, opts.model ?? provider.defaultModel);
8341
8486
  let activeProvider = initial.provider;
8342
8487
  let activeModel = initial.model;
8488
+ let circuitBlockedInitialProvider: string | undefined;
8489
+ // SURVIVAL-PROVIDER-CIRCUIT (S2): 명시·고정 공급자(`provider`·`initialProvider`)가 없고
8490
+ // 회로가 열려(open) 있으면, 체인 해석(`remainingFallbackProviderNames` 순서)의 첫
8491
+ // closed(없으면 half-open) 공급자로 이번 턴을 연다. 모두 open 이면 원래 1순위를 그대로
8492
+ // 쓴다(«갈 곳 없음 = 멈춤»이 아니다) — `circuit-all-open` 관측만 남긴다.
8493
+ if (circuit && circuit.circuitState(activeProvider.name, Date.now()) === 'open') {
8494
+ const chainNames = [activeProvider.name, ...remainingFallbackProviderNames(new Set(opts.skipProviders ?? []), opts)]
8495
+ .filter((name) => providerByName(name)?.available());
8496
+ const pickedName = circuit.pickInitialProvider(chainNames, Date.now());
8497
+ const picked = pickedName && pickedName !== activeProvider.name ? providerByName(pickedName) : undefined;
8498
+ if (picked && picked.available()) {
8499
+ circuitBlockedInitialProvider = activeProvider.name;
8500
+ debug.log('tui.survival', 'turn-opened-on-fallback', { blocked: activeProvider.name, opened: picked.name });
8501
+ handlers.onStatus?.(`⚡ ${activeProvider.name} 회로 열림 — 이번 턴은 ${picked.name}`);
8502
+ const pickedInitial = finalizeStreamingProviderModel(picked, picked.defaultModel);
8503
+ activeProvider = pickedInitial.provider;
8504
+ activeModel = pickedInitial.model;
8505
+ } else {
8506
+ debug.log('tui.survival', 'circuit-all-open', { blocked: activeProvider.name });
8507
+ }
8508
+ }
8343
8509
  opts.onResolvedProvider?.(activeProvider.name, activeModel);
8344
- const attemptedProviders = new Set<string>([activeProvider.name, ...(opts.skipProviders ?? [])]);
8510
+ const attemptedProviders = new Set<string>([
8511
+ activeProvider.name,
8512
+ ...(circuitBlockedInitialProvider ? [circuitBlockedInitialProvider] : []),
8513
+ ...(opts.skipProviders ?? []),
8514
+ ]);
8345
8515
  let blockedProviders: ProviderFallbackAttempt[] = [];
8346
8516
  const effectiveModel = activeModel;
8347
8517
  const modelFamily = getModelFamily(effectiveModel);
@@ -8361,7 +8531,7 @@ export async function streamLLMWithTools(
8361
8531
  tools: opts.tools?.map(t => t.name),
8362
8532
  toolsAvailable: !!provider.streamChat && (opts.tools?.length ?? 0) > 0,
8363
8533
  }, { level: 'info' });
8364
- if (!provider.streamChat || !opts.tools || opts.tools.length === 0) {
8534
+ if (!provider.streamChat || ((opts.tools?.length ?? 0) === 0 && provider.name !== 'openai-codex')) {
8365
8535
  // No tool support or no tools requested — delegate to plain streamLLM.
8366
8536
  debug.log('llm.router', 'streamLLMWithTools.fallback', {
8367
8537
  reason: !provider.streamChat ? 'provider-no-streamChat' : 'no-tools',
@@ -8522,6 +8692,8 @@ export async function streamLLMWithTools(
8522
8692
  }
8523
8693
  // OR-DEEPSEEK-TUNE — 마지막 턴의 «도구 없는» 추론(deepseek/* 만 채워진다). 매 턴 시작에 비운다.
8524
8694
  let turnReasoningDetails: Array<Record<string, unknown>> | undefined;
8695
+ let turnCodexReasoningItems: LLMMessage['codexReasoningItems'];
8696
+ let turnCodexTextPhase: LLMMessage['phase'];
8525
8697
  const emitTurnComplete = (finalAssistantText?: string): void => {
8526
8698
  if (finalAssistantText && finalAssistantText.length > 0) {
8527
8699
  history.push({
@@ -8530,18 +8702,23 @@ export async function streamLLMWithTools(
8530
8702
  type: 'text', text: finalAssistantText,
8531
8703
  ...(turnReasoningDetails ? { providerMeta: { reasoningDetails: turnReasoningDetails } } : {}),
8532
8704
  }],
8705
+ ...(turnCodexReasoningItems?.length ? { codexReasoningItems: turnCodexReasoningItems } : {}),
8706
+ ...(turnCodexTextPhase ? { phase: turnCodexTextPhase } : {}),
8533
8707
  });
8534
8708
  }
8535
8709
  handlers.onTurnComplete?.(history.slice(initialHistoryLen));
8536
8710
  };
8537
8711
  let fullText = '';
8538
8712
  let sawToolRound = false;
8713
+ let codexContinuationNeeded = false;
8539
8714
  // TUI-GOAL-LOOP-FRICTION — 종결 신호 도구(LLMOpts.terminalToolNames). 비면 아래 분기는 전부 꺼진다.
8540
8715
  const terminalToolNames: ReadonlySet<string> = new Set(opts.terminalToolNames ?? []);
8541
8716
  /** 이 스트림에서 종결 신호를 한 번이라도 디스패치했나. */
8542
8717
  let terminalSignalDispatched = false;
8718
+ let completionNeedsFinalTurn = false;
8543
8719
  /** 도구 라운드에 실린 마지막 «비지 않은» 답 텍스트(종결 신호 턴 마감·빈 턴 포기에서 쓴다). */
8544
8720
  let lastToolRoundText = '';
8721
+ let lastToolRoundTextPhase: 'commentary' | 'final' | undefined;
8545
8722
  const agentBatchTickIntervalMs = opts.agentBatchTickIntervalMs ?? BATCH_TICK_INTERVAL_MS;
8546
8723
 
8547
8724
  // Empty-turn guard: smaller models (gpt-5.4-mini, haiku) sometimes
@@ -8715,7 +8892,7 @@ export async function streamLLMWithTools(
8715
8892
  let loopTermination: 'budget-exhausted' | 'aborted' = 'budget-exhausted';
8716
8893
  let terminalTurn = maxTurns;
8717
8894
 
8718
- for (let turn = 0; turn < maxTurns; turn++) {
8895
+ for (let turn = 0; turn < maxTurns || ((completionNeedsFinalTurn || codexContinuationNeeded) && turn === maxTurns); turn++) {
8719
8896
  // ★ /cancel 범용화(대표 2026-07-12) — abort 신호가 오면 tool 루프를 즉시 중단한다. 이게 없으면
8720
8897
  // fetch 는 abort 돼도 루프가 다음 turn 으로 계속 돌아 tool 반복(grep 폭주 등)이 멈추지 않는다.
8721
8898
  if (opts.signal?.aborted) {
@@ -8748,6 +8925,11 @@ export async function streamLLMWithTools(
8748
8925
  }
8749
8926
  }
8750
8927
  let turnText = '';
8928
+ let turnTextPhase: 'commentary' | 'final' | undefined;
8929
+ let turnEnd: boolean | undefined;
8930
+ codexContinuationNeeded = false;
8931
+ turnCodexReasoningItems = undefined;
8932
+ turnCodexTextPhase = undefined;
8751
8933
  // TUI-TURN-NARRATION — 이 라운드에 받은 추론 요약 글자 수(내용은 남기지 않는다).
8752
8934
  let turnReasoningChars = 0;
8753
8935
  turnReasoningDetails = undefined;
@@ -8763,7 +8945,7 @@ export async function streamLLMWithTools(
8763
8945
  turn, historyLen: history.length,
8764
8946
  }, { level: 'info' });
8765
8947
 
8766
- // Mid-loop context backstop — with the codex/unlimited tool-loop cap
8948
+ // Turn context backstop — with the codex/unlimited tool-loop cap
8767
8949
  // removed, a long tool-heavy turn (esp. mission build) grows `history`
8768
8950
  // unbounded until it overflows the model context window and the provider
8769
8951
  // throws. ref codex / opencode bound exploration by COMPACTION, not a
@@ -8847,13 +9029,14 @@ export async function streamLLMWithTools(
8847
9029
  // revert to auto (see above).
8848
9030
  const buildTurnOpts = () => {
8849
9031
  const remainingFallbacks = opts.provider ? 0 : remainingFallbackProviderNames(attemptedProviders, opts).length;
8850
- const base = { ...opts, model: activeModel, tools: activeTools, remainingFallbacks };
9032
+ const base = { ...opts, model: activeModel, tools: completionNeedsFinalTurn ? [] : activeTools, remainingFallbacks };
8851
9033
  return turn === 0 ? base : { ...base, toolChoice: undefined };
8852
9034
  };
8853
9035
  let turnOpts = buildTurnOpts();
8854
9036
  // reasoning-heavy family(codex/claude/gemini/grok/local)는 침묵 추론 구간이 길어 idle watchdog 를
8855
9037
  // family-aware 로 상향(2026-07-19 goal-exec → 아크4 전-family). fast/mini(gpt·other)만 45s 유지.
8856
- const streamIdleTimeoutMs = opts.streamIdleTimeoutMs ?? (usesLongReasoningIdle(modelFamily, activeProvider.name) ? STREAM_IDLE_TIMEOUT_MS_REASONING : STREAM_IDLE_TIMEOUT_MS);
9038
+ const idleTimeout = resolveStreamIdleTimeout(opts, activeProvider, activeModel);
9039
+ const streamIdleTimeoutMs = idleTimeout.ms;
8857
9040
  // ⚠️ hang 진단(2026-07-17) — codex SSE 스트림이 완료 신호 없이 안 끝나는 케이스 확정용.
8858
9041
  // consume-start 후 consume-end 가 없으면 = for-await(streamChat SSE) hang. lastEvType 로 어느
8859
9042
  // 이벤트 뒤에서 멈추는지(usage/text/tool_call) 특정. 무장할 상한도 남겨 실험 런을 구별한다.
@@ -8862,6 +9045,7 @@ export async function streamLLMWithTools(
8862
9045
  provider: activeProvider.name,
8863
9046
  model: activeModel,
8864
9047
  idleMs: streamIdleTimeoutMs,
9048
+ idleMsSource: idleTimeout.source,
8865
9049
  ...(modelFamily !== 'other' ? { modelFamily } : {}),
8866
9050
  });
8867
9051
  let evCount = 0;
@@ -8870,6 +9054,7 @@ export async function streamLLMWithTools(
8870
9054
  // 다시 건다(codex `stream_max_retries` 동형). 이 턴에서 받은 tool_call·image 가 있으면 재시도하지 않는다.
8871
9055
  let streamDisconnectRetries = 0;
8872
9056
  let streamIdleTimeoutRetries = 0;
9057
+ let streamIdleRetryPartial = '';
8873
9058
  let streamEmittedImage = false;
8874
9059
  // 끊긴 조각을 이력(turnText·fullText)에서 «조용히» 되감는다 — 화면엔 지우라고 알리지 않는다
8875
9060
  // (TUI 의 빈 델타는 «커밋»이다). 이미 보인 글과 겹치는 재시도 본문은 `retryDisplay` 가 막는다.
@@ -8887,6 +9072,7 @@ export async function streamLLMWithTools(
8887
9072
  if (turnText.length > 0) {
8888
9073
  fullText = fullText.slice(0, fullText.length - turnText.length);
8889
9074
  turnText = '';
9075
+ turnTextPhase = undefined;
8890
9076
  }
8891
9077
  turnReasoningChars = 0;
8892
9078
  retryDisplay.arm();
@@ -8898,6 +9084,19 @@ export async function streamLLMWithTools(
8898
9084
  // 이벤트 대기를 idle timeout 과 race — STREAM_IDLE_TIMEOUT_MS 무이벤트면 스트림 정리·탈출(받은
8899
9085
  // 텍스트로 진행). 35분 무한 hang → 자동 복구. 원인 규명은 consume-end 부재/idle-timeout 로그가.
8900
9086
  let streamIter: AsyncIterator<LLMStreamEvent> | undefined;
9087
+ let streamAttemptAbort: AbortController | undefined;
9088
+ let streamAttemptAbortCleanup: (() => void) | undefined;
9089
+ const releaseStreamAttempt = (abort = false, reason?: unknown): void => {
9090
+ if (abort) streamAttemptAbort?.abort(reason);
9091
+ streamAttemptAbortCleanup?.();
9092
+ streamAttemptAbortCleanup = undefined;
9093
+ streamAttemptAbort = undefined;
9094
+ };
9095
+ let providerToolSchemaDiagnostics: Record<string, {
9096
+ originalName: string;
9097
+ providerName: string;
9098
+ tupleItemsPaths: Record<string, string>;
9099
+ }> | undefined;
8901
9100
  while (true) {
8902
9101
  let idleTimer: ReturnType<typeof setTimeout> | undefined;
8903
9102
  let step: { idle: true } | { idle: false; r: IteratorResult<LLMStreamEvent, void> };
@@ -8927,8 +9126,22 @@ export async function streamLLMWithTools(
8927
9126
  ? [turnOpts.toolChoice.name]
8928
9127
  : []),
8929
9128
  ]);
9129
+ const attemptAbort = new AbortController();
9130
+ const forwardExternalAbort = () => attemptAbort.abort(opts.signal?.reason);
9131
+ if (opts.signal?.aborted) forwardExternalAbort();
9132
+ else opts.signal?.addEventListener('abort', forwardExternalAbort, { once: true });
9133
+ streamAttemptAbort = attemptAbort;
9134
+ streamAttemptAbortCleanup = () => opts.signal?.removeEventListener('abort', forwardExternalAbort);
9135
+ providerToolSchemaDiagnostics = Object.fromEntries((turnOpts.tools ?? []).map((tool, index) => [String(index), {
9136
+ originalName: tool.name,
9137
+ providerName: nameMap.toProviderName(tool.name),
9138
+ tupleItemsPaths: Object.fromEntries(
9139
+ collectTupleItemsSchemaPaths(tool.parameters).map((path, pathIndex) => [String(pathIndex), path]),
9140
+ ),
9141
+ }]));
8930
9142
  const providerOpts: LLMOpts = {
8931
9143
  ...turnOpts,
9144
+ signal: attemptAbort.signal,
8932
9145
  tools: turnOpts.tools?.map((tool) => ({ ...tool, name: nameMap.toProviderName(tool.name) })),
8933
9146
  toolChoice: turnOpts.toolChoice && typeof turnOpts.toolChoice === 'object'
8934
9147
  ? { name: nameMap.toProviderName(turnOpts.toolChoice.name) }
@@ -8944,21 +9157,25 @@ export async function streamLLMWithTools(
8944
9157
  streamIter.next().then((r) => ({ idle: false as const, r })),
8945
9158
  new Promise<{ idle: true }>((res) => { idleTimer = setTimeout(() => res({ idle: true }), streamIdleTimeoutMs); }),
8946
9159
  ]);
8947
- if (step.idle) {
8948
- const error = new Error(`stream idle timeout after ${streamIdleTimeoutMs}ms`);
8949
- error.name = 'StreamIdleTimeoutError';
8950
- Object.assign(error, { code: 'ETIMEDOUT' });
8951
- throw error;
8952
- }
9160
+ if (step.idle) throw new StreamIdleTimeoutError(streamIdleTimeoutMs);
8953
9161
  } catch (err) {
8954
9162
  if (idleTimer) clearTimeout(idleTimer);
8955
- if (err instanceof Error && err.name === 'StreamIdleTimeoutError') {
9163
+ releaseStreamAttempt(true, err);
9164
+ if (err instanceof ApiHttpError && err.status === 400 && providerToolSchemaDiagnostics) {
9165
+ debug.log('llm.tool-schema', 'provider-request-rejected', {
9166
+ provider: activeProvider.name,
9167
+ model: activeModel,
9168
+ turn,
9169
+ status: err.status,
9170
+ tools: providerToolSchemaDiagnostics,
9171
+ }, { level: 'warn' });
9172
+ }
9173
+ if (err instanceof StreamIdleTimeoutError) {
8956
9174
  debug.log('llm.stream', 'idle-timeout', {
8957
- turn, evCount, lastEvType, idleMs: streamIdleTimeoutMs,
9175
+ turn, evCount, lastEvType, idleMs: err.idleMs, idleMsSource: idleTimeout.source,
8958
9176
  textChars: turnText.length, pendingCalls: pendingCalls.length,
8959
9177
  }, { level: 'warn' });
8960
- // A provider async iterator can queue return() behind the pending next().
8961
- // Detach cleanup so it cannot keep the user turn busy forever.
9178
+ // Provider iterators can queue return() behind a pending next(). Detach cleanup.
8962
9179
  const timedOutIterator = streamIter;
8963
9180
  if (timedOutIterator?.return) {
8964
9181
  try { void Promise.resolve(timedOutIterator.return(undefined)).catch(() => {}); } catch { /* fail-soft cleanup */ }
@@ -8971,18 +9188,29 @@ export async function streamLLMWithTools(
8971
9188
  attempt: streamIdleTimeoutRetries, maxRetries: STREAM_IDLE_TIMEOUT_MAX_RETRIES,
8972
9189
  delayMs: 0, statusLine: '스트림 무응답 · 재시도',
8973
9190
  }, { level: 'warn' });
8974
- handlers.onStatus?.(`스트림 무응답 · 재시도 ${streamIdleTimeoutRetries}/${STREAM_IDLE_TIMEOUT_MAX_RETRIES}`);
9191
+ try { handlers.onStatus?.(`스트림 무응답 · 재시도 ${streamIdleTimeoutRetries}/${STREAM_IDLE_TIMEOUT_MAX_RETRIES}`); } catch { /* UI notification must not block recovery */ }
9192
+ streamIdleRetryPartial = turnText;
8975
9193
  rewindDisconnectedStream();
8976
9194
  streamIter = undefined;
8977
9195
  continue;
8978
9196
  }
8979
- // Once a tool call/image escaped, keep the old behavior: finish this
8980
- // provider turn and never replay or switch providers around side effects.
8981
- if (pendingCalls.length > 0 || streamEmittedImage || opts.signal?.aborted) break;
8982
- if (opts.provider) break; // pinned route preserves the prior partial-text result
8983
- // The retry's text was already sent to the view; drop it from the
8984
- // logical result before allowing the normal provider fallback path.
8985
- if (pendingCalls.length === 0 && !streamEmittedImage) rewindDisconnectedStream();
9197
+ // Do not replay or advance providers after a tool side effect or image escaped.
9198
+ if (pendingCalls.length > 0 || streamEmittedImage) break;
9199
+ if (opts.signal?.aborted) throw err;
9200
+ if (opts.provider && (turnText.length > 0 || streamIdleRetryPartial.length > 0)) {
9201
+ // A pinned provider cannot advance to fallback. Keep already shown partial text
9202
+ // and complete the turn instead of rewinding it and failing the whole response.
9203
+ if (turnText.length === 0 && streamIdleRetryPartial.length > 0) {
9204
+ turnText = streamIdleRetryPartial;
9205
+ fullText += streamIdleRetryPartial;
9206
+ }
9207
+ try { handlers.onStatus?.('스트림 무응답 · 재시도 소진 (부분 응답 유지)'); } catch { /* status is fail-soft */ }
9208
+ debug.log('llm.stream', 'idle-timeout-pinned-partial-preserved', { turn, evCount, lastEvType, textChars: turnText.length }, { level: 'warn' });
9209
+ break;
9210
+ }
9211
+ if (opts.provider) throw err;
9212
+ rewindDisconnectedStream();
9213
+ streamIter = undefined;
8986
9214
  }
8987
9215
  if (err instanceof OverloadFailoverError && !opts.provider) {
8988
9216
  const switched = activeProvider.name === 'openai-codex'
@@ -8990,6 +9218,7 @@ export async function streamLLMWithTools(
8990
9218
  : providerByName('grok');
8991
9219
  if (switched && !attemptedProviders.has(switched.name)
8992
9220
  && (activeProvider.name === 'openai-codex' || switched.available()) && evCount === 0) {
9221
+ (circuit ?? await import('./session-runtime/provider-circuit.js')).recordProviderFailure(activeProvider.name, 'overload-failover', Date.now());
8993
9222
  debug.log('llm.router', 'overload-failover', {
8994
9223
  turn,
8995
9224
  blockedProvider: activeProvider.name,
@@ -9004,13 +9233,13 @@ export async function streamLLMWithTools(
9004
9233
  activeModel = finalized.model;
9005
9234
  attemptedProviders.add(activeProvider.name);
9006
9235
  streamDisconnectRetries = 0;
9236
+ streamIdleTimeoutRetries = 0;
9007
9237
  turnOpts = buildTurnOpts();
9008
9238
  streamIter = undefined;
9009
9239
  continue;
9010
9240
  }
9011
9241
  }
9012
- if (!(err instanceof Error && err.name === 'StreamIdleTimeoutError')
9013
- && evCount > 0 && isStreamDisconnectRetryable(err, pendingCalls.length, streamEmittedImage) && !opts.signal?.aborted) {
9242
+ if (!(err instanceof StreamIdleTimeoutError) && evCount > 0 && isStreamDisconnectRetryable(err, pendingCalls.length, streamEmittedImage) && !opts.signal?.aborted) {
9014
9243
  const retryData = {
9015
9244
  provider: activeProvider.name,
9016
9245
  model: activeModel,
@@ -9054,6 +9283,7 @@ export async function streamLLMWithTools(
9054
9283
  remainingProviders: remaining,
9055
9284
  attempted: blockedProviders,
9056
9285
  });
9286
+ (circuit ?? await import('./session-runtime/provider-circuit.js')).recordProviderFailure(activeProvider.name, verdict.category, Date.now());
9057
9287
  blockedProviders = [...verdict.attempts];
9058
9288
  const fallback = verdict.action === 'advance' ? providerByName(verdict.nextProvider) : undefined;
9059
9289
  if (!fallback) {
@@ -9086,24 +9316,36 @@ export async function streamLLMWithTools(
9086
9316
  attemptedProviders.add(activeProvider.name);
9087
9317
  // 재시도 상한은 «공급자마다» — 넘어간 공급자도 스트림 도중 끊김이면 다시 5회까지 건다.
9088
9318
  streamDisconnectRetries = 0;
9319
+ streamIdleTimeoutRetries = 0;
9089
9320
  turnOpts = buildTurnOpts();
9090
9321
  streamIter = undefined;
9091
9322
  continue;
9092
9323
  }
9093
9324
  if (idleTimer) clearTimeout(idleTimer);
9094
- if (step.idle) {
9095
- // The race's idle branch is converted to StreamIdleTimeoutError above.
9096
- // Keep a fail-closed guard for future changes to this loop.
9097
- throw new Error('stream idle timeout branch escaped recovery');
9325
+ if (step.r.done) {
9326
+ (circuit ?? await import('./session-runtime/provider-circuit.js')).recordProviderSuccess(activeProvider.name, Date.now());
9327
+ releaseStreamAttempt();
9328
+ break;
9098
9329
  }
9099
- if (step.r.done) break;
9100
9330
  const ev = step.r.value;
9101
9331
  evCount++;
9102
9332
  lastEvType = ev.type;
9333
+ // One same-provider idle retry is allowed per turn; progress clears the stale partial marker,
9334
+ // but does not reset that retry budget.
9335
+ if (streamIdleTimeoutRetries > 0) streamIdleRetryPartial = '';
9103
9336
  if (ev.type === 'text') {
9104
9337
  turnText += ev.delta;
9338
+ if (ev.phase) turnTextPhase = ev.phase;
9339
+ if (ev.phase) turnCodexTextPhase = ev.phase === 'final' ? 'final_answer' : 'commentary';
9105
9340
  fullText += ev.delta;
9106
9341
  emitRetryDisplayStep(retryDisplay.onText(ev.delta, turnText));
9342
+ } else if (ev.type === 'text_phase') {
9343
+ turnTextPhase = ev.phase;
9344
+ turnCodexTextPhase = ev.phase === 'final' ? 'final_answer' : 'commentary';
9345
+ } else if (ev.type === 'turn_end') {
9346
+ turnEnd = ev.endTurn;
9347
+ } else if (ev.type === 'codex_reasoning_item') {
9348
+ (turnCodexReasoningItems ??= []).push(ev.item);
9107
9349
  } else if (ev.type === 'reasoning_details') {
9108
9350
  turnReasoningDetails = ev.details;
9109
9351
  } else if (ev.type === 'tool_call') {
@@ -9153,6 +9395,8 @@ export async function streamLLMWithTools(
9153
9395
  ...(ev.revisedPrompt !== undefined ? { revisedPrompt: ev.revisedPrompt } : {}),
9154
9396
  });
9155
9397
  } else if (ev.type === 'usage') {
9398
+ // Provider parsers normalize inputTokens to uncached input; cache
9399
+ // reads/writes are separate fields (prompt-cache/types.ts contract).
9156
9400
  // ⭐ 주 에이전트 턴의 토큰·비용을 «게이트 없이» 남긴다(2026-09-24 실측: 이 줄이 없어 3시간 `llm.usage`
9157
9401
  // 18행이 전부 곁가지 두 자리였고, 하니스 런 비용의 거의 전부가 관측 밖이었다). 곁가지와 같은 모양 ·
9158
9402
  // 모르는 모델은 금액 대신 `cost.kind='unknown'`. runId 는 debug.log 가 붙인다. 실패해도 턴은 계속.
@@ -9230,6 +9474,26 @@ export async function streamLLMWithTools(
9230
9474
  }
9231
9475
  }
9232
9476
 
9477
+ // Codex can explicitly mark a response as unfinished even when it emitted
9478
+ // text and no tools. Keep that text in history with its phase, then ask
9479
+ // again. The extra turn is bounded to one past maxTurns.
9480
+ if (pendingCalls.length === 0 && turnEnd === false) {
9481
+ if (turnText || turnCodexReasoningItems?.length) history.push({
9482
+ role: 'assistant', content: turnText,
9483
+ ...(turnTextPhase ? { phase: turnTextPhase === 'final' ? 'final_answer' as const : 'commentary' as const } : {}),
9484
+ ...(turnCodexReasoningItems?.length ? { codexReasoningItems: turnCodexReasoningItems } : {}),
9485
+ });
9486
+ if (turnText) {
9487
+ clearVisibleAssistantTextForToolRound(handlers, sawToolRound);
9488
+ sawToolRound = true;
9489
+ }
9490
+ codexContinuationNeeded = true;
9491
+ debug.log('llm.router', 'codex.end-turn-false-continue', { turn, phase: turnTextPhase ?? 'unknown', textChars: turnText.length });
9492
+ // The one grace turn is also unfinished. Fall through to the existing
9493
+ // budget notice; never present a phase-marked progress item as final.
9494
+ if (turn >= maxTurns) break;
9495
+ continue;
9496
+ }
9233
9497
  // No tool calls + non-empty text = the model gave its final answer.
9234
9498
  if (pendingCalls.length === 0 && turnText.length > 0) {
9235
9499
  const visibleText = finalizeVisibleAssistantText(fullText, turnText, sawToolRound);
@@ -9267,7 +9531,7 @@ export async function streamLLMWithTools(
9267
9531
  // TUI-H1-GOALLOOP-EMPTY-TAIL — 답이 이 스트림 «앞»에 이미 보였어도 같다(opts.terminalPriorAnswerVisible —
9268
9532
  // 예: Bash → «끝» → 재주입 → update_goal 만 → 빈 턴). 이 스트림에 답이 없으면 빈 문자열로 끝내고 화면은
9269
9533
  // 건드리지 않는다(onText 생략 · emitTurnComplete 는 빈 답을 이력에 안 싣는다). 마감은 호출자(goal loop) 몫.
9270
- if (terminalSignalDispatched && (lastToolRoundText.trim().length > 0 || opts.terminalPriorAnswerVisible === true)) {
9534
+ if (!completionNeedsFinalTurn && terminalSignalDispatched && (lastToolRoundText.trim().length > 0 || opts.terminalPriorAnswerVisible === true)) {
9271
9535
  const answer = lastToolRoundText.trim().length > 0 ? lastToolRoundText : '';
9272
9536
  debug.log('goal.loop', 'empty-turn-after-terminal-signal', {
9273
9537
  turn, emptyRetries, answerChars: answer.length,
@@ -9311,7 +9575,10 @@ export async function streamLLMWithTools(
9311
9575
  clearVisibleAssistantTextForToolRound(handlers, sawToolRound);
9312
9576
  }
9313
9577
  sawToolRound = true;
9314
- if (turnText.trim().length > 0) lastToolRoundText = turnText;
9578
+ if (turnText.trim().length > 0) {
9579
+ lastToolRoundText = turnText;
9580
+ lastToolRoundTextPhase = turnTextPhase;
9581
+ }
9315
9582
  // TUI-GOAL-LOOP-FRICTION(#46) — 이 턴의 호출이 «종결 신호뿐»이면 예산·후속 단계 거부를 걸지 않는다.
9316
9583
  const terminalOnlyTurn = terminalToolNames.size > 0
9317
9584
  && pendingCalls.length > 0
@@ -9360,7 +9627,10 @@ export async function streamLLMWithTools(
9360
9627
  ...(call.providerMeta !== undefined ? { providerMeta: call.providerMeta } : {}),
9361
9628
  });
9362
9629
  }
9363
- history.push({ role: 'assistant', content: assistantBlocks });
9630
+ history.push({ role: 'assistant', content: assistantBlocks,
9631
+ ...(turnTextPhase ? { phase: turnTextPhase === 'final' ? 'final_answer' as const : 'commentary' as const } : {}),
9632
+ ...(turnCodexReasoningItems?.length ? { codexReasoningItems: turnCodexReasoningItems } : {}),
9633
+ });
9364
9634
 
9365
9635
  // Final-synthesis window: in the last FINAL_SYNTHESIS_TURNS of the
9366
9636
  // budget, refuse to execute tool calls. Each pending call gets a
@@ -11114,6 +11384,11 @@ export async function streamLLMWithTools(
11114
11384
  }
11115
11385
  const dispatchIndex = callsToDispatch.findIndex((candidate) => candidate.id === call.id);
11116
11386
  const { result, isError } = dispatchedResults[dispatchIndex]!;
11387
+ const toolResultInlineLimitBytes = opts.toolResultInlineLimitBytes === undefined
11388
+ ? undefined
11389
+ : activeProvider.name === 'openai-codex'
11390
+ ? Math.min(opts.toolResultInlineLimitBytes, 10 * 1024)
11391
+ : opts.toolResultInlineLimitBytes;
11117
11392
  const imageBearing = maybeImageBearingResult(result);
11118
11393
  if (imageBearing) {
11119
11394
  // Image-bearing tool result (Phase 1, 2026-05-05) — see
@@ -11126,7 +11401,7 @@ export async function streamLLMWithTools(
11126
11401
  tool_use_id: call.id,
11127
11402
  content: [
11128
11403
  { type: 'image', mediaType: imageBearing.mediaType, base64: imageBearing.dataB64 },
11129
- { type: 'text', text: JSON.stringify(imageBearing.rest) },
11404
+ { type: 'text', text: toolResultContentForCall(call.name, JSON.stringify(imageBearing.rest), toolResultInlineLimitBytes) },
11130
11405
  ],
11131
11406
  ...(isError ? { isError: true } : {}),
11132
11407
  });
@@ -11134,7 +11409,7 @@ export async function streamLLMWithTools(
11134
11409
  resultBlocks.push({
11135
11410
  type: 'tool_result',
11136
11411
  tool_use_id: call.id,
11137
- content: toolResultContentForCall(call.name, result),
11412
+ content: toolResultContentForCall(call.name, result, toolResultInlineLimitBytes),
11138
11413
  ...(isError ? { isError: true } : {}),
11139
11414
  });
11140
11415
  }
@@ -11183,10 +11458,43 @@ export async function streamLLMWithTools(
11183
11458
  }
11184
11459
  // 신호뿐인 턴 · 섞인 턴(정상 디스패치 · 부분 거부 · 통째 거부의 수신 확인) 모두 — 종결 신호가 이 턴에 «받아들여졌다».
11185
11460
  if (!hardStopText && pendingCalls.some(isTerminalCall)) terminalSignalDispatched = true;
11461
+ // A goal completion with evidence has already been acknowledged by dispatch. The
11462
+ // across-turn goal loop owns final acceptance (test evidence / rejected calls),
11463
+ // but no further provider request is needed to make that decision. Keep the
11464
+ // tool-result blocks in onTurnComplete so it can inspect the original call.
11465
+ const hasAcceptedCompletion = !hardStopText && opts.terminalCompletionAccepted && pendingCalls.some((call) =>
11466
+ isTerminalCall(call) && resultBlocks.some((block) => block.type === 'tool_result'
11467
+ && block.tool_use_id === call.id && !block.isError)
11468
+ && opts.terminalCompletionAccepted!(call.name, call.args));
11469
+ const hasVisibleAnswer = lastToolRoundText.trim().length > 0 || opts.terminalPriorAnswerVisible === true;
11470
+ // A completion tool can follow a commentary/progress segment on any provider.
11471
+ // Its successful ack does not turn that segment into a final user answer.
11472
+ const completionTextIsProgress = lastToolRoundTextPhase === 'commentary'
11473
+ || (lastToolRoundTextPhase !== 'final' && lastToolRoundText.trim().length <= 240
11474
+ && /(?:하겠습니다|드리겠습니다|할게요|할 예정입니다|확인하겠|살펴보겠|작성하겠|정리하겠|I(?:'ll| will)\b|\bnext[,]?\s+I\b)/i.test(lastToolRoundText));
11475
+ if (hasAcceptedCompletion && completionTextIsProgress) {
11476
+ completionNeedsFinalTurn = true;
11477
+ 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.' });
11478
+ debug.log('goal.loop', 'completion-await-final', { turn, progressChars: lastToolRoundText.length, phase: lastToolRoundTextPhase ?? 'unknown' }, { level: 'info' });
11479
+ }
11480
+ if (hasAcceptedCompletion && hasVisibleAnswer && !completionNeedsFinalTurn) {
11481
+ const answer = lastToolRoundText.trim().length > 0 ? lastToolRoundText : '';
11482
+ const visiblePriorAnswer = opts.terminalPriorAnswerVisible === true;
11483
+ debug.log('goal.loop', 'accepted-terminal-completion-finish', {
11484
+ turn, answerChars: answer.length, priorAnswerVisible: visiblePriorAnswer,
11485
+ tools: pendingCalls.map((call) => call.name),
11486
+ }, { level: 'info' });
11487
+ // Every text delta was already streamed above (and fullText accumulates
11488
+ // all rounds). A second onText here would violate the cumulative-full
11489
+ // contract and duplicate text shown before this terminal tool call.
11490
+ const finalAnswer = answer || (opts.terminalPriorAnswerVisible ? fullText.trim() : '');
11491
+ emitTurnComplete(finalAnswer);
11492
+ return finalAnswer;
11493
+ }
11186
11494
  // TUI-GOAL-LOOP-FRICTION(#63) — 종결 신호뿐인 턴 ⊕ 같은 턴에 쓴 답 = 이 턴이 최종 답이다.
11187
11495
  // 다음 모델 턴을 열면 할 말이 없는 모델이 빈 턴을 되풀이하고 `[NO FINAL SYNTHESIS]` 가 붙는다(10-15 22:36Z 표본).
11188
11496
  // 이 턴에 답이 없으면 종전처럼 다음 턴을 연다(앞 라운드 글은 «하겠습니다» 류 서술일 수 있다 — 모델이 답을 쓰게).
11189
- if (terminalOnlyTurn && !hardStopText && !postToolResultUserNote && turnText.trim().length > 0) {
11497
+ if (terminalOnlyTurn && !completionNeedsFinalTurn && !hardStopText && !postToolResultUserNote && turnText.trim().length > 0) {
11190
11498
  const answer = turnText;
11191
11499
  debug.log('goal.loop', 'terminal-signal-finish', {
11192
11500
  turn, maxTurns, tools: pendingCalls.map((call) => call.name), answerChars: answer.length,
@@ -11400,6 +11708,7 @@ export async function streamLLMWithTools(
11400
11708
  let synthesizedText: string | null = null;
11401
11709
  const shouldForceSynthesisOnBudget =
11402
11710
  loopTermination === 'budget-exhausted'
11711
+ && !codexContinuationNeeded
11403
11712
  && !forceSynthesisAttempted
11404
11713
  && (modelFamily === 'gemini'
11405
11714
  || applyCodexBehaviorGuard('force-synthesis', effectiveModel, codexGuardCandidate, true));
@@ -11486,11 +11795,11 @@ import {
11486
11795
  } from './oauth/codex.js';
11487
11796
  import { extractChatGPTClaims } from './oauth/jwt.js';
11488
11797
 
11489
- // ⭐ 2026-09-23 (대표) — 운영 기본을 GPT-6 Sol 로 옮겼다(구 gpt-5.6-terra).
11798
+ // ⭐ 2026-10-28 — current Codex default is GPT-6.1 Terra.
11490
11799
  // ⛔ 이 상수는 «최후 폴백»이다 — 정상 경로는 `llm-tier-map` 의 사다리를 탄다.
11491
- // 📏 구독 경로 실호출 확인(codex-cli 0.155.1 · 2026-09-23): `codex exec --model gpt-6-sol` ✅
11492
- // ⚠️ 0.154.0 에서는 같은 계정이 400 이었다 — 클라이언트 판이 게이트였다.
11493
- export const CODEX_DEFAULT_MODEL = 'gpt-6-sol';
11800
+ // Codex CLI 0.164.0: `codex exec -m gpt-6.1-terra` returned successfully on 2026-10-28.
11801
+ // Elanous ACP Codex app-server also returned on this model; Elanous's better-tier effort remains medium.
11802
+ export const CODEX_DEFAULT_MODEL = 'gpt-6.1-terra';
11494
11803
  export const CODEX_DEFAULT_URL = 'https://api.openai.com/v1/chat/completions';
11495
11804
 
11496
11805
  /** Build an OpenAI-compatible provider (grok / openai) using the
@@ -11867,6 +12176,8 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
11867
12176
  const responsesUrl = cfgBase
11868
12177
  ? (cfgBase.endsWith('/responses') ? cfgBase : `${cfgBase.replace(/\/$/, '')}/responses`)
11869
12178
  : `${CODEX_API_BASE_URL}/responses`;
12179
+ const endpointSupportsStore = !responsesUrl.includes('chatgpt.com/backend-api');
12180
+ const useStore = endpointSupportsStore && cfg.codexStore !== false;
11870
12181
  // Image-pipeline P3 (2026-05-05) — gate the function_call_output
11871
12182
  // ContentItem[] wire on the active model. gpt-5 family supports
11872
12183
  // image_url in tool results; legacy gpt-4 / non-vision routes
@@ -11886,6 +12197,7 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
11886
12197
  const { instructions, input } = messagesToResponsesInput(messages, {
11887
12198
  acceptToolImages: codexAcceptToolImages,
11888
12199
  acceptUserMessageImages: codexAcceptUserImages,
12200
+ replayCodexReasoning: !useStore,
11889
12201
  onOrphanToolResult: (callId) => {
11890
12202
  try { debug.log('llm.codex', 'orphan-tool-output-dropped', { callId }); }
11891
12203
  catch { /* observation must not block the request */ }
@@ -11935,12 +12247,17 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
11935
12247
  // wave for ChatGPT regardless of cfg.codexStore — the
11936
12248
  // infrastructure stays in place for Azure/API-key paths and
11937
12249
  // for any future ChatGPT-side policy change.
11938
- const endpointSupportsStore = !responsesUrl.includes('chatgpt.com/backend-api');
11939
- const useStore = endpointSupportsStore && cfg.codexStore !== false;
11940
12250
  const delta = useStore ? getIncrementalItems(lastInput, input) : null;
11941
- const wireInput = delta ?? input;
12251
+ // A stored continuation already has old reasoning. A full rebase has
12252
+ // no previous_response_id, so it must replay those opaque items.
12253
+ const wireInput = delta ?? (useStore
12254
+ ? messagesToResponsesInput(messages, {
12255
+ acceptToolImages: codexAcceptToolImages,
12256
+ acceptUserMessageImages: codexAcceptUserImages,
12257
+ }).input
12258
+ : input);
11942
12259
  const wirePrevId = (delta && lastResponseId) ? lastResponseId : undefined;
11943
- yield* streamCodexResponsesEvents(responsesUrl, tokens.accessToken, {
12260
+ let requestBody = {
11944
12261
  model: opts.model || model,
11945
12262
  instructions,
11946
12263
  input: wireInput,
@@ -11954,8 +12271,77 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
11954
12271
  // can disable via llm.codexStore=false.
11955
12272
  store: useStore,
11956
12273
  ...(wirePrevId ? { previousResponseId: wirePrevId } : {}),
11957
- }, opts.signal, {
12274
+ };
12275
+ const requestGuard = opts.requestSizeGuard;
12276
+ const promptCache = opts.promptCache !== false;
12277
+ let serializedWireBody: string | undefined;
12278
+ if (requestGuard) {
12279
+ serializedWireBody = serializeCodexResponsesWireBody(requestBody, promptCache);
12280
+ let requestChars = serializedWireBody.length;
12281
+ const initialRequestChars = requestChars;
12282
+ let requestCompacted = false;
12283
+ let requestCompactReason = requestChars > requestGuard.maxChars
12284
+ ? 'store-threading-skipped'
12285
+ : 'under-cap';
12286
+ if (requestChars > requestGuard.maxChars && !useStore) {
12287
+ try {
12288
+ const compacted = await requestGuard.compactMessages(messages, {
12289
+ provider: 'openai-codex', requestChars, maxChars: requestGuard.maxChars,
12290
+ });
12291
+ if (compacted) {
12292
+ const compactInput = messagesToResponsesInput(compacted.messages, {
12293
+ acceptToolImages: codexAcceptToolImages,
12294
+ acceptUserMessageImages: codexAcceptUserImages,
12295
+ // Compaction runs only when store threading is off, matching
12296
+ // the original non-threaded wire input policy.
12297
+ replayCodexReasoning: !useStore,
12298
+ });
12299
+ const compactBody = { ...requestBody, instructions: compactInput.instructions, input: compactInput.input };
12300
+ const compactWireBody = serializeCodexResponsesWireBody(compactBody, promptCache);
12301
+ const compactChars = compactWireBody.length;
12302
+ if (compactChars < requestChars) {
12303
+ messages.splice(0, messages.length, ...compacted.messages);
12304
+ requestBody = compactBody;
12305
+ serializedWireBody = compactWireBody;
12306
+ requestChars = compactChars;
12307
+ requestCompacted = true;
12308
+ requestCompactReason = compactChars > requestGuard.maxChars
12309
+ ? `compaction-still-over-cap:${compacted.reason}`
12310
+ : compacted.reason;
12311
+ } else {
12312
+ requestCompactReason = 'compaction-no-reduction';
12313
+ }
12314
+ } else {
12315
+ requestCompactReason = 'compaction-no-op';
12316
+ }
12317
+ } catch (error) {
12318
+ requestCompactReason = 'compaction-error';
12319
+ debug.log('llm.request', 'request-size-compact-error', {
12320
+ provider: 'openai-codex',
12321
+ requestChars,
12322
+ maxChars: requestGuard.maxChars,
12323
+ message: error instanceof Error ? error.message : String(error),
12324
+ }, { level: 'warn' });
12325
+ }
12326
+ }
12327
+ requestGuard.onMeasured?.({
12328
+ provider: 'openai-codex', requestChars, maxChars: requestGuard.maxChars,
12329
+ compacted: requestCompacted, reason: requestCompactReason,
12330
+ ...(requestCompacted ? { previousChars: initialRequestChars } : {}),
12331
+ });
12332
+ debug.log('llm.request', 'request-size-compact', {
12333
+ provider: 'openai-codex',
12334
+ beforeChars: initialRequestChars,
12335
+ afterChars: requestChars,
12336
+ maxChars: requestGuard.maxChars,
12337
+ compacted: requestCompacted,
12338
+ reason: requestCompactReason,
12339
+ });
12340
+ }
12341
+ yield* streamCodexResponsesEvents(responsesUrl, tokens.accessToken, requestBody, opts.signal, {
11958
12342
  accountId,
12343
+ promptCache,
12344
+ ...(serializedWireBody ? { serializedWireBody } : {}),
11959
12345
  // Capture the new response id from the SSE stream so the
11960
12346
  // next call can thread it. If the backend skips the event
11961
12347
  // lastResponseId stays undefined and we fall back to full