elanous 0.2.28 → 0.2.31

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 (818) 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/{y1ltt7oCZxVpC6zVPhZ2O → LSXfY0u9QtfDbejY0Ukf_}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{ab4d57c2.e90bc2cb31949045.js → 0bb0ff36.b8d418980951e5ba.js} +7 -7
  5. package/apps/pwa/out/_next/static/chunks/1034-8b639cee57b8e4a8.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1085-6c2d35526f11f840.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1220.1ff7d5cdc34b1ae5.js +27 -0
  8. package/apps/pwa/out/_next/static/chunks/1247.5884ec81b39fe2e7.js +287 -0
  9. package/apps/pwa/out/_next/static/chunks/1302.87681521a7a4661e.js +62 -0
  10. package/apps/pwa/out/_next/static/chunks/1324-59dae3002413623b.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{5492-e05e26a033ba80d2.js → 1335-6731f392c35216eb.js} +3 -3
  12. package/apps/pwa/out/_next/static/chunks/1343-f40b5870ac1eda5a.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/146-b6cd56be11dda017.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/1654.32de4b324981a188.js +23 -0
  15. package/apps/pwa/out/_next/static/chunks/1834-50625904996a8fa8.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/1849.1ce38efa6c527d9a.js +149 -0
  17. package/apps/pwa/out/_next/static/chunks/1913.7cc6fb6055b1ed3f.js +41 -0
  18. package/apps/pwa/out/_next/static/chunks/2001.6c7e0fd02190886c.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/2030.41fae98c7e907304.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/2099-08b2f2dfba742c9b.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/2112-e4efc21a6505d5bc.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2134.ecee03a550ee7250.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{8578.dff22d988cc2b106.js → 2213.2ccbada92c33ea0f.js} +3 -3
  24. package/apps/pwa/out/_next/static/chunks/2236.66061832181cb745.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/2422.b0bd272e7020d7e6.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/2534.e85ae21ad0dac829.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/{5081.a240b63fad28663a.js → 2871.0bca6137343b3f76.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/2898.fd781bc7cf01a7b7.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2940-619bfd8ef337c611.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/{2846.d6a433b8663819a5.js → 2985.39ccdecd36848b12.js} +4 -4
  31. package/apps/pwa/out/_next/static/chunks/3040.92ff9ffc9f971cb9.js +115 -0
  32. package/apps/pwa/out/_next/static/chunks/3144.6861096c7b4edef7.js +64 -0
  33. package/apps/pwa/out/_next/static/chunks/{f4dfbfc0.a69fb12f854882aa.js → 3267c0e6.d34854e7b3984a6e.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/3278.2e5d15b53a4091c8.js +2 -0
  35. package/apps/pwa/out/_next/static/chunks/3541-ce3a5424ed5ad7f1.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3583.a05e88cc8d594628.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3771.e928edf2867d28c0.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/{3143.257997a5f1ed79df.js → 3902.e1e71aaac850a045.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/4063-03284b5de59965f3.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/{16e57f67.3ea19e893461e611.js → 409f1e57.d0649fc47691ebfe.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/{1146.80d15ea7c6e6ee95.js → 4111.2bfedbb233ad206b.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/{1516.83dd2c1b86b4e507.js → 4174.92ed04922709ac9e.js} +2 -2
  43. package/apps/pwa/out/_next/static/chunks/4300-c7ec4eeabbb5f3f7.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/4483.646d5065199e1b55.js +36 -0
  45. package/apps/pwa/out/_next/static/chunks/4519.28199ff26b2fd80d.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/4581.ef2443578054544f.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/4608-8d894e9cbe4f08bf.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/4639.ae48a01d1bb50a93.js +286 -0
  49. package/apps/pwa/out/_next/static/chunks/4739.08742ac668aada67.js +77 -0
  50. package/apps/pwa/out/_next/static/chunks/4745.67d6f99c245af3f7.js +226 -0
  51. package/apps/pwa/out/_next/static/chunks/4882-16cef71d6a1a32be.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/{9938.b23e4d4dfa0114cd.js → 4999.c064a4000c2a9e94.js} +2 -2
  53. package/apps/pwa/out/_next/static/chunks/5173-233188df2f42c626.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/5260-69f35419bca263e7.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/5272.b92d1b72a15c25fe.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/{1157.f08bf2b70915e176.js → 5304.04d24756f242a094.js} +2 -2
  57. package/apps/pwa/out/_next/static/chunks/{7752.5cbdd0b433c54af9.js → 5307.60f2209e809bd627.js} +1 -1
  58. package/apps/pwa/out/_next/static/chunks/5415-997980f828667736.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/{9917-9e26abc5f7772735.js → 5458-16cd7a2b51b74c8c.js} +1 -1
  60. package/apps/pwa/out/_next/static/chunks/{6196.34d6b229079e9e28.js → 5525.86debf231e181813.js} +1 -1
  61. package/apps/pwa/out/_next/static/chunks/{4593.10f5b9bd702d4a6c.js → 5543.a438b8ab5a13ea47.js} +1 -1
  62. package/apps/pwa/out/_next/static/chunks/5572-42c9084f27ca761e.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/5661-80470aac963d9f36.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/5723.421338408b5c30c4.js +74 -0
  65. package/apps/pwa/out/_next/static/chunks/5809.64c3664267c50bce.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/6000.64bc21ac8627f2f5.js +94 -0
  67. package/apps/pwa/out/_next/static/chunks/6013.39459b9c68eabf5f.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/607-06466dff012b03ef.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/{7483.ba01c6c209b81bba.js → 6092.a7f99e68b188ca66.js} +1 -1
  70. package/apps/pwa/out/_next/static/chunks/60fb96c9.360b01d8d4927b7b.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/{9974.e77b4ff4884557b3.js → 6157.0c9af021de4fb31b.js} +2 -2
  72. package/apps/pwa/out/_next/static/chunks/6192.a190b989b81e9743.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/{3309.364573ceedbdfd5f.js → 6323.8672b4817a306928.js} +1 -1
  74. package/apps/pwa/out/_next/static/chunks/{94.4cfed21c0b781a06.js → 6433.fc573af22bbc9ab7.js} +4 -4
  75. package/apps/pwa/out/_next/static/chunks/6444.7d1fdcb9b09ae71c.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/{949.1e7d45ccb71580aa.js → 6517.4147878ac06d584d.js} +2 -2
  77. package/apps/pwa/out/_next/static/chunks/6532.e34302010b2f0b68.js +32 -0
  78. package/apps/pwa/out/_next/static/chunks/6544cb48.a87fe20ddf137be2.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/6546.74bc4ed82f19016d.js +104 -0
  80. package/apps/pwa/out/_next/static/chunks/6684-e7523ce7c2fad075.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/6726.ebdc82feb161431e.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/6739.00fb6f892e5e392b.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/6745.934c4921bf15513a.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/{2528.3868070ef1be69f9.js → 6767.ea607cf0c20a25af.js} +2 -2
  85. package/apps/pwa/out/_next/static/chunks/6843.67bfd020da0926c5.js +93 -0
  86. package/apps/pwa/out/_next/static/chunks/6948.1062d4594652b5bf.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/7138-eaf841fc1dc03463.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/7174-cda29ee13d7790e7.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/7304-3c07583f4e405f4f.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/7371.80c05c653b08e907.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/7391.ebae168025c91b6a.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/{1743.563d3168e95825ef.js → 7502.ccd5b8065545edd0.js} +2 -2
  93. package/apps/pwa/out/_next/static/chunks/7557.e325503a090a4310.js +8 -0
  94. package/apps/pwa/out/_next/static/chunks/7569-9ce9432c7f10989f.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/{5408.377c8c543635b81f.js → 7862.30a8d6482bb238eb.js} +1 -1
  96. package/apps/pwa/out/_next/static/chunks/7886-55854585fadeded1.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/7947-d0759430f8042240.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{65ebff95.081daeff622d238e.js → 7a1887c4.f36b88fba31da8fd.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/{2697.76ca2ac3efef6f3b.js → 8018.a32663f04a6fc5f9.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/8097-92f87bd108322f66.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/8215-1661812b6e9ad71f.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8260-19c921a51ca8ec3e.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8440.3a5cf5c85aa76db8.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8519-4599be2249161e68.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/8547.b028b90c5c01ace6.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/8587-cbe02a953a7f4990.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/877-2b977259c4b15655.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/9002.e5658224e3dc1b6f.js +24 -0
  109. package/apps/pwa/out/_next/static/chunks/9019-0a4b40c50c7314fb.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/9138.b6e80031afb1d973.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/9188-d36e4fc869ddc166.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/{4204.a409ab6250c0f6a8.js → 9221.7d22eaf353a2d4e7.js} +1 -1
  113. package/apps/pwa/out/_next/static/chunks/9230.44f5039e796e4ab4.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/{5785-ea8d2c633b6688e3.js → 9279-d28c5b7754d010ae.js} +1 -1
  115. package/apps/pwa/out/_next/static/chunks/939.336acce73e98482c.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/9398-6aed28f0a96d0d6f.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/9407.e49124dedf0f5e72.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/9610.c07cce4bfcc14ed7.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/9667-ce78c2ce91050ff9.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/997ce8a5-2ad501927003b41b.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/{9311.24c8f5ea793253dd.js → 9989.3694300e5f000b14.js} +1 -1
  122. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-339fabb3499da0e1.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/approvals/page-bf3634a117322df7.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-3535a4a69f1fafaa.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/board/page-b9f51447f757fbf2.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-fa46d4588e8442c2.js → page-2ebb8fd59b9461f9.js} +1 -1
  127. package/apps/pwa/out/_next/static/chunks/app/bots/page-a2fc4e49a4fa4ba8.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/ceo/page-5a503541353e60a8.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/chat/page-6025101384e361a5.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/consult/page-f4b630cc8b5273cd.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/control/page-a645f00a06483e9a.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/decisions/page-61ac22cbab1ee4ec.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e9bab7413b028b28.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/editor/page-c992eafe028b642e.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/exec/page-e547e6ce55f1fb33.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/field/page-d27e1b9850fd54bf.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/healthz/route-339fabb3499da0e1.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/inside/page-805ac2e137b7a121.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/intake/page-0fec29c7ba01894b.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/layout-339a07410cd6f36b.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live/{page-ced0bcfc6542b3fc.js → page-349f2752d8cd37ad.js} +1 -1
  142. package/apps/pwa/out/_next/static/chunks/app/live-run/page-d5c6dcf31c25cecc.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/loops/page-2fd93ed8c9f16580.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/market/page-9b564dbff0fefc35.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-62f4475d3c38fb9b.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/morning/page-814ba51385225776.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/not-found-397d479626e5a459.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/observatory/page-af1bba4e3251b728.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-3ad3be5e7d3fdce9.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-f219ccdbc0bf27df.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4da93ad35d4a8d28.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/outputs/page-456666766e5ce6fc.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/page-7404e377e8adb8c5.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/reflection/page-14456240ed2a36ad.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-3a658d5fa6d16a25.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/sessions/page-0157de6e85f8bb6b.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-cb249e81ed339318.js → page-0a07363b130182a1.js} +1 -1
  158. package/apps/pwa/out/_next/static/chunks/app/settings/page-841d73d4ac44ed49.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-6f43a3d1146e6377.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/layout-339fabb3499da0e1.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/page-521ee23ac8c0abfa.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/share/page-a9992ee285efaa83.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/showroom/page-3d48e85fa89ec04f.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/tasks/page-d2ced71132fac9b2.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/term/page-f08358bdf251adb9.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/today/page-233c71cd5f68f0d7.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/trace/page-70151d78de2f375e.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/vault/page-dcac33a3e2cb72c0.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-149dc1caba411bb9.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/page-0448145ccd5c45c1.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9cb0c444903da65d.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-17e504c396e0e389.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/{6def8a8e.c81f9ca41028eccf.js → b313b242.cb1265eea908db61.js} +1 -1
  174. package/apps/pwa/out/_next/static/chunks/{e87d89b9.2e71c0ae475d28a1.js → e5012ffe.f727e23c231f4342.js} +1 -1
  175. package/apps/pwa/out/_next/static/chunks/ea695e3c-1f1d70d740cf3031.js +1 -0
  176. package/apps/pwa/out/_next/static/chunks/{e117f801.e92bf10242653497.js → ebc7d829.264a0bcb6c750b50.js} +1 -1
  177. package/apps/pwa/out/_next/static/chunks/framework-906af9bd029e166c.js +1 -0
  178. package/apps/pwa/out/_next/static/chunks/main-a24847f9f684d469.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-app-7bdcd8aa7c8397d3.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/pages/_app-6ba46a5066fc226d.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_error-a8dc6d07591f2a25.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/webpack-f13df28c15c55eee.js +1 -0
  183. package/apps/pwa/out/_next/static/css/{72becfd42807b50d.css → 3f5fc6f96e2cf7b2.css} +1 -1
  184. package/apps/pwa/out/approvals/index.html +1 -1
  185. package/apps/pwa/out/approvals/index.txt +23 -23
  186. package/apps/pwa/out/autopilot/index.html +1 -1
  187. package/apps/pwa/out/autopilot/index.txt +23 -23
  188. package/apps/pwa/out/board/index.html +1 -1
  189. package/apps/pwa/out/board/index.txt +23 -23
  190. package/apps/pwa/out/botlab/index.html +1 -1
  191. package/apps/pwa/out/botlab/index.txt +22 -22
  192. package/apps/pwa/out/bots/index.html +1 -1
  193. package/apps/pwa/out/bots/index.txt +22 -22
  194. package/apps/pwa/out/ceo/index.html +1 -1
  195. package/apps/pwa/out/ceo/index.txt +23 -23
  196. package/apps/pwa/out/chat/index.html +1 -1
  197. package/apps/pwa/out/chat/index.txt +22 -22
  198. package/apps/pwa/out/consult/index.html +1 -1
  199. package/apps/pwa/out/consult/index.txt +23 -23
  200. package/apps/pwa/out/control/index.html +1 -1
  201. package/apps/pwa/out/control/index.txt +23 -23
  202. package/apps/pwa/out/decisions/index.html +1 -1
  203. package/apps/pwa/out/decisions/index.txt +22 -22
  204. package/apps/pwa/out/design-check/index.html +1 -1
  205. package/apps/pwa/out/design-check/index.txt +23 -23
  206. package/apps/pwa/out/editor/index.html +1 -1
  207. package/apps/pwa/out/editor/index.txt +23 -23
  208. package/apps/pwa/out/exec/index.html +1 -1
  209. package/apps/pwa/out/exec/index.txt +23 -23
  210. package/apps/pwa/out/field/index.html +1 -1
  211. package/apps/pwa/out/field/index.txt +23 -23
  212. package/apps/pwa/out/healthz +1 -1
  213. package/apps/pwa/out/index.html +1 -1
  214. package/apps/pwa/out/index.txt +22 -22
  215. package/apps/pwa/out/inside/index.html +1 -1
  216. package/apps/pwa/out/inside/index.txt +22 -22
  217. package/apps/pwa/out/intake/index.html +1 -1
  218. package/apps/pwa/out/intake/index.txt +23 -23
  219. package/apps/pwa/out/live/index.html +1 -1
  220. package/apps/pwa/out/live/index.txt +23 -23
  221. package/apps/pwa/out/live-run/index.html +1 -1
  222. package/apps/pwa/out/live-run/index.txt +23 -23
  223. package/apps/pwa/out/loops/index.html +1 -1
  224. package/apps/pwa/out/loops/index.txt +22 -22
  225. package/apps/pwa/out/market/index.html +1 -1
  226. package/apps/pwa/out/market/index.txt +22 -22
  227. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  228. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  229. package/apps/pwa/out/morning/index.html +1 -1
  230. package/apps/pwa/out/morning/index.txt +23 -23
  231. package/apps/pwa/out/observatory/index.html +1 -1
  232. package/apps/pwa/out/observatory/index.txt +23 -23
  233. package/apps/pwa/out/ops/checklist/index.html +1 -1
  234. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  235. package/apps/pwa/out/ops/release/index.html +1 -1
  236. package/apps/pwa/out/ops/release/index.txt +22 -22
  237. package/apps/pwa/out/ops/seats/index.html +1 -1
  238. package/apps/pwa/out/ops/seats/index.txt +22 -22
  239. package/apps/pwa/out/outputs/index.html +1 -1
  240. package/apps/pwa/out/outputs/index.txt +22 -22
  241. package/apps/pwa/out/reflection/index.html +1 -1
  242. package/apps/pwa/out/reflection/index.txt +23 -23
  243. package/apps/pwa/out/scheduler/index.html +1 -1
  244. package/apps/pwa/out/scheduler/index.txt +23 -23
  245. package/apps/pwa/out/sessions/index.html +1 -1
  246. package/apps/pwa/out/sessions/index.txt +22 -22
  247. package/apps/pwa/out/settings/devices/index.html +1 -1
  248. package/apps/pwa/out/settings/devices/index.txt +23 -23
  249. package/apps/pwa/out/settings/index.html +1 -1
  250. package/apps/pwa/out/settings/index.txt +23 -23
  251. package/apps/pwa/out/setup/done/index.html +1 -1
  252. package/apps/pwa/out/setup/done/index.txt +23 -23
  253. package/apps/pwa/out/setup/index.html +1 -1
  254. package/apps/pwa/out/setup/index.txt +23 -23
  255. package/apps/pwa/out/share/index.html +1 -1
  256. package/apps/pwa/out/share/index.txt +23 -23
  257. package/apps/pwa/out/showroom/index.html +1 -1
  258. package/apps/pwa/out/showroom/index.txt +22 -22
  259. package/apps/pwa/out/tasks/index.html +1 -1
  260. package/apps/pwa/out/tasks/index.txt +23 -23
  261. package/apps/pwa/out/term/index.html +1 -1
  262. package/apps/pwa/out/term/index.txt +23 -23
  263. package/apps/pwa/out/today/index.html +1 -1
  264. package/apps/pwa/out/today/index.txt +23 -23
  265. package/apps/pwa/out/trace/index.html +1 -1
  266. package/apps/pwa/out/trace/index.txt +23 -23
  267. package/apps/pwa/out/vault/index.html +1 -1
  268. package/apps/pwa/out/vault/index.txt +23 -23
  269. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  270. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  271. package/apps/pwa/out/workflows/index.html +1 -1
  272. package/apps/pwa/out/workflows/index.txt +23 -23
  273. package/apps/pwa/out/workspace/index.html +1 -1
  274. package/apps/pwa/out/workspace/index.txt +23 -23
  275. package/apps/pwa/out/worktrees/index.html +1 -1
  276. package/apps/pwa/out/worktrees/index.txt +23 -23
  277. package/bin/elanous.mjs +7 -3
  278. package/graphs/context/nightly-condense.yaml +1 -0
  279. package/graphs/intake/intake-daily.yaml +8 -1
  280. package/graphs/intake/recipes.yaml +5 -0
  281. package/graphs/lecture/lecture-note.yaml +2 -2
  282. package/graphs/ops/harness-gc.yaml +1 -0
  283. package/graphs/release/recipes.yaml +3 -0
  284. package/graphs/release/release-loop.yaml +15 -7
  285. package/graphs/units/recipes.yaml +3 -0
  286. package/graphs/units/release-gate-recheck.yaml +15 -0
  287. package/package.json +2 -1
  288. package/plugins/agent-team/agents/research.md +1 -0
  289. package/scripts/check-native-tool-wiring.ts +3 -2
  290. package/scripts/ci-android-unit-tests.ts +16 -0
  291. package/scripts/ci-typecheck-changed.ts +40 -11
  292. package/scripts/coord-channel-watch.sh +45 -19
  293. package/scripts/coord-role-pack.ts +222 -0
  294. package/scripts/cron-run.ts +96 -3
  295. package/scripts/detect-test-interference.ts +38 -5
  296. package/scripts/gate-pr-cache.ts +65 -0
  297. package/scripts/gh-surface/capture.ts +126 -0
  298. package/scripts/gh-surface/gh +40 -0
  299. package/scripts/import-graph-scc.ts +38 -0
  300. package/scripts/install.ps1 +19 -0
  301. package/scripts/intake-social.ts +181 -0
  302. package/scripts/lecture-note/node.ts +71 -4
  303. package/scripts/lib/android-toolchain.ts +32 -0
  304. package/scripts/lib/deterministic-env.ts +62 -0
  305. package/scripts/lib/gate-test-env.ts +19 -0
  306. package/scripts/lib/morning-report-options.ts +41 -0
  307. package/scripts/measure-goal-grounding-corpus.ts +2 -1
  308. package/scripts/pr-land-extra-remote-gates.ts +49 -0
  309. package/scripts/ratchets/test-interference-patterns.allowlist.txt +257 -0
  310. package/scripts/ratchets/test-interference-patterns.ts +367 -0
  311. package/scripts/release-loop/cut-branch.ts +4 -3
  312. package/scripts/release-loop/export-check-node.ts +0 -1
  313. package/scripts/release-loop/fixtures/warm-failure-assertion/sample.junit.xml +8 -0
  314. package/scripts/release-loop/fixtures/warm-job-029/sample.json +1 -0
  315. package/scripts/release-loop/fixtures/warm-job-029/sample.junit.xml +10 -0
  316. package/scripts/release-loop/gate-batch-safe.ts +23 -0
  317. package/scripts/release-loop/gate-closure.ts +12 -8
  318. package/scripts/release-loop/gate-diff.ts +1 -1
  319. package/scripts/release-loop/gate-history.ts +422 -0
  320. package/scripts/release-loop/gate-host-mirror.ts +66 -0
  321. package/scripts/release-loop/gate-node.ts +806 -94
  322. package/scripts/release-loop/gate-partial.ts +106 -9
  323. package/scripts/release-loop/gate-pr-cache.ts +66 -0
  324. package/scripts/release-loop/gate-prewarm-plan.ts +66 -0
  325. package/scripts/release-loop/gate-r2-verdict.ts +189 -0
  326. package/scripts/release-loop/gate-recheck-plan-node.ts +52 -0
  327. package/scripts/release-loop/gate-triage-node.ts +190 -0
  328. package/scripts/release-loop/gate-warm-artifacts.ts +253 -0
  329. package/scripts/release-loop/import-graph-scc-run.ts +54 -0
  330. package/scripts/release-loop/import-graph-scc.ts +215 -0
  331. package/scripts/release-loop/node-verdict.ts +2 -0
  332. package/scripts/release-loop/probe-pod-host-dirs-live.ts +113 -0
  333. package/scripts/release-loop/resume-release.ts +33 -4
  334. package/scripts/release-loop/resume.ts +13 -5
  335. package/scripts/release-loop/spawn-lane.ts +51 -0
  336. package/scripts/release-loop/tui-node-checks.ts +23 -0
  337. package/scripts/release-loop/tui-sim-node.ts +3 -2
  338. package/scripts/release-loop/unattended-release.ts +16 -0
  339. package/scripts/release-loop/version-node.ts +62 -4
  340. package/scripts/test-deterministic.ts +46 -73
  341. package/scripts/train-reach-grouping.ts +48 -0
  342. package/scripts/tsc-incremental-cache.ts +86 -0
  343. package/scripts/tsc-incremental-diagnostics.ts +76 -0
  344. package/scripts/tsc-main-safety-net.ts +2 -2
  345. package/scripts/usage-rollup.ts +11 -3
  346. package/src/acp/client.ts +4 -1
  347. package/src/acp/core-turn-bridge.ts +145 -24
  348. package/src/acp/elanous-extensions.ts +2 -0
  349. package/src/acp/server.ts +2 -0
  350. package/src/ad-pipeline/run.ts +4 -2
  351. package/src/agent/ref-grounding.ts +18 -0
  352. package/src/agent/runner.ts +169 -3
  353. package/src/agent/subagent-callable.ts +3 -2
  354. package/src/agent-mission/review-loop.ts +6 -3
  355. package/src/agent-substrate/acp-implementer.ts +14 -2
  356. package/src/agent-substrate/pr-reviewer.ts +41 -21
  357. package/src/agent-substrate/self-review-cli.ts +30 -4
  358. package/src/autopilot/pr-manager.ts +62 -7
  359. package/src/briefing/brief-items.ts +24 -22
  360. package/src/budget/pty-usage-reemit.ts +7 -0
  361. package/src/budget/run-usage-rollup.ts +9 -0
  362. package/src/budget/usage-daily-store.ts +98 -0
  363. package/src/capture/tui-self-report.ts +37 -4
  364. package/src/chat/chat-cli.ts +10 -2
  365. package/src/chat/index.ts +107 -20
  366. package/src/chat/input-control-key.ts +5 -3
  367. package/src/chat/pickers/modal-runtime.ts +1 -0
  368. package/src/chat/pickers/modals.ts +3 -1
  369. package/src/chat/tool-interrupt-behavior.ts +38 -0
  370. package/src/chat/tool-render/dispatch.ts +5 -1
  371. package/src/chat/turn-typeahead.ts +191 -29
  372. package/src/cli/__fixtures__/agent-help-before/agent.txt +4 -2
  373. package/src/cli/agent-cli.ts +41 -13
  374. package/src/cli/config-audit.ts +123 -0
  375. package/src/cli/dashboard-name-flag.ts +48 -0
  376. package/src/cli/decisions-cli.ts +80 -6
  377. package/src/cli/doctor-cli.ts +80 -0
  378. package/src/cli/log-event-names.ts +19 -0
  379. package/src/cli/mcp-login.ts +21 -5
  380. package/src/cli/msg-cli.ts +133 -2
  381. package/src/cli/pr-cli.ts +546 -112
  382. package/src/cli/pr-land-heavy-remote.ts +233 -0
  383. package/src/cli/pr-land-overlap-evidence.ts +167 -14
  384. package/src/cli/pr-land-pod-shadow-job.ts +83 -0
  385. package/src/cli/pr-land-pod-shadow.ts +16 -3
  386. package/src/cli/pr-land-virtual-rebase.ts +108 -0
  387. package/src/cli/pty-takeover-cli.ts +125 -16
  388. package/src/cli/release-cli.ts +122 -14
  389. package/src/cli/remote-resolve.ts +2 -1
  390. package/src/cli/seat-cli.ts +164 -19
  391. package/src/cli/self-update.ts +5 -2
  392. package/src/cli/tasks-cli.ts +105 -21
  393. package/src/cli/usage-cli.ts +146 -12
  394. package/src/cli/usage-daily-cli.ts +84 -0
  395. package/src/context-bus/context-first.ts +13 -0
  396. package/src/context-bus/context-now.ts +95 -7
  397. package/src/context-bus/ops-events.ts +75 -0
  398. package/src/control-plane/lease-holder.ts +12 -8
  399. package/src/control-plane/ledger.ts +163 -4
  400. package/src/control-plane/server.ts +91 -11
  401. package/src/core-turn/auth-rejected-notice.ts +41 -0
  402. package/src/core-turn/run-core-turn.ts +14 -2
  403. package/src/core-turn/run-goal-loop.ts +239 -4
  404. package/src/core-turn/seat-turn-cap.ts +79 -0
  405. package/src/core-turn/short-question.ts +78 -13
  406. package/src/core-turn/types.ts +10 -0
  407. package/src/dashboard/acp-active-model.ts +40 -0
  408. package/src/dashboard/acp-boot.ts +3 -0
  409. package/src/dashboard/auto-compact-runtime.ts +2 -0
  410. package/src/dashboard/boot-banner.ts +80 -0
  411. package/src/dashboard/dashboard-status-lines.ts +11 -0
  412. package/src/dashboard/detail-viewer-runtime.ts +15 -2
  413. package/src/dashboard/event-loop-stall-monitor.ts +4 -0
  414. package/src/dashboard/goal-loop-thinking.ts +16 -0
  415. package/src/dashboard/hitl-boot.ts +9 -4
  416. package/src/dashboard/index.ts +448 -998
  417. package/src/dashboard/input/chat-main-entry-runtime.ts +2 -0
  418. package/src/dashboard/input/chat-main-log-actions.ts +4 -0
  419. package/src/dashboard/input/chat-main-log-command.ts +12 -1
  420. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +176 -4
  421. package/src/dashboard/input/chat-main-session.ts +14 -3
  422. package/src/dashboard/input/chat-main-slash-command.ts +7 -0
  423. package/src/dashboard/input/chat-main-turn.ts +14 -4
  424. package/src/dashboard/input/compact-prepass-recovery.ts +130 -0
  425. package/src/dashboard/input/dashboard-chord-actions.ts +10 -1
  426. package/src/dashboard/input/dashboard-chord-runtime.ts +2 -0
  427. package/src/dashboard/input/external-input-host.ts +71 -0
  428. package/src/dashboard/input/internal-turn.ts +261 -0
  429. package/src/dashboard/input/log-fold-binding.ts +18 -0
  430. package/src/dashboard/input/mouse-wiring.ts +17 -4
  431. package/src/dashboard/input/seat-autowake.ts +318 -0
  432. package/src/dashboard/input/turn-steer-bridge.ts +164 -0
  433. package/src/dashboard/log-click-runtime.ts +5 -3
  434. package/src/dashboard/loop-scheduler.ts +72 -0
  435. package/src/dashboard/running-build-status.ts +74 -0
  436. package/src/dashboard/seat-boot-preamble.ts +131 -0
  437. package/src/dashboard/seat-local-instructions.ts +305 -0
  438. package/src/dashboard/skill-runtime.ts +2 -0
  439. package/src/dashboard/slash-runtime/dashboard-handlers.ts +220 -1203
  440. package/src/dashboard/slash-runtime/help-from-registry.ts +2 -1
  441. package/src/dashboard/slash-runtime/index.ts +13 -0
  442. package/src/dashboard/slash-runtime/model-choices.ts +35 -0
  443. package/src/dashboard/term-slash-runtime.ts +15 -0
  444. package/src/dashboard/turn-busy-teardown.ts +50 -0
  445. package/src/dashboard/turn-preamble.ts +30 -2
  446. package/src/dashboard/turn-session-layers.ts +176 -0
  447. package/src/dashboard/turn-stream-formatter.ts +14 -6
  448. package/src/dashboard/turn-stream-presentation-applier.ts +8 -0
  449. package/src/decisions/decision-ledger.ts +45 -4
  450. package/src/discord-self-message.ts +4 -4
  451. package/src/domains/release-tool.ts +26 -10
  452. package/src/domains/surface-events.ts +4 -1
  453. package/src/domains/watch-default-scheduler.ts +8 -9
  454. package/src/field-note/transcribe.ts +17 -0
  455. package/src/fold-stack.ts +24 -1
  456. package/src/git-fs/dirty.ts +32 -4
  457. package/src/git-fs/index.ts +43 -1
  458. package/src/github/remote-call.ts +81 -0
  459. package/src/goals/registry.ts +46 -6
  460. package/src/goals/types.ts +7 -1
  461. package/src/graph-runner/runner.ts +2 -0
  462. package/src/grok/credential.ts +26 -0
  463. package/src/harness/harness-clean.ts +2 -1
  464. package/src/harness/harness-cli-command.ts +52 -5
  465. package/src/harness/harness-gc.ts +34 -6
  466. package/src/harness/harness-incidents.ts +57 -20
  467. package/src/harness/harness-pod-dispatch.ts +87 -10
  468. package/src/harness/harness-queue.ts +53 -4
  469. package/src/harness/harness-stop.ts +4 -3
  470. package/src/harness/harness-substrate-default.ts +78 -7
  471. package/src/harness/harness-worktree-auto.ts +2 -1
  472. package/src/harness/harness-worktrees.ts +3 -2
  473. package/src/hooks/dispatch.ts +2 -1
  474. package/src/index.ts +166 -26
  475. package/src/input/external-input-queue.ts +251 -0
  476. package/src/intake-plane/slash.ts +14 -3
  477. package/src/interactive-terminal-modal.ts +13 -0
  478. package/src/keybindings.ts +4 -1
  479. package/src/llm/route-decision.ts +25 -0
  480. package/src/llm/tool-loop-steered-rejection.ts +33 -0
  481. package/src/llm-stream-retry-display.ts +59 -0
  482. package/src/llm.ts +391 -74
  483. package/src/log-entry.ts +3 -3
  484. package/src/log-pane/search.ts +29 -4
  485. package/src/loops/agent-reply.ts +256 -0
  486. package/src/loops/budget.ts +37 -7
  487. package/src/loops/consensus.ts +348 -0
  488. package/src/loops/loop-cli.ts +3 -2
  489. package/src/loops/msg-address.ts +57 -0
  490. package/src/loops/msg-wake.ts +484 -0
  491. package/src/loops/observe.ts +19 -3
  492. package/src/loops/orchestrator/inbox.ts +60 -3
  493. package/src/loops/orchestrator/tick.ts +80 -6
  494. package/src/loops/orchestrator/traffic.ts +64 -1
  495. package/src/loops/registry.ts +72 -4
  496. package/src/market/signed-index.ts +14 -0
  497. package/src/mcp/mcp-oauth.ts +54 -5
  498. package/src/memory.ts +20 -5
  499. package/src/models/prompts.ts +28 -0
  500. package/src/msg/msg-sse-client.ts +223 -0
  501. package/src/msg/msg-store.ts +86 -2
  502. package/src/mss/logging/log-store.ts +225 -1
  503. package/src/native-tool-catalog.ts +125 -0
  504. package/src/nexus/DAEMON-THIN-inventory-2026-10-15.md +122 -0
  505. package/src/nexus/api/http-server.ts +59 -15
  506. package/src/nexus/api/loop-resources.ts +19 -3
  507. package/src/nexus/api/ops-api.ts +13 -0
  508. package/src/nexus/api/project-autopsy.ts +127 -0
  509. package/src/nexus/api/terminals.ts +3 -0
  510. package/src/nexus/api/workflow-approvals.ts +2 -0
  511. package/src/nexus/daemon-thin-inventory.md +37 -0
  512. package/src/nexus/index.ts +70 -0
  513. package/src/nexus/onboarding/first-boot-wizard.ts +56 -2
  514. package/src/nexus/state/state.ts +1 -0
  515. package/src/oauth/codex-account-store.ts +15 -3
  516. package/src/oauth/codex-billing-route.ts +31 -0
  517. package/src/oauth/store.ts +32 -7
  518. package/src/onboarding/first-run.ts +28 -3
  519. package/src/ops/overview-cache.ts +16 -4
  520. package/src/ops/overview-collect.ts +19 -2
  521. package/src/ops/resources.ts +82 -0
  522. package/src/panes/log-pane.ts +67 -8
  523. package/src/persona/presets.ts +44 -0
  524. package/src/plugins/maker/connector-maker.ts +41 -0
  525. package/src/plugins/maker/skill-maker.ts +42 -0
  526. package/src/plugins/maker/wizard-generate.ts +6 -4
  527. package/src/pr-tool/census.ts +288 -0
  528. package/src/preview/terminal.ts +14 -1
  529. package/src/prompt-cache/types.ts +2 -0
  530. package/src/prompt-enhance/enhance.ts +24 -8
  531. package/src/prompt-library/chat-conciseness.ts +13 -0
  532. package/src/prompt-library/universal-preamble.ts +283 -61
  533. package/src/pty-shell/pty-control-ipc.ts +4 -1
  534. package/src/pty-shell/pty-manifest.ts +77 -5
  535. package/src/pty-shell/registry.ts +19 -2
  536. package/src/pty-shell/tui-run-state.ts +253 -0
  537. package/src/release-loop/gate-host-reservation.ts +97 -0
  538. package/src/release-loop/landing-freeze.ts +5 -0
  539. package/src/release-loop/light-rc.ts +32 -4
  540. package/src/release-loop/placement.ts +92 -1
  541. package/src/release-loop/rehearsal-shard-plan.ts +149 -0
  542. package/src/release-loop/rehearsal.ts +73 -17
  543. package/src/render.ts +5 -1
  544. package/src/repl/index.ts +20 -2
  545. package/src/seat-loop/seat-knowhow.ts +282 -0
  546. package/src/seat-loop/seat-loop.ts +41 -2
  547. package/src/seat-loop/seat-report.ts +42 -2
  548. package/src/seat-loop/traffic-signal.ts +3 -0
  549. package/src/self-dev/ask-launch-flow.ts +24 -3
  550. package/src/self-dev/decompose-compose-graph.ts +28 -27
  551. package/src/self-dev/decompose-proposal.ts +53 -38
  552. package/src/self-dev/decompose.ts +17 -0
  553. package/src/self-dev/dev-pipeline.ts +31 -2
  554. package/src/self-dev/draft-residue.ts +24 -9
  555. package/src/self-dev/draft-sweep.ts +59 -4
  556. package/src/self-dev/external-call-zero.ts +77 -0
  557. package/src/self-dev/launch-preflight-text.ts +6 -0
  558. package/src/self-dev/launch-preflight.ts +25 -3
  559. package/src/self-dev/local-supervisor-judge.ts +185 -0
  560. package/src/self-dev/run-supervisor.ts +300 -24
  561. package/src/self-dev/run-types.ts +20 -0
  562. package/src/self-dev/shard-aggregate.ts +15 -2
  563. package/src/self-dev/shard-plan.ts +47 -0
  564. package/src/self-dev/shard-run.ts +135 -12
  565. package/src/self-dev/spawn-lane-files.ts +19 -0
  566. package/src/self-health/signatures.ts +132 -0
  567. package/src/self-implement/app-api-budget.ts +51 -11
  568. package/src/self-implement/config-gate.ts +2 -1
  569. package/src/self-implement/doctor-visit.ts +118 -0
  570. package/src/self-implement/expose-gate.ts +2 -1
  571. package/src/self-implement/gate-artifacts.ts +405 -0
  572. package/src/self-implement/gate-cli.ts +212 -18
  573. package/src/self-implement/gate-remote.ts +110 -25
  574. package/src/self-implement/gate-scenario.ts +85 -0
  575. package/src/self-implement/goal-author-decompose.ts +80 -16
  576. package/src/self-implement/goal-author.ts +270 -76
  577. package/src/self-implement/goal-authoring-grounding.ts +1 -1
  578. package/src/self-implement/goal-file-name.ts +65 -0
  579. package/src/self-implement/graph-authority.ts +38 -6
  580. package/src/self-implement/graph-overlay-condition.ts +92 -1
  581. package/src/self-implement/ground-goal.ts +2 -1
  582. package/src/self-implement/heal-triage.ts +79 -0
  583. package/src/self-implement/host-regate.ts +74 -2
  584. package/src/self-implement/orchestrator.ts +14 -123
  585. package/src/self-implement/pr-title.ts +123 -0
  586. package/src/self-implement/review-mustfix-class.ts +115 -0
  587. package/src/self-implement/run-checkpoint.ts +235 -0
  588. package/src/self-implement/run-ledger.ts +266 -17
  589. package/src/self-implement/run-stop.ts +4 -3
  590. package/src/self-implement/run-ttl.ts +28 -4
  591. package/src/self-implement/running-runs.ts +10 -6
  592. package/src/self-implement/seams.ts +2 -0
  593. package/src/self-implement/self-implement-runtime.ts +32 -0
  594. package/src/self-implement/shadow-stage.ts +7 -6
  595. package/src/self-implement/stop-autoheal.ts +2 -1
  596. package/src/self-implement/train-gate-remote.ts +37 -8
  597. package/src/self-implement/train-reach-grouping.ts +157 -0
  598. package/src/self-implement/train-scenario-layers.ts +133 -0
  599. package/src/self-implement/uncommitted-work.ts +2 -0
  600. package/src/session/pending-input.ts +81 -11
  601. package/src/session/session-start.ts +224 -0
  602. package/src/session-runtime/index.ts +7 -1
  603. package/src/session-runtime/parallel-dispatch.ts +22 -5
  604. package/src/skills/tools/dev-harness.ts +6 -4
  605. package/src/skills/tools/ops-ledger.ts +707 -0
  606. package/src/skills/tools/persistent-grounding.ts +47 -7
  607. package/src/skills/tools/shell.ts +117 -1
  608. package/src/status/bar.ts +32 -2
  609. package/src/status/status-provider-info.ts +58 -0
  610. package/src/steward/resync-tick.ts +114 -23
  611. package/src/steward/ta-repair-event-collect.ts +158 -0
  612. package/src/task-agent/card-evidence.ts +25 -1
  613. package/src/task-agent/green-sweep.ts +8 -3
  614. package/src/task-agent/judge.ts +76 -0
  615. package/src/task-agent/land-queue.ts +79 -0
  616. package/src/task-agent/live-moves.ts +30 -9
  617. package/src/task-agent/postland-card.ts +5 -0
  618. package/src/task-agent/prelaunch-already-done.ts +98 -0
  619. package/src/task-agent/real-launch-guard.ts +37 -0
  620. package/src/task-agent/rejudge-on-head.ts +93 -13
  621. package/src/task-agent/remote-rejudge-bind.ts +62 -13
  622. package/src/task-agent/repair-cycle.ts +58 -7
  623. package/src/task-agent/repair-worker.ts +7 -0
  624. package/src/task-agent/run-identity.ts +200 -0
  625. package/src/task-agent/shadow.ts +50 -3
  626. package/src/task-agent/task-hand.ts +158 -5
  627. package/src/task-agent/train-landing.ts +77 -0
  628. package/src/task-agent/work-order.ts +104 -0
  629. package/src/task-orchestrator/surfaces/kubectl-transient-retry.ts +51 -0
  630. package/src/task-orchestrator/surfaces/pod-command-job.ts +138 -22
  631. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +88 -19
  632. package/src/task-orchestrator/surfaces/pod-host-acp-review.ts +2 -1
  633. package/src/task-orchestrator/surfaces/pod-image-source.ts +2 -1
  634. package/src/task-orchestrator/surfaces/pod-install-slots.ts +122 -15
  635. package/src/task-orchestrator/surfaces/pod-lease.ts +20 -6
  636. package/src/task-orchestrator/surfaces/pod-lifecycle-stages.ts +169 -0
  637. package/src/task-orchestrator/surfaces/pod-mirror-seed.ts +103 -0
  638. package/src/task-orchestrator/surfaces/pod-result-marker-wait.ts +33 -0
  639. package/src/task-orchestrator/surfaces/pod-reuse-cleanup.ts +63 -0
  640. package/src/task-orchestrator/surfaces/pod-source-receive.ts +19 -3
  641. package/src/task-orchestrator/surfaces/pod-warm-switch.ts +141 -0
  642. package/src/task-orchestrator/surfaces/pod-warm-ta-shadow.ts +85 -0
  643. package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +153 -0
  644. package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +208 -0
  645. package/src/task-orchestrator/surfaces/self-implement-pod.ts +237 -46
  646. package/src/telegram.ts +2 -4
  647. package/src/terminal/session-registry.ts +6 -0
  648. package/src/thinking-line.ts +12 -3
  649. package/src/tool-runtime/index.ts +4 -0
  650. package/src/tool-runtime/ops-ledger-runtimes.ts +84 -0
  651. package/src/tool-runtime/ref-consult-runtime.ts +17 -2
  652. package/src/tool-runtime/run-shell-runtime.ts +4 -1
  653. package/src/tool-runtime/self-cognition-runtimes.ts +5 -1
  654. package/src/tui/input/pickers.ts +732 -0
  655. package/src/tui/slash/mission.ts +492 -0
  656. package/src/tui/slash/term.ts +877 -0
  657. package/src/tui-client/dashboard-session.ts +9 -0
  658. package/src/tui.ts +53 -0
  659. package/src/user-config/gemini-server-tools.ts +9 -0
  660. package/src/user-config.ts +106 -21
  661. package/src/util/spawn-sync-output.ts +7 -0
  662. package/src/ux-sim/fake-bin.ts +154 -0
  663. package/src/ux-sim/fake-clock.ts +36 -0
  664. package/src/ux-sim/fake-llm.ts +29 -0
  665. package/src/ux-sim/headless-tui.ts +72 -0
  666. package/src/ux-sim/in-process-cli.ts +176 -0
  667. package/src/ux-sim/temp-git-remote.ts +82 -0
  668. package/src/version/packed-revision.json +1 -1
  669. package/src/video-pipeline/recipes/vlog.ts +25 -7
  670. package/apps/pwa/out/_next/static/chunks/1027.a0f04663d24c2779.js +0 -1
  671. package/apps/pwa/out/_next/static/chunks/1038.676007ae505822e7.js +0 -1
  672. package/apps/pwa/out/_next/static/chunks/1113.a37eadf7f740d955.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/112-fa7c526c20ce1405.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/1144.5fa1fa1d48dbcd12.js +0 -36
  675. package/apps/pwa/out/_next/static/chunks/119b405e.8fdd0e44d1ff37aa.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/1296.04560d98e73fbf51.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/1314-f604ba013ec535b0.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/1454-29d1d773d5996a49.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/1464.f3209af0d8ae4ff2.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/1729-d73dd25e564679d4.js +0 -1
  681. package/apps/pwa/out/_next/static/chunks/1843.5924c24295243261.js +0 -27
  682. package/apps/pwa/out/_next/static/chunks/1987.e28149724b92918b.js +0 -1
  683. package/apps/pwa/out/_next/static/chunks/2066-5ca18ffa5a4be68b.js +0 -1
  684. package/apps/pwa/out/_next/static/chunks/2240.d10a66695155fd93.js +0 -1
  685. package/apps/pwa/out/_next/static/chunks/2391.e2cbc98d4abbb41a.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/2566-734cb83bbd405886.js +0 -1
  687. package/apps/pwa/out/_next/static/chunks/275.0e6bad414729bfa0.js +0 -32
  688. package/apps/pwa/out/_next/static/chunks/2803-33bdae771855a8d1.js +0 -1
  689. package/apps/pwa/out/_next/static/chunks/3259-86f3be56e43eaf6a.js +0 -1
  690. package/apps/pwa/out/_next/static/chunks/3280-e177c33bfbdcfb0a.js +0 -1
  691. package/apps/pwa/out/_next/static/chunks/3294-2b6a6d45a46a6c1c.js +0 -1
  692. package/apps/pwa/out/_next/static/chunks/3522.758840fc5e7fe630.js +0 -287
  693. package/apps/pwa/out/_next/static/chunks/3525-a7c989c1a661e880.js +0 -1
  694. package/apps/pwa/out/_next/static/chunks/3694.b1a66387756458c0.js +0 -1
  695. package/apps/pwa/out/_next/static/chunks/370.124f9e678a76fc01.js +0 -1
  696. package/apps/pwa/out/_next/static/chunks/3797-9643105335e660fe.js +0 -1
  697. package/apps/pwa/out/_next/static/chunks/4084.15ecaf4240bd9e77.js +0 -286
  698. package/apps/pwa/out/_next/static/chunks/4089-b3382b8843114eee.js +0 -1
  699. package/apps/pwa/out/_next/static/chunks/4293.928a8d573c16aab7.js +0 -1
  700. package/apps/pwa/out/_next/static/chunks/4402.f9238a3b18a7bd43.js +0 -1
  701. package/apps/pwa/out/_next/static/chunks/4490-9b090b0f9427f902.js +0 -1
  702. package/apps/pwa/out/_next/static/chunks/4548.13502d0ca3b4f862.js +0 -8
  703. package/apps/pwa/out/_next/static/chunks/4565-67e4c620597965c8.js +0 -1
  704. package/apps/pwa/out/_next/static/chunks/4599-548bc96cda44a5a7.js +0 -1
  705. package/apps/pwa/out/_next/static/chunks/4625.206574e33a9e7ae8.js +0 -104
  706. package/apps/pwa/out/_next/static/chunks/4862-392c9a71f0d8dfb4.js +0 -1
  707. package/apps/pwa/out/_next/static/chunks/5054.2906d1b02542edc5.js +0 -149
  708. package/apps/pwa/out/_next/static/chunks/5102-c822bbbee5a022a4.js +0 -1
  709. package/apps/pwa/out/_next/static/chunks/5194-8c93f2e7e4b6f717.js +0 -1
  710. package/apps/pwa/out/_next/static/chunks/5305-bacd7eca0b9a5848.js +0 -1
  711. package/apps/pwa/out/_next/static/chunks/5311.9071ee3f44fd7ff5.js +0 -1
  712. package/apps/pwa/out/_next/static/chunks/5369.9f34353442daa421.js +0 -1
  713. package/apps/pwa/out/_next/static/chunks/589-49d730cb8138b397.js +0 -1
  714. package/apps/pwa/out/_next/static/chunks/5904-264a6412ae3c9028.js +0 -1
  715. package/apps/pwa/out/_next/static/chunks/5980.728531d5c566535c.js +0 -1
  716. package/apps/pwa/out/_next/static/chunks/599-56f2c0a5cc4a7295.js +0 -1
  717. package/apps/pwa/out/_next/static/chunks/6011.f525002e21bef0ac.js +0 -23
  718. package/apps/pwa/out/_next/static/chunks/6132.4381ab93683f1acf.js +0 -1
  719. package/apps/pwa/out/_next/static/chunks/6149.2e698635e454d8f6.js +0 -1
  720. package/apps/pwa/out/_next/static/chunks/6356-0afafc4230cf9767.js +0 -1
  721. package/apps/pwa/out/_next/static/chunks/6406.64756073f7df35b8.js +0 -77
  722. package/apps/pwa/out/_next/static/chunks/6479-16e62aedafa0e310.js +0 -1
  723. package/apps/pwa/out/_next/static/chunks/6599.b5246b46a2453329.js +0 -64
  724. package/apps/pwa/out/_next/static/chunks/6610.5c26eec011565acb.js +0 -1
  725. package/apps/pwa/out/_next/static/chunks/666.37d18474edc1c552.js +0 -2
  726. package/apps/pwa/out/_next/static/chunks/6904.a0b5b1ed4f2d2c20.js +0 -1
  727. package/apps/pwa/out/_next/static/chunks/7247-1f4aba94aba50d9f.js +0 -1
  728. package/apps/pwa/out/_next/static/chunks/7274-e71a7c5b7760cd02.js +0 -1
  729. package/apps/pwa/out/_next/static/chunks/7290.1fcaeef3adb5d0d4.js +0 -1
  730. package/apps/pwa/out/_next/static/chunks/7477-2c64e42760f82f33.js +0 -1
  731. package/apps/pwa/out/_next/static/chunks/7505-47002b76ff1aef6c.js +0 -1
  732. package/apps/pwa/out/_next/static/chunks/7600-11884ca4da68e5d9.js +0 -1
  733. package/apps/pwa/out/_next/static/chunks/7607.40d8bb3689697ccb.js +0 -62
  734. package/apps/pwa/out/_next/static/chunks/7763.f51fcc19ca1414ec.js +0 -1
  735. package/apps/pwa/out/_next/static/chunks/7785.846140c859b1adf6.js +0 -1
  736. package/apps/pwa/out/_next/static/chunks/7807-a0fdac6393e0bf36.js +0 -1
  737. package/apps/pwa/out/_next/static/chunks/7837-20a63b4b7e9342b1.js +0 -1
  738. package/apps/pwa/out/_next/static/chunks/800.7815414cf280545d.js +0 -1
  739. package/apps/pwa/out/_next/static/chunks/8085-1e5fce59500a2cc2.js +0 -1
  740. package/apps/pwa/out/_next/static/chunks/8100.415f2105faa63e97.js +0 -1
  741. package/apps/pwa/out/_next/static/chunks/8290.701aa42ed13aa767.js +0 -226
  742. package/apps/pwa/out/_next/static/chunks/83a80514-b897b64df43ce108.js +0 -1
  743. package/apps/pwa/out/_next/static/chunks/8444.d9c74214a3145a43.js +0 -93
  744. package/apps/pwa/out/_next/static/chunks/8569.98c915534928c7f9.js +0 -24
  745. package/apps/pwa/out/_next/static/chunks/8591.b1c7f876e5ec7cfb.js +0 -1
  746. package/apps/pwa/out/_next/static/chunks/8715-f63756021d3ab989.js +0 -1
  747. package/apps/pwa/out/_next/static/chunks/8879.776c2ec7732aa114.js +0 -115
  748. package/apps/pwa/out/_next/static/chunks/8881.ddca15b3c82c490c.js +0 -1
  749. package/apps/pwa/out/_next/static/chunks/8946.5da5c87f32a2e1a8.js +0 -41
  750. package/apps/pwa/out/_next/static/chunks/9117.080b9b75be90e14d.js +0 -74
  751. package/apps/pwa/out/_next/static/chunks/9167.f3ecb28f367d6e69.js +0 -94
  752. package/apps/pwa/out/_next/static/chunks/9224.66095370ee28c93b.js +0 -1
  753. package/apps/pwa/out/_next/static/chunks/9238-0682b013c83d6449.js +0 -1
  754. package/apps/pwa/out/_next/static/chunks/9449.d3ed795da4befb1c.js +0 -1
  755. package/apps/pwa/out/_next/static/chunks/9638-d268329f2c6f4f8d.js +0 -1
  756. package/apps/pwa/out/_next/static/chunks/973cbf1b.3bbcf2b41548eca7.js +0 -1
  757. package/apps/pwa/out/_next/static/chunks/9842-d6644eb53d3dc6bf.js +0 -1
  758. package/apps/pwa/out/_next/static/chunks/9863.94ca3263e760696e.js +0 -1
  759. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d9babd8795f396a5.js +0 -1
  760. package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +0 -1
  761. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-24453f6a302816b4.js +0 -1
  762. package/apps/pwa/out/_next/static/chunks/app/board/page-6c131d30bb1cf871.js +0 -1
  763. package/apps/pwa/out/_next/static/chunks/app/bots/page-dc0772b11861af51.js +0 -1
  764. package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +0 -1
  765. package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +0 -1
  766. package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +0 -1
  767. package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +0 -1
  768. package/apps/pwa/out/_next/static/chunks/app/decisions/page-256ffd365d32e100.js +0 -1
  769. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e0de78ae7b99f01c.js +0 -1
  770. package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +0 -1
  771. package/apps/pwa/out/_next/static/chunks/app/exec/page-4c741eb760f2f77c.js +0 -1
  772. package/apps/pwa/out/_next/static/chunks/app/field/page-e9ba98f5f1440a21.js +0 -1
  773. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d9babd8795f396a5.js +0 -1
  774. package/apps/pwa/out/_next/static/chunks/app/inside/page-dafc901bb1b72b5f.js +0 -1
  775. package/apps/pwa/out/_next/static/chunks/app/intake/page-2f6f303fa114ec24.js +0 -1
  776. package/apps/pwa/out/_next/static/chunks/app/layout-1634081c5272006c.js +0 -1
  777. package/apps/pwa/out/_next/static/chunks/app/live-run/page-bb1f05cd51c38944.js +0 -1
  778. package/apps/pwa/out/_next/static/chunks/app/loops/page-646b50f63b6fe732.js +0 -1
  779. package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +0 -1
  780. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-f736324afa2117c1.js +0 -1
  781. package/apps/pwa/out/_next/static/chunks/app/morning/page-6f131b51193a36f9.js +0 -1
  782. package/apps/pwa/out/_next/static/chunks/app/not-found-435f5e2faf02bc01.js +0 -1
  783. package/apps/pwa/out/_next/static/chunks/app/observatory/page-497e0eedf20e7890.js +0 -1
  784. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a8d28fdf995e16ea.js +0 -1
  785. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8966452228753c5e.js +0 -1
  786. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +0 -1
  787. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +0 -1
  788. package/apps/pwa/out/_next/static/chunks/app/page-66a67eb19ebd976b.js +0 -1
  789. package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +0 -1
  790. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-c71267e7681da982.js +0 -1
  791. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a21d1e1797172692.js +0 -1
  792. package/apps/pwa/out/_next/static/chunks/app/settings/page-781cfc5114e097da.js +0 -1
  793. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-478157214db6a023.js +0 -1
  794. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d9babd8795f396a5.js +0 -1
  795. package/apps/pwa/out/_next/static/chunks/app/setup/page-86141f12cf090b7f.js +0 -1
  796. package/apps/pwa/out/_next/static/chunks/app/share/page-7256c73bb1c36751.js +0 -1
  797. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +0 -1
  798. package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +0 -1
  799. package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +0 -1
  800. package/apps/pwa/out/_next/static/chunks/app/today/page-979d318a534e92da.js +0 -1
  801. package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +0 -1
  802. package/apps/pwa/out/_next/static/chunks/app/vault/page-21050928a697ed7a.js +0 -1
  803. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +0 -1
  804. package/apps/pwa/out/_next/static/chunks/app/workflows/page-601fb77340e63c9a.js +0 -1
  805. package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +0 -1
  806. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-11bf9e5725a72c22.js +0 -1
  807. package/apps/pwa/out/_next/static/chunks/dcf24383-2f82e35f2d827393.js +0 -1
  808. package/apps/pwa/out/_next/static/chunks/framework-bf135b997155e90e.js +0 -1
  809. package/apps/pwa/out/_next/static/chunks/main-5571537543bea91c.js +0 -1
  810. package/apps/pwa/out/_next/static/chunks/main-app-25c51c7df7f3a442.js +0 -1
  811. package/apps/pwa/out/_next/static/chunks/pages/_app-e3b2c1f5d82abd65.js +0 -1
  812. package/apps/pwa/out/_next/static/chunks/pages/_error-083f2e23e9bc3641.js +0 -1
  813. package/apps/pwa/out/_next/static/chunks/webpack-e94d22b7a028217b.js +0 -1
  814. package/src/dashboard/bench-slash-runtime.ts +0 -40
  815. package/src/dashboard/input/search-modal-router.ts +0 -9
  816. package/src/dashboard/modals/pane-key-router.ts +0 -27
  817. package/src/dashboard/view-slash-runtime.ts +0 -48
  818. /package/apps/pwa/out/_next/static/{y1ltt7oCZxVpC6zVPhZ2O → LSXfY0u9QtfDbejY0Ukf_}/_ssgManifest.js +0 -0
package/src/llm.ts CHANGED
@@ -3,6 +3,10 @@
3
3
  // Auto-routes based on model name prefix, or picks the first available provider.
4
4
 
5
5
  import { createHash } from 'node:crypto';
6
+ import { testFakeLlmEnabled, testFakeLlmProvider } from './ux-sim/fake-llm.js';
7
+ import { realpathSync } from 'node:fs';
8
+ import { resolve } from 'node:path';
9
+ import { globalAnchorPath } from './prompt-library/universal-preamble.js';
6
10
  import type { LLMUsage } from './prompt-cache/types.js';
7
11
  import { applyOpenRouterCacheControl, openRouterPromptCacheFamily } from './prompt-cache/openrouter.js';
8
12
  import { detectLocale, type Locale } from './expression/locale.js';
@@ -22,13 +26,17 @@ import {
22
26
  import { debug, redactSecrets, redactSecretText } from './debug/log.js';
23
27
  import { llmUsageCostFields } from './budget/llm-cost.js';
24
28
  import { accumulateByokUsage } from './llm/openrouter-byok-cost.js';
29
+ import { ACTION_PHASE_OPS_TOOLS, isSteeredShellRejection, REPAIR_PHASE_READ_ONLY_OPS_TOOLS } from './llm/tool-loop-steered-rejection.js';
25
30
  import { ToolCallTiming } from './chat/tool-call-timing.js';
26
31
  import {
27
32
  dispatchWithParallelSafety,
28
33
  isSafeForParallel,
29
34
  } from './session-runtime/parallel-dispatch.js';
35
+ import { createRetryDisplayGate, type RetryDisplayStep } from './llm-stream-retry-display.js';
30
36
  import {
31
37
  DoomLoopTracker,
38
+ backoffMs,
39
+ classifyError,
32
40
  decideRetry,
33
41
  fingerprintError,
34
42
  decideProviderFallback,
@@ -336,6 +344,8 @@ export type ToolChoice = 'auto' | 'required' | 'none' | { name: string };
336
344
 
337
345
  export interface LLMOpts {
338
346
  model?: string;
347
+ /** Reports the provider and model actually attempted, including fallbacks. */
348
+ onResolvedProvider?(provider: string, model: string): void;
339
349
  temperature?: number;
340
350
  maxTokens?: number;
341
351
  signal?: AbortSignal;
@@ -388,6 +398,14 @@ export interface LLMOpts {
388
398
  * e.g. driving a headless coding-agent terminal (SpawnCodingAgentHeadless
389
399
  * + PtyShell Snapshot/Send loop). Absent ⇒ no extension (default). */
390
400
  budgetGrant?: { tools: readonly string[]; perCall: number; ceiling: number };
401
+ /** TUI-GOAL-LOOP-FRICTION(#46·#63) — «종결 신호» 도구 이름(예: goal loop 의 `update_goal`).
402
+ * ① 도구 예산·탐색 예산·후속 단계의 거부를 받지 않는다 — 완료 신호를 «TOOL CALL REJECTED» 로
403
+ * 막으면 모델이 둘째 답 ⊕ 사과를 쓴다(#46).
404
+ * ② 한 턴의 호출이 «종결 신호뿐»이고 그 턴(또는 이전 도구 라운드)에 답 텍스트가 있으면, 신호를
405
+ * 디스패치한 뒤 다음 모델 턴을 열지 않고 그 답으로 끝낸다 — 빈 턴 반복 → `[NO FINAL SYNTHESIS]`(#63) 차단.
406
+ * ③ 종결 신호를 받은 뒤의 빈 턴 포기에도 이미 쓴 답이 있으면 `[NO FINAL SYNTHESIS]` 를 붙이지 않는다.
407
+ * 비우면(기본) 종전 동작 그대로. */
408
+ terminalToolNames?: readonly string[];
391
409
  /** Prompt-caching toggle. Honored by Anthropic (attaches
392
410
  * cache_control markers on system + tools + 2nd-to-last history
393
411
  * message) and OpenAI (passes stream_options.include_usage so
@@ -4228,17 +4246,11 @@ function permitsAutomaticProviderFallback(opts: LLMOpts & { provider?: LLMProvid
4228
4246
  }
4229
4247
  }
4230
4248
 
4231
- function configuredFallbackProviders(): LLMProvider[] {
4249
+ function configuredFallbackChainSteps(): readonly string[] {
4232
4250
  try {
4233
4251
  const { getUserConfig } = require('./user-config.js') as typeof import('./user-config.js');
4234
4252
  const { normalizeFallbackChain } = require('./oauth/fallback-chain.js') as typeof import('./oauth/fallback-chain.js');
4235
- const { chain } = normalizeFallbackChain(getUserConfig().llm.fallbackChain);
4236
- const out: LLMProvider[] = [];
4237
- for (const name of providerNamesFromFallbackChain(chain)) {
4238
- const provider = PROVIDERS[name];
4239
- if (provider) out.push(provider);
4240
- }
4241
- return out;
4253
+ return normalizeFallbackChain(getUserConfig().llm.fallbackChain).chain;
4242
4254
  } catch (err) {
4243
4255
  // ⛔ 조용히 삼키면 「설정을 못 읽었다」가 「폴백 후보가 없다」로 «둔갑»한다 —
4244
4256
  // 호출자는 그것을 `fallback candidates exhausted` 로 보고 사람에게 그렇게 말한다.
@@ -4250,35 +4262,116 @@ function configuredFallbackProviders(): LLMProvider[] {
4250
4262
  }
4251
4263
  }
4252
4264
 
4253
- function remainingFallbackProviderNames(
4265
+ /** 체인 칸 하나가 후보에서 «왜» 빠졌나. ⭐ `exhausted` 를 설명 가능하게 하려고 남긴다(TUI-STREAM-RETRY).
4266
+ * `folded-into-earlier-step` = `codex-credits` 처럼 앞 칸과 같은 provider 로 접힌 칸 — 접는 동작 자체는
4267
+ * `providerNamesFromFallbackChain` 소유이고 여기선 «기록만» 한다. */
4268
+ export type FallbackCandidateSkipReason =
4269
+ | 'already-attempted'
4270
+ | 'not-available'
4271
+ | 'folded-into-earlier-step'
4272
+ | 'unknown-step';
4273
+
4274
+ export interface FallbackCandidateSkip {
4275
+ step: string;
4276
+ provider?: string;
4277
+ reason: FallbackCandidateSkipReason;
4278
+ }
4279
+
4280
+ function explainFallbackCandidates(
4254
4281
  attemptedProviders: ReadonlySet<string>,
4255
4282
  opts: LLMOpts & { provider?: LLMProvider },
4256
- ): string[] {
4283
+ chain: readonly string[] = configuredFallbackChainSteps(),
4284
+ ): { candidates: string[]; skipped: FallbackCandidateSkip[] } {
4257
4285
  // Priority:
4258
4286
  // 1. per-call opts.provider → no fallback (caller pinned the route)
4259
4287
  // 2. llm.provider === 'auto' → ⭐ 2026-09-24 결정: `llm.fallbackChain` 을 «먼저»(codex-rotate→openai-codex · grok),
4260
4288
  // 그다음 체인이 «다루지 않는» 자동 후보(anthropic·gemini·local). 체인이 grok 을 뺐으면 grok 으로 새지 않는다.
4261
4289
  // 종전엔 auto 가 체인을 통째로 무시하고 AUTOMATIC_PROVIDER_ORDER 만 걸었다.
4262
4290
  // 3. otherwise → configured llm.fallbackChain (read from config; never baked here)
4263
- if (opts.provider) return [];
4291
+ const candidates: string[] = [];
4292
+ const skipped: FallbackCandidateSkip[] = [];
4293
+ if (opts.provider) return { candidates, skipped };
4294
+ const seen = new Set<string>();
4295
+ const consider = (step: string, provider: LLMProvider) => {
4296
+ if (attemptedProviders.has(provider.name)) skipped.push({ step, provider: provider.name, reason: 'already-attempted' });
4297
+ else if (!provider.available()) skipped.push({ step, provider: provider.name, reason: 'not-available' });
4298
+ else candidates.push(provider.name);
4299
+ };
4300
+ for (const step of chain) {
4301
+ const name = providerNamesFromFallbackChain([step])[0];
4302
+ const provider = name ? PROVIDERS[name] : undefined;
4303
+ if (!name || !provider) {
4304
+ skipped.push({ step, ...(name ? { provider: name } : {}), reason: 'unknown-step' });
4305
+ continue;
4306
+ }
4307
+ if (seen.has(name)) {
4308
+ skipped.push({ step, provider: name, reason: 'folded-into-earlier-step' });
4309
+ continue;
4310
+ }
4311
+ seen.add(name);
4312
+ consider(step, provider);
4313
+ }
4264
4314
  if (permitsAutomaticProviderFallback(opts)) {
4265
4315
  const chainGoverned = new Set(providerNamesFromFallbackChain(['codex-rotate', 'grok']));
4266
- const ordered: LLMProvider[] = [
4267
- ...configuredFallbackProviders(),
4268
- ...automaticProviderCandidates().filter((candidate) => !chainGoverned.has(candidate.name)),
4269
- ];
4270
- const seen = new Set<string>();
4271
- return ordered
4272
- .filter((candidate) => {
4273
- if (seen.has(candidate.name)) return false;
4274
- seen.add(candidate.name);
4275
- return !attemptedProviders.has(candidate.name) && candidate.available();
4276
- })
4277
- .map((candidate) => candidate.name);
4278
- }
4279
- return configuredFallbackProviders()
4280
- .filter((candidate) => !attemptedProviders.has(candidate.name) && candidate.available())
4281
- .map((candidate) => candidate.name);
4316
+ for (const candidate of automaticProviderCandidates()) {
4317
+ if (chainGoverned.has(candidate.name) || seen.has(candidate.name)) continue;
4318
+ seen.add(candidate.name);
4319
+ consider(`auto:${candidate.name}`, candidate);
4320
+ }
4321
+ }
4322
+ return { candidates, skipped };
4323
+ }
4324
+
4325
+ function remainingFallbackProviderNames(
4326
+ attemptedProviders: ReadonlySet<string>,
4327
+ opts: LLMOpts & { provider?: LLMProvider },
4328
+ ): string[] {
4329
+ return explainFallbackCandidates(attemptedProviders, opts).candidates;
4330
+ }
4331
+
4332
+ /** 폴백 후보를 고르는 «그 순간»에 빠진 칸과 이유를 남긴다 — `provider-fallback-exhausted` 가
4333
+ * 「무엇을 봤고 왜 아무도 안 남았나」를 혼자 말하지 못하던 결손(2026-10-14 TUI 실측) 수리. */
4334
+ function explainAndLogFallbackCandidates(
4335
+ attemptedProviders: ReadonlySet<string>,
4336
+ opts: LLMOpts & { provider?: LLMProvider },
4337
+ context: { failedProvider: string; turn?: number },
4338
+ chain?: readonly string[],
4339
+ ): string[] {
4340
+ const { candidates, skipped } = explainFallbackCandidates(attemptedProviders, opts, chain);
4341
+ debug.log('llm.router', 'fallback-candidates', {
4342
+ ...(context.turn !== undefined ? { turn: context.turn } : {}),
4343
+ failedProvider: context.failedProvider,
4344
+ candidates,
4345
+ skipped,
4346
+ });
4347
+ return candidates;
4348
+ }
4349
+
4350
+ /** 스트림 중 끊김 재시도 상한 — codex `DEFAULT_STREAM_MAX_RETRIES`(5) 와 같은 값. */
4351
+ const STREAM_DISCONNECT_MAX_RETRIES = 5;
4352
+ let streamDisconnectRetryDelayMs: (attempt: number) => number = backoffMs;
4353
+
4354
+ /** @internal 시험 seam — 재시도 대기(기본 `backoffMs`)를 바꾼다. `undefined` 면 원복. */
4355
+ export function _setStreamDisconnectRetryDelayForTest(fn: ((attempt: number) => number) | undefined): void {
4356
+ streamDisconnectRetryDelayMs = fn ?? backoffMs;
4357
+ }
4358
+
4359
+ /** 흐르던 스트림의 끊김을 같은 공급자로 다시 걸어도 되나.
4360
+ * ⛔ 이 스트림에서 tool_call 을 받았거나 image 를 이미 내보냈으면 아니다 — 그 조각을 다시 보내면
4361
+ * 부작용·화면이 두 번 생길 수 있다(실행 전이라도 «추측해서» 버리지 않는다). */
4362
+ function isStreamDisconnectRetryable(err: unknown, pendingCallCount: number, emittedImage: boolean): boolean {
4363
+ if (pendingCallCount > 0 || emittedImage) return false;
4364
+ if (err instanceof OverloadFailoverError) return false;
4365
+ return classifyError(err) === 'network-transient';
4366
+ }
4367
+
4368
+ function sleepUnlessAborted(ms: number, signal?: AbortSignal): Promise<void> {
4369
+ if (ms <= 0 || signal?.aborted) return Promise.resolve();
4370
+ return new Promise((resolve) => {
4371
+ const onAbort = () => { clearTimeout(timer); resolve(); };
4372
+ const timer = setTimeout(() => { signal?.removeEventListener('abort', onAbort); resolve(); }, ms);
4373
+ signal?.addEventListener('abort', onAbort, { once: true });
4374
+ });
4282
4375
  }
4283
4376
 
4284
4377
  /** @internal 시험 seam — auto 폴백 순서(체인 먼저 · 체인이 안 다루는 자동 후보 뒤)를 결정적으로 누른다. */
@@ -4286,6 +4379,14 @@ export function _remainingFallbackProviderNamesForTest(attempted: readonly strin
4286
4379
  return remainingFallbackProviderNames(new Set(attempted), {});
4287
4380
  }
4288
4381
 
4382
+ /** @internal 시험 seam — 체인 칸별 skip 이유 ⊕ `llm.router fallback-candidates` 관측. */
4383
+ export function _explainAndLogFallbackCandidatesForTest(
4384
+ attempted: readonly string[],
4385
+ chain: readonly string[],
4386
+ ): string[] {
4387
+ return explainAndLogFallbackCandidates(new Set(attempted), {}, { failedProvider: attempted[0] ?? 'none' }, chain);
4388
+ }
4389
+
4289
4390
  function terminalFallbackVerdict(
4290
4391
  verdict: ReturnType<typeof decideProviderFallback>,
4291
4392
  attempts: readonly ProviderFallbackAttempt[],
@@ -4388,6 +4489,9 @@ export function getProvider(model?: string): LLMProvider {
4388
4489
  }
4389
4490
  if (PROVIDERS.openrouter!.available()) return PROVIDERS.openrouter!;
4390
4491
 
4492
+ // Gate Pod may intentionally have no credentials. The explicit test env
4493
+ // supplies a deterministic answer only after every real auto candidate failed.
4494
+ if (testFakeLlmEnabled()) return testFakeLlmProvider;
4391
4495
  throw new NoLlmProviderAvailableError();
4392
4496
  }
4393
4497
 
@@ -4725,7 +4829,7 @@ export async function streamLLM(
4725
4829
  debug.log('llm', 'outcome', { provider: activeProvider.name, status: 'error', kind: 'streamLLM', durationMs: Date.now() - started });
4726
4830
  throw err;
4727
4831
  }
4728
- const remaining = remainingFallbackProviderNames(attemptedProviders, opts);
4832
+ const remaining = explainAndLogFallbackCandidates(attemptedProviders, opts, { failedProvider: activeProvider.name });
4729
4833
  const verdict = decideProviderFallback({
4730
4834
  err,
4731
4835
  failedProvider: activeProvider.name,
@@ -4889,6 +4993,11 @@ export interface StreamWithToolsHandlers {
4889
4993
  turnIndex?: number;
4890
4994
  },
4891
4995
  ): Promise<unknown>;
4996
+ /** Optional: the final answer was enriched with a runtime verification
4997
+ * supplement (변경 파일·검증 명령·검증 최신성·루프 마감 상태). Fires just
4998
+ * before the `onText('', enriched)` re-emit so presentation hosts can
4999
+ * keep the user answer to `answer` and send `supplement` to logs. */
5000
+ onFinalAnswerSupplement?(info: { answer: string; supplement: string; finalText: string }): void;
4892
5001
  /** Optional: notify when a tool_call is about to fire (for log line,
4893
5002
  * HUD segment, etc). */
4894
5003
  onToolCall?(call: { id: string; name: string; args: Record<string, unknown> }): void;
@@ -5674,10 +5783,13 @@ const TOOL_LOOP_PHASE_DIRECTIVES: Record<ToolLoopFollowupPhase, (autoNarrowedRea
5674
5783
  'repair-action': () =>
5675
5784
  'REPAIR ACTION REQUIRED — execution has already failed or a debugging repair step is armed. '
5676
5785
  + 'Stop gathering more files and move to Edit/Write/RunShell/Bash or a narrow code-intel followup (Read/Lsp/AstGrep/Grep content/count), '
5786
+ + 'ops ledger/delegate tools (MsgList/MsgPost/MsgAck/Checklist*/AgentOutput) still run, '
5677
5787
  + 'or answer in plain text if no code change is needed (e.g. a read-only investigation whose finding is the deliverable).',
5678
5788
  'verify-action': () =>
5679
5789
  'VERIFY ACTION REQUIRED — edits already happened. '
5680
- + 'Stop gathering more files and move to RunShell/Bash/Agent or a narrow verification followup (Read/Lsp/Grep content/count), or answer in plain text if verification is not needed.',
5790
+ + 'Stop gathering more files and move to RunShell/Bash/Agent or a narrow verification followup (Read/Lsp/Grep content/count), '
5791
+ + 'ops ledger/delegate tools (MsgList/MsgPost/MsgAck/Checklist*/AgentOutput) still run, '
5792
+ + 'or answer in plain text if verification is not needed.',
5681
5793
  };
5682
5794
 
5683
5795
  const TOOL_LOOP_PHASE_REJECTION_EVENTS: Record<ToolLoopFollowupPhase, string> = {
@@ -7719,6 +7831,7 @@ const EXPLORATORY_TOOLS = new Set([
7719
7831
  ]);
7720
7832
 
7721
7833
  const INSPECT_ACTION_TOOLS = new Set([
7834
+ ...ACTION_PHASE_OPS_TOOLS,
7722
7835
  'Edit',
7723
7836
  'Write',
7724
7837
  'Bash',
@@ -7733,6 +7846,7 @@ const INSPECT_ACTION_TOOLS = new Set([
7733
7846
  ]);
7734
7847
 
7735
7848
  const VERIFY_ACTION_TOOLS = new Set([
7849
+ ...ACTION_PHASE_OPS_TOOLS,
7736
7850
  'Edit',
7737
7851
  'Write',
7738
7852
  'RunShell',
@@ -7748,6 +7862,7 @@ const VERIFY_ACTION_TOOLS = new Set([
7748
7862
  ]);
7749
7863
 
7750
7864
  const EXECUTION_ACTION_TOOLS = new Set([
7865
+ ...ACTION_PHASE_OPS_TOOLS,
7751
7866
  'RunShell',
7752
7867
  'Bash',
7753
7868
  'Agent',
@@ -7761,6 +7876,8 @@ const EXECUTION_ACTION_TOOLS = new Set([
7761
7876
  ]);
7762
7877
 
7763
7878
  const REPAIR_ACTION_TOOLS = new Set([
7879
+ ...REPAIR_PHASE_READ_ONLY_OPS_TOOLS,
7880
+ ...ACTION_PHASE_OPS_TOOLS,
7764
7881
  'Edit',
7765
7882
  'Write',
7766
7883
  'RunShell',
@@ -8048,6 +8165,8 @@ export function billingRouteForProvider(provider: string | undefined, model: str
8048
8165
  if (name === 'local') return 'local';
8049
8166
  try {
8050
8167
  const auth = authKindForProvider(name, model);
8168
+ // CREDIT-BILLING-LABEL(10-14) — codex 회전이 크레딧 소진을 정한 호출은 구독이 아니라 크레딧이다.
8169
+ if (auth === 'oauth' && name === 'openai-codex' && codexCreditSpendReason()) return 'credit';
8051
8170
  return auth === 'oauth' ? 'subscription' : auth === 'local' ? 'local' : auth === 'apikey' ? 'api' : 'unknown';
8052
8171
  } catch {
8053
8172
  return 'unknown';
@@ -8087,14 +8206,19 @@ export async function streamLLMWithTools(
8087
8206
  const initial = finalizeStreamingProviderModel(provider, opts.model ?? provider.defaultModel);
8088
8207
  let activeProvider = initial.provider;
8089
8208
  let activeModel = initial.model;
8209
+ opts.onResolvedProvider?.(activeProvider.name, activeModel);
8090
8210
  const attemptedProviders = new Set<string>([activeProvider.name]);
8091
8211
  let blockedProviders: ProviderFallbackAttempt[] = [];
8092
8212
  const effectiveModel = activeModel;
8093
8213
  const modelFamily = getModelFamily(effectiveModel);
8214
+ // TUI-GPT6-GUARD-PARITY — codex «모델 행동» 가드 켬 여부(단일 원천 = models/prompts.ts
8215
+ // usesCodexBehaviorGuards). gpt-6 세대도 true. 넓힌 자리·안 넓힌 자리는 그 함수 주석이 정본.
8216
+ const codexBehaviorGuards = usesCodexBehaviorGuards(effectiveModel);
8094
8217
  debug.log('llm.router', 'streamLLMWithTools', {
8095
8218
  provider: provider.name,
8096
8219
  model: effectiveModel,
8097
8220
  modelFamily,
8221
+ codexBehaviorGuards,
8098
8222
  messageCount: messages.length,
8099
8223
  messageRoles: summarizeRoles(messages),
8100
8224
  tools: opts.tools?.map(t => t.name),
@@ -8270,6 +8394,12 @@ export async function streamLLMWithTools(
8270
8394
  };
8271
8395
  let fullText = '';
8272
8396
  let sawToolRound = false;
8397
+ // TUI-GOAL-LOOP-FRICTION — 종결 신호 도구(LLMOpts.terminalToolNames). 비면 아래 분기는 전부 꺼진다.
8398
+ const terminalToolNames: ReadonlySet<string> = new Set(opts.terminalToolNames ?? []);
8399
+ /** 이 스트림에서 종결 신호를 한 번이라도 디스패치했나. */
8400
+ let terminalSignalDispatched = false;
8401
+ /** 도구 라운드에 실린 마지막 «비지 않은» 답 텍스트(종결 신호 턴 마감·빈 턴 포기에서 쓴다). */
8402
+ let lastToolRoundText = '';
8273
8403
  const agentBatchTickIntervalMs = opts.agentBatchTickIntervalMs ?? BATCH_TICK_INTERVAL_MS;
8274
8404
 
8275
8405
  // Empty-turn guard: smaller models (gpt-5.4-mini, haiku) sometimes
@@ -8333,6 +8463,8 @@ export async function streamLLMWithTools(
8333
8463
  // instructing the model to reuse prior tool_results or write its
8334
8464
  // final answer. Codex-family-only: Claude path is unaffected.
8335
8465
  const seenToolCallSignatures = new Map<string, number>();
8466
+ // TUI-INITIATIVE-S ② — 같은 도구·같은 인자가 «같은 오류»로 끝난 횟수(성공하면 지운다). 아래 L-2 차단을 gpt 계열·비탐색 도구로 넓힌다.
8467
+ const erroredToolCallSignatures = new Map<string, { error: string; count: number }>();
8336
8468
  // W3-A + extension (2026-05-03) — Broad-spot dedup map. MUST live
8337
8469
  // outside the turn loop so the counter persists across turns. (Bug
8338
8470
  // 2026-05-03 PM: original placement inside turn loop reset the map
@@ -8588,6 +8720,32 @@ export async function streamLLMWithTools(
8588
8720
  });
8589
8721
  let evCount = 0;
8590
8722
  let lastEvType = 'none';
8723
+ // ⭐ TUI-STREAM-RETRY(2026-10-14 실측) — 200 OK 로 흐르던 스트림이 소켓 끊김으로 죽으면 같은 공급자로
8724
+ // 다시 건다(codex `stream_max_retries` 동형). 이 턴에서 받은 tool_call·image 가 있으면 재시도하지 않는다.
8725
+ let streamDisconnectRetries = 0;
8726
+ let streamEmittedImage = false;
8727
+ // 끊긴 조각을 이력(turnText·fullText)에서 «조용히» 되감는다 — 화면엔 지우라고 알리지 않는다
8728
+ // (TUI 의 빈 델타는 «커밋»이다). 이미 보인 글과 겹치는 재시도 본문은 `retryDisplay` 가 막는다.
8729
+ const retryDisplay = createRetryDisplayGate();
8730
+ const emitRetryDisplayStep = (step: RetryDisplayStep) => {
8731
+ if (step.kind === 'emit') {
8732
+ if (step.delta) handlers.onText(step.delta, fullText);
8733
+ } else if (step.kind === 'diverged') {
8734
+ debug.log('llm.retry', 'stream-retry-display-diverged', { provider: activeProvider.name, turn, textChars: step.text.length }, { level: 'warn' });
8735
+ handlers.onText('', '');
8736
+ if (step.text) handlers.onText(step.text, fullText);
8737
+ }
8738
+ };
8739
+ const rewindDisconnectedStream = () => {
8740
+ if (turnText.length > 0) {
8741
+ fullText = fullText.slice(0, fullText.length - turnText.length);
8742
+ turnText = '';
8743
+ }
8744
+ retryDisplay.arm();
8745
+ turnReasoningDetails = undefined;
8746
+ evCount = 0;
8747
+ lastEvType = 'none';
8748
+ };
8591
8749
  // ⚠️ idle watchdog(2026-07-17) — codex SSE 가 완료 신호 없이 hang 하는 간헐 케이스 방어. 각
8592
8750
  // 이벤트 대기를 idle timeout 과 race — STREAM_IDLE_TIMEOUT_MS 무이벤트면 스트림 정리·탈출(받은
8593
8751
  // 텍스트로 진행). 35분 무한 hang → 자동 복구. 원인 규명은 consume-end 부재/idle-timeout 로그가.
@@ -8600,6 +8758,7 @@ export async function streamLLMWithTools(
8600
8758
  const finalized = finalizeStreamingProviderModel(activeProvider, activeModel);
8601
8759
  activeProvider = finalized.provider;
8602
8760
  activeModel = finalized.model;
8761
+ opts.onResolvedProvider?.(activeProvider.name, activeModel);
8603
8762
  turnOpts = { ...turnOpts, model: activeModel };
8604
8763
  streamIter = activeProvider.streamChat!(history, turnOpts)[Symbol.asyncIterator]();
8605
8764
  }
@@ -8624,16 +8783,49 @@ export async function streamLLMWithTools(
8624
8783
  activeProvider = finalized.provider;
8625
8784
  activeModel = finalized.model;
8626
8785
  attemptedProviders.add(activeProvider.name);
8786
+ streamDisconnectRetries = 0;
8627
8787
  turnOpts = buildTurnOpts();
8628
8788
  streamIter = undefined;
8629
8789
  continue;
8630
8790
  }
8631
8791
  }
8792
+ if (evCount > 0 && isStreamDisconnectRetryable(err, pendingCalls.length, streamEmittedImage) && !opts.signal?.aborted) {
8793
+ const retryData = {
8794
+ provider: activeProvider.name,
8795
+ model: activeModel,
8796
+ turn,
8797
+ scope: 'stream-disconnect',
8798
+ category: 'network-transient',
8799
+ reason: sanitizeProviderFailureReason(err instanceof Error ? err.message : String(err)),
8800
+ evCount,
8801
+ lastEvType,
8802
+ rewoundTextChars: turnText.length,
8803
+ maxRetries: STREAM_DISCONNECT_MAX_RETRIES,
8804
+ };
8805
+ if (streamDisconnectRetries < STREAM_DISCONNECT_MAX_RETRIES) {
8806
+ const delayMs = streamDisconnectRetryDelayMs(streamDisconnectRetries);
8807
+ streamDisconnectRetries++;
8808
+ debug.log('llm.retry', 'retry', { ...retryData, attempt: streamDisconnectRetries, delayMs }, { level: 'warn' });
8809
+ rewindDisconnectedStream();
8810
+ await sleepUnlessAborted(delayMs, opts.signal);
8811
+ if (opts.signal?.aborted) throw err;
8812
+ streamIter = undefined;
8813
+ continue;
8814
+ }
8815
+ debug.log('llm.retry', 'max-attempts-exhausted', { ...retryData, attempt: streamDisconnectRetries }, { level: 'warn' });
8816
+ // 되감았으니 이 턴은 «출력 전» 상태다 — 아래 폴백이 다음 공급자로 깨끗이 넘어갈 수 있다.
8817
+ rewindDisconnectedStream();
8818
+ }
8632
8819
  // 명시 provider 선택만 폴백 대상이 아니다 — `streamLLM` 과 같은 계약으로
8633
8820
  // «원본 오류»를 그대로 올린다. 모델만 전달된 호출은 다음 provider의 기본 모델로 재시도한다.
8634
8821
  if (opts.provider) throw err;
8822
+ if (evCount > 0) {
8823
+ debug.log('llm.router', 'fallback-candidates', {
8824
+ turn, failedProvider: activeProvider.name, candidates: [], skipped: [], outputEmitted: true, evCount, pendingCalls: pendingCalls.length,
8825
+ });
8826
+ }
8635
8827
  const remaining = evCount === 0
8636
- ? remainingFallbackProviderNames(attemptedProviders, opts)
8828
+ ? explainAndLogFallbackCandidates(attemptedProviders, opts, { failedProvider: activeProvider.name, turn })
8637
8829
  : [];
8638
8830
  const verdict = decideProviderFallback({
8639
8831
  err,
@@ -8670,6 +8862,8 @@ export async function streamLLMWithTools(
8670
8862
  activeProvider = finalized.provider;
8671
8863
  activeModel = finalized.model;
8672
8864
  attemptedProviders.add(activeProvider.name);
8865
+ // 재시도 상한은 «공급자마다» — 넘어간 공급자도 스트림 도중 끊김이면 다시 5회까지 건다.
8866
+ streamDisconnectRetries = 0;
8673
8867
  turnOpts = buildTurnOpts();
8674
8868
  streamIter = undefined;
8675
8869
  continue;
@@ -8687,7 +8881,7 @@ export async function streamLLMWithTools(
8687
8881
  if (ev.type === 'text') {
8688
8882
  turnText += ev.delta;
8689
8883
  fullText += ev.delta;
8690
- handlers.onText(ev.delta, fullText);
8884
+ emitRetryDisplayStep(retryDisplay.onText(ev.delta, turnText));
8691
8885
  } else if (ev.type === 'reasoning_details') {
8692
8886
  turnReasoningDetails = ev.details;
8693
8887
  } else if (ev.type === 'tool_call') {
@@ -8724,6 +8918,7 @@ export async function streamLLMWithTools(
8724
8918
  });
8725
8919
  }
8726
8920
  } else if (ev.type === 'image') {
8921
+ streamEmittedImage = true;
8727
8922
  // Y2·1 (2026-05-17) — forward Codex server-tool image result.
8728
8923
  // Only fires when caller opted into opts.serverTools.imageGeneration
8729
8924
  // and the model invoked the built-in tool. No-op when handler
@@ -8780,6 +8975,7 @@ export async function streamLLMWithTools(
8780
8975
  }
8781
8976
 
8782
8977
  // hang 진단 — for-await(SSE 스트림) 정상 종료 확정. 이 로그가 없으면 codex SSE 스트림 hang.
8978
+ emitRetryDisplayStep(retryDisplay.settle(turnText));
8783
8979
  debug.log('llm.stream', 'consume-end', { turn, evCount, lastEvType, textChars: turnText.length, pendingCalls: pendingCalls.length });
8784
8980
  const turnInfo = {
8785
8981
  turn,
@@ -8826,6 +9022,10 @@ export async function streamLLMWithTools(
8826
9022
  verificationLoopState,
8827
9023
  finalization,
8828
9024
  );
9025
+ if (finalText !== visibleText) {
9026
+ const answer = visibleText.trim();
9027
+ handlers.onFinalAnswerSupplement?.({ answer, supplement: finalText.slice(answer.length).trim(), finalText });
9028
+ }
8829
9029
  if (finalText !== fullText) handlers.onText('', finalText);
8830
9030
  emitTurnComplete(finalText);
8831
9031
  return finalText;
@@ -8835,6 +9035,17 @@ export async function streamLLMWithTools(
8835
9035
  // MAX_EMPTY_RETRIES times. Beyond that, return an explicit
8836
9036
  // no-synthesis notice instead of silently ending the turn empty.
8837
9037
  if (pendingCalls.length === 0 && turnText.length === 0) {
9038
+ // TUI-GOAL-LOOP-FRICTION(#63) — 종결 신호(update_goal)를 이미 받았고 답도 이미 썼다 = 할 말이 없는 게 맞다.
9039
+ // 빈 턴을 «정체»로 보고 재촉·`[NO FINAL SYNTHESIS]` 를 붙이지 않고 그 답으로 끝낸다.
9040
+ if (terminalSignalDispatched && lastToolRoundText.trim().length > 0) {
9041
+ const answer = lastToolRoundText;
9042
+ debug.log('goal.loop', 'empty-turn-after-terminal-signal', {
9043
+ turn, emptyRetries, answerChars: answer.length,
9044
+ }, { level: 'info' });
9045
+ if (answer !== fullText) handlers.onText('', answer);
9046
+ emitTurnComplete(answer);
9047
+ return answer;
9048
+ }
8838
9049
  if (emptyRetries >= MAX_EMPTY_RETRIES) {
8839
9050
  const notice = buildNoFinalSynthesisNotice('empty-turn');
8840
9051
  const visibleText = finalizeVisibleAssistantText(
@@ -8870,6 +9081,11 @@ export async function streamLLMWithTools(
8870
9081
  clearVisibleAssistantTextForToolRound(handlers, sawToolRound);
8871
9082
  }
8872
9083
  sawToolRound = true;
9084
+ if (turnText.trim().length > 0) lastToolRoundText = turnText;
9085
+ // TUI-GOAL-LOOP-FRICTION(#46) — 이 턴의 호출이 «종결 신호뿐»이면 예산·후속 단계 거부를 걸지 않는다.
9086
+ const terminalOnlyTurn = terminalToolNames.size > 0
9087
+ && pendingCalls.length > 0
9088
+ && pendingCalls.every((call) => terminalToolNames.has(call.name));
8873
9089
  const wasExploratoryTurn = isExploratoryTurn(pendingCalls);
8874
9090
  const prevExploratoryStreak = exploratoryTurnStreak;
8875
9091
  exploratoryTurnStreak = wasExploratoryTurn ? exploratoryTurnStreak + 1 : 0;
@@ -8926,40 +9142,47 @@ export async function streamLLMWithTools(
8926
9142
  // maxTurns hard-stops it, producing 0 chars of output. Hard
8927
9143
  // rejection forces the synthesis that soft pressure couldn't.
8928
9144
  const inSynthesisPhase =
8929
- maxTurns >= BUDGET_WARNING_MIN_TURNS
9145
+ !terminalOnlyTurn
9146
+ && maxTurns >= BUDGET_WARNING_MIN_TURNS
8930
9147
  && turn >= maxTurns - FINAL_SYNTHESIS_TURNS;
8931
9148
  const codexInspectionStillProgressing =
8932
9149
  modelFamily === 'codex'
8933
9150
  && autoNarrowedReadCount > 0
8934
9151
  && autoNarrowedReadCount < inspectBudgetThreshold;
8935
9152
  const inExplorationSynthesisPhase =
8936
- exploratoryTurnStreak >= explorationThresholdForTurn
9153
+ !terminalOnlyTurn
9154
+ && exploratoryTurnStreak >= explorationThresholdForTurn
8937
9155
  && isExploratoryTurn(pendingCalls)
8938
9156
  && !codexInspectionStillProgressing;
8939
9157
  const inInspectSynthesisPhase =
8940
- inspectSynthesisArmed
9158
+ !terminalOnlyTurn
9159
+ && inspectSynthesisArmed
8941
9160
  && inspectFollowupMode === 'synthesis'
8942
9161
  && pendingCalls.length > 0;
8943
9162
  const inInspectActionPhase =
8944
- inspectSynthesisArmed
9163
+ !terminalOnlyTurn
9164
+ && inspectSynthesisArmed
8945
9165
  && inspectFollowupMode === 'action'
8946
9166
  && !verifyActionArmed
8947
9167
  && pendingCalls.length > 0
8948
9168
  && !isInspectActionTurn(pendingCalls);
8949
9169
  const inInspectExecutionPhase =
8950
- inspectSynthesisArmed
9170
+ !terminalOnlyTurn
9171
+ && inspectSynthesisArmed
8951
9172
  && inspectFollowupMode === 'execution'
8952
9173
  && !repairActionArmed
8953
9174
  && !verifyActionArmed
8954
9175
  && pendingCalls.length > 0
8955
9176
  && !isExecutionActionTurn(pendingCalls);
8956
9177
  const inRepairActionPhase =
8957
- repairActionArmed
9178
+ !terminalOnlyTurn
9179
+ && repairActionArmed
8958
9180
  && !verifyActionArmed
8959
9181
  && pendingCalls.length > 0
8960
9182
  && !isRepairActionTurn(pendingCalls);
8961
9183
  const inVerifyActionPhase =
8962
- verifyActionArmed
9184
+ !terminalOnlyTurn
9185
+ && verifyActionArmed
8963
9186
  && pendingCalls.length > 0
8964
9187
  && !isVerifyActionTurn(pendingCalls);
8965
9188
  const inInspectFollowupPhase =
@@ -9009,11 +9232,18 @@ export async function streamLLMWithTools(
9009
9232
  let callsToDispatch = pendingCalls;
9010
9233
  let appendedToolResultNotice: string | null = null;
9011
9234
  let phaseRejectionMessage: string | null = null;
9012
- const phaseRejectedCalls = getToolLoopPhaseRejectedCalls(inspectFollowupPhase, pendingCalls);
9235
+ // TUI-GOAL-LOOP-FRICTION(#46) — 섞인 턴에서도 종결 신호는 거부 대상에서 뺀다(다른 호출만 거부).
9236
+ const phaseRejectedCalls = getToolLoopPhaseRejectedCalls(inspectFollowupPhase, pendingCalls)
9237
+ .filter((call) => !terminalToolNames.has(call.name));
9013
9238
  const phaseRejectedCallIds = new Set(phaseRejectedCalls.map((call) => call.id));
9014
9239
  const rejectsWholeBatch = inSynthesisPhase || inExplorationSynthesisPhase || inInspectSynthesisPhase;
9240
+ /** 통째 거부 턴에 섞인 종결 신호 — 거부 스텁 대신 수신 확인을 준다. */
9241
+ const isTerminalCall = (call: { name: string }): boolean => terminalToolNames.has(call.name);
9242
+ const terminalAck = (call: { name: string }): string => JSON.stringify({
9243
+ ok: true, terminalSignal: call.name, note: 'terminal signal recorded; other tool calls in this turn did not run',
9244
+ });
9015
9245
  if (rejectsWholeBatch || phaseRejectedCalls.length > 0) {
9016
- const rejectedCalls = rejectsWholeBatch ? pendingCalls : phaseRejectedCalls;
9246
+ const rejectedCalls = rejectsWholeBatch ? pendingCalls.filter((call) => !isTerminalCall(call)) : phaseRejectedCalls;
9017
9247
  const executedCalls = rejectsWholeBatch ? [] : pendingCalls.filter((call) => !phaseRejectedCallIds.has(call.id));
9018
9248
  const rejectionMsg =
9019
9249
  inspectFollowupPhase
@@ -9036,8 +9266,17 @@ export async function streamLLMWithTools(
9036
9266
  phaseRejectionMessage = rejectionMsg;
9037
9267
  if (rejectsWholeBatch) {
9038
9268
  callsToDispatch = [];
9039
- for (const call of rejectedCalls) {
9040
- resultBlocks.push({ type: 'tool_result', tool_use_id: call.id, content: rejectionMsg });
9269
+ // 공급자 순서를 지키려고 pendingCalls 순서대로 채운다 — 종결 신호만 수신 확인, 나머지는 거부 스텁.
9270
+ for (const call of pendingCalls) {
9271
+ if (isTerminalCall(call)) {
9272
+ terminalSignalDispatched = true;
9273
+ resultBlocks.push({ type: 'tool_result', tool_use_id: call.id, content: terminalAck(call) });
9274
+ debug.log('goal.loop', 'terminal-signal-exempt-from-budget', {
9275
+ turn, maxTurns, tool: call.name, rejectedTools: rejectedCalls.map((c) => c.name),
9276
+ }, { level: 'info' });
9277
+ } else {
9278
+ resultBlocks.push({ type: 'tool_result', tool_use_id: call.id, content: rejectionMsg });
9279
+ }
9041
9280
  }
9042
9281
  }
9043
9282
  // P2 (2026-05-03) — Three predicates as before, but each now consults
@@ -9288,7 +9527,9 @@ export async function streamLLMWithTools(
9288
9527
  for (const call of pendingCalls) {
9289
9528
  handlers.onToolCall?.(call);
9290
9529
  toolCallHistory.push({ name: call.name, args: call.args });
9291
- handlers.onToolResult?.({ id: call.id, name: call.name, result: phaseRejectionMessage! });
9530
+ handlers.onToolResult?.({
9531
+ id: call.id, name: call.name, result: isTerminalCall(call) ? terminalAck(call) : phaseRejectionMessage!,
9532
+ });
9292
9533
  }
9293
9534
  }
9294
9535
  if (!rejectsWholeBatch && hardStopText === null) {
@@ -9332,6 +9573,14 @@ export async function streamLLMWithTools(
9332
9573
  result: unknown,
9333
9574
  ): void => {
9334
9575
  if (call.name !== 'RunShell' && call.name !== 'Bash') return;
9576
+ if (isSteeredShellRejection(result)) {
9577
+ debug.log('llm.router', 'tool-loop.steered-rejection-not-failure', {
9578
+ turn,
9579
+ tool: call.name,
9580
+ approvalKey: (result as { approvalKey: string }).approvalKey,
9581
+ });
9582
+ return;
9583
+ }
9335
9584
  const verificationAttempt = verifyActionArmed;
9336
9585
  const executionCommand = summarizeExecutionCommand(call.name, call.args);
9337
9586
  const executionSummary = summarizeExecutionResult(result);
@@ -9488,32 +9737,55 @@ export async function streamLLMWithTools(
9488
9737
  | { kind: 'absent' }
9489
9738
  | { kind: 'complete'; label: string }
9490
9739
  | { kind: 'truncated'; label: string };
9491
- const anchorFileState = (systemMessages: LLMMessage[], filename: string): AnchorFileState => {
9492
- const escapedFilename = filename.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
9493
- const opening = new RegExp(`^=== (.+) \\(${escapedFilename}(?: @ [^)]+)?\\) ===$`, 'm');
9494
- const closing = new RegExp(`^=== end ${escapedFilename}(?: @ .+)? ===$`, 'm');
9740
+ const anchorFileState = (systemMessages: LLMMessage[], filename: string, filePath?: string): AnchorFileState => {
9741
+ const opening = /^=== (.+) \(([^()]+?)(?: @ ([^)]+))?\) ===$/gm;
9495
9742
  for (const message of systemMessages) {
9496
9743
  if (message.role !== 'system' || typeof message.content !== 'string') continue;
9497
- const openingMatch = opening.exec(message.content);
9498
- if (openingMatch === null || !closing.test(message.content.slice(openingMatch.index))) continue;
9499
- const contentStart = openingMatch.index + openingMatch[0].length;
9500
- const closingMatch = closing.exec(message.content.slice(contentStart));
9501
- const content = message.content.slice(contentStart, contentStart + (closingMatch?.index ?? 0));
9502
- return content.includes('…[truncated to fit anchor budget]')
9503
- ? { kind: 'truncated', label: openingMatch[1] }
9504
- : { kind: 'complete', label: openingMatch[1] };
9744
+ for (const openingMatch of message.content.matchAll(opening)) {
9745
+ const fencedPath = openingMatch[2]!;
9746
+ const scope = openingMatch[3];
9747
+ if (fencedPath.split('/').pop() !== filename) continue;
9748
+ // The actual project-anchor fence names a filename + its
9749
+ // directory relative to this turn's cwd. Keep the full path
9750
+ // when one is supplied explicitly, never just its basename.
9751
+ const anchorPath = scope === 'instance root'
9752
+ ? globalAnchorPath()
9753
+ : resolve(scope ?? '.', fencedPath);
9754
+ if (filePath !== undefined) {
9755
+ // macOS may spell the same /var path as /private/var in cwd;
9756
+ // Read accepts either. Canonicalize both real files so a
9757
+ // symlink spelling cannot turn a loaded anchor into a miss.
9758
+ const samePath = (path: string): string => {
9759
+ try { return realpathSync(path); } catch { return resolve(path); }
9760
+ };
9761
+ if (!anchorPath || samePath(anchorPath) !== samePath(filePath)) continue;
9762
+ }
9763
+ const contentStart = openingMatch.index + openingMatch[0].length;
9764
+ const escapedPath = fencedPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
9765
+ const escapedScope = scope?.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
9766
+ // Current fences close with their scope. Accept a legacy
9767
+ // scopeless closing fence as well, but never another scope.
9768
+ const closing = new RegExp(`^=== end ${escapedPath}${escapedScope ? `(?: @ ${escapedScope})?` : ''} ===$`, 'm');
9769
+ const closingMatch = closing.exec(message.content.slice(contentStart));
9770
+ if (closingMatch === null) continue;
9771
+ const content = message.content.slice(contentStart, contentStart + closingMatch.index);
9772
+ return content.includes('…[truncated to fit anchor budget]')
9773
+ ? { kind: 'truncated', label: openingMatch[1] }
9774
+ : { kind: 'complete', label: openingMatch[1] };
9775
+ }
9505
9776
  }
9506
9777
  return { kind: 'absent' };
9507
9778
  };
9508
- const anchorStubContext = (filename: string): string => {
9509
- const state = anchorFileState(messages, filename);
9779
+ const anchorStubContext = (filename: string, filePath?: string): string => {
9780
+ const state = anchorFileState(messages, filename, filePath);
9781
+ const namedPath = filePath ?? filename;
9510
9782
  if (state.kind === 'absent') {
9511
- return `\`${filename}\` is not present in the system prompt.`;
9783
+ return `\`${namedPath}\` is not present in the system prompt.`;
9512
9784
  }
9513
9785
  if (state.kind === 'truncated') {
9514
- return `Only a prefix of \`${filename}\` is present in the system prompt under the \`${state.label}\` anchor; the remainder is unavailable.`;
9786
+ return `Only a prefix of \`${namedPath}\` is present in the system prompt under the \`${state.label}\` anchor; the remainder is unavailable.`;
9515
9787
  }
9516
- return `The full text of \`${filename}\` is present in the system prompt under the \`${state.label}\` anchor.`;
9788
+ return `The full text of \`${namedPath}\` is present in the system prompt under the \`${state.label}\` anchor.`;
9517
9789
  };
9518
9790
  // W4-anchor (2026-05-03 PM) — Scope: codex family only. Glob anchor detection is permissive:
9519
9791
  // contains 'AGENTS' or 'CLAUDE' AND contains '.md'. This catches
@@ -9575,11 +9847,10 @@ export async function streamLLMWithTools(
9575
9847
  // instead. A guardian test keeps the repo's own candidates under
9576
9848
  // the cap — see test/prompt-library-universal-preamble.test.ts.
9577
9849
  //
9578
- // Match: file_path's basename equals AGENTS.md or CLAUDE.md
9579
- // (case-sensitive). Catches `AGENTS.md`, `./AGENTS.md`,
9580
- // `/abs/path/AGENTS.md`, `subdir/CLAUDE.md`, etc. Other
9581
- // AGENTS-prefixed files (`AGENTS-helper.md`) are NOT blocked
9582
- // since basename match is strict.
9850
+ // Match only a file whose resolved path corresponds to a complete or
9851
+ // truncated fence in this turn's system prompt. A same-named file in
9852
+ // another directory (or a policy-excluded CLAUDE.md) must remain
9853
+ // readable; a filename alone is not evidence that it was loaded.
9583
9854
  const isAnchorRead = (
9584
9855
  call: { name: string; args: Record<string, unknown> },
9585
9856
  ): { filePath: string } | null => {
@@ -9587,7 +9858,8 @@ export async function streamLLMWithTools(
9587
9858
  const filePath = typeof call.args.file_path === 'string' ? call.args.file_path : '';
9588
9859
  if (filePath.length === 0) return null;
9589
9860
  const basename = filePath.split('/').pop() ?? '';
9590
- if (basename === 'AGENTS.md' || basename === 'CLAUDE.md') {
9861
+ if ((basename === 'AGENTS.md' || basename === 'CLAUDE.md')
9862
+ && anchorFileState(messages, basename, filePath).kind !== 'absent') {
9591
9863
  return { filePath };
9592
9864
  }
9593
9865
  return null;
@@ -9760,7 +10032,14 @@ export async function streamLLMWithTools(
9760
10032
  // naturally rotates files (verified 2026-04-25T06:56
9761
10033
  // reproduction: docs/CAPABILITIES → PLAN → HANDOFF → ROADMAP
9762
10034
  // → src/code-edit/{index,apply,safety}.ts across 5 turns).
9763
- if (modelFamily === 'codex' && EXPLORATORY_TOOLS.has(call.name)) {
10035
+ // TUI-INITIATIVE-S ② — 같은 호출이 같은 오류로 DEDUP_THRESHOLD 번 끝났으면 계열·도구 무관(openai-codex·gpt 계열)하게 같은 RE-CALL 차단을 건다.
10036
+ const repeatedError = (modelFamily === 'codex' || modelFamily === 'gpt' || activeProvider.name === 'openai-codex')
10037
+ ? erroredToolCallSignatures.get(signatureOf(call)) : undefined;
10038
+ if (repeatedError && repeatedError.count >= DEDUP_THRESHOLD) {
10039
+ debug.log('llm.router', 'tool-loop.dedup-blocked', { turn, tool: call.name, signature: signatureOf(call), priorCount: repeatedError.count, threshold: DEDUP_THRESHOLD, reason: 'repeated-error' });
10040
+ return { result: `${buildDedupStub(call, repeatedError.count)} Every one of those calls failed with the same error: ${repeatedError.error.slice(0, 200)}`, isError: false, phaseRejected: false };
10041
+ }
10042
+ if (codexBehaviorGuards && EXPLORATORY_TOOLS.has(call.name)) {
9764
10043
  // W4-anchor — Anchor file Grep blocking. AGENTS.md / CLAUDE.md
9765
10044
  // are already in the system prompt; greppling them is pure
9766
10045
  // token waste and trains codex to ignore the in-prompt copy.
@@ -9916,7 +10195,7 @@ export async function streamLLMWithTools(
9916
10195
  const filename = anchorRead.filePath.split('/').pop() ?? anchorRead.filePath;
9917
10196
  const stub =
9918
10197
  `ANCHOR READ BLOCKED — \`${anchorRead.filePath}\` matches an anchor-file read. ` +
9919
- `${anchorStubContext(filename)} Move to a source file (e.g. \`src/...\`) for on-disk investigation.`;
10198
+ `${anchorStubContext(filename, anchorRead.filePath)} Move to a source file (e.g. \`src/...\`) for on-disk investigation.`;
9920
10199
  return { result: stub, isError: false, phaseRejected: false };
9921
10200
  }
9922
10201
  // W5-A — Literal-path Glob redirect. Codex uses Glob({pattern:
@@ -10065,6 +10344,13 @@ export async function streamLLMWithTools(
10065
10344
  // 스펙을 프로바이더에 선언하지 않는다는 의도가 우회되지 않게.
10066
10345
  const resultSignalsError = !!result && typeof result === 'object'
10067
10346
  && typeof (result as { error?: unknown }).error === 'string';
10347
+ {
10348
+ const errSig = signatureOf(call);
10349
+ const errText = resultSignalsError ? (result as { error: string }).error : isError ? String(result) : null;
10350
+ const prevErr = erroredToolCallSignatures.get(errSig);
10351
+ if (errText === null) erroredToolCallSignatures.delete(errSig);
10352
+ else erroredToolCallSignatures.set(errSig, { error: errText, count: prevErr?.error === errText ? prevErr.count + 1 : 1 });
10353
+ }
10068
10354
  if (!isError && !resultSignalsError) {
10069
10355
  for (const spec of hydrated) {
10070
10356
  if (activeToolNames.has(spec.name)) continue;
@@ -10092,7 +10378,7 @@ export async function streamLLMWithTools(
10092
10378
  // W4-A — Track Read dispatches (only successful ones; error
10093
10379
  // results don't count as "the file is in your context"). The
10094
10380
  // post-Read broad-search guard above reads this counter.
10095
- if (call.name === 'Read' && !isError && modelFamily === 'codex') {
10381
+ if (call.name === 'Read' && !isError && codexBehaviorGuards) {
10096
10382
  dispatchedReadCount++;
10097
10383
  }
10098
10384
  return { result, isError, phaseRejected: false };
@@ -10285,12 +10571,25 @@ export async function streamLLMWithTools(
10285
10571
  };
10286
10572
  const partition = phaseRejectedCalls.length > 0
10287
10573
  ? { parallelActivated: false, safeCount: 0, unsafeCount: callsToDispatch.length }
10288
- : await dispatchWithParallelSafety(callsToDispatch, dispatchWithPostProc);
10574
+ : await dispatchWithParallelSafety(callsToDispatch, dispatchWithPostProc, opts.signal);
10289
10575
  if (phaseRejectedCalls.length > 0) {
10290
10576
  for (let index = 0; index < callsToDispatch.length; index++) {
10577
+ if (opts.signal?.aborted) continue;
10291
10578
  await dispatchWithPostProc(callsToDispatch[index]!, index);
10292
10579
  }
10293
10580
  }
10581
+ // ⭐ TUI-ESC-ABORT-TOOLS — 중단(Esc)으로 «시작하지 않은» 호출은 «중단됨» 결과로 채운다
10582
+ // (tool_call/tool_result 짝 유지). `phaseRejected: true` 로 둬 doom·반복 추적에서 뺀다.
10583
+ // 다음 바퀴 머리의 `opts.signal?.aborted` 가 루프를 멈춘다.
10584
+ if (opts.signal?.aborted) {
10585
+ let skipped = 0;
10586
+ for (let index = 0; index < callsToDispatch.length; index++) {
10587
+ if (dispatchedResults[index]) continue;
10588
+ dispatchedResults[index] = { result: 'Tool call not started: the turn was aborted by the user (Esc).', isError: true, phaseRejected: true };
10589
+ skipped++;
10590
+ }
10591
+ if (skipped > 0) debug.log('llm.tools.aborted', 'batch-remaining-skipped', { turn, skipped });
10592
+ }
10294
10593
  if (debug.enabled && partition.parallelActivated) {
10295
10594
  debug.log('llm.tool-loop.parallel', 'safe-batch-dispatch', {
10296
10595
  safeCount: partition.safeCount,
@@ -10616,6 +10915,20 @@ export async function streamLLMWithTools(
10616
10915
  // route — see routeDoomLoopToAskUser above.
10617
10916
  history.push({ role: LOOP_SELF_NOTE_ROLE, content: postToolResultUserNote });
10618
10917
  }
10918
+ // 신호뿐인 턴 · 섞인 턴(정상 디스패치 · 부분 거부 · 통째 거부의 수신 확인) 모두 — 종결 신호가 이 턴에 «받아들여졌다».
10919
+ if (!hardStopText && pendingCalls.some(isTerminalCall)) terminalSignalDispatched = true;
10920
+ // TUI-GOAL-LOOP-FRICTION(#63) — 종결 신호뿐인 턴 ⊕ 같은 턴에 쓴 답 = 이 턴이 최종 답이다.
10921
+ // 다음 모델 턴을 열면 할 말이 없는 모델이 빈 턴을 되풀이하고 `[NO FINAL SYNTHESIS]` 가 붙는다(10-15 22:36Z 표본).
10922
+ // 이 턴에 답이 없으면 종전처럼 다음 턴을 연다(앞 라운드 글은 «하겠습니다» 류 서술일 수 있다 — 모델이 답을 쓰게).
10923
+ if (terminalOnlyTurn && !hardStopText && !postToolResultUserNote && turnText.trim().length > 0) {
10924
+ const answer = turnText;
10925
+ debug.log('goal.loop', 'terminal-signal-finish', {
10926
+ turn, maxTurns, tools: pendingCalls.map((call) => call.name), answerChars: answer.length,
10927
+ }, { level: 'info' });
10928
+ if (answer !== fullText) handlers.onText('', answer);
10929
+ emitTurnComplete(answer);
10930
+ return answer;
10931
+ }
10619
10932
  if (hardStopText) {
10620
10933
  // W5-E (2026-05-03 PM) — Force-synthesis no-tools pass. Before
10621
10934
  // emitting the [SYNTHESIS IGNORED] / [ACTION IGNORED] / [VERIFY
@@ -10651,7 +10964,7 @@ export async function streamLLMWithTools(
10651
10964
  // fix shape (no-tools synthesis with prior tool_results as
10652
10965
  // context) lifts the empty reply to substantive answer.
10653
10966
  const isForceSynthesisFamily =
10654
- modelFamily === 'codex' || modelFamily === 'gemini';
10967
+ codexBehaviorGuards || modelFamily === 'gemini';
10655
10968
  const shouldForceSynthesis =
10656
10969
  isForceSynthesisFamily
10657
10970
  && !isUserInterventionRequired
@@ -10819,7 +11132,7 @@ export async function streamLLMWithTools(
10819
11132
  let synthesizedText: string | null = null;
10820
11133
  const shouldForceSynthesisOnBudget =
10821
11134
  loopTermination === 'budget-exhausted'
10822
- && (modelFamily === 'codex' || modelFamily === 'gemini')
11135
+ && (codexBehaviorGuards || modelFamily === 'gemini')
10823
11136
  && !forceSynthesisAttempted;
10824
11137
  if (shouldForceSynthesisOnBudget) {
10825
11138
  forceSynthesisAttempted = true;
@@ -10897,6 +11210,7 @@ export async function streamLLMWithTools(
10897
11210
  import type { LLMConfig as UCLLMConfig, UserConfig } from './user-config.js';
10898
11211
  import { loadTokens, readCodexMirrorTokens } from './oauth/store.js';
10899
11212
  import { resolveCodexAccount, effectiveCodexHome } from './oauth/codex-account.js';
11213
+ import { codexCreditSpendReason } from './oauth/codex-billing-route.js';
10900
11214
  import { join } from 'node:path';
10901
11215
  import {
10902
11216
  CODEX_API_BASE_URL, getCodexUserAgent, loadFreshCodexAuthState,
@@ -11620,7 +11934,10 @@ export function getProviderForConfig(
11620
11934
  ? decision.provider.slice('auto:'.length)
11621
11935
  : decision.provider;
11622
11936
  if (provider === 'auto') {
11623
- if (resolvedProviderName === 'auto') throw new NoLlmProviderAvailableError();
11937
+ if (resolvedProviderName === 'auto') {
11938
+ if (testFakeLlmEnabled()) return testFakeLlmProvider;
11939
+ throw new NoLlmProviderAvailableError();
11940
+ }
11624
11941
  if (!isKnownProviderName(resolvedProviderName)) {
11625
11942
  throw new Error(`unknown provider: ${resolvedProviderName}`);
11626
11943
  }
@@ -11696,7 +12013,7 @@ export function getProviderForConfig(
11696
12013
  // union without a case here fails loud at first call.
11697
12014
  throw new Error(`unknown provider: ${provider as string}`);
11698
12015
  }
11699
- import { getModelFamily } from './models/prompts.js';
12016
+ import { getModelFamily, usesCodexBehaviorGuards } from './models/prompts.js';
11700
12017
  import type { ModelFamily } from './models/prompts.js';
11701
12018
 
11702
12019
  // COMPAT(llm-facade-openai-sampling-fields) · 기존 llm.ts 공개 API 유지.