elanous 0.2.38 → 0.2.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (615) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/{RrjXTwz-urRAbChP1bX02 → HjeK7zONHfR7op1hKXp4k}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{7954.4f7a8832663ee979.js → 1060.014f1f01a17aec26.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/{8911.bf5e712acb25fb6c.js → 1281.d81877e35b9068f4.js} +1 -1
  6. package/apps/pwa/out/_next/static/chunks/{2104.70e086d5174a4736.js → 1350.59f40e39e02d91b1.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/1415.79821ec7ae99acdb.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1452.162bb2d9d699e82a.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/147.f92f63bbc5e85fa5.js +286 -0
  10. package/apps/pwa/out/_next/static/chunks/1537-6bb97b5214928ea9.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/1661.fb425a1d8a065b72.js +149 -0
  12. package/apps/pwa/out/_next/static/chunks/1671-1c22b40b50b208f2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/1758.0204d3f48783c49d.js +90 -0
  14. package/apps/pwa/out/_next/static/chunks/1806.027bf78b6a94d340.js +104 -0
  15. package/apps/pwa/out/_next/static/chunks/2047.8f23e210995d4548.js +2 -0
  16. package/apps/pwa/out/_next/static/chunks/2061-7f2bbc53ae96c88b.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/{1632.7d87f998e714bede.js → 2066.a77257c8c350770a.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/2096-a938496bc996dcbd.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/{8019.21ca554a821dad01.js → 2177.df8547f9d7215c4e.js} +3 -3
  20. package/apps/pwa/out/_next/static/chunks/2200-9a453b8f99687d49.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/{3528.e4b8ab70d339f965.js → 2226.2cf61ec781827219.js} +2 -2
  22. package/apps/pwa/out/_next/static/chunks/2281-b8cede695c6c650e.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{6493.27ad77de7aac7966.js → 2499.02fbfd18126c10c0.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/{7628.6b16863ac3972333.js → 2522.f2766b76d22c8bdf.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/{3086.fb14b60ce98b53d3.js → 2566.beaffc3a5e1fcd2e.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/{9299.24e2a7d9b6d1f91b.js → 261.0d471e8aded799f3.js} +3 -3
  27. package/apps/pwa/out/_next/static/chunks/2615.3c9738153de3ef3f.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2727.e703d99ac414916d.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2752.7904b8df0254d0ae.js +27 -0
  30. package/apps/pwa/out/_next/static/chunks/2762-5051a862dfa40e99.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/2767-31784bfedf8e245d.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/2774-719820e3f5b2a3eb.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/2812-f016aa0dda149287.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/2829-734fe8bf2eee7347.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/311-32dcac79970985e8.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3175-f93f7495cd28253c.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3184.8da993f5502d2140.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/3231-d7c981db0ff8bdcc.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/3250.151309c5176d6b4b.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/{7524.ce973525d30b3d52.js → 3330.7443183b7e6fdbdf.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/3517.a5aa1797a9dbf2ee.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/3567-45f676877ed9e8a5.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/{3124-561f7f037943cde2.js → 3746-f80a78f220bf37b5.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/3819.8a1de79b2e8c7298.js +24 -0
  45. package/apps/pwa/out/_next/static/chunks/39.9b37b0be4c128a83.js +77 -0
  46. package/apps/pwa/out/_next/static/chunks/{bb6987f9.3647e1a23b0e3217.js → 3b355b3d.6f73c98bf6f9c749.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/4023.a19045970a9ce16a.js +36 -0
  48. package/apps/pwa/out/_next/static/chunks/{8994d93e.33ba491f95345fc2.js → 409c787b.e48c5bec8ec10456.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/{5041-5aada92ca215ffdd.js → 413-24c8284bda08241d.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/4135.e45d6ebc29e20647.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/4283.65502f5d0e6ff6c0.js +287 -0
  52. package/apps/pwa/out/_next/static/chunks/4305-4e28fa92a7b34a5b.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/4335-50efa61fd7613142.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/4351.0f37a6db2b34d50b.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/{5638.107314dc479e2f37.js → 470.51a72a2348306455.js} +2 -2
  56. package/apps/pwa/out/_next/static/chunks/4720.ecbd1693fa151068.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/{8487.291666583bebe640.js → 4769.9fd582071301f382.js} +1 -1
  58. package/apps/pwa/out/_next/static/chunks/{8509.3a54d4e61893d7cb.js → 483.725e4a61be922788.js} +1 -1
  59. package/apps/pwa/out/_next/static/chunks/{9fe1b809.738da85c8b17e3fb.js → 4864a2ca.1c1c280a7772c5e5.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/4969-4b209d7307410c00.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/5068-c7f53862f83c0917.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/5315.bc96711c24dfefe2.js +74 -0
  63. package/apps/pwa/out/_next/static/chunks/{8879.ac06755bcf3e44a1.js → 5383.bdfa5d886e39a826.js} +1 -1
  64. package/apps/pwa/out/_next/static/chunks/{6142.913b44e260a3f925.js → 5436.fdf6fa8ae3e9588d.js} +1 -1
  65. package/apps/pwa/out/_next/static/chunks/5542.efbf14f16baed4dd.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/5622.94893f5aea7185d0.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/5644.8f401a25601d05fc.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/5658.35f70eb9871a54bf.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/5721.7bdc0e904c76ab51.js +8 -0
  70. package/apps/pwa/out/_next/static/chunks/5790-eef0ca20884a6bdf.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/5802.ef0d8598ca68fe84.js +62 -0
  72. package/apps/pwa/out/_next/static/chunks/5890.fb974729788dcb71.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/5924.5ef25cddd36d5dbf.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/5979-e59c099ebf8d1f35.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/6020.714cfeada2315e2e.js +94 -0
  76. package/apps/pwa/out/_next/static/chunks/6027-dfe0349d8c50f698.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/{7161-8f22db3ec5ed005e.js → 6051-0ae955eedb10adb9.js} +3 -3
  78. package/apps/pwa/out/_next/static/chunks/6178.bc4730c86cf74536.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/{3291.f6c1005387d514bd.js → 6257.c0037b8949239f5e.js} +2 -2
  80. package/apps/pwa/out/_next/static/chunks/6273.d808b0dcb3c2b155.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/6292.6ef1df64fa6a4c8a.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/647-af8dd920ba46533c.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/6541-170dc5c59e135c0c.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/6624.981ef7caeb680560.js +32 -0
  85. package/apps/pwa/out/_next/static/chunks/6742-a1ad4a94ba7bc37a.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/{5203.51e5d8b5f1dcfe53.js → 6749.0744ce6eb08d40e9.js} +3 -3
  87. package/apps/pwa/out/_next/static/chunks/6836-493dc55c39187949.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/6856-53ac421c06039a18.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/{1938.0e6c60ee91c4d7f5.js → 6908.06008c67f6380ea9.js} +2 -2
  90. package/apps/pwa/out/_next/static/chunks/6915.096931864206f522.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/6925.5e181fb7dcf90193.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/{de8b930d.d94c0f42acf807fc.js → 6cc1d1d6.9a98dd4622f1afc6.js} +1 -1
  93. package/apps/pwa/out/_next/static/chunks/7203-800e11037adc41eb.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/7219-15bdd9a80661af8e.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/7279.6170f591105936bb.js +93 -0
  96. package/apps/pwa/out/_next/static/chunks/7349-373165c7fbdcc3df.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/7514-a533ba0ca1cae4f4.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/7515.1ad6efe157fa4448.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/7521.d919cc1fab80c1d9.js +24 -0
  100. package/apps/pwa/out/_next/static/chunks/7538.90e97bbdf9178cff.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/7611.85f045bf232053ac.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/7825-39203196a12579b2.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/7869.5e1a9a12fd70d6c7.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8086.1ab9103c41be884f.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/8099.1cccf31c1baa7e1c.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/8179-e61fffbdb70f1152.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/8265.bdeb991b34db6a06.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/{1026-44010f405704050c.js → 8272-025653884f9645d2.js} +1 -1
  109. package/apps/pwa/out/_next/static/chunks/{5307.d7328020227880c2.js → 8357.1aaa713b052a78b1.js} +4 -4
  110. package/apps/pwa/out/_next/static/chunks/8638-e42615f2166165b5.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/{4199-f7c6eb40cd0322f8.js → 8639-92c11d069792429e.js} +1 -1
  112. package/apps/pwa/out/_next/static/chunks/8691.54e22831d5e8ccf8.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/8d173e38.18a2f7b4e1848bdb.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/9069.657590a84afb8fac.js +41 -0
  115. package/apps/pwa/out/_next/static/chunks/9125-ae588d88a0293b93.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/9175.2de5209a5ce6f68a.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/9184.45afbf44fec87574.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/{eaad0cba.771200940c529df7.js → 9189505a.980427ac048c8c60.js} +1 -1
  119. package/apps/pwa/out/_next/static/chunks/9311.f9f641a46a508611.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/9371-a6fee8378ebfa3ee.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/9421-3aa9ff4fa7f63d8a.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/9992.07ce73c10bd5e6a1.js +79 -0
  123. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-76394dc618d322c8.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/approvals/page-1b948637c37b3abd.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-c63400f1bcf3e47c.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/board/page-95d0957267819586.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-866aac5ff226a7f3.js → page-ba957ff875a026a0.js} +1 -1
  128. package/apps/pwa/out/_next/static/chunks/app/bots/page-bb6fd0714879cb87.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8f9038520d2acbef.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/chat/page-d4b48397e05fce2c.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/consult/page-72d5744245dc0c27.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/control/page-37625dc8300fa43a.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/decisions/page-fbef9d160272dbc1.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/design-check/page-58424168df84928e.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/editor/page-72c12ba430ac57c0.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/exec/page-d53f3e9ed806d7e0.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/field/page-4d2fbd45feee0829.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/healthz/route-76394dc618d322c8.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/inside/page-8783dc0ed7590a2c.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/intake/page-3706cb390606e930.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/layout-9b0a86598e0111fd.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/live/page-4edb8df90f4537ce.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/live-run/page-83d27757e2b8060a.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/loops/page-47398b9ba1cc5e1b.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/market/page-fb86f55cfe2e681d.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e22345fc8fd47b1b.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/morning/page-fca2e59a81744d9e.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/not-found-6e96bda63100b31e.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/observatory/page-4d17ad8e0db8cea4.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-121f92766c33beaa.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-43f14212c545f92d.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ccec2023e7a974ec.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/outputs/page-b04f33f548b89a94.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/page-c8792823c0917021.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/reflection/page-8c0e32782ceef4cb.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-94bd3922eb230ce8.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/sessions/page-bca565887bafa03c.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-ac1dffafe649fc6c.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/settings/page-52374edb5e23e455.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ad4ebca120d43ca7.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/layout-76394dc618d322c8.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/setup/page-718b89ce4b4ae4e4.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/share/page-204eca33eeeaddec.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/showroom/page-e58ba9bcdb9ac0f9.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/tasks/page-98f6dd50bafd7734.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/term/page-139d722de1dcd6c1.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/today/page-5a4bdd72bff27057.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/trace/page-6f6ab17d44f984ce.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/vault/page-aabb7bd15fb72da9.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-e4322319164a4436.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workflows/page-e9efc0c8f32e2371.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/workspace/page-ccde99f07aaf71f7.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-80464aced9614390.js +1 -0
  174. package/apps/pwa/out/_next/static/chunks/{b7478fef.5917716286213fcc.js → b84cad00.594027ebe0531fe2.js} +1 -1
  175. package/apps/pwa/out/_next/static/chunks/c696986b.5dc8bb1143af16d1.js +1 -0
  176. package/apps/pwa/out/_next/static/chunks/{b7459770.6539b11860eb8a9c.js → ca3702f8.2913182e73889fa2.js} +7 -7
  177. package/apps/pwa/out/_next/static/chunks/{3f0de3de-2340c532c00395db.js → d5d3cefc-373c4eb722ed848b.js} +1 -1
  178. package/apps/pwa/out/_next/static/chunks/{89d6c04f-6b8ab341748e468a.js → d8de0de2-dd0f4ac8e5ab1832.js} +1 -1
  179. package/apps/pwa/out/_next/static/chunks/{framework-4b12a946f352cee2.js → framework-002b472a66935d31.js} +1 -1
  180. package/apps/pwa/out/_next/static/chunks/main-49b88d47292c0476.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/main-app-fff2549cb10a133f.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/pages/_app-32abbbba3f3b76b7.js +1 -0
  183. package/apps/pwa/out/_next/static/chunks/pages/_error-9d9cfef60c60dd7a.js +1 -0
  184. package/apps/pwa/out/_next/static/chunks/webpack-cc7f483fa294034a.js +1 -0
  185. package/apps/pwa/out/approvals/index.html +1 -1
  186. package/apps/pwa/out/approvals/index.txt +22 -22
  187. package/apps/pwa/out/autopilot/index.html +1 -1
  188. package/apps/pwa/out/autopilot/index.txt +22 -22
  189. package/apps/pwa/out/board/index.html +1 -1
  190. package/apps/pwa/out/board/index.txt +22 -22
  191. package/apps/pwa/out/botlab/index.html +1 -1
  192. package/apps/pwa/out/botlab/index.txt +21 -21
  193. package/apps/pwa/out/bots/index.html +1 -1
  194. package/apps/pwa/out/bots/index.txt +21 -21
  195. package/apps/pwa/out/ceo/index.html +1 -1
  196. package/apps/pwa/out/ceo/index.txt +22 -22
  197. package/apps/pwa/out/chat/index.html +1 -1
  198. package/apps/pwa/out/chat/index.txt +21 -21
  199. package/apps/pwa/out/consult/index.html +1 -1
  200. package/apps/pwa/out/consult/index.txt +22 -22
  201. package/apps/pwa/out/control/index.html +1 -1
  202. package/apps/pwa/out/control/index.txt +22 -22
  203. package/apps/pwa/out/decisions/index.html +1 -1
  204. package/apps/pwa/out/decisions/index.txt +21 -21
  205. package/apps/pwa/out/design-check/index.html +1 -1
  206. package/apps/pwa/out/design-check/index.txt +22 -22
  207. package/apps/pwa/out/editor/index.html +1 -1
  208. package/apps/pwa/out/editor/index.txt +22 -22
  209. package/apps/pwa/out/exec/index.html +1 -1
  210. package/apps/pwa/out/exec/index.txt +22 -22
  211. package/apps/pwa/out/field/index.html +1 -1
  212. package/apps/pwa/out/field/index.txt +22 -22
  213. package/apps/pwa/out/healthz +1 -1
  214. package/apps/pwa/out/index.html +1 -1
  215. package/apps/pwa/out/index.txt +21 -21
  216. package/apps/pwa/out/inside/index.html +1 -1
  217. package/apps/pwa/out/inside/index.txt +21 -21
  218. package/apps/pwa/out/intake/index.html +1 -1
  219. package/apps/pwa/out/intake/index.txt +22 -22
  220. package/apps/pwa/out/live/index.html +1 -1
  221. package/apps/pwa/out/live/index.txt +22 -22
  222. package/apps/pwa/out/live-run/index.html +1 -1
  223. package/apps/pwa/out/live-run/index.txt +22 -22
  224. package/apps/pwa/out/loops/index.html +1 -1
  225. package/apps/pwa/out/loops/index.txt +21 -21
  226. package/apps/pwa/out/market/index.html +1 -1
  227. package/apps/pwa/out/market/index.txt +21 -21
  228. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  229. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  230. package/apps/pwa/out/morning/index.html +1 -1
  231. package/apps/pwa/out/morning/index.txt +22 -22
  232. package/apps/pwa/out/observatory/index.html +1 -1
  233. package/apps/pwa/out/observatory/index.txt +22 -22
  234. package/apps/pwa/out/ops/checklist/index.html +1 -1
  235. package/apps/pwa/out/ops/checklist/index.txt +21 -21
  236. package/apps/pwa/out/ops/release/index.html +1 -1
  237. package/apps/pwa/out/ops/release/index.txt +21 -21
  238. package/apps/pwa/out/ops/seats/index.html +1 -1
  239. package/apps/pwa/out/ops/seats/index.txt +21 -21
  240. package/apps/pwa/out/outputs/index.html +1 -1
  241. package/apps/pwa/out/outputs/index.txt +21 -21
  242. package/apps/pwa/out/reflection/index.html +1 -1
  243. package/apps/pwa/out/reflection/index.txt +22 -22
  244. package/apps/pwa/out/scheduler/index.html +1 -1
  245. package/apps/pwa/out/scheduler/index.txt +22 -22
  246. package/apps/pwa/out/sessions/index.html +1 -1
  247. package/apps/pwa/out/sessions/index.txt +21 -21
  248. package/apps/pwa/out/settings/devices/index.html +1 -1
  249. package/apps/pwa/out/settings/devices/index.txt +22 -22
  250. package/apps/pwa/out/settings/index.html +1 -1
  251. package/apps/pwa/out/settings/index.txt +22 -22
  252. package/apps/pwa/out/setup/done/index.html +1 -1
  253. package/apps/pwa/out/setup/done/index.txt +22 -22
  254. package/apps/pwa/out/setup/index.html +1 -1
  255. package/apps/pwa/out/setup/index.txt +22 -22
  256. package/apps/pwa/out/share/index.html +1 -1
  257. package/apps/pwa/out/share/index.txt +22 -22
  258. package/apps/pwa/out/showroom/index.html +1 -1
  259. package/apps/pwa/out/showroom/index.txt +21 -21
  260. package/apps/pwa/out/tasks/index.html +1 -1
  261. package/apps/pwa/out/tasks/index.txt +22 -22
  262. package/apps/pwa/out/term/index.html +1 -1
  263. package/apps/pwa/out/term/index.txt +22 -22
  264. package/apps/pwa/out/today/index.html +1 -1
  265. package/apps/pwa/out/today/index.txt +22 -22
  266. package/apps/pwa/out/trace/index.html +1 -1
  267. package/apps/pwa/out/trace/index.txt +22 -22
  268. package/apps/pwa/out/vault/index.html +1 -1
  269. package/apps/pwa/out/vault/index.txt +22 -22
  270. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  271. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  272. package/apps/pwa/out/workflows/index.html +1 -1
  273. package/apps/pwa/out/workflows/index.txt +22 -22
  274. package/apps/pwa/out/workspace/index.html +1 -1
  275. package/apps/pwa/out/workspace/index.txt +22 -22
  276. package/apps/pwa/out/worktrees/index.html +1 -1
  277. package/apps/pwa/out/worktrees/index.txt +22 -22
  278. package/graphs/demo/persona-marketer/fixtures/marketer-scenario.yaml +10 -0
  279. package/graphs/demo/persona-marketer/persona-marketer.yaml +25 -0
  280. package/graphs/demo/persona-marketer/recipes.yaml +19 -0
  281. package/graphs/video/explainer-line.yaml +4 -1
  282. package/package.json +1 -1
  283. package/scripts/dead-code/report.ts +169 -0
  284. package/scripts/lib/morning-report-options.ts +3 -4
  285. package/scripts/lib/tui-regress-evidence.ts +56 -0
  286. package/scripts/morning-report.ts +5 -1
  287. package/scripts/public-export.ts +15 -7
  288. package/scripts/release-loop/export-check-node.ts +21 -3
  289. package/scripts/release-loop/gate-node.ts +379 -48
  290. package/scripts/release-loop/gate-shard-dispatch.ts +128 -0
  291. package/scripts/release-loop/gate-shard-receipts.ts +89 -0
  292. package/scripts/release-loop/gate-wall.ts +5 -5
  293. package/scripts/release-loop/preflight.ts +6 -2
  294. package/scripts/release-loop/resume-release.ts +20 -5
  295. package/scripts/release-loop/resume.ts +48 -0
  296. package/scripts/release-loop/version-node.ts +7 -2
  297. package/scripts/release-story/draft.ts +1 -1
  298. package/scripts/research-bench/README.md +53 -0
  299. package/scripts/research-bench/questions.json +66 -0
  300. package/scripts/research-bench/rubric.md +33 -0
  301. package/scripts/research-bench/run.ts +523 -0
  302. package/scripts/tui-regress.ts +85 -10
  303. package/src/acp/core-turn-bridge.ts +8 -0
  304. package/src/agent/autonomous-tools.ts +10 -3
  305. package/src/agent/monad-agent-turn.ts +4 -1
  306. package/src/agent/registry.ts +26 -2
  307. package/src/agent/shared-app-tools.ts +1 -1
  308. package/src/agent/surface-ux/build.ts +12 -3
  309. package/src/agent/surface-ux/types.ts +1 -1
  310. package/src/agent/task-notification.ts +75 -6
  311. package/src/agent/types.ts +3 -0
  312. package/src/agent-mission/driver.ts +29 -3
  313. package/src/agent-mission/mission-cli.ts +2 -1
  314. package/src/agent-substrate/review-context-pack.ts +365 -0
  315. package/src/agent-substrate/self-review-cli.ts +226 -17
  316. package/src/ask-user-question/index.ts +6 -0
  317. package/src/ask-user-question/inline-resolver.ts +49 -0
  318. package/src/auto-research/question-ladder-contract.ts +22 -3
  319. package/src/auto-research/research-turn.ts +350 -17
  320. package/src/autopilot/agent-loop.ts +1 -1
  321. package/src/autopilot/pty-control-loop.ts +8 -1
  322. package/src/boot/daemon-tools/index.ts +4 -3
  323. package/src/boot/daemon-tools/self-implement.ts +270 -2
  324. package/src/boot/daemon-tools/types.ts +2 -0
  325. package/src/budget/llm-cost.ts +44 -5
  326. package/src/budget/unified-usage.ts +17 -1
  327. package/src/capture/tui-self-report.ts +14 -0
  328. package/src/chat/index.ts +33 -7
  329. package/src/cli/agent-cli.ts +43 -8
  330. package/src/cli/intake-cli.ts +22 -1
  331. package/src/cli/lesson-cli.ts +19 -3
  332. package/src/cli/log-event-names.ts +1 -0
  333. package/src/cli/pod-cli.ts +17 -2
  334. package/src/cli/pty-takeover-cli.ts +67 -7
  335. package/src/cli/release-cli.ts +52 -6
  336. package/src/cli/repo-cli.ts +2 -1
  337. package/src/cli/tasks-cli.ts +36 -9
  338. package/src/control-plane/ledger.ts +21 -4
  339. package/src/core-turn/run-core-turn.ts +44 -3
  340. package/src/core-turn/run-goal-loop.ts +152 -8
  341. package/src/core-turn/types.ts +9 -0
  342. package/src/dashboard/chat/acp-chat.ts +2 -2
  343. package/src/dashboard/classic-sigquit.ts +16 -0
  344. package/src/dashboard/index.ts +321 -27
  345. package/src/dashboard/input/chat-main-interaction-opts.ts +3 -0
  346. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +7 -2
  347. package/src/dashboard/input/chat-main-slash-command.ts +7 -0
  348. package/src/dashboard/input/dashboard-plain-turn-stream.ts +4 -0
  349. package/src/dashboard/input/dashboard-priority-key-route.ts +21 -5
  350. package/src/dashboard/input/external-input-host.ts +2 -0
  351. package/src/dashboard/sidetab-runtime.ts +228 -0
  352. package/src/dashboard/slash-runtime/ask-clarification.ts +7 -1
  353. package/src/dashboard/slash-runtime/dashboard-handlers.ts +20 -8
  354. package/src/dashboard/slash-runtime/help-from-registry.ts +2 -2
  355. package/src/dashboard/term-slash-runtime.ts +16 -18
  356. package/src/dashboard/turn-preamble.ts +2 -0
  357. package/src/domains/knowledge.ts +1 -1
  358. package/src/esc-abort-gate.ts +33 -1
  359. package/src/execution-loop/launch-gate.ts +206 -1
  360. package/src/graph-runner/graph-cli.ts +6 -4
  361. package/src/graph-runner/runner.ts +31 -9
  362. package/src/graph-wizard/generate.ts +16 -7
  363. package/src/graph-wizard/mission-compose.ts +201 -0
  364. package/src/grok/agent-search.ts +8 -2
  365. package/src/grok/credential.ts +5 -0
  366. package/src/grounding/ref-index.ts +11 -0
  367. package/src/grounding/ref-vector.ts +61 -0
  368. package/src/grounding/sources-cli.ts +2 -2
  369. package/src/grounding/sources.ts +15 -1
  370. package/src/harness/harness-cli-command.ts +38 -9
  371. package/src/harness/harness-pod-dispatch.ts +7 -1
  372. package/src/index.ts +18 -4
  373. package/src/input/external-input-queue.ts +10 -0
  374. package/src/intake-plane/collect-telegram-saved.ts +36 -1
  375. package/src/intake-plane/intake-to-tasks.ts +246 -3
  376. package/src/lessons/friction-ledger.ts +154 -0
  377. package/src/live-supply/collector.ts +149 -0
  378. package/src/live-supply/probes.ts +52 -0
  379. package/src/live-supply/snapshot.ts +40 -0
  380. package/src/llm.ts +431 -76
  381. package/src/loops/orchestrator/tick.ts +8 -0
  382. package/src/nexus/api/harness-api.ts +2 -1
  383. package/src/pty/snapshot.py +132 -0
  384. package/src/pty-shell/pty-control-ipc.ts +29 -2
  385. package/src/pty-shell/pty-manifest.ts +59 -3
  386. package/src/pty-shell/registry.ts +2 -0
  387. package/src/pty-shell/tui-run-state.ts +80 -3
  388. package/src/release-loop/gate-host-reservation.ts +13 -4
  389. package/src/release-loop/gate-shard-ledger.ts +68 -0
  390. package/src/release-loop/merged-pr-checklist.ts +25 -2
  391. package/src/release-loop/rehearsal-shard-plan.ts +6 -1
  392. package/src/release-loop/rehearsal.ts +27 -0
  393. package/src/research/edge-decision.ts +124 -15
  394. package/src/research/gap-followup.ts +249 -0
  395. package/src/research/lenses/counter-evidence.ts +100 -0
  396. package/src/research/lenses/working-backwards.ts +89 -0
  397. package/src/research/plugin-discovery.ts +242 -0
  398. package/src/research-bridge/invoke.ts +4 -2
  399. package/src/research-bridge/types.ts +2 -0
  400. package/src/self-dev/ask-launch-flow.ts +34 -106
  401. package/src/self-dev/dev-cli.ts +2 -0
  402. package/src/self-dev/dev-pipeline.ts +8 -4
  403. package/src/self-dev/graph-journey-route.ts +10 -2
  404. package/src/self-dev/launch-preflight-text.ts +2 -2
  405. package/src/self-dev/launch-preflight.ts +38 -7
  406. package/src/self-dev/orchestrate-cli.ts +1 -0
  407. package/src/self-dev/run-supervisor.ts +19 -0
  408. package/src/self-implement/device-first.ts +8 -2
  409. package/src/self-implement/doctor-visit.ts +76 -58
  410. package/src/self-implement/gate-artifacts.ts +3 -0
  411. package/src/self-implement/gate-baseline.ts +75 -4
  412. package/src/self-implement/gate-cli.ts +317 -9
  413. package/src/self-implement/gate-receipt-registry.ts +180 -0
  414. package/src/self-implement/gate-remote.ts +145 -22
  415. package/src/self-implement/goal-author-clarification.ts +178 -24
  416. package/src/self-implement/goal-author-decompose.ts +24 -15
  417. package/src/self-implement/goal-author.ts +9 -5
  418. package/src/self-implement/host-regate.ts +120 -45
  419. package/src/self-implement/impl-findings-feedback.ts +188 -0
  420. package/src/self-implement/knowhow-pack.ts +189 -10
  421. package/src/self-implement/off-diff-evidence.ts +124 -0
  422. package/src/self-implement/orchestrator.ts +85 -7
  423. package/src/self-implement/run-ledger.ts +46 -8
  424. package/src/self-implement/run-stop.ts +12 -0
  425. package/src/self-implement/seams.ts +6 -3
  426. package/src/self-implement/self-implement-cli.ts +4 -2
  427. package/src/session/chat.ts +89 -15
  428. package/src/session-runtime/provider-circuit.ts +140 -0
  429. package/src/shell-runner/file-engine.ts +1 -0
  430. package/src/shell-runner/types.ts +4 -0
  431. package/src/sidetab-core/model.ts +176 -0
  432. package/src/skills/tools/schedule-wakeup.ts +23 -1
  433. package/src/skills/tools/shell.ts +51 -5
  434. package/src/steward/directive-audit.ts +354 -0
  435. package/src/surface/child-process.ts +31 -0
  436. package/src/surface/tui-stderr-owner.ts +48 -0
  437. package/src/task-agent/cover.ts +30 -12
  438. package/src/task-agent/judge.ts +56 -0
  439. package/src/task-agent/postland-card.ts +35 -2
  440. package/src/task-agent/rejudge-on-head.ts +46 -2
  441. package/src/task-agent/repair-cycle.ts +1 -1
  442. package/src/task-agent/task-hand.ts +129 -24
  443. package/src/task-agent/tier-split.ts +105 -21
  444. package/src/task-orchestrator/surfaces/pod-command-job.ts +330 -2
  445. package/src/task-orchestrator/surfaces/pod-config-snapshot.ts +123 -0
  446. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +27 -2
  447. package/src/task-orchestrator/surfaces/pod-mirror-seed.ts +11 -8
  448. package/src/task-orchestrator/surfaces/pod-si-warm-protocol.ts +1 -1
  449. package/src/task-orchestrator/surfaces/pod-source-receive.ts +74 -6
  450. package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +2 -1
  451. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +56 -15
  452. package/src/task-orchestrator/surfaces/self-implement-pod.ts +186 -33
  453. package/src/tool-runtime/skill-exec-runtime.ts +95 -5
  454. package/src/tui/entry.ts +15 -2
  455. package/src/tui/inline-choice.ts +188 -0
  456. package/src/tui/sidetab/list.ts +2 -176
  457. package/src/tui/slash/term.ts +12 -1
  458. package/src/tui-client/dashboard-session.ts +7 -1
  459. package/src/user-config.ts +34 -4
  460. package/src/ux-sim/classic-tui-seat-scenarios.ts +1 -7
  461. package/src/ux-sim/fake-land-queue.ts +88 -0
  462. package/src/ux-sim/gate-shard-host-reroute.ts +82 -0
  463. package/src/ux-sim/goal-interim-trace.ts +24 -0
  464. package/src/ux-sim/next-tui-seat-scenarios.ts +8 -13
  465. package/src/ux-sim/task-orchestrator-surfaces-self-implement-pod-device.ts +4 -1
  466. package/src/version/packed-revision.json +1 -1
  467. package/src/video-pipeline/recipes/explainer.ts +54 -2
  468. package/src/web-search/grok.ts +32 -6
  469. package/src/web-search/index.ts +8 -7
  470. package/src/workflow-runtime/node-catalog.ts +53 -4
  471. package/apps/pwa/out/_next/static/chunks/1118.a8a74ea29aac3e46.js +0 -27
  472. package/apps/pwa/out/_next/static/chunks/1204-2aa830479adf80f4.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/1341-43b7d2dc77d45c9d.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/1440-c3ea8882badfd9bf.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/1698-9c11cbf97f13717e.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/1770-3b1fc92253ef9f2f.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/1923-5c1695332240246a.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/213-20675f20673ba22a.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/214.186a430da28338f1.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/2277.3bd4c0ac7f1b5996.js +0 -93
  481. package/apps/pwa/out/_next/static/chunks/240.e09f433f71c78ff4.js +0 -104
  482. package/apps/pwa/out/_next/static/chunks/2415.738b777642a02233.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/2417.02151393605a7cd0.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/2463.cd2641a76daaa9ff.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/2576.b9e9c629c4b84c52.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/265-c021de53f0865bd9.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/2998-b1cc13956cd6a6e7.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/2e4782d3.ba2f0ab2b63a53e7.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/3130.626b54382b4dc379.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/3221-1bb3f99a55088613.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/3280.24edb1ffd03a7f3f.js +0 -24
  492. package/apps/pwa/out/_next/static/chunks/3288-eb3c22a1cbf2ae68.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/33.ef99e8374f96c6c4.js +0 -36
  494. package/apps/pwa/out/_next/static/chunks/3341-f09b9efbd7a82f70.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/3389.012c70767a872f9b.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/3410-d7c0f14b329d6fda.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/3421.c87d6313a7d670f7.js +0 -287
  498. package/apps/pwa/out/_next/static/chunks/3422-83a3953f9bd67e26.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/3587-002e24e368c515a9.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/3644.f12a7d821ab940af.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/3660.d7516588e2d875c3.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/3695.b82e2610f4cd4ea9.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/3858.ca1f5bc0f577eac9.js +0 -94
  504. package/apps/pwa/out/_next/static/chunks/3959-e6ac4a139d9d4bcc.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/4032.53f62176514ec0ea.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/4204-ef8484cbecd5d62f.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/4259-163129ca79913c4b.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/428-eaa77df3d0c85969.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/4432-97e400ffa1e161f5.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/449-aa427cf2cbb75f50.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/4491-39a0fd32bbaf3840.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/4588.b7cf06d382953979.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/4690.70ec8060825366ab.js +0 -32
  514. package/apps/pwa/out/_next/static/chunks/4804.6012b5cd33c22792.js +0 -90
  515. package/apps/pwa/out/_next/static/chunks/4881.e4d22d730e3b9df5.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/4937.ce2e340dc29cad3e.js +0 -79
  517. package/apps/pwa/out/_next/static/chunks/5-bb1c08d84bd71df3.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/5005.de98df23b37cdf3c.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/5035.dc64a89cad5573cc.js +0 -74
  520. package/apps/pwa/out/_next/static/chunks/5207.1a224035455790fd.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/5255-1b60d50eabe9f057.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/5269.d0436ee6d773382a.js +0 -286
  523. package/apps/pwa/out/_next/static/chunks/5300.677c97e0393742e6.js +0 -1
  524. package/apps/pwa/out/_next/static/chunks/5309.3367c4d50f52b489.js +0 -1
  525. package/apps/pwa/out/_next/static/chunks/552-689827d49780fa00.js +0 -1
  526. package/apps/pwa/out/_next/static/chunks/5539.34cc9d60622cb69b.js +0 -8
  527. package/apps/pwa/out/_next/static/chunks/5568.218dea0c00ca2ec7.js +0 -1
  528. package/apps/pwa/out/_next/static/chunks/5645.b7e3b055455a04ea.js +0 -1
  529. package/apps/pwa/out/_next/static/chunks/5662.18cc0f30353cbd96.js +0 -1
  530. package/apps/pwa/out/_next/static/chunks/5671.da24fc9dc5e98325.js +0 -1
  531. package/apps/pwa/out/_next/static/chunks/5b141171.324c40fd49cb6821.js +0 -1
  532. package/apps/pwa/out/_next/static/chunks/6175-d4621f6a10b0cfab.js +0 -1
  533. package/apps/pwa/out/_next/static/chunks/6290.3eb53a20746b72ce.js +0 -1
  534. package/apps/pwa/out/_next/static/chunks/6415-9307f3eafa90837f.js +0 -1
  535. package/apps/pwa/out/_next/static/chunks/6579-3de35d00aacd71b5.js +0 -1
  536. package/apps/pwa/out/_next/static/chunks/680-2f324c4e039a5b67.js +0 -1
  537. package/apps/pwa/out/_next/static/chunks/6845-331b3c7481da3ba3.js +0 -1
  538. package/apps/pwa/out/_next/static/chunks/7000.28157393caaf5343.js +0 -62
  539. package/apps/pwa/out/_next/static/chunks/7311-60a7ea80d23c7521.js +0 -1
  540. package/apps/pwa/out/_next/static/chunks/7401.525d0a12f7d02c06.js +0 -1
  541. package/apps/pwa/out/_next/static/chunks/7438.c2cf72b66dca6aa4.js +0 -1
  542. package/apps/pwa/out/_next/static/chunks/7488.966b116789f3d41a.js +0 -1
  543. package/apps/pwa/out/_next/static/chunks/7573.2376e13c9c21f1f8.js +0 -77
  544. package/apps/pwa/out/_next/static/chunks/7577.ea6e9ac72e035520.js +0 -1
  545. package/apps/pwa/out/_next/static/chunks/7657.8d1fb3d2f7ebe273.js +0 -1
  546. package/apps/pwa/out/_next/static/chunks/7903-1435d5d2f0dd7807.js +0 -1
  547. package/apps/pwa/out/_next/static/chunks/7978-30a91192c5dbe3c2.js +0 -1
  548. package/apps/pwa/out/_next/static/chunks/8025-8795821525cf1a07.js +0 -1
  549. package/apps/pwa/out/_next/static/chunks/8191.3674a00f0b565c23.js +0 -1
  550. package/apps/pwa/out/_next/static/chunks/8217-604955d8885c5ef2.js +0 -1
  551. package/apps/pwa/out/_next/static/chunks/8298-eed6f5f2585a63f4.js +0 -1
  552. package/apps/pwa/out/_next/static/chunks/8758.ac421f726a189125.js +0 -1
  553. package/apps/pwa/out/_next/static/chunks/891.d6c723a51a067b36.js +0 -149
  554. package/apps/pwa/out/_next/static/chunks/9223.cbcc88e0e9950426.js +0 -41
  555. package/apps/pwa/out/_next/static/chunks/9317.6a9869039c231bb3.js +0 -24
  556. package/apps/pwa/out/_next/static/chunks/9383.e4755f275b3c11ff.js +0 -1
  557. package/apps/pwa/out/_next/static/chunks/9581.da63ddf41acfad46.js +0 -1
  558. package/apps/pwa/out/_next/static/chunks/9715.8c5529a0a5c41a36.js +0 -1
  559. package/apps/pwa/out/_next/static/chunks/9817.3009d65f0889ef1f.js +0 -2
  560. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-76873dd80da4f715.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/app/approvals/page-ed32b6ec5dea8f23.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-fdd40e15babf3d25.js +0 -1
  563. package/apps/pwa/out/_next/static/chunks/app/board/page-384efc78e85e400b.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/app/bots/page-d91809c5aeed319a.js +0 -1
  565. package/apps/pwa/out/_next/static/chunks/app/ceo/page-128f3522f89a81ab.js +0 -1
  566. package/apps/pwa/out/_next/static/chunks/app/chat/page-595961abf3b43ad1.js +0 -1
  567. package/apps/pwa/out/_next/static/chunks/app/consult/page-f41dccb76d8c0263.js +0 -1
  568. package/apps/pwa/out/_next/static/chunks/app/control/page-a3205c2e28b7d0d9.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/app/decisions/page-c39a80277814a3f4.js +0 -1
  570. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1ab3f98194513195.js +0 -1
  571. package/apps/pwa/out/_next/static/chunks/app/editor/page-a5246f7e254d77d0.js +0 -1
  572. package/apps/pwa/out/_next/static/chunks/app/exec/page-0f92287bdb81217d.js +0 -1
  573. package/apps/pwa/out/_next/static/chunks/app/field/page-5a37131ff63793ef.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/app/healthz/route-76873dd80da4f715.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/app/inside/page-ac4bd14bb2348bfd.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/app/intake/page-c348a3959c5b5fc6.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/app/layout-2addd81c0d3e2906.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/app/live/page-da6b765f58a3eefb.js +0 -1
  579. package/apps/pwa/out/_next/static/chunks/app/live-run/page-0661e40f8780c32c.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/app/loops/page-b89af1b1c8e4875b.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/app/market/page-da8aeb82ba751cdb.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-cc71317b239439db.js +0 -1
  583. package/apps/pwa/out/_next/static/chunks/app/morning/page-bde7fc1d64ffe369.js +0 -1
  584. package/apps/pwa/out/_next/static/chunks/app/not-found-e2797214f713fa58.js +0 -1
  585. package/apps/pwa/out/_next/static/chunks/app/observatory/page-2a35bcda70defaf6.js +0 -1
  586. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-3af03deedde45032.js +0 -1
  587. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d1d1210f99f620ae.js +0 -1
  588. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-46d1b5500885861d.js +0 -1
  589. package/apps/pwa/out/_next/static/chunks/app/outputs/page-291ab19fe2acb173.js +0 -1
  590. package/apps/pwa/out/_next/static/chunks/app/page-7f569e533ddeecba.js +0 -1
  591. package/apps/pwa/out/_next/static/chunks/app/reflection/page-990129c9fee46d8f.js +0 -1
  592. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-7737f785ebb1cdaf.js +0 -1
  593. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f3c73a4f7ffd2f8.js +0 -1
  594. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-03be7fb347f01058.js +0 -1
  595. package/apps/pwa/out/_next/static/chunks/app/settings/page-96b0280625e4df2b.js +0 -1
  596. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-56855545049eb56d.js +0 -1
  597. package/apps/pwa/out/_next/static/chunks/app/setup/layout-76873dd80da4f715.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/app/setup/page-79a909c9981f0338.js +0 -1
  599. package/apps/pwa/out/_next/static/chunks/app/share/page-a9d9a3e66459c1e0.js +0 -1
  600. package/apps/pwa/out/_next/static/chunks/app/showroom/page-c383cbf2013f7069.js +0 -1
  601. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f57c6a2e54dbdca4.js +0 -1
  602. package/apps/pwa/out/_next/static/chunks/app/term/page-6b140f77593576eb.js +0 -1
  603. package/apps/pwa/out/_next/static/chunks/app/today/page-6b767ae2d9d0adfc.js +0 -1
  604. package/apps/pwa/out/_next/static/chunks/app/trace/page-116d6198d5da0e3d.js +0 -1
  605. package/apps/pwa/out/_next/static/chunks/app/vault/page-b9c9b778c7501370.js +0 -1
  606. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-a6c78392ab8f6335.js +0 -1
  607. package/apps/pwa/out/_next/static/chunks/app/workflows/page-455183deb4ed0cfe.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/app/workspace/page-6a63e99526bd63d0.js +0 -1
  609. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31c22caafe788c08.js +0 -1
  610. package/apps/pwa/out/_next/static/chunks/main-941155250ed2644b.js +0 -1
  611. package/apps/pwa/out/_next/static/chunks/main-app-f8b6765abed6d149.js +0 -1
  612. package/apps/pwa/out/_next/static/chunks/pages/_app-78432194c85388f9.js +0 -1
  613. package/apps/pwa/out/_next/static/chunks/pages/_error-cdf7390829512b1b.js +0 -1
  614. package/apps/pwa/out/_next/static/chunks/webpack-d5f0964a9c2e92d2.js +0 -1
  615. /package/apps/pwa/out/_next/static/{RrjXTwz-urRAbChP1bX02 → HjeK7zONHfR7op1hKXp4k}/_ssgManifest.js +0 -0
package/src/llm.ts CHANGED
@@ -269,6 +269,10 @@ export interface LLMMessage {
269
269
  role: 'system' | 'user' | 'assistant';
270
270
  /** Plain string for text-only messages; ContentBlock[] when images are mixed in. */
271
271
  content: string | ContentBlock[];
272
+ /** Codex Responses message phase, preserved when replaying a stateless turn. */
273
+ phase?: 'commentary' | 'partial_answer' | 'final_answer';
274
+ /** Opaque Codex stateless reasoning items, before this assistant message. */
275
+ codexReasoningItems?: Array<{ type: 'reasoning'; encrypted_content: string; summary: unknown[]; id?: string }>;
272
276
  }
273
277
 
274
278
  /** JSONSchema-shaped tool definition sent to the provider (no handler). */
@@ -284,6 +288,21 @@ function toolUseNames(messages: readonly LLMMessage[]): string[] {
284
288
  : []);
285
289
  }
286
290
 
291
+ /** Return paths for legacy tuple-form `items` schemas without logging schema
292
+ * values. Provider 400s often omit the rejected tool name, so these paths let
293
+ * the next TUI recurrence identify the source contract without logging args. */
294
+ function collectTupleItemsSchemaPaths(schema: unknown, path = 'parameters'): string[] {
295
+ if (Array.isArray(schema)) {
296
+ return schema.flatMap((value, index) => collectTupleItemsSchemaPaths(value, `${path}[${index}]`));
297
+ }
298
+ if (!schema || typeof schema !== 'object') return [];
299
+ return Object.entries(schema as Record<string, unknown>).flatMap(([key, value]) => {
300
+ const childPath = `${path}.${key}`;
301
+ const tuplePath = key === 'items' && Array.isArray(value) ? [childPath] : [];
302
+ return [...tuplePath, ...collectTupleItemsSchemaPaths(value, childPath)];
303
+ });
304
+ }
305
+
287
306
  function mapProviderToolNamesInMessages(
288
307
  messages: readonly LLMMessage[],
289
308
  mapName: (name: string) => string,
@@ -340,7 +359,10 @@ function restoreProviderToolCallNames(
340
359
  * (currently Anthropic only) — consumers that don't care should
341
360
  * ignore it; `textOnly()` filters it out. */
342
361
  export type LLMStreamEvent =
343
- | { type: 'text'; delta: string }
362
+ | { type: 'text'; delta: string; phase?: 'commentary' | 'final' }
363
+ | { type: 'text_phase'; phase: 'commentary' | 'final' }
364
+ | { type: 'turn_end'; endTurn: boolean }
365
+ | { type: 'codex_reasoning_item'; item: { type: 'reasoning'; encrypted_content: string; summary: unknown[]; id?: string } }
344
366
  | {
345
367
  type: 'tool_call';
346
368
  id: string;
@@ -415,6 +437,24 @@ export interface LLMOpts {
415
437
  streamIdleTimeoutMs?: number;
416
438
  /** Optional per-request usage telemetry from the provider stream. */
417
439
  onUsage?(usage: LLMUsage): void;
440
+ /** Optional request-size policy. Providers that expose a serialized request
441
+ * can compact the normalized conversation before sending it. The callback
442
+ * is opt-in; providers without a serializer hook preserve their wire path. */
443
+ requestSizeGuard?: {
444
+ maxChars: number;
445
+ compactMessages: (
446
+ messages: LLMMessage[],
447
+ info: { provider: string; requestChars: number; maxChars: number },
448
+ ) => Promise<{ messages: LLMMessage[]; reason: string } | null>;
449
+ onMeasured?: (info: {
450
+ provider: string;
451
+ requestChars: number;
452
+ maxChars: number;
453
+ compacted: boolean;
454
+ reason: string;
455
+ previousChars?: number;
456
+ }) => void;
457
+ };
418
458
  /** Optional model role attached to this request's usage observation. */
419
459
  usageRole?: ModelRole;
420
460
  /** Optional per-call metering phase; absent calls keep the existing ledger shape. */
@@ -476,6 +516,9 @@ export interface LLMOpts {
476
516
  * 그러면 종결 신호 뒤 빈 턴은 이 스트림에 쓴 답이 없어도 재촉·`[NO FINAL SYNTHESIS]` 없이 빈 답으로 끝낸다.
477
517
  * 비우면(기본) 종전대로 — 답이 «어디에도» 없을 때의 경고는 그대로 보인다. */
478
518
  terminalPriorAnswerVisible?: boolean;
519
+ /** Goal-loop only: a validated completion ends the provider loop after tool results settle.
520
+ * Omitted for ordinary terminal tools, which may need a further answer turn. */
521
+ terminalCompletionAccepted?: (name: string, args: Record<string, unknown>) => boolean;
479
522
  /** Prompt-caching toggle. Honored by Anthropic (attaches
480
523
  * cache_control markers on system + tools + 2nd-to-last history
481
524
  * message) and OpenAI (passes stream_options.include_usage so
@@ -1628,7 +1671,8 @@ type ResponsesContentBlock =
1628
1671
  * codex answer began with "tool_result 가 없다" because, from the
1629
1672
  * model's perspective, no tool result ever arrived. */
1630
1673
  type ResponsesInputItem =
1631
- | { role: string; content: ResponsesContentBlock[] }
1674
+ | { role: string; content: ResponsesContentBlock[]; phase?: 'commentary' | 'partial_answer' | 'final_answer' }
1675
+ | { type: 'reasoning'; encrypted_content: string; summary: unknown[]; id?: string }
1632
1676
  | { type: 'function_call'; name: string; arguments: string; call_id: string }
1633
1677
  // Image-pipeline P3 (2026-05-05) — `output` accepts string OR a
1634
1678
  // ContentItem[] mirroring the user-message content block shape.
@@ -1655,6 +1699,8 @@ type ResponsesInputItem =
1655
1699
  * Codex variants — current gpt-5 family all true per allowlist). */
1656
1700
  export interface MessagesToResponsesInputOpts {
1657
1701
  acceptToolImages?: boolean;
1702
+ /** Server-stored continuation already contains prior reasoning items. */
1703
+ replayCodexReasoning?: boolean;
1658
1704
  acceptUserMessageImages?: boolean;
1659
1705
  onOrphanToolResult?: (callId: string) => void;
1660
1706
  }
@@ -1827,6 +1873,9 @@ export function messagesToResponsesInput(
1827
1873
  continue;
1828
1874
  }
1829
1875
 
1876
+ // Stateless Codex requires the opaque reasoning item in its original
1877
+ // position before the corresponding assistant message/tool calls.
1878
+ if (m.role === 'assistant' && opts.replayCodexReasoning !== false && m.codexReasoningItems) input.push(...m.codexReasoningItems);
1830
1879
  // Assemble the final content block list: text first (matches
1831
1880
  // how users usually write prompts — "describe this image" then
1832
1881
  // attachment), images second. Responses API is order-sensitive
@@ -1844,7 +1893,7 @@ export function messagesToResponsesInput(
1844
1893
  // representation — emitting an empty Message wrapper would just
1845
1894
  // add a stray empty-text block to the conversation history.
1846
1895
  if (content.length > 0) {
1847
- input.push({ role: m.role, content });
1896
+ input.push({ role: m.role, content, ...(m.role === 'assistant' && m.phase ? { phase: m.phase } : {}) });
1848
1897
  }
1849
1898
  input.push(...trailingItems);
1850
1899
  }
@@ -2175,6 +2224,41 @@ function mapReasoningLevelToGeminiThinking(
2175
2224
  /** Stream against /responses (Codex, OpenAI Responses API). Exported
2176
2225
  * so tests can exercise header/body shape without spinning up the
2177
2226
  * full makeCodexProvider path. */
2227
+ function buildCodexResponsesWireBody(
2228
+ body: {
2229
+ model: string;
2230
+ instructions: string;
2231
+ input: unknown[];
2232
+ tools?: unknown[];
2233
+ tool_choice?: unknown;
2234
+ reasoning?: { effort?: 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'; summary?: 'auto' | 'concise' | 'detailed' };
2235
+ store?: boolean;
2236
+ previousResponseId?: string;
2237
+ },
2238
+ promptCache: boolean,
2239
+ ): Record<string, unknown> {
2240
+ const cacheKey = promptCache ? computePromptCacheKey(body.model, body.instructions) : undefined;
2241
+ return {
2242
+ ...body,
2243
+ stream: true,
2244
+ store: body.store === true,
2245
+ ...(body.tool_choice ? { tool_choice: body.tool_choice } : {}),
2246
+ ...(body.previousResponseId ? { previous_response_id: body.previousResponseId } : {}),
2247
+ ...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
2248
+ ...(body.reasoning ? {
2249
+ reasoning: body.reasoning,
2250
+ include: ['reasoning.encrypted_content'],
2251
+ } : {}),
2252
+ };
2253
+ }
2254
+
2255
+ function serializeCodexResponsesWireBody(
2256
+ body: Parameters<typeof buildCodexResponsesWireBody>[0],
2257
+ promptCache: boolean,
2258
+ ): string {
2259
+ return JSON.stringify(buildCodexResponsesWireBody(body, promptCache));
2260
+ }
2261
+
2178
2262
  export async function* streamCodexResponsesEvents(
2179
2263
  url: string,
2180
2264
  bearer: string,
@@ -2221,6 +2305,9 @@ export async function* streamCodexResponsesEvents(
2221
2305
  /** Opt-out hook (tests). When false, skips the
2222
2306
  * `prompt_cache_key` body field; production always passes true. */
2223
2307
  promptCache?: boolean;
2308
+ /** Exact body string already serialized by the request-size preflight.
2309
+ * Passing it through ensures the measured bytes are the bytes fetched. */
2310
+ serializedWireBody?: string;
2224
2311
  /** Callback fired when the SSE stream emits `response.created`
2225
2312
  * carrying the new response's id. Used by makeCodexProvider to
2226
2313
  * capture the id and thread `previousResponseId` on the next
@@ -2274,32 +2361,16 @@ export async function* streamCodexResponsesEvents(
2274
2361
  // value codex_cli_rs ships) so the backend
2275
2362
  // applies the same tool/SSE conventions
2276
2363
  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.
2364
+ // TUI-REQUEST-SIZE-COMPACT — the exact serializer is shared with the
2365
+ // preflight guard, so the size decision and fetch body cannot drift.
2366
+ const wireBody = buildCodexResponsesWireBody(body, promptCache);
2367
+ const wireJson = extra.serializedWireBody ?? JSON.stringify(wireBody);
2368
+ debug.log('llm.request', 'request-size', {
2369
+ provider: 'openai-codex',
2370
+ requestChars: wireJson.length,
2287
2371
  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);
2372
+ previousResponseId: body.previousResponseId ? `${body.previousResponseId.slice(0, 12)}…` : null,
2373
+ });
2303
2374
  const response = await fetchApiWithRetry(
2304
2375
  url,
2305
2376
  {
@@ -2328,6 +2399,10 @@ export async function* streamCodexResponsesEvents(
2328
2399
  debug.log('llm.response.status', `codex ${response.status}`, {
2329
2400
  url, status: response.status, elapsedMs: Date.now() - startedAt,
2330
2401
  });
2402
+ let messagePhase: 'commentary' | 'final' | undefined;
2403
+ const codexTextPhase = (value: unknown): 'commentary' | 'final' | undefined =>
2404
+ value === 'commentary' || value === 'partial_answer' ? 'commentary'
2405
+ : value === 'final_answer' ? 'final' : undefined;
2331
2406
  try {
2332
2407
  for await (const line of sseLineStream(response.body!)) {
2333
2408
  if (!line.startsWith('data: ')) continue;
@@ -2347,15 +2422,32 @@ export async function* streamCodexResponsesEvents(
2347
2422
  extra.onResponseCreated(id);
2348
2423
  }
2349
2424
  }
2425
+ if (type === 'response.output_item.added' || type === 'response.output_item.done') {
2426
+ if (parsed.item?.type === 'message') {
2427
+ const phase = codexTextPhase(parsed.item.phase);
2428
+ if (type === 'response.output_item.added') messagePhase = phase;
2429
+ if (phase) {
2430
+ messagePhase = phase;
2431
+ yield { type: 'text_phase', phase };
2432
+ }
2433
+ }
2434
+ }
2350
2435
  // Text tokens — both `response.output_text.delta` (new naming) and
2351
2436
  // raw `output_text.delta` (older streams).
2352
2437
  if (type.endsWith('output_text.delta')) {
2353
2438
  const delta = parsed.delta || '';
2354
- if (delta) yield { type: 'text', delta };
2439
+ if (delta) yield { type: 'text', delta, ...(messagePhase ? { phase: messagePhase } : {}) };
2355
2440
  }
2356
2441
  // Tool calls come in as `response.output_item.done` with item.type = 'function_call'.
2357
2442
  if (type === 'response.output_item.done') {
2358
2443
  const item = parsed.item;
2444
+ if (item?.type === 'reasoning' && typeof item.encrypted_content === 'string' && item.encrypted_content) {
2445
+ yield { type: 'codex_reasoning_item', item: {
2446
+ type: 'reasoning', encrypted_content: item.encrypted_content,
2447
+ summary: Array.isArray(item.summary) ? item.summary : [],
2448
+ ...(typeof item.id === 'string' ? { id: item.id } : {}),
2449
+ } };
2450
+ }
2359
2451
  if (item && item.type === 'function_call') {
2360
2452
  let args: Record<string, unknown> = {};
2361
2453
  try { args = item.arguments ? JSON.parse(item.arguments) : {}; } catch { /* empty */ }
@@ -2410,6 +2502,9 @@ export async function* streamCodexResponsesEvents(
2410
2502
  // hit rate for codex regardless. See store=true wave (next+1)
2411
2503
  // for the volume-side counterpart.
2412
2504
  if (type === 'response.completed') {
2505
+ if (typeof parsed.response?.end_turn === 'boolean') {
2506
+ yield { type: 'turn_end', endTurn: parsed.response.end_turn };
2507
+ }
2413
2508
  const u = parsed.response?.usage;
2414
2509
  if (u && typeof u === 'object') {
2415
2510
  const cached = u.input_tokens_details?.cached_tokens
@@ -4474,6 +4569,13 @@ function explainAndLogFallbackCandidates(
4474
4569
 
4475
4570
  /** 스트림 중 끊김 재시도 상한 — codex `DEFAULT_STREAM_MAX_RETRIES`(5) 와 같은 값. */
4476
4571
  const STREAM_DISCONNECT_MAX_RETRIES = 5;
4572
+ class StreamIdleTimeoutError extends Error {
4573
+ constructor(readonly idleMs: number) {
4574
+ super(`stream idle timeout after ${idleMs}ms`);
4575
+ this.name = 'StreamIdleTimeoutError';
4576
+ Object.assign(this, { code: 'ETIMEDOUT' });
4577
+ }
4578
+ }
4477
4579
  const STREAM_IDLE_TIMEOUT_MAX_RETRIES = 1;
4478
4580
  let streamDisconnectRetryDelayMs: (attempt: number) => number = backoffMs;
4479
4581
 
@@ -4799,6 +4901,20 @@ type ProviderTextResult =
4799
4901
  | { ok: true; full: string; usage: ObservedUsage | 'unmeasured' }
4800
4902
  | { ok: false; full: string; error: unknown };
4801
4903
 
4904
+ function resolveStreamIdleTimeout(opts: LLMOpts, provider: LLMProvider, model: string): { ms: number; source: 'call' | 'config' | 'default' } {
4905
+ if (opts.streamIdleTimeoutMs !== undefined
4906
+ && Number.isFinite(opts.streamIdleTimeoutMs) && opts.streamIdleTimeoutMs > 0) {
4907
+ return { ms: opts.streamIdleTimeoutMs, source: 'call' };
4908
+ }
4909
+ let configured: number | undefined;
4910
+ try {
4911
+ configured = (require('./user-config.js') as typeof import('./user-config.js')).getUserConfig().llm.streamIdleTimeoutMs;
4912
+ } catch { /* config lookup is fail-soft; preserve provider defaults */ }
4913
+ if (configured !== undefined) return { ms: configured, source: 'config' };
4914
+ const longIdle = usesLongReasoningIdle(getModelFamily(model), provider.name);
4915
+ return { ms: longIdle ? STREAM_IDLE_TIMEOUT_MS_REASONING : STREAM_IDLE_TIMEOUT_MS, source: 'default' };
4916
+ }
4917
+
4802
4918
  function accumulateUsage(usage: ObservedUsage | undefined, received: LLMUsage): ObservedUsage {
4803
4919
  const total = { ...usage };
4804
4920
  for (const field of ['inputTokens', 'outputTokens', 'cacheReadInputTokens', 'cacheCreationInputTokens', 'reportedCostUsd'] as const) {
@@ -4836,10 +4952,8 @@ async function consumeProviderText(
4836
4952
  // Preserve the provider or callback error that caused this close.
4837
4953
  }
4838
4954
  };
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
- );
4955
+ const idleTimeout = resolveStreamIdleTimeout(opts, provider, opts.model ?? provider.defaultModel);
4956
+ const idleMs = idleTimeout.ms;
4843
4957
 
4844
4958
  while (true) {
4845
4959
  let idleTimer: ReturnType<typeof setTimeout> | undefined;
@@ -4860,10 +4974,9 @@ async function consumeProviderText(
4860
4974
  if (step.idle) {
4861
4975
  debug.log('llm', 'stream-idle-timeout', {
4862
4976
  provider: provider.name, model: opts.model ?? provider.defaultModel,
4863
- idleMs, receivedChars: full.length, site: 'stream-llm',
4977
+ idleMs, idleMsSource: idleTimeout.source, receivedChars: full.length, site: 'stream-llm',
4864
4978
  });
4865
- const error = new Error(`stream idle timeout after ${idleMs}ms`);
4866
- error.name = 'StreamIdleTimeoutError';
4979
+ const error = new StreamIdleTimeoutError(idleMs);
4867
4980
  // An async generator can leave return() pending behind a stalled next().
4868
4981
  void closeIterator();
4869
4982
  return { ok: false, full, error };
@@ -4945,6 +5058,7 @@ export async function streamLLM(
4945
5058
  textChars: full.length,
4946
5059
  usage: consumed.usage,
4947
5060
  });
5061
+ (await import('./session-runtime/provider-circuit.js')).recordProviderSuccess(activeProvider.name, Date.now());
4948
5062
  return full;
4949
5063
  }
4950
5064
 
@@ -4962,6 +5076,7 @@ export async function streamLLM(
4962
5076
  status: err.status,
4963
5077
  kind: err.kind,
4964
5078
  }, { level: 'warn' });
5079
+ (await import('./session-runtime/provider-circuit.js')).recordProviderFailure(activeProvider.name, 'overload-failover', Date.now());
4965
5080
  activeProvider = switched;
4966
5081
  modelOverride = undefined;
4967
5082
  attemptedProviders.add(activeProvider.name);
@@ -4998,6 +5113,7 @@ export async function streamLLM(
4998
5113
  tools: opts.tools?.map((tool) => tool.name) ?? [],
4999
5114
  ...(opts.model ? { modelOverrideDropped: opts.model } : {}),
5000
5115
  }, { level: 'warn' });
5116
+ (await import('./session-runtime/provider-circuit.js')).recordProviderFailure(activeProvider.name, verdict.category, Date.now());
5001
5117
  activeProvider = fallback;
5002
5118
  modelOverride = undefined;
5003
5119
  attemptedProviders.add(activeProvider.name);
@@ -5123,7 +5239,7 @@ export function resolveDefaultProvider(model?: string): LLMProvider {
5123
5239
  export interface StreamWithToolsHandlers {
5124
5240
  /** Streaming text delta — invoked for every text chunk across all turns. */
5125
5241
  onText(delta: string, fullAcrossTurns: string): void;
5126
- /** Optional status line for hosts that surface transient stream recovery. */
5242
+ /** Transient user-visible status emitted by stream recovery (not model text). */
5127
5243
  onStatus?(line: string): void;
5128
5244
  /** Dispatch a tool call. Returning undefined/null throws in the loop so
5129
5245
  * callers should always return a JSON-serializable value (can be an
@@ -8336,12 +8452,42 @@ export async function streamLLMWithTools(
8336
8452
  skipProviders?: readonly string[];
8337
8453
  } = {},
8338
8454
  ): Promise<string> {
8455
+ // Fixed providers keep the original synchronous launch path. Load the circuit before
8456
+ // selection only when it can change this turn's initial provider.
8457
+ const circuit = !opts.provider && !opts.initialProvider
8458
+ ? await import('./session-runtime/provider-circuit.js')
8459
+ : undefined;
8339
8460
  const provider = opts.provider ?? opts.initialProvider ?? resolveDefaultProvider(opts.model);
8340
8461
  const initial = finalizeStreamingProviderModel(provider, opts.model ?? provider.defaultModel);
8341
8462
  let activeProvider = initial.provider;
8342
8463
  let activeModel = initial.model;
8464
+ let circuitBlockedInitialProvider: string | undefined;
8465
+ // SURVIVAL-PROVIDER-CIRCUIT (S2): 명시·고정 공급자(`provider`·`initialProvider`)가 없고
8466
+ // 회로가 열려(open) 있으면, 체인 해석(`remainingFallbackProviderNames` 순서)의 첫
8467
+ // closed(없으면 half-open) 공급자로 이번 턴을 연다. 모두 open 이면 원래 1순위를 그대로
8468
+ // 쓴다(«갈 곳 없음 = 멈춤»이 아니다) — `circuit-all-open` 관측만 남긴다.
8469
+ if (circuit && circuit.circuitState(activeProvider.name, Date.now()) === 'open') {
8470
+ const chainNames = [activeProvider.name, ...remainingFallbackProviderNames(new Set(opts.skipProviders ?? []), opts)]
8471
+ .filter((name) => providerByName(name)?.available());
8472
+ const pickedName = circuit.pickInitialProvider(chainNames, Date.now());
8473
+ const picked = pickedName && pickedName !== activeProvider.name ? providerByName(pickedName) : undefined;
8474
+ if (picked && picked.available()) {
8475
+ circuitBlockedInitialProvider = activeProvider.name;
8476
+ debug.log('tui.survival', 'turn-opened-on-fallback', { blocked: activeProvider.name, opened: picked.name });
8477
+ handlers.onStatus?.(`⚡ ${activeProvider.name} 회로 열림 — 이번 턴은 ${picked.name}`);
8478
+ const pickedInitial = finalizeStreamingProviderModel(picked, picked.defaultModel);
8479
+ activeProvider = pickedInitial.provider;
8480
+ activeModel = pickedInitial.model;
8481
+ } else {
8482
+ debug.log('tui.survival', 'circuit-all-open', { blocked: activeProvider.name });
8483
+ }
8484
+ }
8343
8485
  opts.onResolvedProvider?.(activeProvider.name, activeModel);
8344
- const attemptedProviders = new Set<string>([activeProvider.name, ...(opts.skipProviders ?? [])]);
8486
+ const attemptedProviders = new Set<string>([
8487
+ activeProvider.name,
8488
+ ...(circuitBlockedInitialProvider ? [circuitBlockedInitialProvider] : []),
8489
+ ...(opts.skipProviders ?? []),
8490
+ ]);
8345
8491
  let blockedProviders: ProviderFallbackAttempt[] = [];
8346
8492
  const effectiveModel = activeModel;
8347
8493
  const modelFamily = getModelFamily(effectiveModel);
@@ -8361,7 +8507,7 @@ export async function streamLLMWithTools(
8361
8507
  tools: opts.tools?.map(t => t.name),
8362
8508
  toolsAvailable: !!provider.streamChat && (opts.tools?.length ?? 0) > 0,
8363
8509
  }, { level: 'info' });
8364
- if (!provider.streamChat || !opts.tools || opts.tools.length === 0) {
8510
+ if (!provider.streamChat || ((opts.tools?.length ?? 0) === 0 && provider.name !== 'openai-codex')) {
8365
8511
  // No tool support or no tools requested — delegate to plain streamLLM.
8366
8512
  debug.log('llm.router', 'streamLLMWithTools.fallback', {
8367
8513
  reason: !provider.streamChat ? 'provider-no-streamChat' : 'no-tools',
@@ -8522,6 +8668,8 @@ export async function streamLLMWithTools(
8522
8668
  }
8523
8669
  // OR-DEEPSEEK-TUNE — 마지막 턴의 «도구 없는» 추론(deepseek/* 만 채워진다). 매 턴 시작에 비운다.
8524
8670
  let turnReasoningDetails: Array<Record<string, unknown>> | undefined;
8671
+ let turnCodexReasoningItems: LLMMessage['codexReasoningItems'];
8672
+ let turnCodexTextPhase: LLMMessage['phase'];
8525
8673
  const emitTurnComplete = (finalAssistantText?: string): void => {
8526
8674
  if (finalAssistantText && finalAssistantText.length > 0) {
8527
8675
  history.push({
@@ -8530,18 +8678,23 @@ export async function streamLLMWithTools(
8530
8678
  type: 'text', text: finalAssistantText,
8531
8679
  ...(turnReasoningDetails ? { providerMeta: { reasoningDetails: turnReasoningDetails } } : {}),
8532
8680
  }],
8681
+ ...(turnCodexReasoningItems?.length ? { codexReasoningItems: turnCodexReasoningItems } : {}),
8682
+ ...(turnCodexTextPhase ? { phase: turnCodexTextPhase } : {}),
8533
8683
  });
8534
8684
  }
8535
8685
  handlers.onTurnComplete?.(history.slice(initialHistoryLen));
8536
8686
  };
8537
8687
  let fullText = '';
8538
8688
  let sawToolRound = false;
8689
+ let codexContinuationNeeded = false;
8539
8690
  // TUI-GOAL-LOOP-FRICTION — 종결 신호 도구(LLMOpts.terminalToolNames). 비면 아래 분기는 전부 꺼진다.
8540
8691
  const terminalToolNames: ReadonlySet<string> = new Set(opts.terminalToolNames ?? []);
8541
8692
  /** 이 스트림에서 종결 신호를 한 번이라도 디스패치했나. */
8542
8693
  let terminalSignalDispatched = false;
8694
+ let completionNeedsFinalTurn = false;
8543
8695
  /** 도구 라운드에 실린 마지막 «비지 않은» 답 텍스트(종결 신호 턴 마감·빈 턴 포기에서 쓴다). */
8544
8696
  let lastToolRoundText = '';
8697
+ let lastToolRoundTextPhase: 'commentary' | 'final' | undefined;
8545
8698
  const agentBatchTickIntervalMs = opts.agentBatchTickIntervalMs ?? BATCH_TICK_INTERVAL_MS;
8546
8699
 
8547
8700
  // Empty-turn guard: smaller models (gpt-5.4-mini, haiku) sometimes
@@ -8715,7 +8868,7 @@ export async function streamLLMWithTools(
8715
8868
  let loopTermination: 'budget-exhausted' | 'aborted' = 'budget-exhausted';
8716
8869
  let terminalTurn = maxTurns;
8717
8870
 
8718
- for (let turn = 0; turn < maxTurns; turn++) {
8871
+ for (let turn = 0; turn < maxTurns || ((completionNeedsFinalTurn || codexContinuationNeeded) && turn === maxTurns); turn++) {
8719
8872
  // ★ /cancel 범용화(대표 2026-07-12) — abort 신호가 오면 tool 루프를 즉시 중단한다. 이게 없으면
8720
8873
  // fetch 는 abort 돼도 루프가 다음 turn 으로 계속 돌아 tool 반복(grep 폭주 등)이 멈추지 않는다.
8721
8874
  if (opts.signal?.aborted) {
@@ -8748,6 +8901,11 @@ export async function streamLLMWithTools(
8748
8901
  }
8749
8902
  }
8750
8903
  let turnText = '';
8904
+ let turnTextPhase: 'commentary' | 'final' | undefined;
8905
+ let turnEnd: boolean | undefined;
8906
+ codexContinuationNeeded = false;
8907
+ turnCodexReasoningItems = undefined;
8908
+ turnCodexTextPhase = undefined;
8751
8909
  // TUI-TURN-NARRATION — 이 라운드에 받은 추론 요약 글자 수(내용은 남기지 않는다).
8752
8910
  let turnReasoningChars = 0;
8753
8911
  turnReasoningDetails = undefined;
@@ -8847,13 +9005,14 @@ export async function streamLLMWithTools(
8847
9005
  // revert to auto (see above).
8848
9006
  const buildTurnOpts = () => {
8849
9007
  const remainingFallbacks = opts.provider ? 0 : remainingFallbackProviderNames(attemptedProviders, opts).length;
8850
- const base = { ...opts, model: activeModel, tools: activeTools, remainingFallbacks };
9008
+ const base = { ...opts, model: activeModel, tools: completionNeedsFinalTurn ? [] : activeTools, remainingFallbacks };
8851
9009
  return turn === 0 ? base : { ...base, toolChoice: undefined };
8852
9010
  };
8853
9011
  let turnOpts = buildTurnOpts();
8854
9012
  // reasoning-heavy family(codex/claude/gemini/grok/local)는 침묵 추론 구간이 길어 idle watchdog 를
8855
9013
  // 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);
9014
+ const idleTimeout = resolveStreamIdleTimeout(opts, activeProvider, activeModel);
9015
+ const streamIdleTimeoutMs = idleTimeout.ms;
8857
9016
  // ⚠️ hang 진단(2026-07-17) — codex SSE 스트림이 완료 신호 없이 안 끝나는 케이스 확정용.
8858
9017
  // consume-start 후 consume-end 가 없으면 = for-await(streamChat SSE) hang. lastEvType 로 어느
8859
9018
  // 이벤트 뒤에서 멈추는지(usage/text/tool_call) 특정. 무장할 상한도 남겨 실험 런을 구별한다.
@@ -8862,6 +9021,7 @@ export async function streamLLMWithTools(
8862
9021
  provider: activeProvider.name,
8863
9022
  model: activeModel,
8864
9023
  idleMs: streamIdleTimeoutMs,
9024
+ idleMsSource: idleTimeout.source,
8865
9025
  ...(modelFamily !== 'other' ? { modelFamily } : {}),
8866
9026
  });
8867
9027
  let evCount = 0;
@@ -8870,6 +9030,7 @@ export async function streamLLMWithTools(
8870
9030
  // 다시 건다(codex `stream_max_retries` 동형). 이 턴에서 받은 tool_call·image 가 있으면 재시도하지 않는다.
8871
9031
  let streamDisconnectRetries = 0;
8872
9032
  let streamIdleTimeoutRetries = 0;
9033
+ let streamIdleRetryPartial = '';
8873
9034
  let streamEmittedImage = false;
8874
9035
  // 끊긴 조각을 이력(turnText·fullText)에서 «조용히» 되감는다 — 화면엔 지우라고 알리지 않는다
8875
9036
  // (TUI 의 빈 델타는 «커밋»이다). 이미 보인 글과 겹치는 재시도 본문은 `retryDisplay` 가 막는다.
@@ -8887,6 +9048,7 @@ export async function streamLLMWithTools(
8887
9048
  if (turnText.length > 0) {
8888
9049
  fullText = fullText.slice(0, fullText.length - turnText.length);
8889
9050
  turnText = '';
9051
+ turnTextPhase = undefined;
8890
9052
  }
8891
9053
  turnReasoningChars = 0;
8892
9054
  retryDisplay.arm();
@@ -8898,6 +9060,19 @@ export async function streamLLMWithTools(
8898
9060
  // 이벤트 대기를 idle timeout 과 race — STREAM_IDLE_TIMEOUT_MS 무이벤트면 스트림 정리·탈출(받은
8899
9061
  // 텍스트로 진행). 35분 무한 hang → 자동 복구. 원인 규명은 consume-end 부재/idle-timeout 로그가.
8900
9062
  let streamIter: AsyncIterator<LLMStreamEvent> | undefined;
9063
+ let streamAttemptAbort: AbortController | undefined;
9064
+ let streamAttemptAbortCleanup: (() => void) | undefined;
9065
+ const releaseStreamAttempt = (abort = false, reason?: unknown): void => {
9066
+ if (abort) streamAttemptAbort?.abort(reason);
9067
+ streamAttemptAbortCleanup?.();
9068
+ streamAttemptAbortCleanup = undefined;
9069
+ streamAttemptAbort = undefined;
9070
+ };
9071
+ let providerToolSchemaDiagnostics: Record<string, {
9072
+ originalName: string;
9073
+ providerName: string;
9074
+ tupleItemsPaths: Record<string, string>;
9075
+ }> | undefined;
8901
9076
  while (true) {
8902
9077
  let idleTimer: ReturnType<typeof setTimeout> | undefined;
8903
9078
  let step: { idle: true } | { idle: false; r: IteratorResult<LLMStreamEvent, void> };
@@ -8927,8 +9102,22 @@ export async function streamLLMWithTools(
8927
9102
  ? [turnOpts.toolChoice.name]
8928
9103
  : []),
8929
9104
  ]);
9105
+ const attemptAbort = new AbortController();
9106
+ const forwardExternalAbort = () => attemptAbort.abort(opts.signal?.reason);
9107
+ if (opts.signal?.aborted) forwardExternalAbort();
9108
+ else opts.signal?.addEventListener('abort', forwardExternalAbort, { once: true });
9109
+ streamAttemptAbort = attemptAbort;
9110
+ streamAttemptAbortCleanup = () => opts.signal?.removeEventListener('abort', forwardExternalAbort);
9111
+ providerToolSchemaDiagnostics = Object.fromEntries((turnOpts.tools ?? []).map((tool, index) => [String(index), {
9112
+ originalName: tool.name,
9113
+ providerName: nameMap.toProviderName(tool.name),
9114
+ tupleItemsPaths: Object.fromEntries(
9115
+ collectTupleItemsSchemaPaths(tool.parameters).map((path, pathIndex) => [String(pathIndex), path]),
9116
+ ),
9117
+ }]));
8930
9118
  const providerOpts: LLMOpts = {
8931
9119
  ...turnOpts,
9120
+ signal: attemptAbort.signal,
8932
9121
  tools: turnOpts.tools?.map((tool) => ({ ...tool, name: nameMap.toProviderName(tool.name) })),
8933
9122
  toolChoice: turnOpts.toolChoice && typeof turnOpts.toolChoice === 'object'
8934
9123
  ? { name: nameMap.toProviderName(turnOpts.toolChoice.name) }
@@ -8944,21 +9133,25 @@ export async function streamLLMWithTools(
8944
9133
  streamIter.next().then((r) => ({ idle: false as const, r })),
8945
9134
  new Promise<{ idle: true }>((res) => { idleTimer = setTimeout(() => res({ idle: true }), streamIdleTimeoutMs); }),
8946
9135
  ]);
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
- }
9136
+ if (step.idle) throw new StreamIdleTimeoutError(streamIdleTimeoutMs);
8953
9137
  } catch (err) {
8954
9138
  if (idleTimer) clearTimeout(idleTimer);
8955
- if (err instanceof Error && err.name === 'StreamIdleTimeoutError') {
9139
+ releaseStreamAttempt(true, err);
9140
+ if (err instanceof ApiHttpError && err.status === 400 && providerToolSchemaDiagnostics) {
9141
+ debug.log('llm.tool-schema', 'provider-request-rejected', {
9142
+ provider: activeProvider.name,
9143
+ model: activeModel,
9144
+ turn,
9145
+ status: err.status,
9146
+ tools: providerToolSchemaDiagnostics,
9147
+ }, { level: 'warn' });
9148
+ }
9149
+ if (err instanceof StreamIdleTimeoutError) {
8956
9150
  debug.log('llm.stream', 'idle-timeout', {
8957
- turn, evCount, lastEvType, idleMs: streamIdleTimeoutMs,
9151
+ turn, evCount, lastEvType, idleMs: err.idleMs, idleMsSource: idleTimeout.source,
8958
9152
  textChars: turnText.length, pendingCalls: pendingCalls.length,
8959
9153
  }, { 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.
9154
+ // Provider iterators can queue return() behind a pending next(). Detach cleanup.
8962
9155
  const timedOutIterator = streamIter;
8963
9156
  if (timedOutIterator?.return) {
8964
9157
  try { void Promise.resolve(timedOutIterator.return(undefined)).catch(() => {}); } catch { /* fail-soft cleanup */ }
@@ -8971,18 +9164,29 @@ export async function streamLLMWithTools(
8971
9164
  attempt: streamIdleTimeoutRetries, maxRetries: STREAM_IDLE_TIMEOUT_MAX_RETRIES,
8972
9165
  delayMs: 0, statusLine: '스트림 무응답 · 재시도',
8973
9166
  }, { level: 'warn' });
8974
- handlers.onStatus?.(`스트림 무응답 · 재시도 ${streamIdleTimeoutRetries}/${STREAM_IDLE_TIMEOUT_MAX_RETRIES}`);
9167
+ try { handlers.onStatus?.(`스트림 무응답 · 재시도 ${streamIdleTimeoutRetries}/${STREAM_IDLE_TIMEOUT_MAX_RETRIES}`); } catch { /* UI notification must not block recovery */ }
9168
+ streamIdleRetryPartial = turnText;
8975
9169
  rewindDisconnectedStream();
8976
9170
  streamIter = undefined;
8977
9171
  continue;
8978
9172
  }
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();
9173
+ // Do not replay or advance providers after a tool side effect or image escaped.
9174
+ if (pendingCalls.length > 0 || streamEmittedImage) break;
9175
+ if (opts.signal?.aborted) throw err;
9176
+ if (opts.provider && (turnText.length > 0 || streamIdleRetryPartial.length > 0)) {
9177
+ // A pinned provider cannot advance to fallback. Keep already shown partial text
9178
+ // and complete the turn instead of rewinding it and failing the whole response.
9179
+ if (turnText.length === 0 && streamIdleRetryPartial.length > 0) {
9180
+ turnText = streamIdleRetryPartial;
9181
+ fullText += streamIdleRetryPartial;
9182
+ }
9183
+ try { handlers.onStatus?.('스트림 무응답 · 재시도 소진 (부분 응답 유지)'); } catch { /* status is fail-soft */ }
9184
+ debug.log('llm.stream', 'idle-timeout-pinned-partial-preserved', { turn, evCount, lastEvType, textChars: turnText.length }, { level: 'warn' });
9185
+ break;
9186
+ }
9187
+ if (opts.provider) throw err;
9188
+ rewindDisconnectedStream();
9189
+ streamIter = undefined;
8986
9190
  }
8987
9191
  if (err instanceof OverloadFailoverError && !opts.provider) {
8988
9192
  const switched = activeProvider.name === 'openai-codex'
@@ -8990,6 +9194,7 @@ export async function streamLLMWithTools(
8990
9194
  : providerByName('grok');
8991
9195
  if (switched && !attemptedProviders.has(switched.name)
8992
9196
  && (activeProvider.name === 'openai-codex' || switched.available()) && evCount === 0) {
9197
+ (circuit ?? await import('./session-runtime/provider-circuit.js')).recordProviderFailure(activeProvider.name, 'overload-failover', Date.now());
8993
9198
  debug.log('llm.router', 'overload-failover', {
8994
9199
  turn,
8995
9200
  blockedProvider: activeProvider.name,
@@ -9004,13 +9209,13 @@ export async function streamLLMWithTools(
9004
9209
  activeModel = finalized.model;
9005
9210
  attemptedProviders.add(activeProvider.name);
9006
9211
  streamDisconnectRetries = 0;
9212
+ streamIdleTimeoutRetries = 0;
9007
9213
  turnOpts = buildTurnOpts();
9008
9214
  streamIter = undefined;
9009
9215
  continue;
9010
9216
  }
9011
9217
  }
9012
- if (!(err instanceof Error && err.name === 'StreamIdleTimeoutError')
9013
- && evCount > 0 && isStreamDisconnectRetryable(err, pendingCalls.length, streamEmittedImage) && !opts.signal?.aborted) {
9218
+ if (!(err instanceof StreamIdleTimeoutError) && evCount > 0 && isStreamDisconnectRetryable(err, pendingCalls.length, streamEmittedImage) && !opts.signal?.aborted) {
9014
9219
  const retryData = {
9015
9220
  provider: activeProvider.name,
9016
9221
  model: activeModel,
@@ -9054,6 +9259,7 @@ export async function streamLLMWithTools(
9054
9259
  remainingProviders: remaining,
9055
9260
  attempted: blockedProviders,
9056
9261
  });
9262
+ (circuit ?? await import('./session-runtime/provider-circuit.js')).recordProviderFailure(activeProvider.name, verdict.category, Date.now());
9057
9263
  blockedProviders = [...verdict.attempts];
9058
9264
  const fallback = verdict.action === 'advance' ? providerByName(verdict.nextProvider) : undefined;
9059
9265
  if (!fallback) {
@@ -9086,24 +9292,36 @@ export async function streamLLMWithTools(
9086
9292
  attemptedProviders.add(activeProvider.name);
9087
9293
  // 재시도 상한은 «공급자마다» — 넘어간 공급자도 스트림 도중 끊김이면 다시 5회까지 건다.
9088
9294
  streamDisconnectRetries = 0;
9295
+ streamIdleTimeoutRetries = 0;
9089
9296
  turnOpts = buildTurnOpts();
9090
9297
  streamIter = undefined;
9091
9298
  continue;
9092
9299
  }
9093
9300
  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');
9301
+ if (step.r.done) {
9302
+ (circuit ?? await import('./session-runtime/provider-circuit.js')).recordProviderSuccess(activeProvider.name, Date.now());
9303
+ releaseStreamAttempt();
9304
+ break;
9098
9305
  }
9099
- if (step.r.done) break;
9100
9306
  const ev = step.r.value;
9101
9307
  evCount++;
9102
9308
  lastEvType = ev.type;
9309
+ // One same-provider idle retry is allowed per turn; progress clears the stale partial marker,
9310
+ // but does not reset that retry budget.
9311
+ if (streamIdleTimeoutRetries > 0) streamIdleRetryPartial = '';
9103
9312
  if (ev.type === 'text') {
9104
9313
  turnText += ev.delta;
9314
+ if (ev.phase) turnTextPhase = ev.phase;
9315
+ if (ev.phase) turnCodexTextPhase = ev.phase === 'final' ? 'final_answer' : 'commentary';
9105
9316
  fullText += ev.delta;
9106
9317
  emitRetryDisplayStep(retryDisplay.onText(ev.delta, turnText));
9318
+ } else if (ev.type === 'text_phase') {
9319
+ turnTextPhase = ev.phase;
9320
+ turnCodexTextPhase = ev.phase === 'final' ? 'final_answer' : 'commentary';
9321
+ } else if (ev.type === 'turn_end') {
9322
+ turnEnd = ev.endTurn;
9323
+ } else if (ev.type === 'codex_reasoning_item') {
9324
+ (turnCodexReasoningItems ??= []).push(ev.item);
9107
9325
  } else if (ev.type === 'reasoning_details') {
9108
9326
  turnReasoningDetails = ev.details;
9109
9327
  } else if (ev.type === 'tool_call') {
@@ -9230,6 +9448,26 @@ export async function streamLLMWithTools(
9230
9448
  }
9231
9449
  }
9232
9450
 
9451
+ // Codex can explicitly mark a response as unfinished even when it emitted
9452
+ // text and no tools. Keep that text in history with its phase, then ask
9453
+ // again. The extra turn is bounded to one past maxTurns.
9454
+ if (pendingCalls.length === 0 && turnEnd === false) {
9455
+ if (turnText || turnCodexReasoningItems?.length) history.push({
9456
+ role: 'assistant', content: turnText,
9457
+ ...(turnTextPhase ? { phase: turnTextPhase === 'final' ? 'final_answer' as const : 'commentary' as const } : {}),
9458
+ ...(turnCodexReasoningItems?.length ? { codexReasoningItems: turnCodexReasoningItems } : {}),
9459
+ });
9460
+ if (turnText) {
9461
+ clearVisibleAssistantTextForToolRound(handlers, sawToolRound);
9462
+ sawToolRound = true;
9463
+ }
9464
+ codexContinuationNeeded = true;
9465
+ debug.log('llm.router', 'codex.end-turn-false-continue', { turn, phase: turnTextPhase ?? 'unknown', textChars: turnText.length });
9466
+ // The one grace turn is also unfinished. Fall through to the existing
9467
+ // budget notice; never present a phase-marked progress item as final.
9468
+ if (turn >= maxTurns) break;
9469
+ continue;
9470
+ }
9233
9471
  // No tool calls + non-empty text = the model gave its final answer.
9234
9472
  if (pendingCalls.length === 0 && turnText.length > 0) {
9235
9473
  const visibleText = finalizeVisibleAssistantText(fullText, turnText, sawToolRound);
@@ -9267,7 +9505,7 @@ export async function streamLLMWithTools(
9267
9505
  // TUI-H1-GOALLOOP-EMPTY-TAIL — 답이 이 스트림 «앞»에 이미 보였어도 같다(opts.terminalPriorAnswerVisible —
9268
9506
  // 예: Bash → «끝» → 재주입 → update_goal 만 → 빈 턴). 이 스트림에 답이 없으면 빈 문자열로 끝내고 화면은
9269
9507
  // 건드리지 않는다(onText 생략 · emitTurnComplete 는 빈 답을 이력에 안 싣는다). 마감은 호출자(goal loop) 몫.
9270
- if (terminalSignalDispatched && (lastToolRoundText.trim().length > 0 || opts.terminalPriorAnswerVisible === true)) {
9508
+ if (!completionNeedsFinalTurn && terminalSignalDispatched && (lastToolRoundText.trim().length > 0 || opts.terminalPriorAnswerVisible === true)) {
9271
9509
  const answer = lastToolRoundText.trim().length > 0 ? lastToolRoundText : '';
9272
9510
  debug.log('goal.loop', 'empty-turn-after-terminal-signal', {
9273
9511
  turn, emptyRetries, answerChars: answer.length,
@@ -9311,7 +9549,10 @@ export async function streamLLMWithTools(
9311
9549
  clearVisibleAssistantTextForToolRound(handlers, sawToolRound);
9312
9550
  }
9313
9551
  sawToolRound = true;
9314
- if (turnText.trim().length > 0) lastToolRoundText = turnText;
9552
+ if (turnText.trim().length > 0) {
9553
+ lastToolRoundText = turnText;
9554
+ lastToolRoundTextPhase = turnTextPhase;
9555
+ }
9315
9556
  // TUI-GOAL-LOOP-FRICTION(#46) — 이 턴의 호출이 «종결 신호뿐»이면 예산·후속 단계 거부를 걸지 않는다.
9316
9557
  const terminalOnlyTurn = terminalToolNames.size > 0
9317
9558
  && pendingCalls.length > 0
@@ -9360,7 +9601,10 @@ export async function streamLLMWithTools(
9360
9601
  ...(call.providerMeta !== undefined ? { providerMeta: call.providerMeta } : {}),
9361
9602
  });
9362
9603
  }
9363
- history.push({ role: 'assistant', content: assistantBlocks });
9604
+ history.push({ role: 'assistant', content: assistantBlocks,
9605
+ ...(turnTextPhase ? { phase: turnTextPhase === 'final' ? 'final_answer' as const : 'commentary' as const } : {}),
9606
+ ...(turnCodexReasoningItems?.length ? { codexReasoningItems: turnCodexReasoningItems } : {}),
9607
+ });
9364
9608
 
9365
9609
  // Final-synthesis window: in the last FINAL_SYNTHESIS_TURNS of the
9366
9610
  // budget, refuse to execute tool calls. Each pending call gets a
@@ -11183,10 +11427,43 @@ export async function streamLLMWithTools(
11183
11427
  }
11184
11428
  // 신호뿐인 턴 · 섞인 턴(정상 디스패치 · 부분 거부 · 통째 거부의 수신 확인) 모두 — 종결 신호가 이 턴에 «받아들여졌다».
11185
11429
  if (!hardStopText && pendingCalls.some(isTerminalCall)) terminalSignalDispatched = true;
11430
+ // A goal completion with evidence has already been acknowledged by dispatch. The
11431
+ // across-turn goal loop owns final acceptance (test evidence / rejected calls),
11432
+ // but no further provider request is needed to make that decision. Keep the
11433
+ // tool-result blocks in onTurnComplete so it can inspect the original call.
11434
+ const hasAcceptedCompletion = !hardStopText && opts.terminalCompletionAccepted && pendingCalls.some((call) =>
11435
+ isTerminalCall(call) && resultBlocks.some((block) => block.type === 'tool_result'
11436
+ && block.tool_use_id === call.id && !block.isError)
11437
+ && opts.terminalCompletionAccepted!(call.name, call.args));
11438
+ const hasVisibleAnswer = lastToolRoundText.trim().length > 0 || opts.terminalPriorAnswerVisible === true;
11439
+ // A completion tool can follow a commentary/progress segment on any provider.
11440
+ // Its successful ack does not turn that segment into a final user answer.
11441
+ const completionTextIsProgress = lastToolRoundTextPhase === 'commentary'
11442
+ || (lastToolRoundTextPhase !== 'final' && lastToolRoundText.trim().length <= 240
11443
+ && /(?:하겠습니다|드리겠습니다|할게요|할 예정입니다|확인하겠|살펴보겠|작성하겠|정리하겠|I(?:'ll| will)\b|\bnext[,]?\s+I\b)/i.test(lastToolRoundText));
11444
+ if (hasAcceptedCompletion && completionTextIsProgress) {
11445
+ completionNeedsFinalTurn = true;
11446
+ history.push({ role: LOOP_SELF_NOTE_ROLE, content: 'The goal completion was accepted. Write your final answer to the user now as plain text. Do not call tools or describe what you will do next.' });
11447
+ debug.log('goal.loop', 'completion-await-final', { turn, progressChars: lastToolRoundText.length, phase: lastToolRoundTextPhase ?? 'unknown' }, { level: 'info' });
11448
+ }
11449
+ if (hasAcceptedCompletion && hasVisibleAnswer && !completionNeedsFinalTurn) {
11450
+ const answer = lastToolRoundText.trim().length > 0 ? lastToolRoundText : '';
11451
+ const visiblePriorAnswer = opts.terminalPriorAnswerVisible === true;
11452
+ debug.log('goal.loop', 'accepted-terminal-completion-finish', {
11453
+ turn, answerChars: answer.length, priorAnswerVisible: visiblePriorAnswer,
11454
+ tools: pendingCalls.map((call) => call.name),
11455
+ }, { level: 'info' });
11456
+ // Every text delta was already streamed above (and fullText accumulates
11457
+ // all rounds). A second onText here would violate the cumulative-full
11458
+ // contract and duplicate text shown before this terminal tool call.
11459
+ const finalAnswer = answer || (opts.terminalPriorAnswerVisible ? fullText.trim() : '');
11460
+ emitTurnComplete(finalAnswer);
11461
+ return finalAnswer;
11462
+ }
11186
11463
  // TUI-GOAL-LOOP-FRICTION(#63) — 종결 신호뿐인 턴 ⊕ 같은 턴에 쓴 답 = 이 턴이 최종 답이다.
11187
11464
  // 다음 모델 턴을 열면 할 말이 없는 모델이 빈 턴을 되풀이하고 `[NO FINAL SYNTHESIS]` 가 붙는다(10-15 22:36Z 표본).
11188
11465
  // 이 턴에 답이 없으면 종전처럼 다음 턴을 연다(앞 라운드 글은 «하겠습니다» 류 서술일 수 있다 — 모델이 답을 쓰게).
11189
- if (terminalOnlyTurn && !hardStopText && !postToolResultUserNote && turnText.trim().length > 0) {
11466
+ if (terminalOnlyTurn && !completionNeedsFinalTurn && !hardStopText && !postToolResultUserNote && turnText.trim().length > 0) {
11190
11467
  const answer = turnText;
11191
11468
  debug.log('goal.loop', 'terminal-signal-finish', {
11192
11469
  turn, maxTurns, tools: pendingCalls.map((call) => call.name), answerChars: answer.length,
@@ -11400,6 +11677,7 @@ export async function streamLLMWithTools(
11400
11677
  let synthesizedText: string | null = null;
11401
11678
  const shouldForceSynthesisOnBudget =
11402
11679
  loopTermination === 'budget-exhausted'
11680
+ && !codexContinuationNeeded
11403
11681
  && !forceSynthesisAttempted
11404
11682
  && (modelFamily === 'gemini'
11405
11683
  || applyCodexBehaviorGuard('force-synthesis', effectiveModel, codexGuardCandidate, true));
@@ -11867,6 +12145,8 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
11867
12145
  const responsesUrl = cfgBase
11868
12146
  ? (cfgBase.endsWith('/responses') ? cfgBase : `${cfgBase.replace(/\/$/, '')}/responses`)
11869
12147
  : `${CODEX_API_BASE_URL}/responses`;
12148
+ const endpointSupportsStore = !responsesUrl.includes('chatgpt.com/backend-api');
12149
+ const useStore = endpointSupportsStore && cfg.codexStore !== false;
11870
12150
  // Image-pipeline P3 (2026-05-05) — gate the function_call_output
11871
12151
  // ContentItem[] wire on the active model. gpt-5 family supports
11872
12152
  // image_url in tool results; legacy gpt-4 / non-vision routes
@@ -11886,6 +12166,7 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
11886
12166
  const { instructions, input } = messagesToResponsesInput(messages, {
11887
12167
  acceptToolImages: codexAcceptToolImages,
11888
12168
  acceptUserMessageImages: codexAcceptUserImages,
12169
+ replayCodexReasoning: !useStore,
11889
12170
  onOrphanToolResult: (callId) => {
11890
12171
  try { debug.log('llm.codex', 'orphan-tool-output-dropped', { callId }); }
11891
12172
  catch { /* observation must not block the request */ }
@@ -11935,12 +12216,17 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
11935
12216
  // wave for ChatGPT regardless of cfg.codexStore — the
11936
12217
  // infrastructure stays in place for Azure/API-key paths and
11937
12218
  // for any future ChatGPT-side policy change.
11938
- const endpointSupportsStore = !responsesUrl.includes('chatgpt.com/backend-api');
11939
- const useStore = endpointSupportsStore && cfg.codexStore !== false;
11940
12219
  const delta = useStore ? getIncrementalItems(lastInput, input) : null;
11941
- const wireInput = delta ?? input;
12220
+ // A stored continuation already has old reasoning. A full rebase has
12221
+ // no previous_response_id, so it must replay those opaque items.
12222
+ const wireInput = delta ?? (useStore
12223
+ ? messagesToResponsesInput(messages, {
12224
+ acceptToolImages: codexAcceptToolImages,
12225
+ acceptUserMessageImages: codexAcceptUserImages,
12226
+ }).input
12227
+ : input);
11942
12228
  const wirePrevId = (delta && lastResponseId) ? lastResponseId : undefined;
11943
- yield* streamCodexResponsesEvents(responsesUrl, tokens.accessToken, {
12229
+ let requestBody = {
11944
12230
  model: opts.model || model,
11945
12231
  instructions,
11946
12232
  input: wireInput,
@@ -11954,8 +12240,77 @@ function makeCodexProvider(cfg: UCLLMConfig): LLMProvider {
11954
12240
  // can disable via llm.codexStore=false.
11955
12241
  store: useStore,
11956
12242
  ...(wirePrevId ? { previousResponseId: wirePrevId } : {}),
11957
- }, opts.signal, {
12243
+ };
12244
+ const requestGuard = opts.requestSizeGuard;
12245
+ const promptCache = opts.promptCache !== false;
12246
+ let serializedWireBody: string | undefined;
12247
+ if (requestGuard) {
12248
+ serializedWireBody = serializeCodexResponsesWireBody(requestBody, promptCache);
12249
+ let requestChars = serializedWireBody.length;
12250
+ const initialRequestChars = requestChars;
12251
+ let requestCompacted = false;
12252
+ let requestCompactReason = requestChars > requestGuard.maxChars
12253
+ ? 'store-threading-skipped'
12254
+ : 'under-cap';
12255
+ if (requestChars > requestGuard.maxChars && !useStore) {
12256
+ try {
12257
+ const compacted = await requestGuard.compactMessages(messages, {
12258
+ provider: 'openai-codex', requestChars, maxChars: requestGuard.maxChars,
12259
+ });
12260
+ if (compacted) {
12261
+ const compactInput = messagesToResponsesInput(compacted.messages, {
12262
+ acceptToolImages: codexAcceptToolImages,
12263
+ acceptUserMessageImages: codexAcceptUserImages,
12264
+ // Compaction runs only when store threading is off, matching
12265
+ // the original non-threaded wire input policy.
12266
+ replayCodexReasoning: !useStore,
12267
+ });
12268
+ const compactBody = { ...requestBody, instructions: compactInput.instructions, input: compactInput.input };
12269
+ const compactWireBody = serializeCodexResponsesWireBody(compactBody, promptCache);
12270
+ const compactChars = compactWireBody.length;
12271
+ if (compactChars < requestChars) {
12272
+ messages.splice(0, messages.length, ...compacted.messages);
12273
+ requestBody = compactBody;
12274
+ serializedWireBody = compactWireBody;
12275
+ requestChars = compactChars;
12276
+ requestCompacted = true;
12277
+ requestCompactReason = compactChars > requestGuard.maxChars
12278
+ ? `compaction-still-over-cap:${compacted.reason}`
12279
+ : compacted.reason;
12280
+ } else {
12281
+ requestCompactReason = 'compaction-no-reduction';
12282
+ }
12283
+ } else {
12284
+ requestCompactReason = 'compaction-no-op';
12285
+ }
12286
+ } catch (error) {
12287
+ requestCompactReason = 'compaction-error';
12288
+ debug.log('llm.request', 'request-size-compact-error', {
12289
+ provider: 'openai-codex',
12290
+ requestChars,
12291
+ maxChars: requestGuard.maxChars,
12292
+ message: error instanceof Error ? error.message : String(error),
12293
+ }, { level: 'warn' });
12294
+ }
12295
+ }
12296
+ requestGuard.onMeasured?.({
12297
+ provider: 'openai-codex', requestChars, maxChars: requestGuard.maxChars,
12298
+ compacted: requestCompacted, reason: requestCompactReason,
12299
+ ...(requestCompacted ? { previousChars: initialRequestChars } : {}),
12300
+ });
12301
+ debug.log('llm.request', 'request-size-compact', {
12302
+ provider: 'openai-codex',
12303
+ beforeChars: initialRequestChars,
12304
+ afterChars: requestChars,
12305
+ maxChars: requestGuard.maxChars,
12306
+ compacted: requestCompacted,
12307
+ reason: requestCompactReason,
12308
+ });
12309
+ }
12310
+ yield* streamCodexResponsesEvents(responsesUrl, tokens.accessToken, requestBody, opts.signal, {
11958
12311
  accountId,
12312
+ promptCache,
12313
+ ...(serializedWireBody ? { serializedWireBody } : {}),
11959
12314
  // Capture the new response id from the SSE stream so the
11960
12315
  // next call can thread it. If the backend skips the event
11961
12316
  // lastResponseId stays undefined and we fall back to full